Import Cobalt 13.93830
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index 9473e36..891c4a6 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -43,6 +43,7 @@
MacroOpWithNameOnly(animationend) \
MacroOpWithNameOnly(assertive) \
MacroOpWithNameOnly(attributes) \
+ MacroOpWithNameOnly(beforeunload) \
MacroOpWithNameOnly(blur) \
MacroOpWithNameOnly(boundary) \
MacroOpWithNameOnly(canplay) \
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 9f855ca..2ac5506 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -93,16 +93,16 @@
#endif // ENABLE_REMOTE_DEBUGGING
#if defined(ENABLE_WEBDRIVER)
-#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
int GetWebDriverPort() {
// The default port on which the webdriver server should listen for incoming
// connections.
#if defined(SB_OVERRIDE_DEFAULT_WEBDRIVER_PORT)
const int kDefaultWebDriverPort = SB_OVERRIDE_DEFAULT_WEBDRIVER_PORT;
#else
- const int kDefaultWebDriverPort = 9515;
+ const int kDefaultWebDriverPort = 4444;
#endif // defined(SB_OVERRIDE_DEFAULT_WEBDRIVER_PORT)
int webdriver_port = kDefaultWebDriverPort;
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kWebDriverPort)) {
if (!base::StringToInt(
@@ -114,22 +114,24 @@
webdriver_port = kDefaultWebDriverPort;
}
}
+#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
return webdriver_port;
}
std::string GetWebDriverListenIp() {
- // The default port on which the webdriver server should listen for incoming
+ // The default IP on which the webdriver server should listen for incoming
// connections.
std::string webdriver_listen_ip =
webdriver::WebDriverModule::kDefaultListenIp;
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kWebDriverListenIp)) {
webdriver_listen_ip =
command_line->GetSwitchValueASCII(switches::kWebDriverListenIp);
}
+#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
return webdriver_listen_ip;
}
-#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
#endif // ENABLE_WEBDRIVER
GURL GetInitialURL() {
@@ -625,7 +627,12 @@
#if defined(ENABLE_WEBDRIVER)
#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
- if (command_line->HasSwitch(switches::kEnableWebDriver)) {
+ bool create_webdriver_module =
+ !command_line->HasSwitch(switches::kDisableWebDriver);
+#else
+ bool create_webdriver_module = true;
+#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+ if (create_webdriver_module) {
web_driver_module_.reset(new webdriver::WebDriverModule(
GetWebDriverPort(), GetWebDriverListenIp(),
base::Bind(&BrowserModule::CreateSessionDriver,
@@ -636,7 +643,6 @@
base::Unretained(browser_module_.get())),
base::Bind(&Application::Quit, base::Unretained(this))));
}
-#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
#endif // ENABLE_WEBDRIVER
#if defined(ENABLE_REMOTE_DEBUGGING)
@@ -724,6 +730,9 @@
case kSbEventTypeUnpause:
case kSbEventTypeSuspend:
case kSbEventTypeResume:
+#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
+ case kSbEventTypeLowMemory:
+#endif // SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
OnApplicationEvent(starboard_event->type);
break;
case kSbEventTypeNetworkConnect:
@@ -739,11 +748,9 @@
DispatchEventInternal(new base::DeepLinkEvent(link));
break;
}
-#if SB_API_VERSION >= 4
case kSbEventTypeAccessiblitySettingsChanged:
DispatchEventInternal(new base::AccessibilitySettingsChangedEvent());
break;
-#endif // SB_API_VERSION >= 4
default:
DLOG(WARNING) << "Unhandled Starboard event of type: "
<< starboard_event->type;
@@ -815,6 +822,13 @@
browser_module_->Resume();
DLOG(INFO) << "Finished resuming.";
break;
+#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
+ case kSbEventTypeLowMemory:
+ DLOG(INFO) << "Got low memory event.";
+ browser_module_->ReduceMemory();
+ DLOG(INFO) << "Finished reducing memory usage.";
+ break;
+#endif // SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
default:
NOTREACHED() << "Unexpected event type: " << event_type;
return;
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index cf2ba72..41f4937 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -65,6 +65,7 @@
'../dom/comment.idl',
'../dom/console.idl',
'../dom/crypto.idl',
+ '../dom/custom_event.idl',
'../dom/data_view.idl',
'../dom/device_orientation_event.idl',
'../dom/document.idl',
@@ -79,6 +80,7 @@
'../dom/dom_string_map.idl',
'../dom/dom_token_list.idl',
'../dom/element.idl',
+ '../dom/error_event.idl',
'../dom/event.idl',
'../dom/event_listener.idl',
'../dom/event_target.idl',
@@ -219,9 +221,11 @@
'../audio/audio_node_channel_count_mode.idl',
'../audio/audio_node_channel_interpretation.idl',
'../dom/blob_property_bag.idl',
+ '../dom/custom_event_init.idl',
'../dom/device_orientation_event_init.idl',
'../dom/document_ready_state.idl',
'../dom/dom_parser_supported_type.idl',
+ '../dom/error_event_init.idl',
'../dom/event_init.idl',
'../dom/event_modifier_init.idl',
'../dom/focus_event_init.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index a9eb520..cfc4a45 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -111,8 +111,13 @@
// TODO: Subscribe to viewport size changes.
+const int kMainWebModuleZIndex = 1;
+const int kSplashScreenZIndex = 2;
+
#if defined(ENABLE_DEBUG_CONSOLE)
+const int kDebugConsoleZIndex = 3;
+
const char kFuzzerToggleCommand[] = "fuzzer_toggle";
const char kFuzzerToggleCommandShortHelp[] = "Toggles the input fuzzer on/off.";
const char kFuzzerToggleCommandLongHelp[] =
@@ -211,9 +216,7 @@
storage_manager_(make_scoped_ptr(new StorageUpgradeHandler(url))
.PassAs<storage::StorageManager::UpgradeHandler>(),
options_.storage_manager_options),
-#if defined(OS_STARBOARD)
is_rendered_(false),
-#endif // OS_STARBOARD
#if defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
array_buffer_allocator_(
new ResourceProviderArrayBufferAllocator(GetResourceProvider())),
@@ -251,7 +254,8 @@
#endif
will_quit_(false),
application_state_(initial_application_state),
- splash_screen_cache_(new SplashScreenCache()) {
+ splash_screen_cache_(new SplashScreenCache()),
+ produced_render_tree_(false) {
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
on_error_triggered_count_ = 0;
@@ -296,9 +300,7 @@
OnFuzzerToggle(std::string());
}
if (command_line->HasSwitch(switches::kSuspendFuzzer)) {
-#if SB_API_VERSION >= 4
suspend_fuzzer_.emplace();
-#endif
}
#endif // ENABLE_DEBUG_CONSOLE && ENABLE_DEBUG_COMMAND_LINE_SWITCHES
@@ -403,13 +405,18 @@
base::optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url);
if (fallback_splash_screen_url_ ||
(key && splash_screen_cache_->IsSplashScreenCached(*key))) {
+ // Create the splash screen layer.
+ splash_screen_layer_ =
+ render_tree_combiner_->CreateLayer(kSplashScreenZIndex);
+
splash_screen_.reset(new SplashScreen(
application_state_,
- base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
+ base::Bind(&BrowserModule::QueueOnSplashScreenRenderTreeProduced,
base::Unretained(this)),
&network_module_, viewport_size, GetResourceProvider(),
kLayoutMaxRefreshFrequencyInHz, *fallback_splash_screen_url_, url,
- splash_screen_cache_.get()));
+ splash_screen_cache_.get(),
+ base::Bind(&BrowserModule::DestroySplashScreen, weak_this_)));
lifecycle_observers_.AddObserver(splash_screen_.get());
}
@@ -473,8 +480,6 @@
return;
}
- DestroySplashScreen();
-
// This log is relied on by the webdriver benchmark tests, so it shouldn't be
// changed unless the corresponding benchmark logic is changed as well.
LOG(INFO) << "Loaded WebModule";
@@ -519,26 +524,67 @@
base::Bind(&BrowserModule::ProcessRenderTreeSubmissionQueue, weak_this_));
}
+void BrowserModule::QueueOnSplashScreenRenderTreeProduced(
+ const browser::WebModule::LayoutResults& layout_results) {
+ TRACE_EVENT0("cobalt::browser",
+ "BrowserModule::QueueOnSplashScreenRenderTreeProduced()");
+ render_tree_submission_queue_.AddMessage(
+ base::Bind(&BrowserModule::OnSplashScreenRenderTreeProduced,
+ base::Unretained(this), layout_results));
+ self_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&BrowserModule::ProcessRenderTreeSubmissionQueue, weak_this_));
+}
+
void BrowserModule::OnRenderTreeProduced(
const browser::WebModule::LayoutResults& layout_results) {
TRACE_EVENT0("cobalt::browser", "BrowserModule::OnRenderTreeProduced()");
DCHECK_EQ(MessageLoop::current(), self_message_loop_);
+
+ if (splash_screen_ && !produced_render_tree_) {
+ splash_screen_->Shutdown();
+ }
+ produced_render_tree_ = true;
+
if (application_state_ == base::kApplicationStatePreloading ||
- !render_tree_combiner_) {
+ !render_tree_combiner_ || !main_web_module_layer_) {
+ return;
+ }
+ renderer::Submission renderer_submission(layout_results.render_tree,
+ layout_results.layout_time);
+ renderer_submission.on_rasterized_callback = base::Bind(
+ &BrowserModule::OnRendererSubmissionRasterized, base::Unretained(this));
+ main_web_module_layer_->Submit(renderer_submission, true /* receive_time */);
+
+#if defined(ENABLE_SCREENSHOT)
+ screen_shot_writer_->SetLastPipelineSubmission(renderer::Submission(
+ layout_results.render_tree, layout_results.layout_time));
+#endif
+}
+
+void BrowserModule::OnSplashScreenRenderTreeProduced(
+ const browser::WebModule::LayoutResults& layout_results) {
+ TRACE_EVENT0("cobalt::browser",
+ "BrowserModule::OnSplashScreenRenderTreeProduced()");
+ DCHECK_EQ(MessageLoop::current(), self_message_loop_);
+
+ if (application_state_ == base::kApplicationStatePreloading ||
+ !render_tree_combiner_ || !splash_screen_layer_) {
return;
}
renderer::Submission renderer_submission(layout_results.render_tree,
layout_results.layout_time);
-#if defined(OS_STARBOARD)
renderer_submission.on_rasterized_callback = base::Bind(
&BrowserModule::OnRendererSubmissionRasterized, base::Unretained(this));
-#endif // OS_STARBOARD
- render_tree_combiner_->UpdateMainRenderTree(renderer_submission);
+ splash_screen_layer_->Submit(renderer_submission, false /* receive_time */);
#if defined(ENABLE_SCREENSHOT)
- screen_shot_writer_->SetLastPipelineSubmission(renderer::Submission(
- layout_results.render_tree, layout_results.layout_time));
+// TODO: write screen shot using render_tree_combinder_ (to combine
+// splash screen and main web_module). Consider when the splash
+// screen is overlaid on top of the main web module render tree, and
+// a screenshot is taken : there will be a race condition on which
+// web module update their render tree last.
#endif
}
@@ -549,11 +595,7 @@
}
#endif
-#if defined(OS_STARBOARD)
SbSystemRequestStop(0);
-#else
- LOG(WARNING) << "window.close() is not supported on this platform.";
-#endif
}
void BrowserModule::OnWindowMinimize() {
@@ -563,11 +605,7 @@
}
#endif
-#if defined(OS_STARBOARD) && SB_API_VERSION >= 4
SbSystemRequestSuspend();
-#else
- LOG(WARNING) << "window.minimize() is not supported on this platform.";
-#endif
}
#if defined(ENABLE_DEBUG_CONSOLE)
@@ -632,16 +670,16 @@
"BrowserModule::OnDebugConsoleRenderTreeProduced()");
DCHECK_EQ(MessageLoop::current(), self_message_loop_);
if (application_state_ == base::kApplicationStatePreloading ||
- !render_tree_combiner_) {
+ !render_tree_combiner_ || !debug_console_layer_) {
return;
}
if (debug_console_->GetMode() == debug::DebugHub::kDebugConsoleOff) {
- render_tree_combiner_->UpdateDebugConsoleRenderTree(base::nullopt);
+ debug_console_layer_->Submit(base::nullopt);
return;
}
- render_tree_combiner_->UpdateDebugConsoleRenderTree(renderer::Submission(
+ debug_console_layer_->Submit(renderer::Submission(
layout_results.render_tree, layout_results.layout_time));
}
@@ -804,9 +842,15 @@
void BrowserModule::DestroySplashScreen() {
TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroySplashScreen()");
+ if (MessageLoop::current() != self_message_loop_) {
+ self_message_loop_->PostTask(
+ FROM_HERE, base::Bind(&BrowserModule::DestroySplashScreen, weak_this_));
+ return;
+ }
if (splash_screen_) {
lifecycle_observers_.RemoveObserver(splash_screen_.get());
}
+ splash_screen_layer_.reset(NULL);
splash_screen_.reset(NULL);
}
@@ -912,6 +956,22 @@
application_state_ = base::kApplicationStatePaused;
}
+void BrowserModule::ReduceMemory() {
+ if (splash_screen_) {
+ splash_screen_->ReduceMemory();
+ }
+
+#if defined(ENABLE_DEBUG_CONSOLE)
+ if (debug_console_) {
+ debug_console_->ReduceMemory();
+ }
+#endif // defined(ENABLE_DEBUG_CONSOLE)
+
+ if (web_module_) {
+ web_module_->ReduceMemory();
+ }
+}
+
void BrowserModule::CheckMemory(
const int64_t& used_cpu_memory,
const base::optional<int64_t>& used_gpu_memory) {
@@ -923,7 +983,6 @@
used_gpu_memory);
}
-#if defined(OS_STARBOARD)
void BrowserModule::OnRendererSubmissionRasterized() {
TRACE_EVENT0("cobalt::browser",
"BrowserModule::OnRendererSubmissionRasterized()");
@@ -933,7 +992,6 @@
SbSystemHideSplashScreen();
}
}
-#endif // OS_STARBOARD
#if defined(COBALT_CHECK_RENDER_TIMEOUT)
void BrowserModule::OnPollForRenderTimeout(const GURL& url) {
@@ -1021,11 +1079,14 @@
render_tree_combiner_.reset(
new RenderTreeCombiner(renderer_module_.get(), GetViewportSize()));
-
- // Always render the debug console. It will draw nothing if disabled.
- // This setting is ignored if ENABLE_DEBUG_CONSOLE is not defined.
- // TODO: Render tree combiner should probably be refactored.
- render_tree_combiner_->set_render_debug_console(true);
+ // Create the main web module layer.
+ main_web_module_layer_ =
+ render_tree_combiner_->CreateLayer(kMainWebModuleZIndex);
+// Create the debug console layer.
+#if defined(ENABLE_DEBUG_CONSOLE)
+ debug_console_layer_ =
+ render_tree_combiner_->CreateLayer(kDebugConsoleZIndex);
+#endif
#if defined(ENABLE_SCREENSHOT)
screen_shot_writer_.reset(new ScreenShotWriter(renderer_module_->pipeline()));
@@ -1085,8 +1146,14 @@
// Clear out the render tree combiner so that it doesn't hold on to any
// render tree resources either.
- if (render_tree_combiner_) {
- render_tree_combiner_->Reset();
+ if (main_web_module_layer_) {
+ main_web_module_layer_->Reset();
+ }
+ if (splash_screen_layer_) {
+ splash_screen_layer_->Reset();
+ }
+ if (debug_console_layer_) {
+ debug_console_layer_->Reset();
}
#if defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index ba3861d..386eb0e 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -142,6 +142,10 @@
void Suspend();
void Resume();
+ // Attempt to reduce overall memory consumption. Called in response to a
+ // system indication that memory usage is nearing a critical level.
+ void ReduceMemory();
+
void CheckMemory(const int64_t& used_cpu_memory,
const base::optional<int64_t>& used_gpu_memory);
@@ -173,6 +177,13 @@
void OnRenderTreeProduced(
const browser::WebModule::LayoutResults& layout_results);
+ // Glue function to deal with the production of the splash screen render tree,
+ // and will manage handing it off to the renderer.
+ void QueueOnSplashScreenRenderTreeProduced(
+ const browser::WebModule::LayoutResults& layout_results);
+ void OnSplashScreenRenderTreeProduced(
+ const browser::WebModule::LayoutResults& layout_results);
+
// Saves/loads the debug console mode to/from local storage so we can
// persist the user's preference.
void SaveDebugConsoleMode();
@@ -357,8 +368,11 @@
// Sets up the network component for requesting internet resources.
network::NetworkModule network_module_;
- // Manages the two render trees, combines and renders them.
+ // Manages the three render trees, combines and renders them.
scoped_ptr<RenderTreeCombiner> render_tree_combiner_;
+ scoped_ptr<RenderTreeCombiner::Layer> main_web_module_layer_;
+ scoped_ptr<RenderTreeCombiner::Layer> debug_console_layer_;
+ scoped_ptr<RenderTreeCombiner::Layer> splash_screen_layer_;
#if defined(ENABLE_SCREENSHOT)
// Helper object to create screen shots of the last layout tree.
@@ -459,6 +473,9 @@
// The splash screen cache.
scoped_ptr<SplashScreenCache> splash_screen_cache_;
+
+ // Whether or not the main WebModule has produced any render trees yet.
+ bool produced_render_tree_;
};
} // namespace browser
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index 9ecbef5..6ed291d 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -79,6 +79,8 @@
web_module_->Resume(resource_provider);
}
+ void ReduceMemory() { web_module_->ReduceMemory(); }
+
private:
void OnError(const GURL& /* url */, const std::string& error) {
LOG(ERROR) << error;
diff --git a/src/cobalt/browser/debug_console/console_values.js b/src/cobalt/browser/debug_console/console_values.js
index 2ed232e..444ca48 100644
--- a/src/cobalt/browser/debug_console/console_values.js
+++ b/src/cobalt/browser/debug_console/console_values.js
@@ -20,8 +20,9 @@
this.DEFAULT_KEY = 'default';
// Reduced space-separated list of CVal prefixes to display at start-up.
this.DEFAULT_ACTIVE_SET =
- 'Cobalt DevTools Memory.CPU Memory.MainWebModule Memory.JS Memory.Font ' +
- 'Event.Duration.MainWebModule.KeyDown Renderer.Rasterize.Duration';
+ 'Cobalt DevTools WebDriver Memory.CPU Memory.MainWebModule Memory.JS ' +
+ 'Memory.Font Event.Duration.MainWebModule.KeyDown ' +
+ 'Renderer.Rasterize.Duration';
var names = window.debugHub.getConsoleValueNames();
this.allCVals = names.split(' ');
diff --git a/src/cobalt/browser/memory_tracker/tool/tool_impl.cc b/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
index f2a92bb..65de06f 100644
--- a/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
+++ b/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
@@ -30,7 +30,6 @@
#include "cobalt/browser/memory_tracker/tool/params.h"
#include "cobalt/browser/memory_tracker/tool/tool_thread.h"
#include "cobalt/browser/memory_tracker/tool/util.h"
-#include "cobalt/script/mozjs/util/stack_trace_helpers.h"
#include "nb/analytics/memory_tracker.h"
#include "nb/analytics/memory_tracker_helpers.h"
#include "nb/concurrent_map.h"
diff --git a/src/cobalt/browser/render_tree_combiner.cc b/src/cobalt/browser/render_tree_combiner.cc
index 171fbc3..78fe4be 100644
--- a/src/cobalt/browser/render_tree_combiner.cc
+++ b/src/cobalt/browser/render_tree_combiner.cc
@@ -14,99 +14,97 @@
#include "cobalt/browser/render_tree_combiner.h"
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
+#include "base/time.h"
#include "cobalt/render_tree/composition_node.h"
#include "cobalt/render_tree/rect_node.h"
+#include "cobalt/renderer/renderer_module.h"
+#include "cobalt/renderer/submission.h"
namespace cobalt {
namespace browser {
-#if defined(ENABLE_DEBUG_CONSOLE)
+RenderTreeCombiner::Layer::Layer(RenderTreeCombiner* render_tree_combiner)
+ : render_tree_combiner_(render_tree_combiner),
+ render_tree_(base::nullopt),
+ receipt_time_(base::nullopt) {}
+
+RenderTreeCombiner::Layer::~Layer() {
+ DCHECK(render_tree_combiner_);
+ render_tree_combiner_->RemoveLayer(this);
+}
+
+void RenderTreeCombiner::Layer::Submit(
+ const base::optional<renderer::Submission>& render_tree_submission,
+ bool receive_time) {
+ render_tree_ = render_tree_submission;
+ if (receive_time) {
+ receipt_time_ = base::TimeTicks::HighResNow();
+ } else {
+ receipt_time_ = base::nullopt;
+ }
+ DCHECK(render_tree_combiner_);
+ render_tree_combiner_->SubmitToRenderer();
+}
+
RenderTreeCombiner::RenderTreeCombiner(
renderer::RendererModule* renderer_module, const math::Size& viewport_size)
- : render_debug_console_(true),
- renderer_module_(renderer_module),
- viewport_size_(viewport_size) {}
+ : renderer_module_(renderer_module), viewport_size_(viewport_size) {}
-RenderTreeCombiner::~RenderTreeCombiner() {}
+scoped_ptr<RenderTreeCombiner::Layer> RenderTreeCombiner::CreateLayer(
+ int z_index) {
+ if (layers_.count(z_index) > 0) {
+ return scoped_ptr<RenderTreeCombiner::Layer>(NULL);
+ }
+ RenderTreeCombiner::Layer* layer = new Layer(this);
+ layers_[z_index] = layer;
-void RenderTreeCombiner::Reset() {
- main_render_tree_ = base::nullopt;
- debug_console_render_tree_ = base::nullopt;
- main_render_tree_receipt_time_ = base::nullopt;
+ return scoped_ptr<RenderTreeCombiner::Layer>(layers_[z_index]);
}
-void RenderTreeCombiner::UpdateMainRenderTree(
- const renderer::Submission& render_tree_submission) {
- main_render_tree_ = render_tree_submission;
- main_render_tree_receipt_time_ = base::TimeTicks::HighResNow();
- SubmitToRenderer();
-}
-
-void RenderTreeCombiner::UpdateDebugConsoleRenderTree(
- const base::optional<renderer::Submission>& render_tree_submission) {
- debug_console_render_tree_ = render_tree_submission;
- SubmitToRenderer();
+void RenderTreeCombiner::RemoveLayer(const Layer* layer) {
+ for (auto it = layers_.begin(); it != layers_.end(); /* no increment */) {
+ if (it->second == layer) {
+ it = layers_.erase(it);
+ } else {
+ ++it;
+ }
+ }
}
void RenderTreeCombiner::SubmitToRenderer() {
- if (render_debug_console_ && debug_console_render_tree_) {
- if (main_render_tree_) {
- render_tree::CompositionNode::Builder builder;
- builder.AddChild(main_render_tree_->render_tree);
- builder.AddChild(debug_console_render_tree_->render_tree);
- scoped_refptr<render_tree::Node> combined_tree =
- new render_tree::CompositionNode(builder);
+ render_tree::CompositionNode::Builder builder;
- // Setup time to be based off of the main submitted tree only.
- // TODO: Setup a "layers" interface on the Pipeline so that
- // trees can be combined and animated there, properly.
- renderer::Submission combined_submission(*main_render_tree_);
- combined_submission.render_tree = combined_tree;
- combined_submission.time_offset =
- main_render_tree_->time_offset +
- (base::TimeTicks::HighResNow() - *main_render_tree_receipt_time_);
-
- renderer_module_->pipeline()->Submit(combined_submission);
- } else {
- // If we are rendering the debug console by itself, give it a solid black
- // background to it.
- render_tree::CompositionNode::Builder builder;
- builder.AddChild(new render_tree::RectNode(
- math::RectF(viewport_size_),
- scoped_ptr<render_tree::Brush>(new render_tree::SolidColorBrush(
- render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f)))));
- builder.AddChild(debug_console_render_tree_->render_tree);
-
- renderer::Submission combined_submission(*debug_console_render_tree_);
- combined_submission.render_tree =
- new render_tree::CompositionNode(builder);
- renderer_module_->pipeline()->Submit(combined_submission);
+ // Add children for all layers in order.
+ base::optional<renderer::Submission> first_tree = base::nullopt;
+ base::optional<renderer::Submission> combined_submission = base::nullopt;
+ for (auto it = layers_.begin(); it != layers_.end(); ++it) {
+ RenderTreeCombiner::Layer* layer = it->second;
+ if (layer->render_tree_) {
+ builder.AddChild(layer->render_tree_->render_tree);
+ first_tree = layer->render_tree_;
+ // Make the combined submission with the first receipt_time_ we find.
+ if (!combined_submission && layer->receipt_time_) {
+ combined_submission = renderer::Submission(*layer->render_tree_);
+ combined_submission->time_offset =
+ layer->render_tree_->time_offset +
+ (base::TimeTicks::HighResNow() - *layer->receipt_time_);
+ }
}
- } else if (main_render_tree_) {
- renderer_module_->pipeline()->Submit(*main_render_tree_);
}
+ if (!first_tree) {
+ return;
+ }
+ if (!combined_submission) {
+ // None of the layers store the time.
+ combined_submission = renderer::Submission(*first_tree);
+ }
+
+ combined_submission->render_tree = new render_tree::CompositionNode(builder);
+ renderer_module_->pipeline()->Submit(*combined_submission);
}
-#else // ENABLE_DEBUG_CONSOLE
-RenderTreeCombiner::RenderTreeCombiner(
- renderer::RendererModule* renderer_module, const math::Size& viewport_size)
- : renderer_module_(renderer_module) {
- UNREFERENCED_PARAMETER(viewport_size);
-}
-
-RenderTreeCombiner::~RenderTreeCombiner() {}
-
-void RenderTreeCombiner::Reset() {}
-
-void RenderTreeCombiner::UpdateMainRenderTree(
- const renderer::Submission& render_tree_submission) {
- renderer_module_->pipeline()->Submit(render_tree_submission);
-}
-
-void RenderTreeCombiner::UpdateDebugConsoleRenderTree(
- const base::optional<renderer::Submission>& render_tree_submission) {
- UNREFERENCED_PARAMETER(render_tree_submission);
-}
-#endif // ENABLE_DEBUG_CONSOLE
-
} // namespace browser
} // namespace cobalt
diff --git a/src/cobalt/browser/render_tree_combiner.h b/src/cobalt/browser/render_tree_combiner.h
index 76b1a22..65f3127 100644
--- a/src/cobalt/browser/render_tree_combiner.h
+++ b/src/cobalt/browser/render_tree_combiner.h
@@ -15,49 +15,72 @@
#ifndef COBALT_BROWSER_RENDER_TREE_COMBINER_H_
#define COBALT_BROWSER_RENDER_TREE_COMBINER_H_
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
+#include "base/time.h"
#include "cobalt/renderer/renderer_module.h"
#include "cobalt/renderer/submission.h"
namespace cobalt {
namespace browser {
-// Combines the main and debug console render trees. Caches the individual
-// trees as they are produced. Re-renders when either tree changes.
-// This class is only fully implemented when ENABLE_DEBUG_CONSOLE is defined,
-// otherwise (e.g. in release builds) a stub implementation is used.
+// Combines rendering layers (such as the main, splash screen, and
+// debug console). Caches the individual trees as they are produced.
+// Re-renders when any tree changes.
class RenderTreeCombiner {
public:
+ // Layer represents the render layer corresponding to the main web
+ // module, the splash screen, or the debug console and are used to
+ // create and submit a combined tree to the RendererModule's
+ // pipeline. Layers are combined in order of the |z_index| specifed
+ // at the Layers' creation. The RenderTreeCombiner stores pointers
+ // to Layers. The Layers are owned by the caller of
+ // RenderTreeCombiner::CreateLayer.
+ class Layer {
+ public:
+ ~Layer();
+
+ void Reset() {
+ render_tree_ = base::nullopt;
+ receipt_time_ = base::nullopt;
+ }
+
+ // Submit render tree to the layer, and specify whether the time
+ // received should be stored.
+ void Submit(
+ const base::optional<renderer::Submission>& render_tree_submission,
+ bool receive_time = false);
+
+ private:
+ friend class RenderTreeCombiner;
+
+ explicit Layer(RenderTreeCombiner* render_tree_combiner = NULL);
+
+ RenderTreeCombiner* render_tree_combiner_;
+
+ base::optional<renderer::Submission> render_tree_;
+ base::optional<base::TimeTicks> receipt_time_;
+ };
+
explicit RenderTreeCombiner(renderer::RendererModule* renderer_module,
const math::Size& viewport_size);
- ~RenderTreeCombiner();
+ ~RenderTreeCombiner() {}
- void Reset();
-
- // Update the main web module render tree.
- void UpdateMainRenderTree(const renderer::Submission& render_tree_submission);
-
- // Update the debug console render tree.
- void UpdateDebugConsoleRenderTree(
- const base::optional<renderer::Submission>& render_tree_submission);
-
-#if defined(ENABLE_DEBUG_CONSOLE)
- bool render_debug_console() const { return render_debug_console_; }
- void set_render_debug_console(bool render_debug_console) {
- render_debug_console_ = render_debug_console;
- }
-#else // ENABLE_DEBUG_CONSOLE
- bool render_debug_console() const { return false; }
- void set_render_debug_console(bool render_debug_console) {
- UNREFERENCED_PARAMETER(render_debug_console);
- }
-#endif // ENABLE_DEBUG_CONSOLE
+ // Create a Layer with a given |z_index|. If a Layer already exists
+ // at |z_index|, return NULL, and no Layer is created.
+ scoped_ptr<Layer> CreateLayer(int z_index);
private:
-#if defined(ENABLE_DEBUG_CONSOLE)
- // Combines the two cached render trees (main/debug) and renders the result.
- void SubmitToRenderer();
+ // The layers keyed on their z_index.
+ std::map<int, Layer*> layers_;
- bool render_debug_console_;
+ // Removes a layer from |layers_|. Called by the Layer destructor.
+ void RemoveLayer(const Layer* layer);
+
+ // Combines the cached render trees and renders the result.
+ void SubmitToRenderer();
// Local reference to the render pipeline, so we can submit the combined tree.
// Reference counted pointer not necessary here.
@@ -65,22 +88,6 @@
// The size of the output viewport.
math::Size viewport_size_;
-
- // Local references to the main and debug console render trees/animation maps
- // so we can combine them.
- base::optional<renderer::Submission> main_render_tree_;
-
- // This is the time that we received the last main render tree submission.
- // used so that we know what time to forward the submission to the pipeline
- // with.
- base::optional<base::TimeTicks> main_render_tree_receipt_time_;
-
- // The debug console render tree submission.
- base::optional<renderer::Submission> debug_console_render_tree_;
-#else // ENABLE_DEBUG_CONSOLE
- // Use this local reference even in release builds to submit the main tree.
- renderer::RendererModule* renderer_module_;
-#endif // ENABLE_DEBUG_CONSOLE
};
} // namespace browser
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc
index 8c78ce2..2999b41 100644
--- a/src/cobalt/browser/splash_screen.cc
+++ b/src/cobalt/browser/splash_screen.cc
@@ -17,25 +17,51 @@
#include <string>
#include "base/bind.h"
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
#include "base/threading/platform_thread.h"
+#include "base/time.h"
#include "cobalt/browser/splash_screen_cache.h"
#include "cobalt/loader/cache_fetcher.h"
namespace cobalt {
namespace browser {
+namespace {
-SplashScreen::SplashScreen(base::ApplicationState initial_application_state,
- const WebModule::OnRenderTreeProducedCallback&
- render_tree_produced_callback,
- network::NetworkModule* network_module,
- const math::Size& window_dimensions,
- render_tree::ResourceProvider* resource_provider,
- float layout_refresh_rate,
- const GURL& fallback_splash_screen_url,
- const GURL& initial_main_web_module_url,
- SplashScreenCache* splash_screen_cache)
+const int kSplashShutdownSeconds = 2;
+
+void PostCallbackToMessageLoop(const base::Closure& callback,
+ MessageLoop* message_loop) {
+ DCHECK(message_loop);
+ message_loop->PostTask(FROM_HERE, callback);
+}
+
+// TODO: consolidate definitions of BindToLoop / BindToCurrentLoop
+// from here and media in base.
+base::Closure BindToLoop(const base::Closure& callback,
+ MessageLoop* message_loop) {
+ return base::Bind(&PostCallbackToMessageLoop, callback, message_loop);
+}
+
+void OnError(const GURL& /* url */, const std::string& error) {
+ LOG(ERROR) << error;
+}
+
+} // namespace
+
+SplashScreen::SplashScreen(
+ base::ApplicationState initial_application_state,
+ const WebModule::OnRenderTreeProducedCallback&
+ render_tree_produced_callback,
+ network::NetworkModule* network_module, const math::Size& window_dimensions,
+ render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
+ const GURL& fallback_splash_screen_url,
+ const GURL& initial_main_web_module_url,
+ SplashScreenCache* splash_screen_cache,
+ const base::Callback<void()>& on_splash_screen_shutdown_complete)
: render_tree_produced_callback_(render_tree_produced_callback),
- is_ready_(true, false) {
+ self_message_loop_(MessageLoop::current()),
+ on_splash_screen_shutdown_complete_(on_splash_screen_shutdown_complete) {
WebModule::Options web_module_options;
web_module_options.name = "SplashScreenWebModule";
@@ -57,11 +83,14 @@
web_module_options.splash_screen_cache = splash_screen_cache;
}
+ base::Callback<void()> on_window_close(
+ BindToLoop(on_splash_screen_shutdown_complete, self_message_loop_));
+
+ web_module_options.on_before_unload_fired_but_not_handled = on_window_close;
+
web_module_.reset(new WebModule(
- url_to_pass, initial_application_state,
- base::Bind(&SplashScreen::OnRenderTreeProduced, base::Unretained(this)),
- base::Bind(&SplashScreen::OnError, base::Unretained(this)),
- base::Bind(&SplashScreen::OnWindowClosed, base::Unretained(this)),
+ url_to_pass, initial_application_state, render_tree_produced_callback_,
+ base::Bind(&OnError), on_window_close,
base::Closure(), // window_minimize_callback
&stub_media_module_, network_module, window_dimensions,
1.f /*video_pixel_ratio*/, resource_provider, layout_refresh_rate,
@@ -69,23 +98,23 @@
}
SplashScreen::~SplashScreen() {
+ DCHECK_EQ(MessageLoop::current(), self_message_loop_);
// Destroy the web module first to prevent our callbacks from being called
// (from another thread) while member objects are being destroyed.
web_module_.reset();
+ // Cancel any pending run of the splash screen shutdown callback.
+ on_splash_screen_shutdown_complete_.Cancel();
}
-void SplashScreen::WaitUntilReady() {
- is_ready_.Wait();
-}
-
-void SplashScreen::OnRenderTreeProduced(
- const browser::WebModule::LayoutResults& layout_results) {
- is_ready_.Signal();
- render_tree_produced_callback_.Run(layout_results);
-}
-
-void SplashScreen::OnWindowClosed() {
- is_ready_.Signal();
+void SplashScreen::Shutdown() {
+ DCHECK_EQ(MessageLoop::current(), self_message_loop_);
+ DCHECK(web_module_);
+ if (!on_splash_screen_shutdown_complete_.callback().is_null()) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, on_splash_screen_shutdown_complete_.callback(),
+ base::TimeDelta::FromSeconds(kSplashShutdownSeconds));
+ }
+ web_module_->InjectBeforeUnloadEvent();
}
} // namespace browser
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index 0e07915..73db062 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -33,16 +33,17 @@
//
class SplashScreen : public LifecycleObserver {
public:
- SplashScreen(base::ApplicationState initial_application_state,
- const WebModule::OnRenderTreeProducedCallback&
- render_tree_produced_callback,
- network::NetworkModule* network_module,
- const math::Size& window_dimensions,
- render_tree::ResourceProvider* resource_provider,
- float layout_refresh_rate,
- const GURL& fallback_splash_screen_url,
- const GURL& initial_main_web_module_url,
- cobalt::browser::SplashScreenCache* splash_screen_cache);
+ SplashScreen(
+ base::ApplicationState initial_application_state,
+ const WebModule::OnRenderTreeProducedCallback&
+ render_tree_produced_callback,
+ network::NetworkModule* network_module,
+ const math::Size& window_dimensions,
+ render_tree::ResourceProvider* resource_provider,
+ float layout_refresh_rate, const GURL& fallback_splash_screen_url,
+ const GURL& initial_main_web_module_url,
+ cobalt::browser::SplashScreenCache* splash_screen_cache,
+ const base::Callback<void()>& on_splash_screen_shutdown_complete);
~SplashScreen();
void SetSize(const math::Size& window_dimensions, float video_pixel_ratio) {
@@ -61,29 +62,32 @@
web_module_->Resume(resource_provider);
}
- // Block the caller until the splash screen is ready to be rendered.
- void WaitUntilReady();
+ void ReduceMemory() { web_module_->ReduceMemory(); }
+
+ // This dispatches event beforeunload in the WebModule. If
+ // beforeunload has any handlers or listeners, Shutdown waits for
+ // window.close to be called or a maximum of kSplashShutdownSeconds
+ // before running |on_splash_screen_shutdown_complete_|. If beforeunload has
+ // no handlers, |on_splash_screen_shutdown_complete_| is run immediately.
+ void Shutdown();
private:
- void OnRenderTreeProduced(
- const browser::WebModule::LayoutResults& layout_results);
-
- void OnError(const GURL& /* url */, const std::string& error) {
- is_ready_.Signal();
- LOG(ERROR) << error;
- }
-
+ // Run when window.close() is called by the WebModule.
void OnWindowClosed();
+ void OnWindowClosedInternal();
media::MediaModuleStub stub_media_module_;
WebModule::OnRenderTreeProducedCallback render_tree_produced_callback_;
- // Signalled once the splash screen has produced its first render tree or
- // an error occurred.
- base::WaitableEvent is_ready_;
-
scoped_ptr<WebModule> web_module_;
+
+ // The splash screen runs on this message loop.
+ MessageLoop* const self_message_loop_;
+
+ // This is called by Shutdown (via window.close) or after
+ // the time limit has been exceeded.
+ base::CancelableClosure on_splash_screen_shutdown_complete_;
};
} // namespace browser
diff --git a/src/cobalt/browser/suspend_fuzzer.cc b/src/cobalt/browser/suspend_fuzzer.cc
index b94158b..51a7265 100644
--- a/src/cobalt/browser/suspend_fuzzer.cc
+++ b/src/cobalt/browser/suspend_fuzzer.cc
@@ -43,20 +43,13 @@
void SuspendFuzzer::DoStep() {
DCHECK(MessageLoop::current() == thread_.message_loop());
-#if SB_API_VERSION < 4
- NOTREACHED() << "Cannot run suspend_fuzzer on SB_API_VERSION < 4.";
-#endif
if (step_type_ == kShouldRequestSuspend) {
SB_DLOG(INFO) << "suspend_fuzzer: Requesting suspend.";
-#if SB_API_VERSION >= 4
SbSystemRequestSuspend();
-#endif
step_type_ = kShouldRequestUnpause;
} else if (step_type_ == kShouldRequestUnpause) {
SB_DLOG(INFO) << "suspend_fuzzer: Requesting unpause.";
-#if SB_API_VERSION >= 4
SbSystemRequestUnpause();
-#endif
step_type_ = kShouldRequestSuspend;
} else {
NOTREACHED();
diff --git a/src/cobalt/browser/suspend_fuzzer.h b/src/cobalt/browser/suspend_fuzzer.h
index 4aa97cc..d1681cf 100644
--- a/src/cobalt/browser/suspend_fuzzer.h
+++ b/src/cobalt/browser/suspend_fuzzer.h
@@ -22,7 +22,7 @@
namespace browser {
// Repeatedly switch off between calling |SbSystemRequestSuspend| and
-// |SbSystemRequestUnpause|, or just no-op if on an SB_API_VERSION < 4.
+// |SbSystemRequestUnpause|.
class SuspendFuzzer {
public:
SuspendFuzzer();
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 2c39eed..11e98c3 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -34,12 +34,12 @@
// Switches different debug console modes: on | hud | off
const char kDebugConsoleMode[] = "debug_console";
+// Do not create the WebDriver server.
+const char kDisableWebDriver[] = "disable_webdriver";
+
// Disable webm/vp9.
const char kDisableWebmVp9[] = "disable_webm_vp9";
-// Create WebDriver server.
-const char kEnableWebDriver[] = "enable_webdriver";
-
// Additional base directory for accessing web files via file://.
const char kExtraWebFileDir[] = "web_file_path";
@@ -83,8 +83,7 @@
const char kStubImageDecoder[] = "stub_image_decoder";
// If this flag is set, alternating calls to |SbSystemRequestSuspend| and
-// |SbSystemRequestUnpause| will be made periodically. Requires
-// SB_API_VERSION >= 4, and will otherwise just no-op.
+// |SbSystemRequestUnpause| will be made periodically.
const char kSuspendFuzzer[] = "suspend_fuzzer";
// If this is set, then a trace (see base/debug/trace_eventh.h) is started on
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 4470c4e..4dc02ef 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -24,8 +24,8 @@
extern const char kAudioDecoderStub[];
extern const char kCspMode[];
extern const char kDebugConsoleMode[];
+extern const char kDisableWebDriver[];
extern const char kDisableWebmVp9[];
-extern const char kEnableWebDriver[];
extern const char kExtraWebFileDir[];
extern const char kFakeMicrophone[];
extern const char kIgnoreCertificateErrors[];
diff --git a/src/cobalt/browser/testdata/splash_screen/beforeunload.html b/src/cobalt/browser/testdata/splash_screen/beforeunload.html
new file mode 100644
index 0000000..5b6ecf9
--- /dev/null
+++ b/src/cobalt/browser/testdata/splash_screen/beforeunload.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+.box {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ color : yellow;
+ font-size: 20px;
+ left: 0px;
+ top: 0px;
+ position: absolute;
+ transition: background-color .25s;
+ transition-timing-function: ease;
+}
+.box1{
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ color: yellow;
+ font-size: 20px;
+ left: 0px;
+ top: 0px;
+ position:absolute;
+ transition: background-color .25s;
+ transition-timing-function: ease;
+}
+ </style>
+ </head>
+<body style="background-color: #1f52a5;">
+<div class="box" id="box">Sample</div>
+<div id="immediately" style="display:block;">
+THIS SHOWS IMMEDIATELY
+</div>
+
+<div id="beforeUnload" style="display:none;">
+THIS SHOWS AT BEFOREUNLOAD
+</div>
+
+<div id="transitionEnd" style="display:none;">
+THIS SHOWS AT TRANSITIONEND
+</div>
+
+
+<script>
+ console.log('Running the script in beforeunload.html');
+
+ function updateTransition() {
+ var el = document.getElementById("box");
+ if (el.className == "box") {
+ el.className = "box1";
+ } else {
+ el.className = "box";
+ }
+ }
+
+ function transitionEndFunction() {
+ console.log("transitionend event");
+ // Set this to true to simulate an unresponsive splash screen which does
+ // not call window.close().
+ var unresponsive = true;
+ // This style change is only shown if unresponsive.
+ document.getElementById("transitionEnd").style.display="block";
+ // Comment this out to test out an unresponsive
+ // window.ontransitionend function.
+ if (!unresponsive) {
+ window.close();
+ }
+ }
+ // Either event handler or event listeners should work.
+ window.ontransitionend = transitionEndFunction;
+ //window.addEventListener("transitionend", transitionEndFunction, true);
+
+ function beforeUnloadFunction() {
+ console.log("beforeunload event");
+ document.getElementById("immediately").style.display="none";
+ document.getElementById("beforeUnload").style.display="block";
+ window.updateTransition();
+ // Returning a string shows a confirmation dialog in Chrome.
+ return "returning text is futile";
+ };
+ // Either event handler or event listeners should work.
+ // window.onbeforeunload = beforeUnloadFunction;
+ window.addEventListener("beforeunload", beforeUnloadFunction, true);
+
+ console.log("Ran the script in beforeunload.html");
+</script>
+
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/splash_screen/block_render_tree_head_body_display_none.html b/src/cobalt/browser/testdata/splash_screen/block_render_tree_head_body_display_none.html
new file mode 100644
index 0000000..f481c4d
--- /dev/null
+++ b/src/cobalt/browser/testdata/splash_screen/block_render_tree_head_body_display_none.html
@@ -0,0 +1,441 @@
+<!DOCTYPE html>
+<html>
+
+<head style="display : none">
+ <meta http-equiv="Content-Security-Policy" content="
+ default-src 'unsafe-inline';
+ style-src 'unsafe-inline';
+ script-src 'unsafe-inline';">
+</head>
+
+<script>
+ window.setTimeout(function() {
+ document.getElementsByTagName('body')[0].style.display = 'block';
+ }, 5000);
+</script>
+
+<body style="background-color: #1f52a5; display: none">
+<h1>Heading</h1>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/splash_screen/block_render_tree_html_display_none.html b/src/cobalt/browser/testdata/splash_screen/block_render_tree_html_display_none.html
new file mode 100644
index 0000000..d5f4552
--- /dev/null
+++ b/src/cobalt/browser/testdata/splash_screen/block_render_tree_html_display_none.html
@@ -0,0 +1,440 @@
+<!DOCTYPE html>
+<html style="display: none">
+
+<head>
+ <meta http-equiv="Content-Security-Policy" content="
+ default-src 'unsafe-inline';
+ style-src 'unsafe-inline';
+ script-src 'unsafe-inline';">
+</head>
+
+<script>
+ window.setTimeout(function() {
+ document.getElementsByTagName('html')[0].style.display = 'block';
+ }, 5000);
+</script>
+<body style="background-color: #1f52a5;">
+<h1>Heading</h1>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+</body>
+</html>
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 87fc9f2..729697f 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -54,6 +54,7 @@
#include "cobalt/loader/image/animated_image_tracker.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/page_visibility/visibility_state.h"
+#include "cobalt/script/error_report.h"
#include "cobalt/script/javascript_engine.h"
#include "cobalt/storage/storage_manager.h"
#include "starboard/accessibility.h"
@@ -143,6 +144,12 @@
void InjectWheelEvent(scoped_refptr<dom::Element> element, base::Token type,
const dom::WheelEventInit& event);
+ // Called to inject a beforeunload event into the web module. If
+ // this event is not handled by the web application,
+ // on_before_unload_fired_but_not_handled will be called. The event
+ // is not directed at a specific element.
+ void InjectBeforeUnloadEvent();
+
// Called to execute JavaScript in this WebModule. Sets the |result|
// output parameter and signals |got_result|.
void ExecuteJavascript(const std::string& script_utf8,
@@ -197,8 +204,10 @@
void Unpause();
void Resume(render_tree::ResourceProvider* resource_provider);
- void ReportScriptError(const base::SourceLocation& source_location,
- const std::string& error_message);
+ void ReduceMemory();
+
+ void LogScriptError(const base::SourceLocation& source_location,
+ const std::string& error_message);
private:
class DocumentLoadedObserver;
@@ -233,6 +242,10 @@
error_callback_.Run(window_->location()->url(), error);
}
+ // Report an error encountered while running JS.
+ // Returns whether or not the error was handled.
+ bool ReportScriptError(const script::ErrorReport& error_report);
+
// Inject the DOM event object into the window or the element.
void InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<dom::Event>& event);
@@ -358,6 +371,8 @@
scoped_ptr<media_session::MediaSessionClient> media_session_client_;
scoped_ptr<layout::TopmostEventTarget> topmost_event_target_;
+
+ base::Closure on_before_unload_fired_but_not_handled;
};
class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
@@ -422,6 +437,9 @@
base::Unretained(data.options.splash_screen_cache));
}
+ on_before_unload_fired_but_not_handled =
+ data.options.on_before_unload_fired_but_not_handled;
+
fetcher_factory_.reset(new loader::FetcherFactory(
data.network_module, data.options.extra_web_file_dir,
dom::URL::MakeBlobResolverCallback(blob_registry_.get()),
@@ -475,7 +493,7 @@
#if defined(COBALT_ENABLE_JAVASCRIPT_ERROR_LOGGING)
script::JavaScriptEngine::ErrorHandler error_handler =
- base::Bind(&WebModule::Impl::ReportScriptError, base::Unretained(this));
+ base::Bind(&WebModule::Impl::LogScriptError, base::Unretained(this));
javascript_engine_->RegisterErrorHandler(error_handler);
#endif
@@ -578,6 +596,9 @@
base::Bind(&dom::CspDelegate::ReportEval,
base::Unretained(window_->document()->csp_delegate())));
+ global_environment_->SetReportErrorCallback(
+ base::Bind(&WebModule::Impl::ReportScriptError, base::Unretained(this)));
+
InjectCustomWindowAttributes(data.options.injected_window_attributes);
if (!data.options.loaded_callbacks.empty()) {
@@ -594,6 +615,8 @@
DCHECK(is_running_);
is_running_ = false;
global_environment_->SetReportEvalCallback(base::Closure());
+ global_environment_->SetReportErrorCallback(
+ script::GlobalEnvironment::ReportErrorCallback());
window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
document_load_observer_.reset();
media_session_client_.reset();
@@ -750,6 +773,14 @@
}
}
+bool WebModule::Impl::ReportScriptError(
+ const script::ErrorReport& error_report) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(is_running_);
+ DCHECK(window_);
+ return window_->ReportScriptError(error_report);
+}
+
#if defined(ENABLE_WEBDRIVER)
void WebModule::Impl::CreateWindowDriver(
const webdriver::protocol::WindowId& window_id,
@@ -925,7 +956,23 @@
SetApplicationState(base::kApplicationStatePaused);
}
-void WebModule::Impl::ReportScriptError(
+void WebModule::Impl::ReduceMemory() {
+ TRACE_EVENT0("cobalt::browser", "WebModule::Impl::ReduceMemory()");
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!is_running_) {
+ return;
+ }
+
+ PurgeResourceCaches();
+ window_->document()->PurgeCachedResources();
+
+ // Force garbage collection in |javascript_engine_|.
+ if (javascript_engine_) {
+ javascript_engine_->CollectGarbage();
+ }
+}
+
+void WebModule::Impl::LogScriptError(
const base::SourceLocation& source_location,
const std::string& error_message) {
std::string file_name =
@@ -944,6 +991,15 @@
SbLogRaw(ss.str().c_str());
}
+void WebModule::Impl::InjectBeforeUnloadEvent() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (window_ && window_->HasEventListener(base::Tokens::beforeunload())) {
+ window_->DispatchEvent(new dom::Event(base::Tokens::beforeunload()));
+ } else if (!on_before_unload_fired_but_not_handled.is_null()) {
+ on_before_unload_fired_but_not_handled.Run();
+ }
+}
+
void WebModule::Impl::PurgeResourceCaches() {
image_cache_->Purge();
remote_typeface_cache_->Purge();
@@ -1097,6 +1153,15 @@
scoped_refptr<dom::Element>(), type, event));
}
+void WebModule::InjectBeforeUnloadEvent() {
+ TRACE_EVENT0("cobalt::browser", "WebModule::InjectBeforeUnloadEvent()");
+ DCHECK(message_loop());
+ DCHECK(impl_);
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&WebModule::Impl::InjectBeforeUnloadEvent,
+ base::Unretained(impl_.get())));
+}
+
std::string WebModule::ExecuteJavascript(
const std::string& script_utf8,
const base::SourceLocation& script_location,
@@ -1287,6 +1352,17 @@
base::Unretained(impl_.get()), resource_provider));
}
+void WebModule::ReduceMemory() {
+ // Must only be called by a thread external from the WebModule thread.
+ DCHECK_NE(MessageLoop::current(), message_loop());
+
+ // We block here so that we block the Low Memory event handler until we have
+ // reduced our memory consumption.
+ message_loop()->PostBlockingTask(
+ FROM_HERE, base::Bind(&WebModule::Impl::ReduceMemory,
+ base::Unretained(impl_.get())));
+}
+
void WebModule::Impl::HandlePointerEvents() {
TRACE_EVENT0("cobalt::browser", "WebModule::Impl::HandlePointerEvents");
const scoped_refptr<dom::Document>& document = window_->document();
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index b296d6f..ba23707 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -185,6 +185,14 @@
// The splash screen cache object, owned by the BrowserModule.
SplashScreenCache* splash_screen_cache;
+ // The beforeunload event can give a web page a chance to shut
+ // itself down softly and ultimately call window.close(), however
+ // if it is not handled by the web application, we indicate this
+ // situation externally by calling this callback, so that if the
+ // beforeunload event was generated it can be known that there is
+ // no window.close() call pending.
+ base::Closure on_before_unload_fired_but_not_handled;
+
// Whether or not the WebModule is allowed to fetch from cache via
// h5vcc-cache://.
bool can_fetch_cache;
@@ -222,6 +230,11 @@
// represents the event name, for example 'wheel'.
void InjectWheelEvent(base::Token type, const dom::WheelEventInit& event);
+ // Call this to inject a beforeunload event into the web module. If
+ // this event is not handled by the web application,
+ // on_before_unload_fired_but_not_handled will be called.
+ void InjectBeforeUnloadEvent();
+
// Call this to execute Javascript code in this web module. The calling
// thread will block until the JavaScript has executed and the output results
// are available.
@@ -262,6 +275,10 @@
void Suspend() OVERRIDE;
void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE;
+ // Attempt to reduce overall memory consumption. Called in response to a
+ // system indication that memory usage is nearing a critical level.
+ void ReduceMemory();
+
private:
// Data required to construct a WebModule, initialized in the constructor and
// passed to |Initialize|.
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 3c54c85..248bc70 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-90790
\ No newline at end of file
+93830
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index a3243c1..02c5ec2 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -790,10 +790,9 @@
# Clients must copy over all content; to avoid having to copy over extra data, we
# omit the test data
'conditions': [
- ['cobalt_config != "gold" and cobalt_enable_lib == 0', {
+ ['cobalt_config != "gold"', {
'variables' : {
'cobalt_copy_debug_console': 1,
- 'cobalt_copy_test_data': 1,
'enable_about_scheme': 1,
'enable_fake_microphone': 1,
'enable_file_scheme': 1,
@@ -807,7 +806,6 @@
{
'variables' : {
'cobalt_copy_debug_console': 0,
- 'cobalt_copy_test_data': 0,
'enable_about_scheme': 0,
'enable_fake_microphone': 0,
'enable_file_scheme': 0,
@@ -818,5 +816,15 @@
'sb_allows_memory_tracking': 0,
},
}],
+ ['cobalt_config != "gold" and cobalt_enable_lib == 0', {
+ 'variables' : {
+ 'cobalt_copy_test_data': 1,
+ },
+ },
+ {
+ 'variables' : {
+ 'cobalt_copy_test_data': 0,
+ },
+ }],
],
}
diff --git a/src/cobalt/build/config/starboard.py b/src/cobalt/build/config/starboard.py
index ecba441..2af50cd 100644
--- a/src/cobalt/build/config/starboard.py
+++ b/src/cobalt/build/config/starboard.py
@@ -81,9 +81,6 @@
# Cobalt uses OpenSSL on all platforms.
'use_openssl': 1,
'clang': use_clang,
- # Cobalt relies on the Starboard implementation for DRM on all Starboard
- # platforms.
- 'use_widevine': 0,
# Whether to build with clang's Address Sanitizer instrumentation.
'use_asan': use_asan,
# Whether to build with clang's Thread Sanitizer instrumentation.
diff --git a/src/cobalt/debug/debug_web_server.cc b/src/cobalt/debug/debug_web_server.cc
index 6ffd152..baa0fb9 100644
--- a/src/cobalt/debug/debug_web_server.cc
+++ b/src/cobalt/debug/debug_web_server.cc
@@ -30,12 +30,7 @@
#include "net/base/net_errors.h"
#include "net/base/tcp_listen_socket.h"
#include "net/server/http_server_request_info.h"
-
-#if defined(__LB_SHELL__)
-#include "lb_network_helpers.h" // NOLINT[build/include]
-#elif defined(OS_STARBOARD)
#include "starboard/socket.h"
-#endif
namespace cobalt {
namespace debug {
@@ -84,7 +79,6 @@
net::IPEndPoint ip_addr;
SbSocketAddress local_ip;
SbMemorySet(&local_ip, 0, sizeof(local_ip));
-#if SB_API_VERSION >= 4
bool result = false;
// Prefer IPv4 addresses, as they're easier to type for debugging.
@@ -109,19 +103,6 @@
DLOG(WARNING) << "Unable to get a local interface address.";
return base::nullopt;
}
-#else
- bool result = SbSocketGetLocalInterfaceAddress(&local_ip);
- if (!result) {
- DLOG(WARNING) << "Unable to get a local interface address.";
- return base::nullopt;
- }
-
- result = ip_addr.FromSbSocketAddress(&local_ip);
- if (!result) {
- LOG(WARNING) << "Got invalid local interface address.";
- return base::nullopt;
- }
-#endif // SB_API_VERSION >= 4
return ip_addr.ToStringWithoutPort();
}
diff --git a/src/cobalt/dom/custom_event.h b/src/cobalt/dom/custom_event.h
new file mode 100644
index 0000000..615eb6a
--- /dev/null
+++ b/src/cobalt/dom/custom_event.h
@@ -0,0 +1,76 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_CUSTOM_EVENT_H_
+#define COBALT_DOM_CUSTOM_EVENT_H_
+
+#include <string>
+
+#include "cobalt/dom/custom_event_init.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/script/value_handle.h"
+
+namespace cobalt {
+namespace dom {
+
+// Events using the CustomEvent interface can be used to carry custom data.
+// https://www.w3.org/TR/2015/REC-dom-20151119/#customevent
+class CustomEvent : public Event {
+ public:
+ explicit CustomEvent(const std::string& type) : Event(type) {}
+ CustomEvent(const std::string& type, const CustomEventInit& init_dict)
+ : Event(type, init_dict) {
+ set_detail(init_dict.detail());
+ }
+
+ // Creates an event with its "initialized flag" unset.
+ explicit CustomEvent(UninitializedFlag uninitialized_flag)
+ : Event(uninitialized_flag) {}
+
+ // Web API: CustomEvent
+ //
+ void InitCustomEvent(const std::string& type, bool bubbles, bool cancelable,
+ const script::ValueHandleHolder& detail) {
+ InitEvent(type, bubbles, cancelable);
+ set_detail(&detail);
+ }
+
+ void set_detail(const script::ValueHandleHolder* detail) {
+ if (detail) {
+ detail_.reset(new script::ValueHandleHolder::Reference(this, *detail));
+ } else {
+ detail_.reset();
+ }
+ }
+
+ const script::ValueHandleHolder* detail() const {
+ if (!detail_) {
+ return NULL;
+ }
+
+ return &(detail_->referenced_value());
+ }
+
+ DEFINE_WRAPPABLE_TYPE(CustomEvent);
+
+ protected:
+ ~CustomEvent() OVERRIDE {}
+
+ scoped_ptr<script::ValueHandleHolder::Reference> detail_;
+};
+
+} // namespace dom
+} // namespace cobalt
+
+#endif // COBALT_DOM_CUSTOM_EVENT_H_
diff --git a/src/cobalt/dom/custom_event.idl b/src/cobalt/dom/custom_event.idl
new file mode 100644
index 0000000..cd4f555
--- /dev/null
+++ b/src/cobalt/dom/custom_event.idl
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://www.w3.org/TR/2015/REC-dom-20151119/#customevent
+[Constructor(DOMString type, optional CustomEventInit eventInitDict)]
+interface CustomEvent : Event {
+ readonly attribute any detail;
+
+ void initCustomEvent(DOMString type,
+ boolean bubbles,
+ boolean cancelable,
+ any detail);
+};
diff --git a/src/cobalt/dom/custom_event_init.idl b/src/cobalt/dom/custom_event_init.idl
new file mode 100644
index 0000000..2967077
--- /dev/null
+++ b/src/cobalt/dom/custom_event_init.idl
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://www.w3.org/TR/dom/#customeventinit
+
+dictionary CustomEventInit : EventInit {
+ any detail = null;
+};
diff --git a/src/cobalt/dom/custom_event_test.cc b/src/cobalt/dom/custom_event_test.cc
new file mode 100644
index 0000000..d8b4c42
--- /dev/null
+++ b/src/cobalt/dom/custom_event_test.cc
@@ -0,0 +1,198 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/custom_event.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/css_parser/parser.h"
+#include "cobalt/dom/custom_event_init.h"
+#include "cobalt/dom/local_storage_database.h"
+#include "cobalt/dom/testing/gtest_workarounds.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/dom_parser/parser.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/media/media_module_stub.h"
+#include "cobalt/media_session/media_session.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "nb/pointer_arithmetic.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+using ::cobalt::script::testing::FakeScriptValue;
+
+class MockErrorCallback : public base::Callback<void(const std::string&)> {
+ public:
+ MOCK_METHOD1(Run, void(const std::string&));
+};
+
+namespace {
+class CustomEventTest : public ::testing::Test {
+ public:
+ CustomEventTest()
+ : environment_settings_(new script::EnvironmentSettings),
+ message_loop_(MessageLoop::TYPE_DEFAULT),
+ css_parser_(css_parser::Parser::Create()),
+ dom_parser_(new dom_parser::Parser(mock_error_callback_)),
+ fetcher_factory_(new loader::FetcherFactory(&network_module_)),
+ local_storage_database_(NULL),
+ stub_media_module_(new media::MediaModuleStub()),
+ url_("about:blank"),
+ window_(new Window(
+ 1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
+ dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
+ NULL, NULL, &local_storage_database_, stub_media_module_.get(),
+ stub_media_module_.get(), NULL, NULL, NULL, NULL, NULL, url_, "",
+ "en-US", base::Callback<void(const GURL&)>(),
+ base::Bind(&MockErrorCallback::Run,
+ base::Unretained(&mock_error_callback_)),
+ NULL, network_bridge::PostSender(),
+ std::string() /* default security policy */, kCspEnforcementEnable,
+ base::Closure() /* csp_policy_changed */,
+ base::Closure() /* ran_animation_frame_callbacks */,
+ base::Closure() /* window_close */,
+ base::Closure() /* window_minimize */, NULL, NULL)) {
+ engine_ = script::JavaScriptEngine::CreateEngine(
+ script::JavaScriptEngine::Options());
+ global_environment_ = engine_->CreateGlobalEnvironment();
+ global_environment_->CreateGlobalObject(window_,
+ environment_settings_.get());
+ }
+
+ bool EvaluateScript(const std::string& js_code, std::string* result);
+
+ private:
+ scoped_ptr<script::JavaScriptEngine> engine_;
+ scoped_refptr<script::GlobalEnvironment> global_environment_;
+
+ const scoped_ptr<script::EnvironmentSettings> environment_settings_;
+ MessageLoop message_loop_;
+ MockErrorCallback mock_error_callback_;
+ scoped_ptr<css_parser::Parser> css_parser_;
+ scoped_ptr<dom_parser::Parser> dom_parser_;
+ network::NetworkModule network_module_;
+ scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+ dom::LocalStorageDatabase local_storage_database_;
+ scoped_ptr<media::MediaModule> stub_media_module_;
+ GURL url_;
+ const scoped_refptr<Window> window_;
+};
+
+bool CustomEventTest::EvaluateScript(const std::string& js_code,
+ std::string* result) {
+ DCHECK(global_environment_);
+ DCHECK(result);
+ scoped_refptr<script::SourceCode> source_code =
+ script::SourceCode::CreateSourceCode(
+ js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+
+ global_environment_->EnableEval();
+ global_environment_->SetReportEvalCallback(base::Closure());
+ bool succeeded = global_environment_->EvaluateScript(source_code, result);
+ return succeeded;
+}
+} // namespace
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeString) {
+ scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent");
+
+ EXPECT_EQ("mytestevent", event->type());
+ EXPECT_EQ(NULL, event->target());
+ EXPECT_EQ(NULL, event->current_target());
+ EXPECT_EQ(Event::kNone, event->event_phase());
+ EXPECT_FALSE(event->bubbles());
+ EXPECT_FALSE(event->cancelable());
+ EXPECT_FALSE(event->default_prevented());
+ EXPECT_FALSE(event->IsBeingDispatched());
+ EXPECT_FALSE(event->propagation_stopped());
+ EXPECT_FALSE(event->immediate_propagation_stopped());
+ EXPECT_EQ(NULL, event->detail());
+}
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
+ CustomEventInit init;
+ scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent", init);
+
+ EXPECT_EQ("mytestevent", event->type());
+ EXPECT_EQ(NULL, event->target());
+ EXPECT_EQ(NULL, event->current_target());
+ EXPECT_EQ(Event::kNone, event->event_phase());
+ EXPECT_FALSE(event->bubbles());
+ EXPECT_FALSE(event->cancelable());
+ EXPECT_FALSE(event->default_prevented());
+ EXPECT_FALSE(event->IsBeingDispatched());
+ EXPECT_FALSE(event->propagation_stopped());
+ EXPECT_FALSE(event->immediate_propagation_stopped());
+ EXPECT_EQ(NULL, event->detail());
+}
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeAndCustomInitDict) {
+ std::string result;
+ bool success = EvaluateScript(
+ "var event = new CustomEvent('dog', "
+ " {'bubbles':true, "
+ " 'cancelable':true, "
+ " 'detail':{'cobalt':'rulez'}});"
+ "if (event.type == 'dog' &&"
+ " event.bubbles == true &&"
+ " event.cancelable == true) "
+ " event.detail.cobalt;",
+ &result);
+ EXPECT_EQ("rulez", result);
+
+ if (!success) {
+ DLOG(ERROR) << "Failed to evaluate test: "
+ << "\"" << result << "\"";
+ } else {
+ LOG(INFO) << "Test result : "
+ << "\"" << result << "\"";
+ }
+}
+
+TEST_F(CustomEventTest, InitCustomEvent) {
+ std::string result;
+ bool success = EvaluateScript(
+ "var event = new CustomEvent('cat');\n"
+ "event.initCustomEvent('dog', true, true, {cobalt:'rulez'});"
+ "if (event.type == 'dog' &&"
+ " event.detail &&"
+ " event.bubbles == true &&"
+ " event.cancelable == true) "
+ " event.detail.cobalt;",
+ &result);
+ EXPECT_EQ("rulez", result);
+
+ if (!success) {
+ DLOG(ERROR) << "Failed to evaluate test: "
+ << "\"" << result << "\"";
+ } else {
+ LOG(INFO) << "Test result : "
+ << "\"" << result << "\"";
+ }
+}
+
+} // namespace dom
+} // namespace cobalt
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index ff262c6..ec42f43 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -34,6 +34,7 @@
#include "cobalt/dom/comment.h"
#include "cobalt/dom/csp_delegate.h"
#include "cobalt/dom/csp_delegate_factory.h"
+#include "cobalt/dom/custom_event.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_implementation.h"
#include "cobalt/dom/element.h"
@@ -240,6 +241,8 @@
} else if (base::strcasecmp(interface_name.c_str(), "uievent") == 0 ||
base::strcasecmp(interface_name.c_str(), "uievents") == 0) {
return new UIEvent(Event::Uninitialized);
+ } else if (base::strcasecmp(interface_name.c_str(), "customevent") == 0) {
+ return new CustomEvent(Event::Uninitialized);
}
DOMException::Raise(
diff --git a/src/cobalt/dom/document_test.cc b/src/cobalt/dom/document_test.cc
index 2a4ee06..7e4c391 100644
--- a/src/cobalt/dom/document_test.cc
+++ b/src/cobalt/dom/document_test.cc
@@ -19,6 +19,7 @@
#include "cobalt/cssom/css_style_sheet.h"
#include "cobalt/dom/attr.h"
#include "cobalt/dom/comment.h"
+#include "cobalt/dom/custom_event.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_implementation.h"
#include "cobalt/dom/dom_stat_tracker.h"
@@ -172,6 +173,19 @@
EXPECT_FALSE(event->initialized_flag());
}
+TEST_F(DocumentTest, CreateEventCustomEvent) {
+ StrictMock<MockExceptionState> exception_state;
+ scoped_refptr<script::ScriptException> exception;
+ scoped_refptr<Document> document = new Document(&html_element_context_);
+
+ // Create an Event, the name is case insensitive.
+ scoped_refptr<Event> event =
+ document->CreateEvent("CuStOmEvEnT", &exception_state);
+ EXPECT_TRUE(event);
+ EXPECT_FALSE(event->initialized_flag());
+ EXPECT_TRUE(base::polymorphic_downcast<CustomEvent*>(event.get()));
+}
+
TEST_F(DocumentTest, CreateEventUIEvent) {
StrictMock<MockExceptionState> exception_state;
scoped_refptr<script::ScriptException> exception;
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 88ef23d..27a8f29 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -62,6 +62,7 @@
'css_animations_adapter.h',
'css_transitions_adapter.cc',
'css_transitions_adapter.h',
+ 'custom_event.h',
'data_view.cc',
'data_view.h',
'device_orientation_event.cc',
@@ -93,8 +94,10 @@
'dom_token_list.h',
'element.cc',
'element.h',
+ 'error_event.h',
'event.cc',
'event.h',
+ 'event_init.h',
'event_listener.cc',
'event_listener.h',
'event_queue.cc',
@@ -105,6 +108,7 @@
'float64_array.h',
'focus_event.cc',
'focus_event.h',
+ 'focus_event_init.h',
'font_cache.cc',
'font_cache.h',
'font_face.cc',
@@ -164,6 +168,7 @@
'initial_computed_style.h',
'keyboard_event.cc',
'keyboard_event.h',
+ 'keyboard_event_init.h',
'keycode.h',
'keyframes_map_updater.cc',
'keyframes_map_updater.h',
@@ -182,6 +187,7 @@
'mime_type_array.h',
'mouse_event.cc',
'mouse_event.h',
+ 'mouse_event_init.h',
'mutation_observer.cc',
'mutation_observer.h',
'mutation_observer_init.h',
@@ -212,6 +218,7 @@
'plugin_array.h',
'pointer_event.cc',
'pointer_event.h',
+ 'pointer_event_init.h',
'pointer_state.cc',
'pointer_state.h',
'progress_event.cc',
@@ -248,6 +255,7 @@
'typed_array.h',
'ui_event.cc',
'ui_event.h',
+ 'ui_event_init.h',
'ui_event_with_key_state.cc',
'ui_event_with_key_state.h',
'uint8_array.h',
@@ -260,6 +268,7 @@
'video_track_list.h',
'wheel_event.cc',
'wheel_event.h',
+ 'wheel_event_init.h',
'window.cc',
'window.h',
'window_timers.cc',
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 2c41b83..8cf266a 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -28,6 +28,7 @@
'comment_test.cc',
'crypto_test.cc',
'csp_delegate_test.cc',
+ 'custom_event_test.cc',
'data_view_test.cc',
'document_test.cc',
'document_type_test.cc',
@@ -37,6 +38,7 @@
'dom_string_map_test.cc',
'dom_token_list_test.cc',
'element_test.cc',
+ 'error_event_test.cc',
'event_queue_test.cc',
'event_target_test.cc',
'event_test.cc',
diff --git a/src/cobalt/dom/error_event.h b/src/cobalt/dom/error_event.h
new file mode 100644
index 0000000..d3b3b71
--- /dev/null
+++ b/src/cobalt/dom/error_event.h
@@ -0,0 +1,90 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_ERROR_EVENT_H_
+#define COBALT_DOM_ERROR_EVENT_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/dom/error_event_init.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/script/value_handle.h"
+
+namespace cobalt {
+namespace dom {
+
+// Whenever an uncaught runtime script error occurs in one of the scripts
+// associated with a Document, the user agent must report the error for the
+// relevant script.
+// https://www.w3.org/TR/html5/webappapis.html#errorevent
+class ErrorEvent : public Event {
+ public:
+ explicit ErrorEvent(const std::string& type)
+ : Event(type), lineno_(0), colno_(0) {}
+ ErrorEvent(const std::string& type, const ErrorEventInit& init_dict)
+ : Event(type, init_dict),
+ message_(init_dict.message()),
+ filename_(init_dict.filename()),
+ lineno_(init_dict.lineno()),
+ colno_(init_dict.colno()) {
+ InitError(init_dict);
+ }
+ ErrorEvent(base::Token type, const ErrorEventInit& init_dict)
+ : Event(type, init_dict),
+ message_(init_dict.message()),
+ filename_(init_dict.filename()),
+ lineno_(init_dict.lineno()),
+ colno_(init_dict.colno()) {
+ InitError(init_dict);
+ }
+
+ // Web API: ErrorEvent
+ //
+ std::string message() const { return message_; }
+ std::string filename() const { return filename_; }
+ uint32 lineno() const { return lineno_; }
+ uint32 colno() const { return colno_; }
+
+ const script::ValueHandleHolder* error() const {
+ if (!error_) {
+ return NULL;
+ }
+ return &(error_->referenced_value());
+ }
+
+ DEFINE_WRAPPABLE_TYPE(ErrorEvent);
+
+ protected:
+ ~ErrorEvent() OVERRIDE {}
+
+ private:
+ void InitError(const ErrorEventInit& init_dict) {
+ const script::ValueHandleHolder* error = init_dict.error();
+ if (error) {
+ error_.reset(new script::ValueHandleHolder::Reference(this, *error));
+ }
+ }
+
+ std::string message_;
+ std::string filename_;
+ uint32 lineno_;
+ uint32 colno_;
+ scoped_ptr<script::ValueHandleHolder::Reference> error_;
+};
+
+} // namespace dom
+} // namespace cobalt
+
+#endif // COBALT_DOM_ERROR_EVENT_H_
diff --git a/src/cobalt/dom/error_event.idl b/src/cobalt/dom/error_event.idl
new file mode 100644
index 0000000..10c4699
--- /dev/null
+++ b/src/cobalt/dom/error_event.idl
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://www.w3.org/TR/html5/webappapis.html#errorevent
+
+[Constructor(DOMString type, optional ErrorEventInit eventInitDict)]
+interface ErrorEvent : Event {
+ readonly attribute DOMString message;
+ readonly attribute DOMString filename;
+ readonly attribute unsigned long lineno;
+ readonly attribute unsigned long colno;
+ readonly attribute any error;
+};
diff --git a/src/cobalt/dom/error_event_init.idl b/src/cobalt/dom/error_event_init.idl
new file mode 100644
index 0000000..77e80c0
--- /dev/null
+++ b/src/cobalt/dom/error_event_init.idl
@@ -0,0 +1,23 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://www.w3.org/TR/html5/webappapis.html#erroreventinit
+
+dictionary ErrorEventInit : EventInit {
+ DOMString message = "";
+ DOMString filename = "";
+ unsigned long lineno = 0;
+ unsigned long colno = 0;
+ any error = null;
+};
diff --git a/src/cobalt/dom/error_event_test.cc b/src/cobalt/dom/error_event_test.cc
new file mode 100644
index 0000000..b8ead45
--- /dev/null
+++ b/src/cobalt/dom/error_event_test.cc
@@ -0,0 +1,191 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/error_event.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/css_parser/parser.h"
+#include "cobalt/dom/error_event_init.h"
+#include "cobalt/dom/local_storage_database.h"
+#include "cobalt/dom/testing/gtest_workarounds.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/dom_parser/parser.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/media/media_module_stub.h"
+#include "cobalt/media_session/media_session.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "nb/pointer_arithmetic.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+using ::cobalt::script::testing::FakeScriptValue;
+
+class MockErrorCallback : public base::Callback<void(const std::string&)> {
+ public:
+ MOCK_METHOD1(Run, void(const std::string&));
+};
+
+namespace {
+class ErrorEventTest : public ::testing::Test {
+ public:
+ ErrorEventTest()
+ : environment_settings_(new script::EnvironmentSettings),
+ message_loop_(MessageLoop::TYPE_DEFAULT),
+ css_parser_(css_parser::Parser::Create()),
+ dom_parser_(new dom_parser::Parser(mock_error_callback_)),
+ fetcher_factory_(new loader::FetcherFactory(&network_module_)),
+ local_storage_database_(NULL),
+ stub_media_module_(new media::MediaModuleStub()),
+ url_("about:blank"),
+ window_(new Window(
+ 1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
+ dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
+ NULL, NULL, &local_storage_database_, stub_media_module_.get(),
+ stub_media_module_.get(), NULL, NULL, NULL, NULL, NULL, url_, "",
+ "en-US", base::Callback<void(const GURL&)>(),
+ base::Bind(&MockErrorCallback::Run,
+ base::Unretained(&mock_error_callback_)),
+ NULL, network_bridge::PostSender(),
+ std::string() /* default security policy */, kCspEnforcementEnable,
+ base::Closure() /* csp_policy_changed */,
+ base::Closure() /* ran_animation_frame_callbacks */,
+ base::Closure() /* window_close */,
+ base::Closure() /* window_minimize */, NULL, NULL)) {
+ engine_ = script::JavaScriptEngine::CreateEngine(
+ script::JavaScriptEngine::Options());
+ global_environment_ = engine_->CreateGlobalEnvironment();
+ global_environment_->CreateGlobalObject(window_,
+ environment_settings_.get());
+ }
+
+ bool EvaluateScript(const std::string& js_code, std::string* result);
+
+ private:
+ scoped_ptr<script::JavaScriptEngine> engine_;
+ scoped_refptr<script::GlobalEnvironment> global_environment_;
+
+ const scoped_ptr<script::EnvironmentSettings> environment_settings_;
+ MessageLoop message_loop_;
+ MockErrorCallback mock_error_callback_;
+ scoped_ptr<css_parser::Parser> css_parser_;
+ scoped_ptr<dom_parser::Parser> dom_parser_;
+ network::NetworkModule network_module_;
+ scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+ dom::LocalStorageDatabase local_storage_database_;
+ scoped_ptr<media::MediaModule> stub_media_module_;
+ GURL url_;
+ const scoped_refptr<Window> window_;
+};
+
+bool ErrorEventTest::EvaluateScript(const std::string& js_code,
+ std::string* result) {
+ DCHECK(global_environment_);
+ DCHECK(result);
+ scoped_refptr<script::SourceCode> source_code =
+ script::SourceCode::CreateSourceCode(
+ js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+
+ global_environment_->EnableEval();
+ global_environment_->SetReportEvalCallback(base::Closure());
+ bool succeeded = global_environment_->EvaluateScript(source_code, result);
+ return succeeded;
+}
+} // namespace
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeString) {
+ scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent");
+
+ EXPECT_EQ("mytestevent", event->type());
+ EXPECT_EQ(NULL, event->target());
+ EXPECT_EQ(NULL, event->current_target());
+ EXPECT_EQ(Event::kNone, event->event_phase());
+ EXPECT_FALSE(event->bubbles());
+ EXPECT_FALSE(event->cancelable());
+ EXPECT_FALSE(event->default_prevented());
+ EXPECT_FALSE(event->IsBeingDispatched());
+ EXPECT_FALSE(event->propagation_stopped());
+ EXPECT_FALSE(event->immediate_propagation_stopped());
+ EXPECT_EQ("", event->message());
+ EXPECT_EQ("", event->filename());
+ EXPECT_EQ(0, event->lineno());
+ EXPECT_EQ(0, event->colno());
+ EXPECT_EQ(NULL, event->error());
+}
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
+ ErrorEventInit init;
+ scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent", init);
+
+ EXPECT_EQ("mytestevent", event->type());
+ EXPECT_EQ(NULL, event->target());
+ EXPECT_EQ(NULL, event->current_target());
+ EXPECT_EQ(Event::kNone, event->event_phase());
+ EXPECT_FALSE(event->bubbles());
+ EXPECT_FALSE(event->cancelable());
+ EXPECT_FALSE(event->default_prevented());
+ EXPECT_FALSE(event->IsBeingDispatched());
+ EXPECT_FALSE(event->propagation_stopped());
+ EXPECT_FALSE(event->immediate_propagation_stopped());
+ EXPECT_EQ("", event->message());
+ EXPECT_EQ("", event->filename());
+ EXPECT_EQ(0, event->lineno());
+ EXPECT_EQ(0, event->colno());
+ EXPECT_EQ(NULL, event->error());
+}
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeAndErrorInitDict) {
+ std::string result;
+ bool success = EvaluateScript(
+ "var event = new ErrorEvent('dog', "
+ " {'cancelable':true, "
+ " 'message':'error_message', "
+ " 'filename':'error_filename', "
+ " 'lineno':100, "
+ " 'colno':50, "
+ " 'error':{'cobalt':'rulez'}});"
+ "if (event.type == 'dog' &&"
+ " event.bubbles == false &&"
+ " event.cancelable == true &&"
+ " event.message == 'error_message' &&"
+ " event.filename == 'error_filename' &&"
+ " event.lineno == 100 &&"
+ " event.colno == 50) "
+ " event.error.cobalt;",
+ &result);
+ EXPECT_EQ("rulez", result);
+
+ if (!success) {
+ DLOG(ERROR) << "Failed to evaluate test: "
+ << "\"" << result << "\"";
+ } else {
+ LOG(INFO) << "Test result : "
+ << "\"" << result << "\"";
+ }
+}
+
+} // namespace dom
+} // namespace cobalt
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index ca6593c..b6a3a8c 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -78,7 +78,6 @@
DCHECK(event->initialized_flag());
TRACE_EVENT1("cobalt::dom", "EventTarget::DispatchEvent", "event",
event->type().c_str());
-
if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
return false;
}
@@ -223,6 +222,18 @@
new EventListenerInfo(type, this, listener, use_capture, listener_type));
}
+bool EventTarget::HasEventListener(base::Token type) {
+ TRACK_MEMORY_SCOPE("DOM");
+
+ for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
+ iter != event_listener_infos_.end(); ++iter) {
+ if ((*iter)->type == type) {
+ return true;
+ }
+ }
+ return false;
+}
+
EventTarget::EventListenerInfo::EventListenerInfo(
base::Token type, EventTarget* const event_target,
const EventListenerScriptValue& listener, bool use_capture,
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index 896667c..4e269f3 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -78,6 +78,9 @@
const tracked_objects::Location& location, base::Token event_name,
const base::Closure& dispatched_callback);
+ // Check if target has event listener (atrtibute or not attribute).
+ bool HasEventListener(base::Token type);
+
// Web API: GlobalEventHandlers (implements)
// Many objects can have event handlers specified. These act as non-capture
// event listeners for the object on which they are specified.
@@ -338,6 +341,20 @@
SetAttributeEventListener(base::Tokens::timeupdate(), event_listener);
}
+ const EventListenerScriptValue* onbeforeunload() {
+ return GetAttributeEventListener(base::Tokens::beforeunload());
+ }
+ void set_onbeforeunload(const EventListenerScriptValue& event_listener) {
+ SetAttributeEventListener(base::Tokens::beforeunload(), event_listener);
+ }
+
+ const EventListenerScriptValue* ontransitionend() {
+ return GetAttributeEventListener(base::Tokens::transitionend());
+ }
+ void set_ontransitionend(const EventListenerScriptValue& event_listener) {
+ SetAttributeEventListener(base::Tokens::transitionend(), event_listener);
+ }
+
const EventListenerScriptValue* onunload() {
return GetAttributeEventListener(base::Tokens::unload());
}
diff --git a/src/cobalt/dom/global_event_handlers.idl b/src/cobalt/dom/global_event_handlers.idl
index 372f2f7..df852fc 100644
--- a/src/cobalt/dom/global_event_handlers.idl
+++ b/src/cobalt/dom/global_event_handlers.idl
@@ -44,6 +44,8 @@
attribute EventHandler onresize;
+ attribute EventHandler ontransitionend;
+
// Extensions for the Pointer Events recommendation.
// https://www.w3.org/TR/2015/REC-pointerevents-20150224/#extensions-to-the-globaleventhandlers-interface
attribute EventHandler ongotpointercapture;
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index e62c335..5806cfc 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -272,6 +272,10 @@
// https://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
bool CanbeDesignatedByPointerIfDisplayed() const;
+ // Returns true if this node and all of its ancestors do NOT have display set
+ // to 'none'.
+ bool IsDisplayed() const;
+
DEFINE_WRAPPABLE_TYPE(HTMLElement);
protected:
@@ -330,10 +334,6 @@
// Purge the cached background images on only this node.
void PurgeCachedBackgroundImages();
- // Returns true if this node and all of its ancestors do NOT have display set
- // to 'none'.
- bool IsDisplayed() const;
-
bool locked_for_focus_;
// The directionality of the html element is determined by the 'dir'
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 0a4a3a7..51200ba 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -207,6 +207,12 @@
std::string HTMLMediaElement::CanPlayType(const std::string& mime_type,
const std::string& key_system) {
+ if (!html_element_context()->can_play_type_handler()) {
+ DLOG(ERROR) << __FUNCTION__ << "(" << mime_type << ", " << key_system
+ << "): Media playback in PRELOADING is not supported.";
+ return "";
+ }
+
#if defined(COBALT_MEDIA_SOURCE_2016)
DLOG_IF(ERROR, !key_system.empty())
<< "CanPlayType() only accepts one parameter but (" << key_system
@@ -732,6 +738,11 @@
}
}
+ if (!html_element_context()->web_media_player_factory()) {
+ DLOG(ERROR) << "Media playback in PRELOADING is not supported.";
+ return;
+ }
+
player_ =
html_element_context()->web_media_player_factory()->CreateWebMediaPlayer(
this);
@@ -1517,7 +1528,7 @@
EndProcessingMediaPlayerCallback();
}
-void HTMLMediaElement::TimeChanged() {
+void HTMLMediaElement::TimeChanged(bool eos_played) {
DCHECK(player_);
if (!player_) {
return;
@@ -1543,8 +1554,9 @@
// When the current playback position reaches the end of the media resource
// when the direction of playback is forwards, then the user agent must follow
// these steps:
- if (!SbDoubleIsNan(dur) && (0.0f != dur) && now >= dur &&
- playback_rate_ > 0) {
+ eos_played |=
+ !SbDoubleIsNan(dur) && (0.0f != dur) && now >= dur && playback_rate_ > 0;
+ if (eos_played) {
// If the media element has a loop attribute specified and does not have a
// current media controller,
if (loop()) {
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h
index b3d1f0d..31314e3 100644
--- a/src/cobalt/dom/html_media_element.h
+++ b/src/cobalt/dom/html_media_element.h
@@ -231,7 +231,7 @@
// WebMediaPlayerClient methods
void NetworkStateChanged() OVERRIDE;
void ReadyStateChanged() OVERRIDE;
- void TimeChanged() OVERRIDE;
+ void TimeChanged(bool eos_played) OVERRIDE;
void DurationChanged() OVERRIDE;
void OutputModeChanged() OVERRIDE;
void PlaybackStateChanged() OVERRIDE;
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 0d4e62e..fab5c8c 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -28,6 +28,8 @@
#include "cobalt/dom/document.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/dom/element.h"
+#include "cobalt/dom/error_event.h"
+#include "cobalt/dom/error_event_init.h"
#include "cobalt/dom/event.h"
#include "cobalt/dom/history.h"
#include "cobalt/dom/html_element.h"
@@ -111,6 +113,7 @@
height_(height),
device_pixel_ratio_(device_pixel_ratio),
is_resize_event_pending_(false),
+ is_reporting_script_error_(false),
#if defined(ENABLE_TEST_RUNNER)
test_runner_(new TestRunner()),
#endif // ENABLE_TEST_RUNNER
@@ -430,6 +433,65 @@
html_element_context_->page_visibility_state()->SetApplicationState(state);
}
+bool Window::ReportScriptError(const script::ErrorReport& error_report) {
+ // Runtime script errors: when the user agent is required to report an error
+ // for a particular script, it must run these steps, after which the error is
+ // either handled or not handled:
+ // https://www.w3.org/TR/html5/webappapis.html#runtime-script-errors
+
+ // 1. If target is in error reporting mode, then abort these steps; the error
+ // is not handled.
+ if (is_reporting_script_error_) {
+ return false;
+ }
+
+ // 2. Let target be in error reporting mode.
+ is_reporting_script_error_ = true;
+
+ // 7. Let event be a new trusted ErrorEvent object that does not bubble but is
+ // cancelable, and which has the event name error.
+ // NOTE: Cobalt does not currently support trusted events.
+ ErrorEventInit error_event_init;
+ error_event_init.set_bubbles(false);
+ error_event_init.set_cancelable(true);
+
+ if (error_report.is_muted) {
+ // 6. If script has muted errors, then set message to "Script error.", set
+ // location to the empty string, set line and col to 0, and set error
+ // object to null.
+ error_event_init.set_message("Script error.");
+ error_event_init.set_filename("");
+ error_event_init.set_lineno(0);
+ error_event_init.set_colno(0);
+ error_event_init.set_error(NULL);
+ } else {
+ // 8. Initialize event's message attribute to message.
+ error_event_init.set_message(error_report.message);
+ // 9. Initialize event's filename attribute to location.
+ error_event_init.set_filename(error_report.filename);
+ // 10. Initialize event's lineno attribute to line.
+ error_event_init.set_lineno(error_report.line_number);
+ // 11. Initialize event's colno attribute to col.
+ error_event_init.set_colno(error_report.column_number);
+ // 12. Initialize event's error attribute to error object.
+ error_event_init.set_error(error_report.error ? error_report.error.get()
+ : NULL);
+ }
+
+ scoped_refptr<ErrorEvent> error_event(
+ new ErrorEvent(base::Tokens::error(), error_event_init));
+
+ // 13. Dispatch event at target.
+ DispatchEvent(error_event);
+
+ // 14. Let target no longer be in error reporting mode.
+ is_reporting_script_error_ = false;
+
+ // 15. If event was canceled, then the error is handled. Otherwise, the error
+ // is not handled.
+ return error_event->default_prevented();
+}
+
void Window::SetSynchronousLayoutCallback(
const base::Closure& synchronous_layout_callback) {
document_->set_synchronous_layout_callback(synchronous_layout_callback);
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index e3a3f83..9cd4cb6 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -53,6 +53,7 @@
#include "cobalt/page_visibility/page_visibility_state.h"
#include "cobalt/script/callback_function.h"
#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/error_report.h"
#include "cobalt/script/execution_state.h"
#include "cobalt/script/script_runner.h"
#include "cobalt/script/script_value_factory.h"
@@ -310,6 +311,11 @@
// precipitate events to be dispatched.
void SetApplicationState(base::ApplicationState state);
+ // Performs the steps specified for runtime script errors:
+ // https://www.w3.org/TR/html5/webappapis.html#runtime-script-errors
+ // Returns whether or not the script was handled.
+ bool ReportScriptError(const script::ErrorReport& error_report);
+
// page_visibility::PageVisibilityState::Observer implementation.
void OnWindowFocusChanged(bool has_focus) OVERRIDE;
void OnVisibilityStateChanged(
@@ -347,6 +353,11 @@
// visibility state changes to visible.
bool is_resize_event_pending_;
+ // Whether or not the window is currently reporting a script error. This is
+ // used to prevent infinite recursion, because reporting the error causes an
+ // event to be dispatched, which can generate a new script error.
+ bool is_reporting_script_error_;
+
#if defined(ENABLE_TEST_RUNNER)
scoped_refptr<TestRunner> test_runner_;
#endif // ENABLE_TEST_RUNNER
diff --git a/src/cobalt/dom/window_event_handlers.idl b/src/cobalt/dom/window_event_handlers.idl
index 2cda344..bc2bb42 100644
--- a/src/cobalt/dom/window_event_handlers.idl
+++ b/src/cobalt/dom/window_event_handlers.idl
@@ -17,4 +17,5 @@
[NoInterfaceObject]
interface WindowEventHandlers {
attribute EventHandler onunload;
+ attribute EventHandler onbeforeunload;
};
diff --git a/src/cobalt/h5vcc/h5vcc_accessibility.cc b/src/cobalt/h5vcc/h5vcc_accessibility.cc
index 2f9ddb7..7ab5164 100644
--- a/src/cobalt/h5vcc/h5vcc_accessibility.cc
+++ b/src/cobalt/h5vcc/h5vcc_accessibility.cc
@@ -42,7 +42,6 @@
#if SB_HAS(SPEECH_SYNTHESIS)
bool IsTextToSpeechEnabled() {
-#if SB_API_VERSION >= 4
// Check if the tts feature is enabled in Starboard.
SbAccessibilityTextToSpeechSettings tts_settings = {0};
// Check platform settings.
@@ -50,7 +49,7 @@
return tts_settings.has_text_to_speech_setting &&
tts_settings.is_text_to_speech_enabled;
}
-#endif // SB_API_VERSION >= 4
+
return false;
}
#endif // SB_HAS(SPEECH_SYNTHESIS)
@@ -102,7 +101,6 @@
}
bool H5vccAccessibility::high_contrast_text() const {
-#if SB_API_VERSION >= 4
SbAccessibilityDisplaySettings settings;
SbMemorySet(&settings, 0, sizeof(settings));
@@ -111,13 +109,9 @@
}
return settings.is_high_contrast_text_enabled;
-#else // SB_API_VERSION >= 4
- return false;
-#endif // SB_API_VERSION >= 4
}
bool H5vccAccessibility::text_to_speech() const {
-#if SB_API_VERSION >= 4
SbAccessibilityTextToSpeechSettings settings;
SbMemorySet(&settings, 0, sizeof(settings));
@@ -127,9 +121,6 @@
return settings.has_text_to_speech_setting &&
settings.is_text_to_speech_enabled;
-#else // SB_API_VERSION >= 4
- return false;
-#endif // SB_API_VERSION >= 4
}
void H5vccAccessibility::AddHighContrastTextListener(
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index b1e6b70..34a3853 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -650,10 +650,10 @@
RenderAndAnimateOverflow(padding_rounded_corners, border_node,
&animate_node_builder, border_box_offset);
}
- border_node = RenderAndAnimateOpacity(border_node, &animate_node_builder,
- opacity, opacity_animated);
border_node = RenderAndAnimateTransform(border_node, &animate_node_builder,
border_box_offset);
+ border_node = RenderAndAnimateOpacity(border_node, &animate_node_builder,
+ opacity, opacity_animated);
cached_render_tree_node_info_->node_ =
animate_node_builder.empty()
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc
index 5f9c536..dc00344 100644
--- a/src/cobalt/layout/box_generator.cc
+++ b/src/cobalt/layout/box_generator.cc
@@ -64,7 +64,6 @@
scoped_refptr<render_tree::Image> GetVideoFrame(
const scoped_refptr<ShellVideoFrameProvider>& frame_provider,
render_tree::ResourceProvider* resource_provider) {
-#if SB_API_VERSION >= 4
SbDecodeTarget decode_target = frame_provider->GetCurrentSbDecodeTarget();
if (SbDecodeTargetIsValid(decode_target)) {
#if SB_HAS(GRAPHICS)
@@ -72,12 +71,8 @@
#else // SB_HAS(GRAPHICS)
UNREFERENCED_PARAMETER(resource_provider);
return NULL;
-#endif
+#endif // SB_HAS(GRAPHICS)
} else {
-#else // SB_API_VERSION >= 4
- UNREFERENCED_PARAMETER(resource_provider);
- {
-#endif
DCHECK(frame_provider);
scoped_refptr<VideoFrame> video_frame = frame_provider->GetCurrentFrame();
if (video_frame && video_frame->texture_id()) {
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index e9b8319..7f0cc65 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -24,7 +24,9 @@
#include "base/timer.h"
#include "cobalt/cssom/cascade_precedence.h"
#include "cobalt/dom/camera_3d.h"
+#include "cobalt/dom/html_body_element.h"
#include "cobalt/dom/html_element_context.h"
+#include "cobalt/dom/html_head_element.h"
#include "cobalt/dom/html_html_element.h"
#include "cobalt/layout/benchmark_stat_names.h"
#include "cobalt/layout/block_formatting_block_container_box.h"
@@ -77,6 +79,8 @@
const OnLayoutCallback on_layout_callback_;
const LayoutTrigger layout_trigger_;
+ bool produced_render_tree_;
+
// Setting these flags triggers an update of the layout box tree and the
// generation of a new render tree at a regular interval (e.g. 60Hz). Events
// such as DOM mutations cause them to be set to true. While the render tree
@@ -167,6 +171,7 @@
on_render_tree_produced_callback_(on_render_tree_produced),
on_layout_callback_(on_layout),
layout_trigger_(layout_trigger),
+ produced_render_tree_(false),
are_computed_styles_and_box_tree_dirty_(true),
is_render_tree_pending_(
StringPrintf("%s.Layout.IsRenderTreePending", name.c_str()), true,
@@ -366,11 +371,32 @@
are_computed_styles_and_box_tree_dirty_ = false;
}
+ // If no render tree has been produced yet, check if html, head, and
+ // body display should block the first render tree.
+ if (!produced_render_tree_) {
+ bool displayed_html = document->html()->IsDisplayed();
+ if (!displayed_html) {
+ return;
+ }
+ bool displayed_head = true;
+ if (document->head()) {
+ displayed_head = document->head();
+ }
+ bool displayed_body = true;
+ if (document->body()) {
+ displayed_body = document->body();
+ }
+ if (!displayed_head && !displayed_body) {
+ return;
+ }
+ }
+
scoped_refptr<render_tree::Node> render_tree_root =
layout::GenerateRenderTreeFromBoxTree(used_style_provider_.get(),
layout_stat_tracker_,
&initial_containing_block_);
bool run_on_render_tree_produced_callback = true;
+ produced_render_tree_ = true;
#if defined(ENABLE_TEST_RUNNER)
if (layout_trigger_ == kTestRunnerMode &&
window_->test_runner()->should_wait()) {
diff --git a/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements-expected.png b/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements-expected.png
deleted file mode 100644
index b16b41a..0000000
--- a/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements.html b/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements.html
deleted file mode 100644
index 4d9cc35..0000000
--- a/src/cobalt/layout_tests/testdata/cobalt/display_none_set_to_all_elements.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!--
- | Applying display: none to all elements.
- -->
-<html>
-<head></head>
-<body></body>
-<style>
-* {
- display: none;
-}
-</style>
-
-</html>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
index 03292a0..9509933 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
@@ -4,7 +4,6 @@
changing-css-text-triggers-layout
cobalt-oxide, file:///cobalt/browser/testdata/cobalt-oxide/cobalt-oxide.html
console-trace-should-not-crash
-display_none_set_to_all_elements
divs-with-background-color-and-text
fixed-width-divs-with-background-color
font-weight
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
index 13a4626..87dab78 100644
--- a/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
+++ b/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
@@ -170,7 +170,7 @@
send-redirect-infinite.htm,PASS
send-redirect-infinite-sync.htm,FAIL
send-redirect-no-location.htm,FAIL
-send-redirect-to-cors.htm,DISABLE
+send-redirect-to-cors.htm,PASS
send-redirect-to-non-cors.htm,FAIL
send-response-event-order.htm,FAIL
send-response-upload-event-loadend.htm,PASS
diff --git a/src/cobalt/loader/error_fetcher.cc b/src/cobalt/loader/error_fetcher.cc
new file mode 100644
index 0000000..caf00e2
--- /dev/null
+++ b/src/cobalt/loader/error_fetcher.cc
@@ -0,0 +1,37 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/loader/error_fetcher.h"
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+
+namespace cobalt {
+namespace loader {
+
+ErrorFetcher::ErrorFetcher(Handler* handler, const std::string& error_message)
+ : Fetcher(handler),
+ error_message_(error_message),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ErrorFetcher::Fetch, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ErrorFetcher::Fetch() {
+ handler()->OnError(this, error_message_);
+}
+
+} // namespace loader
+} // namespace cobalt
diff --git a/src/cobalt/loader/error_fetcher.h b/src/cobalt/loader/error_fetcher.h
new file mode 100644
index 0000000..8f7738e
--- /dev/null
+++ b/src/cobalt/loader/error_fetcher.h
@@ -0,0 +1,41 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_LOADER_ERROR_FETCHER_H_
+#define COBALT_LOADER_ERROR_FETCHER_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "cobalt/loader/fetcher.h"
+
+namespace cobalt {
+namespace loader {
+
+// Always returns an error.
+class ErrorFetcher : public Fetcher {
+ public:
+ ErrorFetcher(Handler* handler, const std::string& error_message_);
+
+ private:
+ void Fetch();
+
+ std::string error_message_;
+ base::WeakPtrFactory<ErrorFetcher> weak_ptr_factory_;
+};
+
+} // namespace loader
+} // namespace cobalt
+
+#endif // COBALT_LOADER_ERROR_FETCHER_H_
diff --git a/src/cobalt/loader/fetcher_factory.cc b/src/cobalt/loader/fetcher_factory.cc
index 1e70cf4..9776365 100644
--- a/src/cobalt/loader/fetcher_factory.cc
+++ b/src/cobalt/loader/fetcher_factory.cc
@@ -14,6 +14,7 @@
#include "cobalt/loader/fetcher_factory.h"
+#include <sstream>
#include <string>
#include "base/bind.h"
@@ -25,6 +26,7 @@
#include "cobalt/loader/blob_fetcher.h"
#include "cobalt/loader/cache_fetcher.h"
#include "cobalt/loader/embedded_fetcher.h"
+#include "cobalt/loader/error_fetcher.h"
#include "cobalt/loader/file_fetcher.h"
#include "cobalt/loader/net_fetcher.h"
#include "cobalt/network/network_module.h"
@@ -37,6 +39,7 @@
const char kAboutScheme[] = "about";
#endif
+#if defined(COBALT_ENABLE_FILE_SCHEME)
bool FileURLToFilePath(const GURL& url, FilePath* file_path) {
DCHECK(url.is_valid() && url.SchemeIsFile());
std::string path = url.path();
@@ -45,6 +48,7 @@
*file_path = FilePath(path);
return !file_path->empty();
}
+#endif
std::string ClipUrl(const GURL& url, size_t length) {
const std::string& spec = url.possibly_invalid_spec();
@@ -94,59 +98,67 @@
scoped_ptr<Fetcher> FetcherFactory::CreateSecureFetcher(
const GURL& url, const csp::SecurityCallback& url_security_callback,
Fetcher::Handler* handler) {
+ DLOG(INFO) << "Fetching: " << ClipUrl(url, 60);
+
if (!url.is_valid()) {
- LOG(ERROR) << "URL is invalid: " << url;
- return scoped_ptr<Fetcher>(NULL);
+ std::stringstream error_message;
+ error_message << "URL is invalid: " << url;
+ return scoped_ptr<Fetcher>(new ErrorFetcher(handler, error_message.str()));
}
- DLOG(INFO) << "Fetching: " << ClipUrl(url, 60);
- scoped_ptr<Fetcher> fetcher;
+ if ((url.SchemeIs("https") || url.SchemeIs("http") ||
+ url.SchemeIs("data")) &&
+ network_module_) {
+ NetFetcher::Options options;
+ return scoped_ptr<Fetcher>(new NetFetcher(url, url_security_callback,
+ handler, network_module_,
+ options));
+ }
+
+ if (url.SchemeIs("blob") && !blob_resolver_.is_null()) {
+ return scoped_ptr<Fetcher>(new BlobFetcher(url, handler, blob_resolver_));
+ }
+
if (url.SchemeIs(kEmbeddedScheme)) {
EmbeddedFetcher::Options options;
- fetcher.reset(
- new EmbeddedFetcher(url, url_security_callback, handler, options));
- } else if (url.SchemeIsFile()) {
+ return scoped_ptr<Fetcher>(new EmbeddedFetcher(url, url_security_callback,
+ handler, options));
+ }
+
+ // h5vcc-cache: scheme requires read_cache_callback_ which is not available
+ // in the main WebModule.
+ if (url.SchemeIs(kCacheScheme) && !read_cache_callback_.is_null()) {
+ return scoped_ptr<Fetcher>(new CacheFetcher(url, url_security_callback,
+ handler,
+ read_cache_callback_));
+ }
+
+#if defined(COBALT_ENABLE_FILE_SCHEME)
+ if (url.SchemeIsFile()) {
FilePath file_path;
- if (FileURLToFilePath(url, &file_path)) {
- FileFetcher::Options options;
- options.message_loop_proxy = file_thread_.message_loop_proxy();
- options.extra_search_dir = extra_search_dir_;
- fetcher.reset(new FileFetcher(file_path, handler, options));
- } else {
- LOG(ERROR) << "File URL cannot be converted to file path: " << url;
- }
- }
-#if defined(ENABLE_ABOUT_SCHEME)
- else if (url.SchemeIs(kAboutScheme)) { // NOLINT(readability/braces)
- fetcher.reset(new AboutFetcher(handler));
- }
-#endif
- else if (url.SchemeIs("blob")) { // NOLINT(readability/braces)
- if (!blob_resolver_.is_null()) {
- fetcher.reset(new BlobFetcher(url, handler, blob_resolver_));
- } else {
- LOG(ERROR) << "Fetcher factory not provided the blob registry, "
- "could not fetch the URL: "
- << url;
- }
- } else if (url.SchemeIs(kCacheScheme)) {
- if (read_cache_callback_.is_null()) {
- LOG(ERROR) << "read_cache_callback_ must be provided to CacheFetcher for "
- "accessing h5vcc-cache:// . This is not available in the "
- "main WebModule.";
- DCHECK(!read_cache_callback_.is_null());
- return fetcher.Pass();
+ if (!FileURLToFilePath(url, &file_path)) {
+ std::stringstream error_message;
+ error_message << "File URL cannot be converted to file path: " << url;
+ return scoped_ptr<Fetcher>(new ErrorFetcher(handler,
+ error_message.str()));
}
- fetcher.reset(new CacheFetcher(url, url_security_callback, handler,
- read_cache_callback_));
- } else { // NOLINT(readability/braces)
- DCHECK(network_module_) << "Network module required.";
- NetFetcher::Options options;
- fetcher.reset(new NetFetcher(url, url_security_callback, handler,
- network_module_, options));
+ FileFetcher::Options options;
+ options.message_loop_proxy = file_thread_.message_loop_proxy();
+ options.extra_search_dir = extra_search_dir_;
+ return scoped_ptr<Fetcher>(new FileFetcher(file_path, handler, options));
}
- return fetcher.Pass();
+#endif
+
+#if defined(ENABLE_ABOUT_SCHEME)
+ if (url.SchemeIs(kAboutScheme)) {
+ return scoped_ptr<Fetcher>(new AboutFetcher(handler));
+ }
+#endif
+
+ std::stringstream error_message;
+ error_message << "Scheme " << url.scheme() << ": is not supported";
+ return scoped_ptr<Fetcher>(new ErrorFetcher(handler, error_message.str()));
}
} // namespace loader
diff --git a/src/cobalt/loader/fetcher_factory_test.cc b/src/cobalt/loader/fetcher_factory_test.cc
index 7e525e1..e39d0a4 100644
--- a/src/cobalt/loader/fetcher_factory_test.cc
+++ b/src/cobalt/loader/fetcher_factory_test.cc
@@ -14,6 +14,7 @@
#include <string>
+#include "base/optional.h"
#include "base/run_loop.h"
#include "cobalt/loader/fetcher_factory.h"
#include "cobalt/loader/file_fetcher.h"
@@ -26,32 +27,32 @@
class StubFetcherHandler : public Fetcher::Handler {
public:
explicit StubFetcherHandler(base::RunLoop* run_loop)
- : fetcher_(NULL), run_loop_(run_loop) {}
+ : run_loop_(run_loop), fetcher_(NULL) {}
// From Fetcher::Handler.
void OnReceived(Fetcher* fetcher, const char* data, size_t size) OVERRIDE {
UNREFERENCED_PARAMETER(data);
UNREFERENCED_PARAMETER(size);
- CheckFetcher(fetcher);
+ CheckSameFetcher(fetcher);
}
void OnDone(Fetcher* fetcher) OVERRIDE {
- CheckFetcher(fetcher);
- if (run_loop_) {
- MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
- }
+ CheckSameFetcher(fetcher);
+ MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
}
- void OnError(Fetcher* fetcher, const std::string& error) OVERRIDE {
- UNREFERENCED_PARAMETER(error);
- CheckFetcher(fetcher);
- if (run_loop_) {
- MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
- }
+ void OnError(Fetcher* fetcher, const std::string& error_message) OVERRIDE {
+ CheckSameFetcher(fetcher);
+ error_message_ = error_message;
+ MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
}
Fetcher* fetcher() const { return fetcher_; }
+ const base::optional<std::string>& error_message() const {
+ return error_message_;
+ }
+
private:
- void CheckFetcher(Fetcher* fetcher) {
+ void CheckSameFetcher(Fetcher* fetcher) {
EXPECT_TRUE(fetcher);
if (fetcher_ == NULL) {
fetcher_ = fetcher;
@@ -60,8 +61,9 @@
EXPECT_EQ(fetcher_, fetcher);
}
- Fetcher* fetcher_;
base::RunLoop* run_loop_;
+ Fetcher* fetcher_;
+ base::optional<std::string> error_message_;
};
} // namespace
@@ -79,27 +81,42 @@
};
TEST_F(FetcherFactoryTest, InvalidURL) {
- StubFetcherHandler stub_fetcher_handler(NULL);
+ base::RunLoop run_loop;
+ StubFetcherHandler stub_fetcher_handler(&run_loop);
+
fetcher_ = fetcher_factory_.CreateFetcher(GURL("invalid-url"),
&stub_fetcher_handler);
- EXPECT_FALSE(fetcher_.get());
- EXPECT_FALSE(stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(fetcher_);
+
+ run_loop.Run();
+ EXPECT_EQ(fetcher_.get(), stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(stub_fetcher_handler.error_message().has_engaged());
}
TEST_F(FetcherFactoryTest, EmptyFileURL) {
- StubFetcherHandler stub_fetcher_handler(NULL);
+ base::RunLoop run_loop;
+ StubFetcherHandler stub_fetcher_handler(&run_loop);
+
fetcher_ =
fetcher_factory_.CreateFetcher(GURL("file:///"), &stub_fetcher_handler);
- EXPECT_FALSE(fetcher_.get());
- EXPECT_FALSE(stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(fetcher_);
+
+ run_loop.Run();
+ EXPECT_EQ(fetcher_.get(), stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(stub_fetcher_handler.error_message().has_engaged());
}
TEST_F(FetcherFactoryTest, FileURLCannotConvertToFilePath) {
- StubFetcherHandler stub_fetcher_handler(NULL);
+ base::RunLoop run_loop;
+ StubFetcherHandler stub_fetcher_handler(&run_loop);
+
fetcher_ = fetcher_factory_.CreateFetcher(GURL("file://file.txt"),
&stub_fetcher_handler);
- EXPECT_FALSE(fetcher_.get());
- EXPECT_FALSE(stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(fetcher_);
+
+ run_loop.Run();
+ EXPECT_EQ(fetcher_.get(), stub_fetcher_handler.fetcher());
+ EXPECT_TRUE(stub_fetcher_handler.error_message().has_engaged());
}
TEST_F(FetcherFactoryTest, MultipleCreations) {
diff --git a/src/cobalt/loader/image/image_data_decoder.h b/src/cobalt/loader/image/image_data_decoder.h
index 2d648b5..432942a 100644
--- a/src/cobalt/loader/image/image_data_decoder.h
+++ b/src/cobalt/loader/image/image_data_decoder.h
@@ -46,7 +46,7 @@
}
#if defined(STARBOARD)
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
// Starboard version 3 adds support for hardware accelerated image decoding.
// In order to make use of this feature, subclasses of ImageDataDecoder may
// override this method in order to return an SbDecodeTarget, rather than a
@@ -59,7 +59,7 @@
virtual SbDecodeTarget RetrieveSbDecodeTarget() {
return kSbDecodeTargetInvalid;
}
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
#endif // defined(STARBOARD)
void DecodeChunk(const uint8* data, size_t size);
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index 739c523..50f1699 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -29,9 +29,7 @@
#include "cobalt/loader/image/webp_image_decoder.h"
#include "net/base/mime_util.h"
#include "net/http/http_status_code.h"
-#if defined(STARBOARD)
#include "starboard/image.h"
-#endif
namespace cobalt {
namespace loader {
@@ -161,15 +159,13 @@
DCHECK(decoder_);
if (decoder_->FinishWithSuccess()) {
if (!decoder_->has_animation()) {
-#if defined(STARBOARD)
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
SbDecodeTarget target = decoder_->RetrieveSbDecodeTarget();
if (SbDecodeTargetIsValid(target)) {
success_callback_.Run(new StaticImage(
resource_provider_->CreateImageFromSbDecodeTarget(target)));
} else // NOLINT
#endif
-#endif
{
scoped_ptr<render_tree::ImageData> image_data =
decoder_->RetrieveImageData();
@@ -271,8 +267,7 @@
}
namespace {
-#if defined(STARBOARD)
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
const char* GetMimeTypeFromImageType(ImageDecoder::ImageType image_type) {
switch (image_type) {
case ImageDecoder::kImageTypeJPEG:
@@ -331,8 +326,7 @@
}
return scoped_ptr<ImageDataDecoder>();
}
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
-#endif // defined(STARBOARD)
+#endif // SB_HAS(GRAPHICS)
scoped_ptr<ImageDataDecoder> CreateImageDecoderFromImageType(
ImageDecoder::ImageType image_type,
@@ -383,12 +377,10 @@
image_type_ = DetermineImageType(signature_cache_.data);
}
-#if defined(STARBOARD)
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
decoder_ =
MaybeCreateStarboardDecoder(mime_type_, image_type_, resource_provider_);
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
-#endif // defined(STARBOARD)
+#endif // SB_HAS(GRAPHICS)
if (!decoder_) {
decoder_ = CreateImageDecoderFromImageType(image_type_, resource_provider_);
diff --git a/src/cobalt/loader/image/image_decoder_starboard.cc b/src/cobalt/loader/image/image_decoder_starboard.cc
index a0518c0..1183eea 100644
--- a/src/cobalt/loader/image/image_decoder_starboard.cc
+++ b/src/cobalt/loader/image/image_decoder_starboard.cc
@@ -23,7 +23,7 @@
#include "starboard/decode_target.h"
#include "starboard/image.h"
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_TRUE && SB_HAS(GRAPHICS)
namespace cobalt {
namespace loader {
@@ -35,11 +35,11 @@
: ImageDataDecoder(resource_provider),
mime_type_(mime_type),
format_(format),
-#if SB_API_VERSION >= 4
+#if SB_TRUE
provider_(resource_provider->GetSbDecodeTargetGraphicsContextProvider()),
-#else // #if SB_API_VERSION >= 4
+#else // #if SB_TRUE
provider_(resource_provider->GetSbDecodeTargetProvider()),
-#endif // #if SB_API_VERSION >= 4
+#endif // #if SB_TRUE
target_(kSbDecodeTargetInvalid) {
TRACE_EVENT0("cobalt::loader::image",
"ImageDecoderStarboard::ImageDecoderStarboard()");
@@ -75,6 +75,6 @@
} // namespace loader
} // namespace cobalt
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#endif // SB_TRUE && SB_HAS(GRAPHICS)
#endif // #if defined(STARBOARD)
diff --git a/src/cobalt/loader/image/image_decoder_starboard.h b/src/cobalt/loader/image/image_decoder_starboard.h
index 39b6cbe..14e386d 100644
--- a/src/cobalt/loader/image/image_decoder_starboard.h
+++ b/src/cobalt/loader/image/image_decoder_starboard.h
@@ -15,8 +15,6 @@
#ifndef COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
#define COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
-#if defined(STARBOARD)
-
#include <string>
#include <vector>
@@ -25,7 +23,7 @@
#include "cobalt/loader/image/image_data_decoder.h"
#include "starboard/decode_target.h"
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
namespace cobalt {
namespace loader {
@@ -51,11 +49,7 @@
const char* mime_type_;
SbDecodeTargetFormat format_;
std::vector<uint8> buffer_;
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider* provider_;
-#else // #if SB_API_VERSION >= 4
- SbDecodeTargetProvider* provider_;
-#endif // #if SB_API_VERSION >= 4
SbDecodeTarget target_;
};
@@ -63,8 +57,6 @@
} // namespace loader
} // namespace cobalt
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
-
-#endif // defined(STARBOARD)
+#endif // SB_HAS(GRAPHICS)
#endif // COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 2fad2ec..6a07f01 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -28,29 +28,31 @@
'decoder.h',
'embedded_fetcher.cc',
'embedded_fetcher.h',
- 'fetcher.cc',
- 'fetcher.h',
+ 'error_fetcher.cc',
+ 'error_fetcher.h',
'fetcher_factory.cc',
'fetcher_factory.h',
+ 'fetcher.cc',
+ 'fetcher.h',
'file_fetcher.cc',
'file_fetcher.h',
'font/remote_typeface_cache.h',
'font/typeface_decoder.cc',
'font/typeface_decoder.h',
+ 'image/animated_image_tracker.cc',
+ 'image/animated_image_tracker.h',
'image/animated_webp_image.cc',
'image/animated_webp_image.h',
'image/dummy_gif_image_decoder.cc',
'image/dummy_gif_image_decoder.h',
- 'image/image.h',
'image/image_cache.h',
'image/image_data_decoder.cc',
'image/image_data_decoder.h',
- 'image/image_decoder.cc',
- 'image/image_decoder.h',
'image/image_decoder_starboard.cc',
'image/image_decoder_starboard.h',
- 'image/animated_image_tracker.cc',
- 'image/animated_image_tracker.h',
+ 'image/image_decoder.cc',
+ 'image/image_decoder.h',
+ 'image/image.h',
'image/jpeg_image_decoder.cc',
'image/jpeg_image_decoder.h',
'image/png_image_decoder.cc',
@@ -60,11 +62,11 @@
'image/threaded_image_decoder_proxy.h',
'image/webp_image_decoder.cc',
'image/webp_image_decoder.h',
- 'loader.cc',
- 'loader.h',
'loader_factory.cc',
'loader_factory.h',
'loader_types.h',
+ 'loader.cc',
+ 'loader.h',
'mesh/mesh_cache.h',
'mesh/mesh_decoder.cc',
'mesh/mesh_decoder.h',
diff --git a/src/cobalt/media/base/drm_system.h b/src/cobalt/media/base/drm_system.h
index 00dfa4a..b0a26ff 100644
--- a/src/cobalt/media/base/drm_system.h
+++ b/src/cobalt/media/base/drm_system.h
@@ -26,10 +26,6 @@
#include "base/optional.h"
#include "starboard/drm.h"
-#if SB_API_VERSION < 4
-#error "Cobalt media stack requires Starboard 4 or above."
-#endif // SB_API_VERSION < 4
-
namespace cobalt {
namespace media {
@@ -97,7 +93,7 @@
,
SessionUpdateKeyStatusesCallback update_key_statuses_callback
#endif // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
- );
+ ); // NOLINT(whitespace/parens)
void set_id(const std::string& id) { id_ = id; }
const SessionUpdateRequestGeneratedCallback&
update_request_generated_callback() const {
@@ -133,7 +129,7 @@
#if SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
SessionUpdateKeyStatusesCallback session_update_key_statuses_callback
#endif // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
- );
+ ); // NOLINT(whitespace/parens)
private:
// Stores context of |GenerateSessionUpdateRequest|.
diff --git a/src/cobalt/media/base/shell_media_platform.h b/src/cobalt/media/base/shell_media_platform.h
index 73510fd..1e6c711 100644
--- a/src/cobalt/media/base/shell_media_platform.h
+++ b/src/cobalt/media/base/shell_media_platform.h
@@ -63,14 +63,10 @@
return NULL;
}
-#if SB_API_VERSION >= 4
virtual SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() {
return NULL;
}
-#elif SB_API_VERSION >= 3
- virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() { return NULL; }
-#endif // SB_API_VERSION >= 3
// This function is called before the decoder buffer leaves the demuxer and
// is being sent to the media pipeline for decrypting and decoding. The
diff --git a/src/cobalt/media/base/shell_video_frame_provider.h b/src/cobalt/media/base/shell_video_frame_provider.h
index a5bd969..e2b1569 100644
--- a/src/cobalt/media/base/shell_video_frame_provider.h
+++ b/src/cobalt/media/base/shell_video_frame_provider.h
@@ -49,9 +49,7 @@
ShellVideoFrameProvider() : output_mode_(kOutputModeInvalid) {}
-#if SB_API_VERSION >= 4
typedef base::Callback<SbDecodeTarget()> GetCurrentSbDecodeTargetFunction;
-#endif // SB_API_VERSION >= 4
scoped_refptr<VideoFrame> GetCurrentFrame() { return NULL; }
@@ -65,7 +63,6 @@
return output_mode_;
}
-#if SB_API_VERSION >= 4
// For Starboard platforms that have a decode-to-texture player, we enable
// this ShellVideoFrameProvider to act as a bridge for Cobalt code to query
// for the current SbDecodeTarget. In effect, we bypass all of
@@ -91,15 +88,12 @@
return get_current_sb_decode_target_function_.Run();
}
}
-#endif // SB_API_VERSION >= 4
private:
mutable base::Lock lock_;
OutputMode output_mode_;
-#if SB_API_VERSION >= 4
GetCurrentSbDecodeTargetFunction get_current_sb_decode_target_function_;
-#endif // SB_API_VERSION >= 4
DISALLOW_COPY_AND_ASSIGN(ShellVideoFrameProvider);
};
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index cb8749c..9cf382a 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -96,11 +96,9 @@
DCHECK(host_);
DCHECK(set_bounds_helper_);
-#if SB_API_VERSION >= 4
output_mode_ = ComputeSbPlayerOutputMode(
MediaVideoCodecToSbMediaVideoCodec(video_config.codec()), drm_system,
prefer_decode_to_texture);
-#endif // SB_API_VERSION >= 4
CreatePlayer();
@@ -118,11 +116,9 @@
ShellMediaPlatform::Instance()->GetVideoFrameProvider()->SetOutputMode(
ShellVideoFrameProvider::kOutputModeInvalid);
-#if SB_API_VERSION >= 4
ShellMediaPlatform::Instance()
->GetVideoFrameProvider()
->ResetGetCurrentSbDecodeTargetFunction();
-#endif // SB_API_VERSION >= 4
if (SbPlayerIsValid(player_)) {
SbPlayerDestroy(player_);
@@ -211,13 +207,9 @@
}
DCHECK(SbPlayerIsValid(player_));
-#if SB_API_VERSION >= 4
const int kZIndex = 0;
SbPlayerSetBounds(player_, kZIndex, rect.x(), rect.y(), rect.width(),
rect.height());
-#else // SB_API_VERSION >= 4
- SbPlayerSetBounds(player_, rect.x(), rect.y(), rect.width(), rect.height());
-#endif // SB_API_VERSION >= 4
}
void StarboardPlayer::PrepareForSeek() {
@@ -231,11 +223,7 @@
}
++ticket_;
-#if SB_API_VERSION < 4
- SbPlayerSetPause(player_, true);
-#else // SB_API_VERSION < 4
SbPlayerSetPlaybackRate(player_, 0.f);
-#endif // SB_API_VERSION < 4
}
void StarboardPlayer::Seek(base::TimeDelta time) {
@@ -260,11 +248,7 @@
++ticket_;
SbPlayerSeek(player_, TimeDeltaToSbMediaTime(time), ticket_);
seek_pending_ = false;
-#if SB_API_VERSION < 4
- SbPlayerSetPause(player_, playback_rate_ == 0.0);
-#else // SB_API_VERSION < 4
SbPlayerSetPlaybackRate(player_, playback_rate_);
-#endif // SB_API_VERSION < 4
}
void StarboardPlayer::SetVolume(float volume) {
@@ -295,11 +279,7 @@
return;
}
-#if SB_API_VERSION < 4
- SbPlayerSetPause(player_, playback_rate == 0.0);
-#else // SB_API_VERSION < 4
SbPlayerSetPlaybackRate(player_, playback_rate);
-#endif // SB_API_VERSION < 4
}
void StarboardPlayer::GetInfo(uint32* video_frames_decoded,
@@ -349,11 +329,7 @@
DCHECK(SbPlayerIsValid(player_));
-#if SB_API_VERSION < 4
- SbPlayerSetPause(player_, true);
-#else // SB_API_VERSION < 4
SbPlayerSetPlaybackRate(player_, 0.0);
-#endif // SB_API_VERSION < 4
base::AutoLock auto_lock(lock_);
@@ -391,7 +367,6 @@
}
namespace {
-#if SB_API_VERSION >= 4
ShellVideoFrameProvider::OutputMode ToVideoFrameProviderOutputMode(
SbPlayerOutputMode output_mode) {
switch (output_mode) {
@@ -406,7 +381,6 @@
NOTREACHED();
return ShellVideoFrameProvider::kOutputModeInvalid;
}
-#endif // #if SB_API_VERSION >= 4
} // namespace
void StarboardPlayer::CreatePlayer() {
@@ -446,26 +420,17 @@
SbMediaVideoCodec video_codec =
MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
-#if SB_API_VERSION >= 4
DCHECK(SbPlayerOutputModeSupported(output_mode_, video_codec, drm_system_));
-#endif // SB_API_VERSION >= 4
- player_ = SbPlayerCreate(
- window_, video_codec, audio_codec, SB_PLAYER_NO_DURATION, drm_system_,
- &audio_header, &StarboardPlayer::DeallocateSampleCB,
- &StarboardPlayer::DecoderStatusCB, &StarboardPlayer::PlayerStatusCB, this
-#if SB_API_VERSION >= 4
- ,
- output_mode_,
- ShellMediaPlatform::Instance()->GetSbDecodeTargetGraphicsContextProvider()
-#elif SB_API_VERSION >= 3
- ,
- ShellMediaPlatform::Instance()->GetSbDecodeTargetProvider() // provider
-#endif // SB_API_VERSION >= 3
- );
+ player_ = SbPlayerCreate(window_, video_codec, audio_codec,
+ SB_PLAYER_NO_DURATION, drm_system_, &audio_header,
+ &StarboardPlayer::DeallocateSampleCB,
+ &StarboardPlayer::DecoderStatusCB,
+ &StarboardPlayer::PlayerStatusCB, this, output_mode_,
+ ShellMediaPlatform::Instance()
+ ->GetSbDecodeTargetGraphicsContextProvider());
DCHECK(SbPlayerIsValid(player_));
-#if SB_API_VERSION >= 4
if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
// If the player is setup to decode to texture, then provide Cobalt with
// a method of querying that texture.
@@ -477,10 +442,6 @@
}
ShellMediaPlatform::Instance()->GetVideoFrameProvider()->SetOutputMode(
ToVideoFrameProviderOutputMode(output_mode_));
-#else // SB_API_VERSION >= 4
- ShellMediaPlatform::Instance()->GetVideoFrameProvider()->SetOutputMode(
- ShellVideoFrameProvider::kOutputModePunchOut);
-#endif // SB_API_VERSION >= 4
set_bounds_helper_->SetPlayer(this);
@@ -490,14 +451,13 @@
}
}
-#if SB_API_VERSION >= 4
SbDecodeTarget StarboardPlayer::GetCurrentSbDecodeTarget() {
return SbPlayerGetCurrentFrame(player_);
}
+
SbPlayerOutputMode StarboardPlayer::GetSbPlayerOutputMode() {
return output_mode_;
}
-#endif // SB_API_VERSION >= 4
void StarboardPlayer::ClearDecoderBufferCache() {
DCHECK(message_loop_->BelongsToCurrentThread());
@@ -572,11 +532,7 @@
}
SbPlayerSeek(player_, TimeDeltaToSbMediaTime(preroll_timestamp_), ticket_);
SetVolume(volume_);
-#if SB_API_VERSION < 4
- SbPlayerSetPause(player_, playback_rate_ == 0.0);
-#else // SB_API_VERSION < 4
SbPlayerSetPlaybackRate(player_, playback_rate_);
-#endif // SB_API_VERSION < 4
return;
}
host_->OnPlayerStatus(state);
@@ -628,7 +584,6 @@
helper->callback_helper_, sample_buffer));
}
-#if SB_API_VERSION >= 4
// static
SbPlayerOutputMode StarboardPlayer::ComputeSbPlayerOutputMode(
SbMediaVideoCodec codec, SbDrmSystem drm_system,
@@ -658,7 +613,6 @@
return output_mode;
}
-#endif // SB_API_VERSION >= 4
} // namespace media
} // namespace cobalt
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index d2c02f1..ad1a31a 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -73,10 +73,8 @@
void Suspend();
void Resume();
-#if SB_API_VERSION >= 4
SbDecodeTarget GetCurrentSbDecodeTarget();
SbPlayerOutputMode GetSbPlayerOutputMode();
-#endif // SB_API_VERSION >= 4
private:
enum State {
@@ -127,13 +125,11 @@
static void DeallocateSampleCB(SbPlayer player, void* context,
const void* sample_buffer);
-#if SB_API_VERSION >= 4
// Returns the output mode that should be used for a video with the given
// specifications.
static SbPlayerOutputMode ComputeSbPlayerOutputMode(
SbMediaVideoCodec codec, SbDrmSystem drm_system,
bool prefer_decode_to_texture);
-#endif // SB_API_VERSION >= 4
// The following variables are initialized in the ctor and never changed.
const scoped_refptr<base::MessageLoopProxy> message_loop_;
@@ -171,10 +167,8 @@
uint32 cached_video_frames_dropped_;
base::TimeDelta preroll_timestamp_;
-#if SB_API_VERSION >= 4
// Keep track of the output mode we are supposed to output to.
SbPlayerOutputMode output_mode_;
-#endif // SB_API_VERSION >= 4
};
} // namespace media
diff --git a/src/cobalt/media/base/starboard_utils.cc b/src/cobalt/media/base/starboard_utils.cc
index 72c4c72..bc6905a 100644
--- a/src/cobalt/media/base/starboard_utils.cc
+++ b/src/cobalt/media/base/starboard_utils.cc
@@ -128,7 +128,6 @@
}
}
-#if SB_API_VERSION >= 4
// Ensure that the enums in starboard/media.h match enums in gfx::ColorSpace.
#define ENUM_EQ(a, b) \
COMPILE_ASSERT(static_cast<int>(a) == static_cast<int>(b), mismatching_enums)
@@ -279,7 +278,6 @@
return sb_media_color_metadata;
}
-#endif // SB_API_VERSION >= 3
} // namespace media
} // namespace cobalt
diff --git a/src/cobalt/media/base/starboard_utils.h b/src/cobalt/media/base/starboard_utils.h
index 0cb43c1..04196b5 100644
--- a/src/cobalt/media/base/starboard_utils.h
+++ b/src/cobalt/media/base/starboard_utils.h
@@ -39,10 +39,8 @@
SbDrmSampleInfo* drm_info,
SbDrmSubSampleMapping* subsample_mapping);
-#if SB_API_VERSION >= 4
SbMediaColorMetadata MediaToSbMediaColorMetadata(
const WebMColorMetadata& webm_color_metadata);
-#endif
} // namespace media
} // namespace cobalt
diff --git a/src/cobalt/media/player/web_media_player.h b/src/cobalt/media/player/web_media_player.h
index b3b5cb6..beee2aa 100644
--- a/src/cobalt/media/player/web_media_player.h
+++ b/src/cobalt/media/player/web_media_player.h
@@ -194,7 +194,7 @@
public:
virtual void NetworkStateChanged() = 0;
virtual void ReadyStateChanged() = 0;
- virtual void TimeChanged() = 0;
+ virtual void TimeChanged(bool eos_played) = 0;
virtual void DurationChanged() = 0;
virtual void OutputModeChanged() = 0;
virtual void PlaybackStateChanged() = 0;
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index bd0d14b..c8cf80a 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -590,7 +590,8 @@
// Update our paused time.
if (state_.paused) state_.paused_time = pipeline_->GetMediaTime();
- GetClient()->TimeChanged();
+ const bool eos_played = false;
+ GetClient()->TimeChanged(eos_played);
}
void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
@@ -599,7 +600,9 @@
OnPipelineError(status);
return;
}
- GetClient()->TimeChanged();
+
+ const bool eos_played = true;
+ GetClient()->TimeChanged(eos_played);
}
void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
diff --git a/src/cobalt/media/sandbox/web_media_player_helper.cc b/src/cobalt/media/sandbox/web_media_player_helper.cc
index 58618a2..1909a5c 100644
--- a/src/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/src/cobalt/media/sandbox/web_media_player_helper.cc
@@ -35,7 +35,7 @@
// WebMediaPlayerClient methods
void NetworkStateChanged() OVERRIDE {}
void ReadyStateChanged() OVERRIDE {}
- void TimeChanged() OVERRIDE {}
+ void TimeChanged(bool) OVERRIDE {}
void DurationChanged() OVERRIDE {}
void OutputModeChanged() OVERRIDE {}
void PlaybackStateChanged() OVERRIDE {}
@@ -48,9 +48,8 @@
#endif // defined(COBALT_MEDIA_SOURCE_2016)
std::string SourceURL() const OVERRIDE { return ""; }
#if defined(COBALT_MEDIA_SOURCE_2016)
- void EncryptedMediaInitDataEncountered(EmeInitDataType init_data_type,
- const unsigned char* init_data,
- unsigned init_data_length) OVERRIDE {}
+ void EncryptedMediaInitDataEncountered(EmeInitDataType, const unsigned char*,
+ unsigned) OVERRIDE {}
#endif // defined(COBALT_MEDIA_SOURCE_2016)
};
diff --git a/src/cobalt/media/shell_media_platform_starboard.h b/src/cobalt/media/shell_media_platform_starboard.h
index 0119293..8bb5c4c 100644
--- a/src/cobalt/media/shell_media_platform_starboard.h
+++ b/src/cobalt/media/shell_media_platform_starboard.h
@@ -44,7 +44,6 @@
return video_frame_provider_;
}
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() OVERRIDE {
#if SB_HAS(GRAPHICS)
@@ -53,15 +52,6 @@
return NULL;
#endif // SB_HAS(GRAPHICS)
}
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE {
-#if SB_HAS(GRAPHICS)
- return resource_provider_->GetSbDecodeTargetProvider();
-#else // SB_HAS(GRAPHICS)
- return NULL;
-#endif // SB_HAS(GRAPHICS)
- }
-#endif // SB_API_VERSION >= 4
void Suspend() OVERRIDE { resource_provider_ = NULL; }
void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE {
@@ -134,7 +124,6 @@
const scoped_refptr<DecoderBuffer>& buffer) OVERRIDE;
bool IsOutputProtected() OVERRIDE;
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() OVERRIDE {
#if SB_HAS(GRAPHICS)
@@ -143,15 +132,6 @@
return NULL;
#endif // SB_HAS(GRAPHICS)
}
-#elif SB_API_VERSION >= 3
- virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() {
-#if SB_HAS(GRAPHICS)
- return resource_provider_->GetSbDecodeTargetProvider();
-#else // SB_HAS(GRAPHICS)
- return NULL;
-#endif // SB_HAS(GRAPHICS)
- }
-#endif // SB_API_VERSION >= 4
void Suspend() OVERRIDE { resource_provider_ = NULL; }
void Resume(
diff --git a/src/cobalt/network/local_network.cc b/src/cobalt/network/local_network.cc
index 725f3e4..964a54e 100644
--- a/src/cobalt/network/local_network.cc
+++ b/src/cobalt/network/local_network.cc
@@ -23,7 +23,6 @@
namespace {
-#if SB_API_VERSION >= 4
bool CompareNBytesOfAddress(const SbSocketAddress& ip,
const SbSocketAddress& source_address,
const SbSocketAddress& netmask,
@@ -57,22 +56,15 @@
}
}
-#endif // SB_API_VERSION >= 4
-
} // namespace
bool IsIPInLocalNetwork(const SbSocketAddress& destination) {
-#if SB_API_VERSION >= 4
SbSocketAddress source_address;
SbSocketAddress netmask;
if (!(SbSocketGetInterfaceAddress(&destination, &source_address, &netmask))) {
return false;
}
return IsLocalIP(destination, source_address, netmask);
-#else
- UNREFERENCED_PARAMETER(destination);
- return false;
-#endif
}
bool IsIPInPrivateRange(const SbSocketAddress& ip) {
diff --git a/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc b/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
index c82b19d..3487dd7 100644
--- a/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
+++ b/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
@@ -41,9 +41,7 @@
case kSbSystemDeviceTypeOverTheTopBox:
case kSbSystemDeviceTypeSetTopBox:
case kSbSystemDeviceTypeTV:
-#if SB_API_VERSION >= 4
case kSbSystemDeviceTypeAndroidTV:
-#endif // SB_API_VERSION >= 4
return true;
case kSbSystemDeviceTypeDesktopPC:
case kSbSystemDeviceTypeUnknown:
@@ -107,11 +105,9 @@
case kSbSystemDeviceTypeTV:
youtube_tv_info_->device_type = YouTubeTVInfo::kTV;
break;
-#if SB_API_VERSION >= 4
case kSbSystemDeviceTypeAndroidTV:
youtube_tv_info_->device_type = YouTubeTVInfo::kAndroidTV;
break;
-#endif // SB_API_VERSION >= 4
case kSbSystemDeviceTypeDesktopPC:
default:
youtube_tv_info_->device_type = YouTubeTVInfo::kInvalidDeviceType;
diff --git a/src/cobalt/network/user_agent_string_factory.cc b/src/cobalt/network/user_agent_string_factory.cc
index 7ad310b..8bd7815 100644
--- a/src/cobalt/network/user_agent_string_factory.cc
+++ b/src/cobalt/network/user_agent_string_factory.cc
@@ -39,6 +39,28 @@
#error Unknown build configuration.
#endif
+struct SanitizeReplacements {
+ const char* replace_chars;
+ const char* replace_with;
+} kSanitizeReplacements[] = {
+ { ",", u8"\uFF0C" }, // fullwidth comma
+ { "_", u8"\u2E0F" }, // paragraphos
+ { "/", u8"\u2215" }, // division slash
+ { "(", u8"\uFF08" }, // fullwidth left paren
+ { ")", u8"\uFF09" }, // fullwidth right paren
+};
+
+// Replace reserved characters with Unicode homoglyphs
+std::string Sanitize(const std::string& str) {
+ std::string clean(str);
+ for (size_t i=0; i < arraysize(kSanitizeReplacements); i++) {
+ const SanitizeReplacements* replacement = kSanitizeReplacements + i;
+ ReplaceChars(
+ clean, replacement->replace_chars, replacement->replace_with, &clean);
+ }
+ return clean;
+}
+
} // namespace
std::string UserAgentStringFactory::CreateUserAgentString() {
@@ -66,11 +88,12 @@
if (youtube_tv_info_) {
base::StringAppendF(
&user_agent, ", %s_%s_%s/%s (%s, %s, %s)",
- youtube_tv_info_->network_operator.value_or("").c_str(),
+ Sanitize(youtube_tv_info_->network_operator.value_or("")).c_str(),
CreateDeviceTypeString().c_str(),
- youtube_tv_info_->chipset_model_number.value_or("").c_str(),
- youtube_tv_info_->firmware_version.value_or("").c_str(),
- youtube_tv_info_->brand.c_str(), youtube_tv_info_->model.c_str(),
+ Sanitize(youtube_tv_info_->chipset_model_number.value_or("")).c_str(),
+ Sanitize(youtube_tv_info_->firmware_version.value_or("")).c_str(),
+ Sanitize(youtube_tv_info_->brand).c_str(),
+ Sanitize(youtube_tv_info_->model).c_str(),
CreateConnectionTypeString().c_str());
}
@@ -112,10 +135,8 @@
return "STB";
case YouTubeTVInfo::kTV:
return "TV";
-#if SB_API_VERSION >= 4
case YouTubeTVInfo::kAndroidTV:
return "ATV";
-#endif // SB_API_VERSION >= 4
case YouTubeTVInfo::kInvalidDeviceType:
default:
NOTREACHED();
diff --git a/src/cobalt/network/user_agent_string_factory.h b/src/cobalt/network/user_agent_string_factory.h
index 5869855..7f7686f 100644
--- a/src/cobalt/network/user_agent_string_factory.h
+++ b/src/cobalt/network/user_agent_string_factory.h
@@ -47,9 +47,7 @@
struct YouTubeTVInfo {
enum DeviceType {
kInvalidDeviceType,
-#if SB_API_VERSION >= 4
kAndroidTV,
-#endif // SB_API_VERSION >= 4
kBlueRayDiskPlayer,
kGameConsole,
kOverTheTopBox,
diff --git a/src/cobalt/network/user_agent_string_factory_test.cc b/src/cobalt/network/user_agent_string_factory_test.cc
index 6e3a669..80171b8 100644
--- a/src/cobalt/network/user_agent_string_factory_test.cc
+++ b/src/cobalt/network/user_agent_string_factory_test.cc
@@ -82,23 +82,35 @@
class UserAgentStringFactoryWithYouTubeTVInfo : public UserAgentStringFactory {
public:
UserAgentStringFactoryWithYouTubeTVInfo() {
+ // There are deliberately a variety of underscores, commas, slashes, and
+ // parentheses in the strings below to ensure they get sanitized.
os_name_and_version_ = "GLaDOS 3.11";
youtube_tv_info_ = YouTubeTVInfo();
- youtube_tv_info_->network_operator = "ApertureLaboratories";
+ youtube_tv_info_->network_operator = "Aperture_Science_Innovators";
youtube_tv_info_->device_type = YouTubeTVInfo::kOverTheTopBox;
- youtube_tv_info_->chipset_model_number = "Wheatley";
- youtube_tv_info_->firmware_version = "0.01";
- youtube_tv_info_->brand = "Aperture Science";
+ youtube_tv_info_->chipset_model_number = "P-body/Orange_Atlas/Blue";
+ youtube_tv_info_->firmware_version = "0,01";
+ youtube_tv_info_->brand = "Aperture Science (Labs)";
youtube_tv_info_->model = "GLaDOS";
}
};
+// Look-alike replacements expected from sanitizing fields
+#define COMMA u8"\uFF0C" // fullwidth comma
+#define UNDER u8"\u2E0F" // paragraphos
+#define SLASH u8"\u2215" // division slash
+#define LPAREN u8"\uFF08" // fullwidth left paren
+#define RPAREN u8"\uFF09" // fullwidth right paren
+
TEST(UserAgentStringFactoryTest, WithYouTubeTVInfo) {
std::string user_agent_string =
UserAgentStringFactoryWithYouTubeTVInfo().CreateUserAgentString();
- EXPECT_NE(std::string::npos,
- user_agent_string.find("ApertureLaboratories_OTT_Wheatley/0.01 "
- "(Aperture Science, GLaDOS, )"));
+ const char* tv_info_str =
+ "Aperture" UNDER "Science" UNDER "Innovators"
+ "_OTT_"
+ "P-body" SLASH "Orange" UNDER "Atlas" SLASH "Blue"
+ "/0" COMMA "01 (Aperture Science " LPAREN "Labs" RPAREN ", GLaDOS, )";
+ EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
}
class UserAgentStringFactoryWithWiredConnection
diff --git a/src/cobalt/render_tree/mock_resource_provider.h b/src/cobalt/render_tree/mock_resource_provider.h
index 1d13b6c..d30b9fc 100644
--- a/src/cobalt/render_tree/mock_resource_provider.h
+++ b/src/cobalt/render_tree/mock_resource_provider.h
@@ -94,23 +94,17 @@
#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 3
scoped_refptr<Image> CreateImageFromSbDecodeTarget(SbDecodeTarget target) {
UNREFERENCED_PARAMETER(target);
return NULL;
}
bool SupportsSbDecodeTarget() { return false; }
-#endif // SB_API_VERSION >= 3
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() {
return NULL;
}
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() { return NULL; }
-#endif // SB_API_VERSION >= 4
#endif // SB_HAS(GRAPHICS)
diff --git a/src/cobalt/render_tree/resource_provider.h b/src/cobalt/render_tree/resource_provider.h
index 4a648d1..acbbb21 100644
--- a/src/cobalt/render_tree/resource_provider.h
+++ b/src/cobalt/render_tree/resource_provider.h
@@ -27,9 +27,7 @@
#include "cobalt/render_tree/mesh.h"
#include "cobalt/render_tree/node.h"
#include "cobalt/render_tree/typeface.h"
-#if defined(STARBOARD)
#include "starboard/decode_target.h"
-#endif // defined(STARBOARD)
namespace cobalt {
namespace render_tree {
@@ -76,7 +74,6 @@
scoped_ptr<ImageData> pixel_data) = 0;
#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 3
// This function will consume an SbDecodeTarget object produced by
// SbDecodeTargetCreate(), wrap it in a render_tree::Image that can be used
// in a render tree, and return it to the caller.
@@ -85,19 +82,12 @@
// Whether SbDecodeTargetIsSupported or not.
virtual bool SupportsSbDecodeTarget() = 0;
-#endif // SB_API_VERSION >= 3
-#if SB_API_VERSION >= 4
// Return the SbDecodeTargetGraphicsContextProvider associated with the
// ResourceProvider, if it exists. Returns NULL if SbDecodeTarget is not
// supported.
virtual SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() = 0;
-#elif SB_API_VERSION >= 3
- // Return the associated SbDecodeTargetProvider with the ResourceProvider,
- // if it exists. Returns NULL if SbDecodeTarget is not supported.
- virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() = 0;
-#endif // SB__API_VERSION >= 4
#endif // SB_HAS(GRAPHICS)
// Returns a raw chunk of memory that can later be passed into a function like
diff --git a/src/cobalt/render_tree/resource_provider_stub.h b/src/cobalt/render_tree/resource_provider_stub.h
index aee3f69..397d8cb 100644
--- a/src/cobalt/render_tree/resource_provider_stub.h
+++ b/src/cobalt/render_tree/resource_provider_stub.h
@@ -247,30 +247,22 @@
return make_scoped_refptr(new ImageStub(skia_source_data.Pass()));
}
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
scoped_refptr<Image> CreateImageFromSbDecodeTarget(
SbDecodeTarget decode_target) OVERRIDE {
NOTREACHED();
-#if SB_API_VERSION < 4
- SbDecodeTargetDestroy(decode_target);
-#else // 4
SbDecodeTargetRelease(decode_target);
-#endif // 4
return NULL;
}
bool SupportsSbDecodeTarget() OVERRIDE { return false; }
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() OVERRIDE {
return NULL;
}
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
-#endif // SB_API_VERSION >= 4
#endif // SB_HAS(GRAPHICS)
scoped_ptr<RawImageMemory> AllocateRawImageMemory(size_t size_in_bytes,
diff --git a/src/cobalt/render_tree/rounded_corners.cc b/src/cobalt/render_tree/rounded_corners.cc
index b0a3331..7eacb19 100644
--- a/src/cobalt/render_tree/rounded_corners.cc
+++ b/src/cobalt/render_tree/rounded_corners.cc
@@ -17,6 +17,17 @@
namespace cobalt {
namespace render_tree {
+RoundedCorners RoundedCorners::Scale(float sx, float sy) const {
+ return RoundedCorners(RoundedCorner(top_left.horizontal * sx,
+ top_left.vertical * sy),
+ RoundedCorner(top_right.horizontal * sx,
+ top_right.vertical * sy),
+ RoundedCorner(bottom_right.horizontal * sx,
+ bottom_right.vertical * sy),
+ RoundedCorner(bottom_left.horizontal * sx,
+ bottom_left.vertical * sy));
+}
+
RoundedCorners RoundedCorners::Normalize(const math::RectF& rect) const {
float scale = 1.0f;
float size;
@@ -48,14 +59,7 @@
scale = std::min(rect.height() / size, scale);
}
- return RoundedCorners(RoundedCorner(top_left.horizontal * scale,
- top_left.vertical * scale),
- RoundedCorner(top_right.horizontal * scale,
- top_right.vertical * scale),
- RoundedCorner(bottom_right.horizontal * scale,
- bottom_right.vertical * scale),
- RoundedCorner(bottom_left.horizontal * scale,
- bottom_left.vertical * scale));
+ return Scale(scale, scale);
}
bool RoundedCorners::IsNormalized(const math::RectF& rect) const {
diff --git a/src/cobalt/render_tree/rounded_corners.h b/src/cobalt/render_tree/rounded_corners.h
index cf1d03e..7646f65 100644
--- a/src/cobalt/render_tree/rounded_corners.h
+++ b/src/cobalt/render_tree/rounded_corners.h
@@ -91,6 +91,8 @@
return Inset(insets.left(), insets.top(), insets.right(), insets.bottom());
}
+ RoundedCorners Scale(float sx, float sy) const;
+
// Ensure the rounded corners' radii do not exceed the length of the
// corresponding edge of the given rect.
RoundedCorners Normalize(const math::RectF& rect) const;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl
new file mode 100644
index 0000000..168df28
--- /dev/null
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl
@@ -0,0 +1,33 @@
+#version 100
+precision mediump float;
+uniform sampler2D uSampler0_Stage0;
+uniform sampler2D uSampler0_Stage1;
+uniform vec4 uTexDom_Stage1;
+varying vec4 vColor;
+varying vec2 vMatrixCoord_Stage0;
+varying vec2 vMatrixCoord_Stage1;
+
+void main()
+{
+ vec4 output_Stage0;
+ {
+ // Stage 0: Texture
+ output_Stage0 =
+ (vColor * texture2D(uSampler0_Stage0, vMatrixCoord_Stage0).aaaa);
+ }
+ vec4 output_Stage1;
+ {
+ // Stage 1: TextureDomain
+ {
+ bvec4 outside;
+ outside.xy = lessThan(vMatrixCoord_Stage1, uTexDom_Stage1.xy);
+ outside.zw = greaterThan(vMatrixCoord_Stage1, uTexDom_Stage1.zw);
+ output_Stage1 =
+ any(outside) ?
+ vec4(0.0, 0.0, 0.0, 0.0) :
+ (output_Stage0 * texture2D(uSampler0_Stage1,
+ vMatrixCoord_Stage1));
+ }
+ }
+ gl_FragColor = output_Stage1;
+}
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
index 1997d06..d38588c 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
+++ b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
@@ -78,6 +78,7 @@
'fragment_skia_texture_domain.glsl',
'fragment_skia_texture_domain_masked_texture_domain.glsl',
'fragment_skia_texture_masked_texture.glsl',
+ 'fragment_skia_texture_masked_texture_domain.glsl',
'fragment_skia_yuv.glsl',
'fragment_textured_vbo_rgba.glsl',
'fragment_textured_vbo_uyvy_1plane.glsl',
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
index 1da3c9e..e66f6e5 100644
--- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
@@ -41,9 +41,7 @@
: device_(device),
skia_resource_provider_(skia_resource_provider),
submit_offscreen_callback_(submit_offscreen_callback) {
-#if SB_API_VERSION >= 4
decode_target_graphics_context_provider_.device = device;
-#endif // SB_API_VERSION >= 4
}
bool ResourceProvider::PixelFormatSupported(PixelFormat pixel_format) {
@@ -83,25 +81,10 @@
return make_scoped_refptr(new SinglePlaneImage(blitter_source_data.Pass()));
}
-#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
scoped_refptr<render_tree::Image>
ResourceProvider::CreateImageFromSbDecodeTarget(SbDecodeTarget decode_target) {
-#if SB_API_VERSION < 4
- SbDecodeTargetFormat format = SbDecodeTargetGetFormat(decode_target);
- if (format == kSbDecodeTargetFormat1PlaneRGBA) {
- SbBlitterSurface surface =
- SbDecodeTargetGetPlane(decode_target, kSbDecodeTargetPlaneRGBA);
- DCHECK(SbBlitterIsSurfaceValid(surface));
- bool is_opaque = SbDecodeTargetIsOpaque(decode_target);
-
- // Now that we have the surface it contained, we are free to delete
- // |decode_target|.
- SbDecodeTargetDestroy(decode_target);
- return make_scoped_refptr(
- new SinglePlaneImage(surface, is_opaque, base::Closure()));
- }
-#else // SB_API_VERSION < 4
SbDecodeTargetInfo info;
SbMemorySet(&info, 0, sizeof(info));
CHECK(SbDecodeTargetGetInfo(decode_target, &info));
@@ -121,19 +104,14 @@
plane.surface, info.is_opaque,
base::Bind(&SbDecodeTargetRelease, decode_target)));
}
-#endif // SB_API_VERSION < 4
NOTREACHED()
<< "Only format kSbDecodeTargetFormat1PlaneRGBA is currently supported.";
-#if SB_API_VERSION < 4
- SbDecodeTargetDestroy(decode_target);
-#else // SB_API_VERSION < 4
SbDecodeTargetRelease(decode_target);
-#endif // SB_API_VERSION < 4
return NULL;
}
-#endif // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
scoped_ptr<render_tree::RawImageMemory>
ResourceProvider::AllocateRawImageMemory(size_t size_in_bytes,
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
index 3f01c64..92315fe 100644
--- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
@@ -43,21 +43,15 @@
void Finish() OVERRIDE {}
-#if SB_API_VERSION >= 3
scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
SbDecodeTarget decode_target) OVERRIDE;
bool SupportsSbDecodeTarget() OVERRIDE { return true; }
-#endif // SB_API_VERSION >= 3
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider*
GetSbDecodeTargetGraphicsContextProvider() OVERRIDE {
return &decode_target_graphics_context_provider_;
}
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
-#endif // SB_API_VERSION >= 4
bool PixelFormatSupported(render_tree::PixelFormat pixel_format) OVERRIDE;
bool AlphaFormatSupported(render_tree::AlphaFormat alpha_format) OVERRIDE;
@@ -123,10 +117,8 @@
SubmitOffscreenCallback submit_offscreen_callback_;
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider
decode_target_graphics_context_provider_;
-#endif // SB_API_VERSION >= 4
};
} // namespace blitter
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object.cc b/src/cobalt/renderer/rasterizer/egl/draw_object.cc
index c2084cf..24ec5d0 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object.cc
@@ -17,6 +17,7 @@
#include <algorithm>
#include <limits>
+#include "cobalt/math/transform_2d.h"
#include "cobalt/renderer/backend/egl/utils.h"
namespace cobalt {
@@ -24,6 +25,34 @@
namespace rasterizer {
namespace egl {
+namespace {
+// To accommodate large radius values for rounded corners while using mediump
+// floats in the fragment shader, scale 1 / radius.xy by kRCornerGradientScale.
+// The fragment shader will take this into account.
+const float kRCornerGradientScale = 16.0f;
+
+// Get the midpoint of the given rounded rect. A normalized rounded rect
+// should have at least one point which the corners do not cross.
+math::PointF GetRRectCenter(const math::RectF& rect,
+ const render_tree::RoundedCorners& corners) {
+ return math::PointF(
+ 0.5f * (rect.x() + std::max(corners.top_left.horizontal,
+ corners.bottom_left.horizontal) +
+ rect.right() - std::max(corners.top_right.horizontal,
+ corners.bottom_right.horizontal)),
+ 0.5f * (rect.y() + std::max(corners.top_left.vertical,
+ corners.top_right.vertical) +
+ rect.bottom() - std::max(corners.bottom_left.vertical,
+ corners.bottom_right.vertical)));
+}
+
+math::PointF ClampToBounds(const math::RectF& bounds, float x, float y) {
+ return math::PointF(
+ std::min(std::max(bounds.x(), x), bounds.right()),
+ std::min(std::max(bounds.y(), y), bounds.bottom()));
+}
+} // namespace
+
DrawObject::BaseState::BaseState()
: transform(math::Matrix3F::Identity()),
scissor(0, 0,
@@ -38,9 +67,35 @@
rounded_scissor_corners(other.rounded_scissor_corners),
opacity(other.opacity) {}
+DrawObject::RCorner::RCorner(const float (&position)[2], const RCorner& init)
+ : x((position[0] - init.x) * init.rx),
+ y((position[1] - init.y) * init.ry),
+ rx(init.rx * kRCornerGradientScale),
+ ry(init.ry * kRCornerGradientScale) {}
+
DrawObject::DrawObject(const BaseState& base_state)
: base_state_(base_state) {}
+math::Vector2dF DrawObject::GetScale() const {
+ float m00 = base_state_.transform(0, 0);
+ float m01 = base_state_.transform(0, 1);
+ float m10 = base_state_.transform(1, 0);
+ float m11 = base_state_.transform(1, 1);
+ return math::Vector2dF(std::sqrt(m00 * m00 + m10 * m10),
+ std::sqrt(m01 * m01 + m11 * m11));
+}
+
+math::Vector2dF DrawObject::RemoveScaleFromTransform() {
+ // Avoid division by zero.
+ const float kEpsilon = 0.00001f;
+
+ math::Vector2dF scale = GetScale();
+ base_state_.transform = base_state_.transform *
+ math::ScaleMatrix(1.0f / std::max(scale.x(), kEpsilon),
+ 1.0f / std::max(scale.y(), kEpsilon));
+ return scale;
+}
+
// static
uint32_t DrawObject::GetGLRGBA(float r, float g, float b, float a) {
// Ensure color bytes represent RGBA, regardless of endianness.
@@ -56,74 +111,130 @@
}
// static
-void DrawObject::SetRRectUniforms(GLint rect_uniform, GLint corners_uniform,
- const math::RectF& rect, const render_tree::RoundedCorners& corners,
- float inset) {
- math::RectF inset_rect(rect);
- inset_rect.Inset(inset, inset);
- render_tree::RoundedCorners inset_corners =
- corners.Inset(inset, inset, inset, inset);
+void DrawObject::GetRRectAttributes(const math::RectF& bounds,
+ math::RectF rect, render_tree::RoundedCorners corners,
+ RRectAttributes (&out_attributes)[4]) {
+ GetRCornerValues(&rect, &corners, out_attributes);
- // Tweak corners that are square-ish so they have values that play
- // nicely with the shader. Interpolating x^2 / a^2 + y^2 / b^2 does not
- // work well when |a| or |b| are very small.
- if (inset_corners.top_left.horizontal <= 0.5f ||
- inset_corners.top_left.vertical <= 0.5f) {
- inset_corners.top_left.horizontal = 0.0f;
- inset_corners.top_left.vertical = 0.0f;
- }
- if (inset_corners.top_right.horizontal <= 0.5f ||
- inset_corners.top_right.vertical <= 0.5f) {
- inset_corners.top_right.horizontal = 0.0f;
- inset_corners.top_right.vertical = 0.0f;
- }
- if (inset_corners.bottom_left.horizontal <= 0.5f ||
- inset_corners.bottom_left.vertical <= 0.5f) {
- inset_corners.bottom_left.horizontal = 0.0f;
- inset_corners.bottom_left.vertical = 0.0f;
- }
- if (inset_corners.bottom_right.horizontal <= 0.5f ||
- inset_corners.bottom_right.vertical <= 0.5f) {
- inset_corners.bottom_right.horizontal = 0.0f;
- inset_corners.bottom_right.vertical = 0.0f;
- }
+ // Calculate the bounds for each patch. Four patches will be used to cover
+ // the entire bounded area.
+ math::PointF center = GetRRectCenter(rect, corners);
+ center = ClampToBounds(bounds, center.x(), center.y());
+ out_attributes[0].bounds.SetRect(bounds.x(), bounds.y(),
+ center.x() - bounds.x(), center.y() - bounds.y());
+ out_attributes[1].bounds.SetRect(center.x(), bounds.y(),
+ bounds.right() - center.x(), center.y() - bounds.y());
+ out_attributes[2].bounds.SetRect(bounds.x(), center.y(),
+ center.x() - bounds.x(), bounds.bottom() - center.y());
+ out_attributes[3].bounds.SetRect(center.x(), center.y(),
+ bounds.right() - center.x(), bounds.bottom() - center.y());
+}
+
+// static
+void DrawObject::GetRRectAttributes(const math::RectF& bounds,
+ math::RectF rect, render_tree::RoundedCorners corners,
+ RRectAttributes (&out_attributes)[8]) {
+ GetRCornerValues(&rect, &corners, out_attributes);
+ out_attributes[4].rcorner = out_attributes[0].rcorner;
+ out_attributes[5].rcorner = out_attributes[1].rcorner;
+ out_attributes[6].rcorner = out_attributes[2].rcorner;
+ out_attributes[7].rcorner = out_attributes[3].rcorner;
+
+ // Given an ellipse with radii A and B, the largest inscribed rectangle has
+ // dimensions sqrt(2) * A and sqrt(2) * B. To accommodate the antialiased
+ // edge, inset the inscribed rect by a pixel on each side.
+ const float kInsetScale = 0.2929f; // 1 - sqrt(2) / 2
+
+ // Calculate the bounds for each patch. Eight patches will be used to exclude
+ // the inscribed rect:
+ // +---+-----+-----+---+
+ // | | 4 | 5 | |
+ // | 0 +-----+-----+ 1 |
+ // | | | |
+ // +---+ C +---+ C = center point
+ // | | | |
+ // | 2 +-----+-----+ 3 |
+ // | | 6 | 7 | |
+ // +---+-----+-----+---+
+ math::PointF center = GetRRectCenter(rect, corners);
+ center = ClampToBounds(bounds, center.x(), center.y());
+ math::PointF inset0 = ClampToBounds(bounds,
+ rect.x() + kInsetScale * corners.top_left.horizontal + 1.0f,
+ rect.y() + kInsetScale * corners.top_left.vertical + 1.0f);
+ math::PointF inset1 = ClampToBounds(bounds,
+ rect.right() - kInsetScale * corners.top_right.horizontal - 1.0f,
+ rect.y() + kInsetScale * corners.top_right.vertical + 1.0f);
+ math::PointF inset2 = ClampToBounds(bounds,
+ rect.x() + kInsetScale * corners.bottom_left.horizontal + 1.0f,
+ rect.bottom() - kInsetScale * corners.bottom_left.vertical - 1.0f);
+ math::PointF inset3 = ClampToBounds(bounds,
+ rect.right() - kInsetScale * corners.bottom_right.horizontal - 1.0f,
+ rect.bottom() - kInsetScale * corners.bottom_right.vertical - 1.0f);
+
+ out_attributes[0].bounds.SetRect(bounds.x(), bounds.y(),
+ inset0.x() - bounds.x(), center.y() - bounds.y());
+ out_attributes[1].bounds.SetRect(inset1.x(), bounds.y(),
+ bounds.right() - inset1.x(), center.y() - bounds.y());
+ out_attributes[2].bounds.SetRect(bounds.x(), center.y(),
+ inset2.x() - bounds.x(), bounds.bottom() - center.y());
+ out_attributes[3].bounds.SetRect(inset3.x(), center.y(),
+ bounds.right() - inset3.x(), bounds.bottom() - center.y());
+ out_attributes[4].bounds.SetRect(inset0.x(), bounds.y(),
+ center.x() - inset0.x(), inset0.y() - bounds.y());
+ out_attributes[5].bounds.SetRect(center.x(), bounds.y(),
+ inset1.x() - center.x(), inset1.y() - bounds.y());
+ out_attributes[6].bounds.SetRect(inset2.x(), inset2.y(),
+ center.x() - inset2.x(), bounds.bottom() - inset2.y());
+ out_attributes[7].bounds.SetRect(center.x(), inset3.y(),
+ inset3.x() - center.x(), bounds.bottom() - inset3.y());
+}
+
+// static
+void DrawObject::GetRCornerValues(math::RectF* rect,
+ render_tree::RoundedCorners* corners, RRectAttributes out_rcorners[4]) {
// Ensure corner sizes are non-zero to allow generic handling of square and
- // rounded corners.
- const float kMinCornerSize = 0.01f;
- inset_rect.Outset(kMinCornerSize, kMinCornerSize);
- inset_corners = inset_corners.Inset(-kMinCornerSize, -kMinCornerSize,
- -kMinCornerSize, -kMinCornerSize);
- inset_corners = inset_corners.Normalize(inset_rect);
+ // rounded corners. Corner radii must be at least 1 pixel for antialiasing
+ // to work well.
+ const float kMinCornerSize = 1.0f;
- // The rect data is a vec4 representing (min.xy, max.xy).
- float rect_data[4] = {
- inset_rect.x(), inset_rect.y(), inset_rect.right(), inset_rect.bottom(),
- };
- GL_CALL(glUniform4fv(rect_uniform, 1, rect_data));
+ // First inset to make room for the minimum corner size. Then outset to
+ // enforce the minimum corner size. Be careful not to inset more than the
+ // rect size, otherwise the outset rect will be off-centered.
+ rect->Inset(std::min(rect->width() * 0.5f, kMinCornerSize),
+ std::min(rect->height() * 0.5f, kMinCornerSize));
+ *corners = corners->Inset(kMinCornerSize, kMinCornerSize, kMinCornerSize,
+ kMinCornerSize);
+ *corners = corners->Normalize(*rect);
+ rect->Outset(kMinCornerSize, kMinCornerSize);
+ *corners = corners->Inset(-kMinCornerSize, -kMinCornerSize, -kMinCornerSize,
+ -kMinCornerSize);
- // The corners data is a mat4 with each vector representing a corner
- // (ordered top left, top right, bottom left, bottom right). Each corner
- // vec4 represents (start.xy, radius.xy).
- float corners_data[16] = {
- inset_rect.x() + inset_corners.top_left.horizontal,
- inset_rect.y() + inset_corners.top_left.vertical,
- inset_corners.top_left.horizontal,
- inset_corners.top_left.vertical,
- inset_rect.right() - inset_corners.top_right.horizontal,
- inset_rect.y() + inset_corners.top_right.vertical,
- inset_corners.top_right.horizontal,
- inset_corners.top_right.vertical,
- inset_rect.x() + inset_corners.bottom_left.horizontal,
- inset_rect.bottom() - inset_corners.bottom_left.vertical,
- inset_corners.bottom_left.horizontal,
- inset_corners.bottom_left.vertical,
- inset_rect.right() - inset_corners.bottom_right.horizontal,
- inset_rect.bottom() - inset_corners.bottom_right.vertical,
- inset_corners.bottom_right.horizontal,
- inset_corners.bottom_right.vertical,
- };
- GL_CALL(glUniformMatrix4fv(corners_uniform, 1, false, corners_data));
+ // |rcorner| describes (start.xy, 1 / radius.xy) for the relevant corner.
+ // The sign of the radius component is used to facilitate the calculation:
+ // vec2 scaled_offset = (position - corner.xy) * corner.zw
+ // Such that |scaled_offset| is in the first quadrant when the pixel is
+ // in the given rounded corner.
+ COMPILE_ASSERT(sizeof(RCorner) == sizeof(float) * 4, struct_should_be_vec4);
+ out_rcorners[0].rcorner.x = rect->x() + corners->top_left.horizontal;
+ out_rcorners[0].rcorner.y = rect->y() + corners->top_left.vertical;
+ out_rcorners[0].rcorner.rx = -1.0f / corners->top_left.horizontal;
+ out_rcorners[0].rcorner.ry = -1.0f / corners->top_left.vertical;
+
+ out_rcorners[1].rcorner.x = rect->right() - corners->top_right.horizontal;
+ out_rcorners[1].rcorner.y = rect->y() + corners->top_right.vertical;
+ out_rcorners[1].rcorner.rx = 1.0f / corners->top_right.horizontal;
+ out_rcorners[1].rcorner.ry = -1.0f / corners->top_right.vertical;
+
+ out_rcorners[2].rcorner.x = rect->x() + corners->bottom_left.horizontal;
+ out_rcorners[2].rcorner.y = rect->bottom() - corners->bottom_left.vertical;
+ out_rcorners[2].rcorner.rx = -1.0f / corners->bottom_left.horizontal;
+ out_rcorners[2].rcorner.ry = 1.0f / corners->bottom_left.vertical;
+
+ out_rcorners[3].rcorner.x = rect->right() - corners->bottom_right.horizontal;
+ out_rcorners[3].rcorner.y = rect->bottom() - corners->bottom_right.vertical;
+ out_rcorners[3].rcorner.rx = 1.0f / corners->bottom_right.horizontal;
+ out_rcorners[3].rcorner.ry = 1.0f / corners->bottom_right.vertical;
}
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object.h b/src/cobalt/renderer/rasterizer/egl/draw_object.h
index 1e2fb12..19af454 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object.h
@@ -90,9 +90,32 @@
virtual base::TypeId GetTypeId() const = 0;
protected:
+ // Structures describing vertex data for rendering rounded rectangles.
+ struct RCorner {
+ // Constructor to transform a RCorner value into a format which the
+ // shader function IsOutsideRCorner() expects. This expresses the current
+ // vertex position as a scaled offset relevant for the corner, and provides
+ // scalars to assist in calculating the antialiased edge. For more details,
+ // see function_is_outside_rcorner.inc.
+ RCorner(const float (&position)[2], const RCorner& init);
+ RCorner() {}
+ float x, y;
+ float rx, ry;
+ };
+ struct RRectAttributes {
+ math::RectF bounds; // The region in which to use the rcorner data.
+ RCorner rcorner;
+ };
+
DrawObject() {}
explicit DrawObject(const BaseState& base_state);
+ // Extract the scale vector from this object's transform.
+ math::Vector2dF GetScale() const;
+
+ // Remove scale from the transform, and return the scale vector.
+ math::Vector2dF RemoveScaleFromTransform();
+
// Utility function to get the render color for the blend modes that will
// be used. These modes expect alpha to be pre-multiplied.
static render_tree::ColorRGBA GetDrawColor(
@@ -108,14 +131,28 @@
return GetGLRGBA(color.r(), color.g(), color.b(), color.a());
}
- // Set shader uniforms for a rounded rect. Specify a non-zero inset if
- // the rect will be used with anti-aliasing (e.g. 0.5 inset for a 1-pixel
- // anti-aliasing border).
- static void SetRRectUniforms(GLint rect_uniform, GLint corners_uniform,
- const math::RectF& rect, const render_tree::RoundedCorners& corners,
- float inset);
+ // Get the vertex attributes to use to draw the given rounded rect. Each
+ // corner uses a different attribute. These RCorner values must be transformed
+ // before being passed to the shader. (See RCorner constructor.)
+ static void GetRRectAttributes(const math::RectF& bounds,
+ math::RectF rect, render_tree::RoundedCorners corners,
+ RRectAttributes (&out_attributes)[4]);
+
+ // Get the vertex attributes to draw the given rounded rect excluding the
+ // inscribed rect. These RCorner values must be transformed before being
+ // passed to the shader. (See RCorner constructor.)
+ static void GetRRectAttributes(const math::RectF& bounds,
+ math::RectF rect, render_tree::RoundedCorners corners,
+ RRectAttributes (&out_attributes)[8]);
BaseState base_state_;
+
+ private:
+ // Return the RCorner values for the given rounded rect, and the normalized
+ // rect and corner values used.
+ static void GetRCornerValues(math::RectF* rect,
+ render_tree::RoundedCorners* corners,
+ RRectAttributes out_rcorners[4]);
};
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
index cdc1d88..829ef32 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
@@ -121,26 +121,18 @@
const math::RectF& border_rect, const math::RectF& content_rect,
const render_tree::ColorRGBA& border_color,
const render_tree::ColorRGBA& content_color) {
- // Extract scale from the transform matrix. This can be reliably done if no
- // rotations are involved.
- const float kEpsilon = 0.0001f;
- if (std::abs(base_state_.transform(0, 1)) >= kEpsilon ||
- std::abs(base_state_.transform(1, 0)) >= kEpsilon) {
- return false;
- }
-
- // If the scale is 0 in either direction, then there's nothing to render.
- float scale_x = std::abs(base_state_.transform(0, 0));
- float scale_y = std::abs(base_state_.transform(1, 1));
- if (scale_x <= kEpsilon || scale_y <= kEpsilon) {
+ // If the scaled border rect is too small, then don't bother rendering.
+ math::Vector2dF scale = GetScale();
+ if (border_rect.width() * scale.x() < 1.0f ||
+ border_rect.height() * scale.y() < 1.0f) {
return true;
}
// Antialiased subpixel borders are not supported at this time. It can be
// done by attenuating the alpha, but this can get complicated if the borders
// are of different widths.
- float pixel_size_x = 1.0f / scale_x;
- float pixel_size_y = 1.0f / scale_y;
+ float pixel_size_x = 1.0f / scale.x();
+ float pixel_size_y = 1.0f / scale.y();
if (border.left.width < pixel_size_x || border.right.width < pixel_size_x ||
border.top.width < pixel_size_y || border.bottom.width < pixel_size_y) {
return false;
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
index 13401c9..fcabd2d 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
@@ -80,128 +80,340 @@
}
} // namespace
+DrawRectShadowBlur::VertexAttributesSquare::VertexAttributesSquare(
+ float x, float y, float offset_scale) {
+ position[0] = x;
+ position[1] = y;
+ offset[0] = x * offset_scale;
+ offset[1] = y * offset_scale;
+}
+
+DrawRectShadowBlur::VertexAttributesRound::VertexAttributesRound(
+ float x, float y, const RCorner& init) {
+ position[0] = x;
+ position[1] = y;
+ rcorner_scissor = RCorner(position, init);
+}
+
DrawRectShadowBlur::DrawRectShadowBlur(GraphicsState* graphics_state,
const BaseState& base_state, const math::RectF& base_rect,
const OptionalRoundedCorners& base_corners, const math::RectF& spread_rect,
const OptionalRoundedCorners& spread_corners,
const render_tree::ColorRGBA& color, float blur_sigma, bool inset)
- : DrawRectShadowSpread(graphics_state, base_state),
+ : DrawObject(base_state),
spread_rect_(spread_rect),
spread_corners_(spread_corners),
blur_sigma_(blur_sigma),
- is_inset_(inset) {
- const float kBlurExtentInPixels = kBlurExtentInSigmas * blur_sigma;
+ is_inset_(inset),
+ vertex_buffer_(nullptr),
+ index_buffer_(nullptr) {
+ color_ = GetDrawColor(color) * base_state_.opacity;
- if (inset) {
- outer_rect_ = base_rect;
- outer_corners_ = base_corners;
- inner_rect_ = spread_rect;
- inner_rect_.Inset(kBlurExtentInPixels, kBlurExtentInPixels);
- if (inner_rect_.IsEmpty()) {
- inner_rect_.set_origin(spread_rect.CenterPoint());
- }
- if (spread_corners) {
- inner_corners_ = spread_corners->Inset(kBlurExtentInPixels,
- kBlurExtentInPixels, kBlurExtentInPixels, kBlurExtentInPixels);
- }
- } else {
- inner_rect_ = base_rect;
- inner_corners_ = base_corners;
- outer_rect_ = spread_rect;
- outer_rect_.Outset(kBlurExtentInPixels, kBlurExtentInPixels);
- if (spread_corners) {
- outer_corners_ = spread_corners->Inset(-kBlurExtentInPixels,
- -kBlurExtentInPixels, -kBlurExtentInPixels, -kBlurExtentInPixels);
- }
+ // Extract scale from the transform and move it into the vertex attributes
+ // so that the anti-aliased edges remain 1 pixel wide.
+ math::Vector2dF scale = RemoveScaleFromTransform();
+ math::RectF scaled_base_rect(base_rect);
+ scaled_base_rect.Scale(scale.x(), scale.y());
+ OptionalRoundedCorners scaled_base_corners(base_corners);
+ if (scaled_base_corners) {
+ scaled_base_corners = scaled_base_corners->Scale(scale.x(), scale.y());
+ scaled_base_corners = scaled_base_corners->Normalize(scaled_base_rect);
+ }
+ spread_rect_.Scale(scale.x(), scale.y());
+ if (spread_corners_) {
+ spread_corners_ = spread_corners_->Scale(scale.x(), scale.y());
+ spread_corners_ = spread_corners_->Normalize(spread_rect_);
}
- if (base_corners || spread_corners) {
- // If rounded rects are specified, then both the base and spread rects
- // must have rounded corners.
- DCHECK(inner_corners_);
- DCHECK(outer_corners_);
- DCHECK(spread_corners_);
- } else {
- // Non-rounded rects specify vertex offset in terms of sigma from the
- // center of the spread rect.
- offset_scale_ = kBlurDistance / kBlurExtentInPixels;
- offset_center_ = spread_rect_.CenterPoint();
- }
+ // The blur algorithms used by the shaders do not produce good results with
+ // separate x and y blur sigmas. Select a single blur sigma to approximate
+ // the desired blur.
+ blur_sigma_ *= std::sqrt(scale.x() * scale.y());
- color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
+ SetGeometry(graphics_state, scaled_base_rect, scaled_base_corners);
+}
+
+void DrawRectShadowBlur::ExecuteUpdateVertexBuffer(
+ GraphicsState* graphics_state,
+ ShaderProgramManager* program_manager) {
+ if (attributes_square_.size() > 0) {
+ vertex_buffer_ = graphics_state->AllocateVertexData(
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+ SbMemoryCopy(vertex_buffer_, &attributes_square_[0],
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+ } else if (attributes_round_.size() > 0) {
+ vertex_buffer_ = graphics_state->AllocateVertexData(
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ SbMemoryCopy(vertex_buffer_, &attributes_round_[0],
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ index_buffer_ = graphics_state->AllocateVertexIndices(indices_.size());
+ SbMemoryCopy(index_buffer_, &indices_[0],
+ indices_.size() * sizeof(indices_[0]));
+ }
}
void DrawRectShadowBlur::ExecuteRasterize(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
+ if (vertex_buffer_ == nullptr) {
+ return;
+ }
+
// Draw the blurred shadow.
- if (inner_corners_) {
- ShaderProgram<CommonVertexShader,
+ if (spread_corners_) {
+ ShaderProgram<ShaderVertexOffsetRcorner,
ShaderFragmentColorBlurRrects>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
- SetupShader(program->GetVertexShader(), graphics_state);
-
+ SetupVertexShader(graphics_state, program->GetVertexShader());
+ SetFragmentUniforms(program->GetFragmentShader().u_color(),
+ program->GetFragmentShader().u_scale_add());
float sigma_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_);
GL_CALL(glUniform2f(program->GetFragmentShader().u_sigma_scale(),
sigma_scale, sigma_scale));
-
// Pre-calculate the scale values to calculate the normalized gaussian.
GL_CALL(glUniform2f(program->GetFragmentShader().u_gaussian_scale(),
-1.0f / (2.0f * blur_sigma_ * blur_sigma_),
1.0f / (kSqrt2 * kSqrtPi * blur_sigma_)));
- if (is_inset_) {
- // Set the outer rect to be an inclusive scissor, and invert the shadow.
- SetRRectUniforms(program->GetFragmentShader().u_scissor_rect(),
- program->GetFragmentShader().u_scissor_corners(),
- outer_rect_, *outer_corners_, 0.5f);
- GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
- -1.0f, 1.0f));
- } else {
- // Set the inner rect to be an exclusive scissor.
- SetRRectUniforms(program->GetFragmentShader().u_scissor_rect(),
- program->GetFragmentShader().u_scissor_corners(),
- inner_rect_, *inner_corners_, 0.5f);
- GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
- 1.0f, 0.0f));
- }
SetBlurRRectUniforms(program->GetFragmentShader(),
spread_rect_, *spread_corners_, blur_sigma_);
+ GL_CALL(glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT,
+ graphics_state->GetVertexIndexPointer(index_buffer_)));
} else {
- ShaderProgram<CommonVertexShader,
+ ShaderProgram<ShaderVertexOffset,
ShaderFragmentColorBlur>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
- SetupShader(program->GetVertexShader(), graphics_state);
- if (is_inset_) {
- // Invert the shadow.
- GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
- -1.0f, 1.0f));
- } else {
- // Keep the normal (outset) shadow.
- GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
- 1.0f, 0.0f));
- }
+ SetupVertexShader(graphics_state, program->GetVertexShader());
+ SetFragmentUniforms(program->GetFragmentShader().u_color(),
+ program->GetFragmentShader().u_scale_add());
GL_CALL(glUniform4f(program->GetFragmentShader().u_blur_rect(),
- (spread_rect_.x() - offset_center_.x()) * offset_scale_,
- (spread_rect_.y() - offset_center_.y()) * offset_scale_,
- (spread_rect_.right() - offset_center_.x()) * offset_scale_,
- (spread_rect_.bottom() - offset_center_.y()) * offset_scale_));
+ spread_rect_.x(), spread_rect_.y(),
+ spread_rect_.right(), spread_rect_.bottom()));
+ GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_square_.size()));
}
-
- GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, vertex_count_));
}
base::TypeId DrawRectShadowBlur::GetTypeId() const {
- if (inner_corners_) {
- return ShaderProgram<CommonVertexShader,
+ if (spread_corners_) {
+ return ShaderProgram<ShaderVertexOffsetRcorner,
ShaderFragmentColorBlurRrects>::GetTypeId();
} else {
- return ShaderProgram<CommonVertexShader,
+ return ShaderProgram<ShaderVertexOffset,
ShaderFragmentColorBlur>::GetTypeId();
}
}
+void DrawRectShadowBlur::SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexOffset& shader) {
+ graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment());
+ graphics_state->UpdateTransformMatrix(shader.u_view_matrix(),
+ base_state_.transform);
+ graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+ base_state_.scissor.width(), base_state_.scissor.height());
+ graphics_state->VertexAttribPointer(
+ shader.a_position(), 2, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesSquare), vertex_buffer_ +
+ offsetof(VertexAttributesSquare, position));
+ graphics_state->VertexAttribPointer(
+ shader.a_offset(), 2, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesSquare), vertex_buffer_ +
+ offsetof(VertexAttributesSquare, offset));
+ graphics_state->VertexAttribFinish();
+}
+
+void DrawRectShadowBlur::SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexOffsetRcorner& shader) {
+ graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment());
+ graphics_state->UpdateTransformMatrix(shader.u_view_matrix(),
+ base_state_.transform);
+ graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+ base_state_.scissor.width(), base_state_.scissor.height());
+ graphics_state->VertexAttribPointer(
+ shader.a_position(), 2, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesRound), vertex_buffer_ +
+ offsetof(VertexAttributesRound, position));
+ graphics_state->VertexAttribPointer(
+ shader.a_rcorner(), 4, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesRound), vertex_buffer_ +
+ offsetof(VertexAttributesRound, rcorner_scissor));
+ graphics_state->VertexAttribFinish();
+}
+
+void DrawRectShadowBlur::SetFragmentUniforms(
+ GLint color_uniform, GLint scale_add_uniform) {
+ GL_CALL(glUniform4f(color_uniform,
+ color_.r(), color_.g(), color_.b(), color_.a()));
+ if (is_inset_) {
+ // Invert the shadow.
+ GL_CALL(glUniform2f(scale_add_uniform, -1.0f, 1.0f));
+ } else {
+ // Keep the normal (outset) shadow.
+ GL_CALL(glUniform2f(scale_add_uniform, 1.0f, 0.0f));
+ }
+}
+
+void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& base_rect, const OptionalRoundedCorners& base_corners) {
+ const float kBlurExtentInPixels = kBlurExtentInSigmas * blur_sigma_;
+
+ if (base_corners || spread_corners_) {
+ // If rounded rects are specified, then both the base and spread rects
+ // must have rounded corners.
+ DCHECK(base_corners);
+ DCHECK(spread_corners_);
+
+ if (is_inset_) {
+ // Extend the outer rect to include the antialiased edge.
+ math::RectF outer_rect(base_rect);
+ outer_rect.Outset(1.0f, 1.0f);
+ RRectAttributes rrect_outer[4];
+ GetRRectAttributes(outer_rect, base_rect, *base_corners, rrect_outer);
+ // Inset the spread rect by the blur extent. Use that as the inner bounds.
+ RRectAttributes rrect_inner[8];
+ math::RectF inner_rect(spread_rect_);
+ inner_rect.Inset(kBlurExtentInPixels, kBlurExtentInPixels);
+ if (!inner_rect.IsEmpty()) {
+ // Get the inner bounds excluding the inscribed rect.
+ render_tree::RoundedCorners inner_corners = spread_corners_->Inset(
+ kBlurExtentInPixels, kBlurExtentInPixels, kBlurExtentInPixels,
+ kBlurExtentInPixels);
+ inner_corners = inner_corners.Normalize(inner_rect);
+ GetRRectAttributes(outer_rect, inner_rect, inner_corners, rrect_inner);
+ } else {
+ // The blur covers everything inside the outer rect.
+ rrect_inner[0].bounds = outer_rect;
+ }
+ SetGeometry(graphics_state, rrect_outer, rrect_inner);
+ } else {
+ // Extend the outer rect to include the blur.
+ math::RectF outer_rect(spread_rect_);
+ outer_rect.Outset(kBlurExtentInPixels, kBlurExtentInPixels);
+ // Exclude the inscribed rect of the base rounded rect.
+ RRectAttributes rrect[8];
+ GetRRectAttributes(outer_rect, base_rect, *base_corners, rrect);
+ SetGeometry(graphics_state, rrect);
+ }
+ } else {
+ // Handle box shadow with square corners.
+ if (is_inset_) {
+ math::RectF inner_rect(spread_rect_);
+ inner_rect.Inset(kBlurExtentInPixels, kBlurExtentInPixels);
+ SetGeometry(graphics_state, inner_rect, base_rect);
+ } else {
+ math::RectF outer_rect(spread_rect_);
+ outer_rect.Outset(kBlurExtentInPixels, kBlurExtentInPixels);
+ SetGeometry(graphics_state, base_rect, outer_rect);
+ }
+ }
+}
+
+void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& inner_rect, const math::RectF& outer_rect) {
+ // Express offset in terms of blur sigma for the shader.
+ float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_);
+
+ // The spread rect should also be expressed in terms of sigma.
+ spread_rect_.Scale(offset_scale, offset_scale);
+
+ // The box shadow is a triangle strip covering the area between outer rect
+ // and inner rect.
+ if (inner_rect.IsEmpty()) {
+ attributes_square_.reserve(4);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.bottom(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.bottom(), offset_scale);
+ } else {
+ math::RectF inside_rect(inner_rect);
+ inside_rect.Intersect(outer_rect);
+ attributes_square_.reserve(10);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ inside_rect.right(), inside_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.bottom(), offset_scale);
+ attributes_square_.emplace_back(
+ inside_rect.right(), inside_rect.bottom(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.bottom(), offset_scale);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.bottom(), offset_scale);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), offset_scale);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.y(), offset_scale);
+ }
+
+ graphics_state->ReserveVertexData(
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+}
+
+void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state,
+ const RRectAttributes (&rrect)[8]) {
+ // The shadowed area is already split into quads.
+ for (int i = 0; i < arraysize(rrect); ++i) {
+ uint16_t vert = static_cast<uint16_t>(attributes_round_.size());
+ const math::RectF& bounds = rrect[i].bounds;
+ const RCorner& rcorner = rrect[i].rcorner;
+ attributes_round_.emplace_back(bounds.x(), bounds.y(), rcorner);
+ attributes_round_.emplace_back(bounds.right(), bounds.y(), rcorner);
+ attributes_round_.emplace_back(bounds.x(), bounds.bottom(), rcorner);
+ attributes_round_.emplace_back(bounds.right(), bounds.bottom(), rcorner);
+ indices_.emplace_back(vert);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 3);
+ }
+
+ graphics_state->ReserveVertexData(
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ graphics_state->ReserveVertexIndices(indices_.size());
+}
+
+void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state,
+ const RRectAttributes (&rrect_outer)[4],
+ const RRectAttributes (&rrect_inner)[8]) {
+ // Draw the area between the inner rect and outer rect using the outer rect's
+ // rounded corners. The inner quads already exclude the inscribed rectangle.
+ for (int i = 0; i < arraysize(rrect_inner); ++i) {
+ for (int o = 0; o < arraysize(rrect_outer); ++o) {
+ math::RectF rect = math::IntersectRects(
+ rrect_inner[i].bounds, rrect_outer[o].bounds);
+ if (!rect.IsEmpty()) {
+ // Use two triangles to draw the intersection.
+ const RCorner& rcorner = rrect_outer[o].rcorner;
+ uint16_t vert = static_cast<uint16_t>(attributes_round_.size());
+ attributes_round_.emplace_back(rect.x(), rect.y(), rcorner);
+ attributes_round_.emplace_back(rect.right(), rect.y(), rcorner);
+ attributes_round_.emplace_back(rect.x(), rect.bottom(), rcorner);
+ attributes_round_.emplace_back(rect.right(), rect.bottom(), rcorner);
+ indices_.emplace_back(vert);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 3);
+ }
+ }
+ }
+
+ graphics_state->ReserveVertexData(
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ graphics_state->ReserveVertexIndices(indices_.size());
+}
+
} // namespace egl
} // namespace rasterizer
} // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
index 8d36722..107af85 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
@@ -15,7 +15,12 @@
#ifndef COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_SHADOW_BLUR_H_
#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_SHADOW_BLUR_H_
-#include "cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h"
+#include <vector>
+
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
+#include "egl/generated_shader_impl.h"
namespace cobalt {
namespace renderer {
@@ -40,12 +45,7 @@
// Handles drawing a box shadow with blur. This uses a gaussian kernel to fade
// the "blur" region.
-//
-// This uses a shader to mimic skia's SkBlurMask.cpp.
-// See also http://stereopsis.com/shadowrect/ as reference for the formula
-// used to approximate the gaussian integral (which controls the opacity of
-// the shadow).
-class DrawRectShadowBlur : public DrawRectShadowSpread {
+class DrawRectShadowBlur : public DrawObject {
public:
// Draw a blurred box shadow.
// The box shadow exists in the area between |base_rect| and |spread_rect|
@@ -59,16 +59,55 @@
const render_tree::ColorRGBA& color,
float blur_sigma, bool inset);
+ void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+ ShaderProgramManager* program_manager) OVERRIDE;
void ExecuteRasterize(GraphicsState* graphics_state,
ShaderProgramManager* program_manager) OVERRIDE;
base::TypeId GetTypeId() const OVERRIDE;
private:
+ struct VertexAttributesSquare {
+ VertexAttributesSquare(float x, float y, float offset_scale);
+ float position[2];
+ float offset[2];
+ };
+
+ struct VertexAttributesRound {
+ VertexAttributesRound(float x, float y, const RCorner& init);
+ float position[2];
+ RCorner rcorner_scissor;
+ };
+
+ void SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexOffset& shader);
+ void SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexOffsetRcorner& shader);
+ void SetFragmentUniforms(GLint color_uniform, GLint scale_add_uniform);
+
+ void SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& base_rect,
+ const OptionalRoundedCorners& base_corners);
+ void SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& inner_rect,
+ const math::RectF& outer_rect);
+ void SetGeometry(GraphicsState* graphics_state,
+ const RRectAttributes (&rrect)[8]);
+ void SetGeometry(GraphicsState* graphics_state,
+ const RRectAttributes (&rrect_outer)[4],
+ const RRectAttributes (&rrect_inner)[8]);
+
math::RectF spread_rect_;
OptionalRoundedCorners spread_corners_;
-
+ render_tree::ColorRGBA color_;
float blur_sigma_;
bool is_inset_;
+
+ std::vector<VertexAttributesSquare> attributes_square_;
+ std::vector<VertexAttributesRound> attributes_round_;
+ std::vector<uint16_t> indices_;
+
+ uint8_t* vertex_buffer_;
+ uint16_t* index_buffer_;
};
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
index 9dba0d3..faa7286 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
@@ -26,8 +26,19 @@
namespace rasterizer {
namespace egl {
-namespace {
-const int kVertexCount = 10;
+DrawRectShadowSpread::VertexAttributesSquare::VertexAttributesSquare(
+ float x, float y, uint32_t in_color) {
+ position[0] = x;
+ position[1] = y;
+ color = in_color;
+}
+
+DrawRectShadowSpread::VertexAttributesRound::VertexAttributesRound(
+ float x, float y, const RCorner& inner, const RCorner& outer) {
+ position[0] = x;
+ position[1] = y;
+ rcorner_inner = RCorner(position, inner);
+ rcorner_outer = RCorner(position, outer);
}
DrawRectShadowSpread::DrawRectShadowSpread(GraphicsState* graphics_state,
@@ -36,121 +47,94 @@
const OptionalRoundedCorners& outer_corners,
const render_tree::ColorRGBA& color)
: DrawObject(base_state),
- inner_rect_(inner_rect),
- outer_rect_(outer_rect),
- inner_corners_(inner_corners),
- outer_corners_(outer_corners),
- offset_scale_(1.0f),
vertex_buffer_(nullptr),
- vertex_count_(0) {
- color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
- if (inner_corners_ || outer_corners_) {
+ index_buffer_(nullptr) {
+ color_ = GetDrawColor(color) * base_state_.opacity;
+
+ // Extract scale from the transform and move it into the vertex attributes
+ // so that the anti-aliased edges remain 1 pixel wide.
+ math::Vector2dF scale = RemoveScaleFromTransform();
+ math::RectF inside_rect(inner_rect);
+ math::RectF outside_rect(outer_rect);
+ inside_rect.Scale(scale.x(), scale.y());
+ outside_rect.Scale(scale.x(), scale.y());
+
+ if (inner_corners || outer_corners) {
// If using rounded corners, then both inner and outer rects must have
// rounded corner definitions.
- DCHECK(inner_corners_);
- DCHECK(outer_corners_);
+ DCHECK(inner_corners);
+ DCHECK(outer_corners);
+ render_tree::RoundedCorners inside_corners =
+ inner_corners->Scale(scale.x(), scale.y());
+ render_tree::RoundedCorners outside_corners =
+ outer_corners->Scale(scale.x(), scale.y());
+ SetGeometry(graphics_state,
+ inside_rect, inside_corners,
+ outside_rect, outside_corners);
+ } else {
+ SetGeometry(graphics_state, inside_rect, outside_rect);
}
- graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
-}
-
-DrawRectShadowSpread::DrawRectShadowSpread(GraphicsState* graphics_state,
- const BaseState& base_state)
- : DrawObject(base_state),
- offset_scale_(1.0f),
- vertex_buffer_(nullptr),
- vertex_count_(0) {
- graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
}
void DrawRectShadowSpread::ExecuteUpdateVertexBuffer(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
- // Draw the box shadow's spread. This is a triangle strip covering the area
- // between outer rect and inner rect.
- math::RectF inside_rect(inner_rect_);
- math::RectF outside_rect(outer_rect_);
- VertexAttributes attributes[kVertexCount];
-
- if (inner_corners_) {
- // Inset the inside rect to include the rounded corners.
- inside_rect.Inset(
- std::max(inner_corners_->bottom_left.horizontal,
- inner_corners_->top_left.horizontal),
- std::max(inner_corners_->top_left.vertical,
- inner_corners_->top_right.vertical),
- std::max(inner_corners_->top_right.horizontal,
- inner_corners_->bottom_right.horizontal),
- std::max(inner_corners_->bottom_right.vertical,
- inner_corners_->bottom_left.vertical));
-
- // Add a 1 pixel border to the outer rect for anti-aliasing.
- outside_rect.Outset(1.0f, 1.0f);
+ if (attributes_square_.size() > 0) {
+ vertex_buffer_ = graphics_state->AllocateVertexData(
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+ SbMemoryCopy(vertex_buffer_, &attributes_square_[0],
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+ } else if (attributes_round_.size() > 0) {
+ vertex_buffer_ = graphics_state->AllocateVertexData(
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ SbMemoryCopy(vertex_buffer_, &attributes_round_[0],
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ index_buffer_ = graphics_state->AllocateVertexIndices(indices_.size());
+ SbMemoryCopy(index_buffer_, &indices_[0],
+ indices_.size() * sizeof(indices_[0]));
}
-
- // Only pixels inside the outer rect should be touched.
- if (inside_rect.IsEmpty()) {
- vertex_count_ = 4;
- SetVertex(&attributes[0], outside_rect.x(), outside_rect.y());
- SetVertex(&attributes[1], outside_rect.right(), outside_rect.y());
- SetVertex(&attributes[2], outside_rect.x(), outside_rect.bottom());
- SetVertex(&attributes[3], outside_rect.right(), outside_rect.bottom());
- } else {
- inside_rect.Intersect(outside_rect);
- vertex_count_ = 10;
- SetVertex(&attributes[0], outside_rect.x(), outside_rect.y());
- SetVertex(&attributes[1], inside_rect.x(), inside_rect.y());
- SetVertex(&attributes[2], outside_rect.right(), outside_rect.y());
- SetVertex(&attributes[3], inside_rect.right(), inside_rect.y());
- SetVertex(&attributes[4], outside_rect.right(), outside_rect.bottom());
- SetVertex(&attributes[5], inside_rect.right(), inside_rect.bottom());
- SetVertex(&attributes[6], outside_rect.x(), outside_rect.bottom());
- SetVertex(&attributes[7], inside_rect.x(), inside_rect.bottom());
- SetVertex(&attributes[8], outside_rect.x(), outside_rect.y());
- SetVertex(&attributes[9], inside_rect.x(), inside_rect.y());
- }
-
- vertex_buffer_ = graphics_state->AllocateVertexData(
- vertex_count_ * sizeof(VertexAttributes));
- SbMemoryCopy(vertex_buffer_, attributes,
- vertex_count_ * sizeof(VertexAttributes));
}
void DrawRectShadowSpread::ExecuteRasterize(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
- if (inner_corners_) {
- ShaderProgram<CommonVertexShader,
- ShaderFragmentColorBetweenRrects>* program;
- program_manager->GetProgram(&program);
- graphics_state->UseProgram(program->GetHandle());
- SetupShader(program->GetVertexShader(), graphics_state);
-
- SetRRectUniforms(program->GetFragmentShader().u_inner_rect(),
- program->GetFragmentShader().u_inner_corners(),
- inner_rect_, *inner_corners_, 0.5f);
- SetRRectUniforms(program->GetFragmentShader().u_outer_rect(),
- program->GetFragmentShader().u_outer_corners(),
- outer_rect_, *outer_corners_, 0.5f);
- } else {
- ShaderProgram<CommonVertexShader, ShaderFragmentColorInclude>* program;
- program_manager->GetProgram(&program);
- graphics_state->UseProgram(program->GetHandle());
- SetupShader(program->GetVertexShader(), graphics_state);
-
- float include[4] = {
- outer_rect_.x(),
- outer_rect_.y(),
- outer_rect_.right(),
- outer_rect_.bottom()
- };
- GL_CALL(glUniform4fv(program->GetFragmentShader().u_include(), 1, include));
+ if (vertex_buffer_ == nullptr) {
+ return;
}
- GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, vertex_count_));
+ // Draw the box shadow.
+ if (attributes_square_.size() > 0) {
+ ShaderProgram<ShaderVertexColor,
+ ShaderFragmentColor>* program;
+ program_manager->GetProgram(&program);
+ graphics_state->UseProgram(program->GetHandle());
+ SetupVertexShader(graphics_state, program->GetVertexShader());
+ GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_square_.size()));
+ } else {
+ ShaderProgram<ShaderVertexRcorner2,
+ ShaderFragmentRcorner2Color>* program;
+ program_manager->GetProgram(&program);
+ graphics_state->UseProgram(program->GetHandle());
+ SetupVertexShader(graphics_state, program->GetVertexShader());
+ GL_CALL(glUniform4f(program->GetFragmentShader().u_color(),
+ color_.r(), color_.g(), color_.b(), color_.a()));
+ GL_CALL(glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT,
+ graphics_state->GetVertexIndexPointer(index_buffer_)));
+ }
}
-void DrawRectShadowSpread::SetupShader(const CommonVertexShader& shader,
- GraphicsState* graphics_state) {
+base::TypeId DrawRectShadowSpread::GetTypeId() const {
+ if (attributes_square_.size() > 0) {
+ return ShaderProgram<ShaderVertexColor,
+ ShaderFragmentColor>::GetTypeId();
+ } else {
+ return ShaderProgram<ShaderVertexRcorner2,
+ ShaderFragmentRcorner2Color>::GetTypeId();
+ }
+}
+
+void DrawRectShadowSpread::SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexColor& shader) {
graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment());
graphics_state->UpdateTransformMatrix(shader.u_view_matrix(),
base_state_.transform);
@@ -158,36 +142,134 @@
base_state_.scissor.width(), base_state_.scissor.height());
graphics_state->VertexAttribPointer(
shader.a_position(), 2, GL_FLOAT, GL_FALSE,
- sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, position));
+ sizeof(VertexAttributesSquare), vertex_buffer_ +
+ offsetof(VertexAttributesSquare, position));
graphics_state->VertexAttribPointer(
shader.a_color(), 4, GL_UNSIGNED_BYTE, GL_TRUE,
- sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, color));
- graphics_state->VertexAttribPointer(
- shader.a_offset(), 2, GL_FLOAT, GL_FALSE,
- sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, offset));
+ sizeof(VertexAttributesSquare), vertex_buffer_ +
+ offsetof(VertexAttributesSquare, color));
graphics_state->VertexAttribFinish();
}
-base::TypeId DrawRectShadowSpread::GetTypeId() const {
- if (inner_corners_) {
- return ShaderProgram<CommonVertexShader,
- ShaderFragmentColorBetweenRrects>::GetTypeId();
- } else {
- return ShaderProgram<CommonVertexShader,
- ShaderFragmentColorInclude>::GetTypeId();
- }
+void DrawRectShadowSpread::SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexRcorner2& shader) {
+ graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment());
+ graphics_state->UpdateTransformMatrix(shader.u_view_matrix(),
+ base_state_.transform);
+ graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+ base_state_.scissor.width(), base_state_.scissor.height());
+ graphics_state->VertexAttribPointer(
+ shader.a_position(), 2, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesRound), vertex_buffer_ +
+ offsetof(VertexAttributesRound, position));
+ graphics_state->VertexAttribPointer(
+ shader.a_rcorner_inner(), 4, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesRound), vertex_buffer_ +
+ offsetof(VertexAttributesRound, rcorner_inner));
+ graphics_state->VertexAttribPointer(
+ shader.a_rcorner_outer(), 4, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributesRound), vertex_buffer_ +
+ offsetof(VertexAttributesRound, rcorner_outer));
+ graphics_state->VertexAttribFinish();
}
-void DrawRectShadowSpread::SetVertex(VertexAttributes* vertex,
- float x, float y) {
- vertex->position[0] = x;
- vertex->position[1] = y;
- vertex->offset[0] = (x - offset_center_.x()) * offset_scale_;
- vertex->offset[1] = (y - offset_center_.y()) * offset_scale_;
- vertex->color = color_;
+void DrawRectShadowSpread::SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& inner_rect, const math::RectF& outer_rect) {
+ // Draw the box shadow's spread. This is a triangle strip covering the area
+ // between outer rect and inner rect.
+ uint32_t color = GetGLRGBA(color_);
+
+ if (inner_rect.IsEmpty()) {
+ attributes_square_.reserve(4);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), color);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.y(), color);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.bottom(), color);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.bottom(), color);
+ } else {
+ math::RectF inside_rect(inner_rect);
+ inside_rect.Intersect(outer_rect);
+ attributes_square_.reserve(10);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), color);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.y(), color);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.y(), color);
+ attributes_square_.emplace_back(
+ inside_rect.right(), inside_rect.y(), color);
+ attributes_square_.emplace_back(
+ outer_rect.right(), outer_rect.bottom(), color);
+ attributes_square_.emplace_back(
+ inside_rect.right(), inside_rect.bottom(), color);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.bottom(), color);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.bottom(), color);
+ attributes_square_.emplace_back(
+ outer_rect.x(), outer_rect.y(), color);
+ attributes_square_.emplace_back(
+ inside_rect.x(), inside_rect.y(), color);
+ }
+
+ graphics_state->ReserveVertexData(
+ attributes_square_.size() * sizeof(attributes_square_[0]));
+}
+
+void DrawRectShadowSpread::SetGeometry(
+ GraphicsState* graphics_state,
+ const math::RectF& inner_rect,
+ const render_tree::RoundedCorners& inner_corners,
+ const math::RectF& outer_rect,
+ const render_tree::RoundedCorners& outer_corners) {
+ // Draw the area between the inner rounded rect and outer rounded rect. Add
+ // a 1-pixel border to include antialiasing.
+ math::RectF bounds(outer_rect);
+ bounds.Outset(1.0f, 1.0f);
+
+ // Get the render quads for the inner rounded rect excluding its inscribed
+ // rectangle.
+ RRectAttributes rrect_inner[8];
+ GetRRectAttributes(bounds, inner_rect, inner_corners, rrect_inner);
+
+ // Get the render quads for the outer rounded rect.
+ RRectAttributes rrect_outer[4];
+ GetRRectAttributes(bounds, outer_rect, outer_corners, rrect_outer);
+
+ // Add geometry to draw the area between the inner rrect and outer rrect.
+ for (int i = 0; i < arraysize(rrect_inner); ++i) {
+ for (int o = 0; o < arraysize(rrect_outer); ++o) {
+ math::RectF intersection = math::IntersectRects(
+ rrect_inner[i].bounds, rrect_outer[o].bounds);
+ if (!intersection.IsEmpty()) {
+ // Use two triangles to draw the intersection.
+ const RCorner& inner = rrect_inner[i].rcorner;
+ const RCorner& outer = rrect_outer[o].rcorner;
+ uint16_t vert = static_cast<uint16_t>(attributes_round_.size());
+ attributes_round_.emplace_back(
+ intersection.x(), intersection.y(), inner, outer);
+ attributes_round_.emplace_back(
+ intersection.right(), intersection.y(), inner, outer);
+ attributes_round_.emplace_back(
+ intersection.x(), intersection.bottom(), inner, outer);
+ attributes_round_.emplace_back(
+ intersection.right(), intersection.bottom(), inner, outer);
+ indices_.emplace_back(vert);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 1);
+ indices_.emplace_back(vert + 2);
+ indices_.emplace_back(vert + 3);
+ }
+ }
+ }
+
+ graphics_state->ReserveVertexData(
+ attributes_round_.size() * sizeof(attributes_round_[0]));
+ graphics_state->ReserveVertexIndices(indices_.size());
}
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
index 1e4d590..6df79ca 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
@@ -15,6 +15,8 @@
#ifndef COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_SHADOW_SPREAD_H_
#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_SHADOW_SPREAD_H_
+#include <vector>
+
#include "cobalt/math/rect_f.h"
#include "cobalt/render_tree/color_rgba.h"
#include "cobalt/renderer/rasterizer/egl/draw_object.h"
@@ -58,31 +60,43 @@
ShaderProgramManager* program_manager) OVERRIDE;
base::TypeId GetTypeId() const OVERRIDE;
- protected:
- typedef ShaderVertexColorOffset CommonVertexShader;
-
- struct VertexAttributes {
+ private:
+ struct VertexAttributesSquare {
+ VertexAttributesSquare(float x, float y, uint32_t color);
float position[2];
- float offset[2];
uint32_t color;
};
- DrawRectShadowSpread(GraphicsState* graphics_state,
- const BaseState& base_state);
- void SetupShader(const CommonVertexShader& shader,
- GraphicsState* graphics_state);
- void SetVertex(VertexAttributes* vertex, float x, float y);
+ struct VertexAttributesRound {
+ VertexAttributesRound(float x, float y,
+ const RCorner& inner, const RCorner& outer);
+ float position[2];
+ RCorner rcorner_inner;
+ RCorner rcorner_outer;
+ };
- math::RectF inner_rect_;
- math::RectF outer_rect_;
- OptionalRoundedCorners inner_corners_;
- OptionalRoundedCorners outer_corners_;
- math::PointF offset_center_;
- float offset_scale_;
- uint32_t color_;
+ void SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexColor& shader);
+ void SetupVertexShader(GraphicsState* graphics_state,
+ const ShaderVertexRcorner2& shader);
+
+ void SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& inner_rect,
+ const math::RectF& outer_rect);
+ void SetGeometry(GraphicsState* graphics_state,
+ const math::RectF& inner_rect,
+ const render_tree::RoundedCorners& inner_corners,
+ const math::RectF& outer_rect,
+ const render_tree::RoundedCorners& outer_corners);
+
+ render_tree::ColorRGBA color_;
+
+ std::vector<VertexAttributesSquare> attributes_square_;
+ std::vector<VertexAttributesRound> attributes_round_;
+ std::vector<uint16_t> indices_;
uint8_t* vertex_buffer_;
- int vertex_count_;
+ uint16_t* index_buffer_;
};
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
index f12e7ed..2443bbe 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
@@ -26,21 +26,7 @@
namespace egl {
namespace {
-const int kVertexCount = 4;
-
-struct VertexAttributes {
- float position[2];
- float offset[2];
- uint32_t color;
-};
-
-void SetVertex(VertexAttributes* vertex, float x, float y, uint32_t color) {
- vertex->position[0] = x;
- vertex->position[1] = y;
- vertex->offset[0] = x;
- vertex->offset[1] = y;
- vertex->color = color;
-}
+const int kVertexCount = 4 * 6;
} // namespace
DrawRRectColor::DrawRRectColor(GraphicsState* graphics_state,
@@ -51,8 +37,14 @@
rect_(rect),
corners_(corners),
vertex_buffer_(NULL) {
- color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
+ color_ = GetDrawColor(color) * base_state_.opacity;
graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
+
+ // Extract scale from the transform and move it into the vertex attributes
+ // so that the anti-aliased edges remain 1 pixel wide.
+ math::Vector2dF scale = RemoveScaleFromTransform();
+ rect_.Scale(scale.x(), scale.y());
+ corners_ = corners_.Scale(scale.x(), scale.y());
}
void DrawRRectColor::ExecuteUpdateVertexBuffer(
@@ -63,10 +55,31 @@
VertexAttributes attributes[kVertexCount];
math::RectF outer_rect(rect_);
outer_rect.Outset(1.0f, 1.0f);
- SetVertex(&attributes[0], outer_rect.x(), outer_rect.y(), color_);
- SetVertex(&attributes[1], outer_rect.right(), outer_rect.y(), color_);
- SetVertex(&attributes[2], outer_rect.right(), outer_rect.bottom(), color_);
- SetVertex(&attributes[3], outer_rect.x(), outer_rect.bottom(), color_);
+
+ RRectAttributes rrect[4];
+ GetRRectAttributes(outer_rect, rect_, corners_, rrect);
+ for (int r = 0, v = 0; r < arraysize(rrect); ++r) {
+ attributes[v ].position[0] = rrect[r].bounds.x();
+ attributes[v ].position[1] = rrect[r].bounds.y();
+ attributes[v ].rcorner =
+ RCorner(attributes[v ].position, rrect[r].rcorner);
+ attributes[v+1].position[0] = rrect[r].bounds.right();
+ attributes[v+1].position[1] = rrect[r].bounds.y();
+ attributes[v+1].rcorner =
+ RCorner(attributes[v+1].position, rrect[r].rcorner);
+ attributes[v+2].position[0] = rrect[r].bounds.x();
+ attributes[v+2].position[1] = rrect[r].bounds.bottom();
+ attributes[v+2].rcorner =
+ RCorner(attributes[v+2].position, rrect[r].rcorner);
+ attributes[v+3].position[0] = rrect[r].bounds.right();
+ attributes[v+3].position[1] = rrect[r].bounds.bottom();
+ attributes[v+3].rcorner =
+ RCorner(attributes[v+3].position, rrect[r].rcorner);
+ attributes[v+4] = attributes[v+1];
+ attributes[v+5] = attributes[v+2];
+ v += 6;
+ }
+
vertex_buffer_ = graphics_state->AllocateVertexData(sizeof(attributes));
SbMemoryCopy(vertex_buffer_, attributes, sizeof(attributes));
}
@@ -74,8 +87,8 @@
void DrawRRectColor::ExecuteRasterize(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
- ShaderProgram<ShaderVertexColorOffset,
- ShaderFragmentColorRrect>* program;
+ ShaderProgram<ShaderVertexRcorner,
+ ShaderFragmentRcornerColor>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
graphics_state->UpdateClipAdjustment(
@@ -90,23 +103,19 @@
sizeof(VertexAttributes), vertex_buffer_ +
offsetof(VertexAttributes, position));
graphics_state->VertexAttribPointer(
- program->GetVertexShader().a_color(), 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ program->GetVertexShader().a_rcorner(), 4, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, color));
- graphics_state->VertexAttribPointer(
- program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE,
- sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, offset));
+ offsetof(VertexAttributes, rcorner));
graphics_state->VertexAttribFinish();
- SetRRectUniforms(program->GetFragmentShader().u_rect(),
- program->GetFragmentShader().u_corners(),
- rect_, corners_, 0.5f);
- GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, kVertexCount));
+
+ GL_CALL(glUniform4f(program->GetFragmentShader().u_color(),
+ color_.r(), color_.g(), color_.b(), color_.a()));
+ GL_CALL(glDrawArrays(GL_TRIANGLES, 0, kVertexCount));
}
base::TypeId DrawRRectColor::GetTypeId() const {
- return ShaderProgram<ShaderVertexColorOffset,
- ShaderFragmentColorRrect>::GetTypeId();
+ return ShaderProgram<ShaderVertexRcorner,
+ ShaderFragmentRcornerColor>::GetTypeId();
}
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.h b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.h
index c5cd055..de71e9e 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.h
@@ -41,9 +41,14 @@
base::TypeId GetTypeId() const OVERRIDE;
private:
+ struct VertexAttributes {
+ float position[2];
+ RCorner rcorner;
+ };
+
math::RectF rect_;
render_tree::RoundedCorners corners_;
- uint32_t color_;
+ render_tree::ColorRGBA color_;
uint8_t* vertex_buffer_;
};
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
index 7ec422b..dbcbec1 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
@@ -27,22 +27,7 @@
namespace egl {
namespace {
-const int kVertexCount = 4;
-
-struct VertexAttributes {
- float position[2];
- float offset[2];
- float texcoord[2];
-};
-
-void SetVertex(VertexAttributes* vertex, float x, float y, float u, float v) {
- vertex->position[0] = x;
- vertex->position[1] = y;
- vertex->offset[0] = x;
- vertex->offset[1] = y;
- vertex->texcoord[0] = u;
- vertex->texcoord[1] = v;
-}
+const int kVertexCount = 4 * 6;
} // namespace
DrawRRectColorTexture::DrawRRectColorTexture(GraphicsState* graphics_state,
@@ -60,25 +45,58 @@
DCHECK(base_state_.rounded_scissor_corners);
color_ = GetDrawColor(color) * base_state_.opacity;
graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
+
+ // Extract scale from the transform and move it into the vertex attributes
+ // so that the anti-aliased edges remain 1 pixel wide.
+ math::Vector2dF scale = RemoveScaleFromTransform();
+ rect_.Scale(scale.x(), scale.y());
+ base_state_.rounded_scissor_rect.Scale(scale.x(), scale.y());
+ base_state_.rounded_scissor_corners =
+ base_state_.rounded_scissor_corners->Scale(scale.x(), scale.y());
}
void DrawRRectColorTexture::ExecuteUpdateVertexBuffer(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
+ const float kWidthScale = 1.0f / rect_.width();
+ const float kHeightScale = 1.0f / rect_.height();
+
VertexAttributes attributes[kVertexCount];
- SetVertex(&attributes[0], rect_.x(), rect_.y(),
- texcoord_transform_(0, 2), texcoord_transform_(1, 2)); // uv = (0,0)
- SetVertex(&attributes[1], rect_.right(), rect_.y(),
- texcoord_transform_(0, 0) + texcoord_transform_(0, 2), // uv = (1,0)
- texcoord_transform_(1, 0) + texcoord_transform_(1, 2));
- SetVertex(&attributes[2], rect_.right(), rect_.bottom(),
- texcoord_transform_(0, 0) + texcoord_transform_(0, 1) + // uv = (1,1)
- texcoord_transform_(0, 2),
- texcoord_transform_(1, 0) + texcoord_transform_(1, 1) +
- texcoord_transform_(1, 2));
- SetVertex(&attributes[3], rect_.x(), rect_.bottom(),
- texcoord_transform_(0, 1) + texcoord_transform_(0, 2), // uv = (0,1)
- texcoord_transform_(1, 1) + texcoord_transform_(1, 2));
+ RRectAttributes rrect[4];
+ GetRRectAttributes(rect_, base_state_.rounded_scissor_rect,
+ *base_state_.rounded_scissor_corners, rrect);
+ for (int r = 0, v = 0; r < arraysize(rrect); ++r) {
+ attributes[v ].position[0] = rrect[r].bounds.x();
+ attributes[v ].position[1] = rrect[r].bounds.y();
+ attributes[v ].rcorner =
+ RCorner(attributes[v ].position, rrect[r].rcorner);
+ attributes[v+1].position[0] = rrect[r].bounds.right();
+ attributes[v+1].position[1] = rrect[r].bounds.y();
+ attributes[v+1].rcorner =
+ RCorner(attributes[v+1].position, rrect[r].rcorner);
+ attributes[v+2].position[0] = rrect[r].bounds.x();
+ attributes[v+2].position[1] = rrect[r].bounds.bottom();
+ attributes[v+2].rcorner =
+ RCorner(attributes[v+2].position, rrect[r].rcorner);
+ attributes[v+3].position[0] = rrect[r].bounds.right();
+ attributes[v+3].position[1] = rrect[r].bounds.bottom();
+ attributes[v+3].rcorner =
+ RCorner(attributes[v+3].position, rrect[r].rcorner);
+
+ for (int t = v; t < v + 4; ++t) {
+ math::PointF texcoord(
+ (attributes[t].position[0] - rect_.x()) * kWidthScale,
+ (attributes[t].position[1] - rect_.y()) * kHeightScale);
+ texcoord = texcoord_transform_ * texcoord;
+ attributes[t].texcoord[0] = texcoord.x();
+ attributes[t].texcoord[1] = texcoord.y();
+ }
+
+ attributes[v+4] = attributes[v+1];
+ attributes[v+5] = attributes[v+2];
+ v += 6;
+ }
+
vertex_buffer_ = graphics_state->AllocateVertexData(sizeof(attributes));
SbMemoryCopy(vertex_buffer_, attributes, sizeof(attributes));
@@ -119,8 +137,8 @@
void DrawRRectColorTexture::ExecuteRasterize(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
- ShaderProgram<ShaderVertexOffsetTexcoord,
- ShaderFragmentTexcoordColorRrect>* program;
+ ShaderProgram<ShaderVertexRcornerTexcoord,
+ ShaderFragmentRcornerTexcoordColor>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
graphics_state->UpdateClipAdjustment(
@@ -135,19 +153,15 @@
sizeof(VertexAttributes), vertex_buffer_ +
offsetof(VertexAttributes, position));
graphics_state->VertexAttribPointer(
- program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE,
+ program->GetVertexShader().a_rcorner(), 4, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), vertex_buffer_ +
- offsetof(VertexAttributes, offset));
+ offsetof(VertexAttributes, rcorner));
graphics_state->VertexAttribPointer(
program->GetVertexShader().a_texcoord(), 2, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), vertex_buffer_ +
offsetof(VertexAttributes, texcoord));
graphics_state->VertexAttribFinish();
- SetRRectUniforms(program->GetFragmentShader().u_rect(),
- program->GetFragmentShader().u_corners(),
- base_state_.rounded_scissor_rect,
- *base_state_.rounded_scissor_corners, 0.5f);
GL_CALL(glUniform4f(program->GetFragmentShader().u_color(),
color_.r(), color_.g(), color_.b(), color_.a()));
GL_CALL(glUniform4fv(program->GetFragmentShader().u_texcoord_clamp(), 1,
@@ -157,7 +171,7 @@
graphics_state->ActiveBindTexture(
program->GetFragmentShader().u_texture_texunit(),
texture_->GetTarget(), texture_->gl_handle(), GL_REPEAT);
- GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, kVertexCount));
+ GL_CALL(glDrawArrays(GL_TRIANGLES, 0, kVertexCount));
graphics_state->ActiveBindTexture(
program->GetFragmentShader().u_texture_texunit(),
texture_->GetTarget(), texture_->gl_handle(), GL_CLAMP_TO_EDGE);
@@ -165,13 +179,13 @@
graphics_state->ActiveBindTexture(
program->GetFragmentShader().u_texture_texunit(),
texture_->GetTarget(), texture_->gl_handle());
- GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, kVertexCount));
+ GL_CALL(glDrawArrays(GL_TRIANGLES, 0, kVertexCount));
}
}
base::TypeId DrawRRectColorTexture::GetTypeId() const {
- return ShaderProgram<ShaderVertexOffsetTexcoord,
- ShaderFragmentTexcoordColorRrect>::GetTypeId();
+ return ShaderProgram<ShaderVertexRcornerTexcoord,
+ ShaderFragmentRcornerTexcoordColor>::GetTypeId();
}
} // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.h b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.h
index c65347e..98a5c84 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.h
@@ -44,6 +44,12 @@
base::TypeId GetTypeId() const OVERRIDE;
private:
+ struct VertexAttributes {
+ float position[2];
+ float texcoord[2];
+ RCorner rcorner;
+ };
+
math::Matrix3F texcoord_transform_;
math::RectF rect_;
render_tree::ColorRGBA color_;
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc
index 0418992..74aba4b 100644
--- a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc
+++ b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc
@@ -17,6 +17,7 @@
#include <GLES2/gl2.h>
#include "cobalt/renderer/backend/egl/utils.h"
+#include "egl/generated_shader_impl.h"
namespace cobalt {
namespace renderer {
@@ -24,6 +25,13 @@
namespace egl {
ShaderProgramManager::ShaderProgramManager() {
+ // These are shaders that get instantiated during video playback when the
+ // users starts interacting with the transport controls. They are preloaded
+ // to prevent UI-hiccups.
+ // These shaders are generated from egl/generated_shader_impl.h
+ Preload<ShaderVertexOffsetRcorner, ShaderFragmentColorBlurRrects>();
+ Preload<ShaderVertexColorOffset, ShaderFragmentColorInclude>();
+ Preload<ShaderVertexRcornerTexcoord, ShaderFragmentRcornerTexcoordColor>();
}
ShaderProgramManager::~ShaderProgramManager() {
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.h b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.h
index f9c396e..7184e57 100644
--- a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.h
+++ b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.h
@@ -44,6 +44,9 @@
ShaderProgramBase* FindProgram(base::TypeId program_type_id);
void AddProgram(base::TypeId program_type_id, ShaderProgramBase* program);
+ template <typename VertextShaderT, typename FragmentShaderT>
+ void Preload();
+
typedef base::linked_hash_map<base::TypeId, ShaderProgramBase*> ProgramMap;
ProgramMap program_map_;
};
@@ -59,6 +62,12 @@
*out_program = base::polymorphic_downcast<ShaderProgramType*>(program);
}
+template <typename VertextShaderT, typename FragmentShaderT>
+inline void ShaderProgramManager::Preload() {
+ ShaderProgram<VertextShaderT, FragmentShaderT>* program;
+ GetProgram(&program);
+}
+
} // namespace egl
} // namespace rasterizer
} // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_between_rrects.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_between_rrects.glsl
deleted file mode 100644
index f2897ad..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_between_rrects.glsl
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-// A rounded rect is represented by a vec4 specifying (min.xy, max.xy), and a
-// matrix of corners. Each vector in the matrix represents a corner (order:
-// top left, top right, bottom left, bottom right). Each corner vec4 represents
-// (start.xy, radius.xy).
-uniform vec4 u_inner_rect;
-uniform mat4 u_inner_corners;
-uniform vec4 u_outer_rect;
-uniform mat4 u_outer_corners;
-
-varying vec2 v_offset;
-varying vec4 v_color;
-
-#include "function_is_outside_rrect.inc"
-
-void main() {
- float inner_scale = IsOutsideRRect(v_offset, u_inner_rect, u_inner_corners);
- float outer_scale = IsOutsideRRect(v_offset, u_outer_rect, u_outer_corners);
- gl_FragColor = v_color * (inner_scale * (1.0 - outer_scale));
-}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
index 50ebd6a..4f15571 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
@@ -13,11 +13,12 @@
// limitations under the License.
precision mediump float;
+
+uniform vec4 u_color;
uniform vec4 u_blur_rect;
uniform vec2 u_scale_add;
varying vec2 v_offset;
-varying vec4 v_color;
#include "function_gaussian_integral.inc"
@@ -27,5 +28,5 @@
float integral = GaussianIntegral(u_blur_rect.xz - v_offset.xx) *
GaussianIntegral(u_blur_rect.yw - v_offset.yy);
float blur_scale = integral * u_scale_add.x + u_scale_add.y;
- gl_FragColor = v_color * blur_scale;
+ gl_FragColor = u_color * blur_scale;
}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl
index ee8c4ae..efc8747 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl
@@ -14,13 +14,6 @@
precision mediump float;
-// A rounded rect is represented by a vec4 specifying (min.xy, max.xy)
-// and a matrix of corners. Each vector in the matrix represents a corner
-// (order: top left, top right, bottom left, bottom right). Each corner vec4
-// represents (start.xy, radius.xy).
-uniform vec4 u_scissor_rect;
-uniform mat4 u_scissor_corners;
-
// The rounded spread rect is represented in a way to optimize calculation of
// the extents. Each element of a vec4 represents a corner's value -- order
// is top left, top right, bottom left, bottom right. Extents for each corner
@@ -49,14 +42,16 @@
// inset shadow with scissor rect behaving as an inclusive scissor.
uniform vec2 u_scale_add;
+uniform vec4 u_color;
+
// Blur calculations happen in terms in sigma distances. Use sigma_scale to
// translate pixel distances into sigma distances.
uniform vec2 u_sigma_scale;
varying vec2 v_offset;
-varying vec4 v_color;
+varying vec4 v_rcorner;
-#include "function_is_outside_rrect.inc"
+#include "function_is_outside_rcorner.inc"
#include "function_gaussian_integral.inc"
vec2 GetXExtents(float y) {
@@ -126,8 +121,7 @@
void main() {
float scissor_scale =
- IsOutsideRRect(v_offset, u_scissor_rect, u_scissor_corners) *
- u_scale_add.x + u_scale_add.y;
+ IsOutsideRCorner(v_rcorner) * u_scale_add.x + u_scale_add.y;
float blur_scale = GetBlur(v_offset) * u_scale_add.x + u_scale_add.y;
- gl_FragColor = v_color * (blur_scale * scissor_scale);
+ gl_FragColor = u_color * (blur_scale * scissor_scale);
}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_rrect.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_rrect.glsl
deleted file mode 100644
index 26549ed..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_rrect.glsl
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-// A rounded rect is represented by a vec4 specifying (min.xy, max.xy), and a
-// matrix of corners. Each vector in the matrix represents a corner (order:
-// top left, top right, bottom left, bottom right). Each corner vec4 represents
-// (start.xy, radius.xy).
-uniform vec4 u_rect;
-uniform mat4 u_corners;
-
-varying vec2 v_offset;
-varying vec4 v_color;
-
-#include "function_is_outside_rrect.inc"
-
-void main() {
- float scale = IsOutsideRRect(v_offset, u_rect, u_corners);
- gl_FragColor = v_color * (1.0 - scale);
-}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner2_color.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner2_color.glsl
new file mode 100644
index 0000000..c7a8601
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner2_color.glsl
@@ -0,0 +1,26 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+precision mediump float;
+
+uniform vec4 u_color;
+varying vec4 v_rcorner_inner;
+varying vec4 v_rcorner_outer;
+
+#include "function_is_outside_rcorner.inc"
+
+void main() {
+ float inner_scale = IsOutsideRCorner(v_rcorner_inner);
+ float outer_scale = 1.0 - IsOutsideRCorner(v_rcorner_outer);
+ gl_FragColor = u_color * (inner_scale * outer_scale);
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_color.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_color.glsl
new file mode 100644
index 0000000..02c2425
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_color.glsl
@@ -0,0 +1,25 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+precision mediump float;
+
+uniform vec4 u_color;
+varying vec4 v_rcorner;
+
+#include "function_is_outside_rcorner.inc"
+
+void main() {
+ float scale = IsOutsideRCorner(v_rcorner);
+ gl_FragColor = u_color * (1.0 - scale);
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_texcoord_color.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_texcoord_color.glsl
new file mode 100644
index 0000000..4676317
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_texcoord_color.glsl
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+precision mediump float;
+
+uniform vec4 u_color;
+uniform vec4 u_texcoord_clamp;
+uniform sampler2D u_texture;
+
+varying vec4 v_rcorner;
+varying vec2 v_texcoord;
+
+#include "function_is_outside_rcorner.inc"
+
+void main() {
+ float scale = IsOutsideRCorner(v_rcorner);
+ gl_FragColor = u_color * (1.0 - scale) * texture2D(u_texture,
+ clamp(v_texcoord, u_texcoord_clamp.xy, u_texcoord_clamp.zw));
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord_color_rrect.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord_color_rrect.glsl
deleted file mode 100644
index 34a25ad..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord_color_rrect.glsl
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-uniform vec4 u_color;
-uniform vec4 u_texcoord_clamp;
-uniform sampler2D u_texture;
-
-// A rounded rect is represented by a vec4 specifying (min.xy, max.xy), and a
-// matrix of corners. Each vector in the matrix represents a corner (order:
-// top left, top right, bottom left, bottom right). Each corner vec4 represents
-// (start.xy, radius.xy).
-uniform vec4 u_rect;
-uniform mat4 u_corners;
-
-varying vec2 v_offset;
-varying vec2 v_texcoord;
-
-#include "function_is_outside_rrect.inc"
-
-void main() {
- float scale = IsOutsideRRect(v_offset, u_rect, u_corners);
- gl_FragColor = u_color * (1.0 - scale) * texture2D(u_texture,
- clamp(v_texcoord, u_texcoord_clamp.xy, u_texcoord_clamp.zw));
-}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rcorner.inc b/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rcorner.inc
new file mode 100644
index 0000000..5233ae9
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rcorner.inc
@@ -0,0 +1,37 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Return 0 if the given position is inside the rounded corner, or scale
+// towards 1 as it goes outside a 1-pixel anti-aliasing border.
+// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
+// representing the offset of the current position in terms of radius.xy
+// (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
+// current position is outside the corner start.
+float IsOutsideRCorner(vec4 rcorner) {
+ // Estimate the distance to an implicit function using
+ // dist = f(x,y) / length(gradient(f(x,y)))
+ // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
+ vec2 scaled = max(rcorner.xy, 0.0);
+ float implicit = dot(scaled, scaled) - 1.0;
+
+ // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
+ // was scaled by kRCornerGradientScale in the vertex attribute data.
+ // Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
+ const float kRCornerGradientScale = 16.0;
+ vec2 gradient = 2.0 * scaled * rcorner.zw;
+ float inv_gradient = kRCornerGradientScale *
+ inversesqrt(max(dot(gradient, gradient), 0.0001));
+
+ return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rrect.inc b/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rrect.inc
deleted file mode 100644
index e3e58b9..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rrect.inc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Return 0 if the given point is inside the rounded rect, or scale towards 1
-// as it goes outside a 1-pixel anti-aliasing border.
-// |rect| represents (min.xy, max.xy) of the encompassing rectangle.
-// |corners| is a matrix with each vec4 representing (start.xy, radius.xy) of
-// a corner. The order is top left, top right, bottom left, bottom right.
-float IsOutsideRRect(vec2 point, vec4 rect, mat4 corners) {
- vec4 select_corner = vec4(
- step(point.x, corners[0].x) * step(point.y, corners[0].y),
- step(corners[1].x, point.x) * step(point.y, corners[1].y),
- step(point.x, corners[2].x) * step(corners[2].y, point.y),
- step(corners[3].x, point.x) * step(corners[3].y, point.y));
- if (dot(select_corner, vec4(1.0)) > 0.5) {
- // Estimate the amount of anti-aliasing that should be used by comparing
- // x^2 / a^2 + y^2 / b^2 for the ellipse and ellipse + 1 pixel.
- vec4 corner = corners * select_corner;
- vec2 pixel_offset = point - corner.xy;
-
- if (abs(corner.z - corner.w) < 0.1) {
- // This is a square or round corner.
- return clamp(length(pixel_offset) - corner.z, 0.0, 1.0);
- }
-
- vec2 offset_min = pixel_offset / corner.zw;
- vec2 offset_max = pixel_offset / (corner.zw + vec2(1.0));
- float result_min = dot(offset_min, offset_min);
- float result_max = dot(offset_max, offset_max);
-
- // Return 1.0 if outside, or interpolate if in the border, or 0 if inside.
- return (result_max >= 1.0) ? 1.0 :
- max(result_min - 1.0, 0.0) / (result_min - result_max);
- }
-
- return clamp(rect.x - point.x, 0.0, 1.0) +
- clamp(point.x - rect.z, 0.0, 1.0) +
- clamp(rect.y - point.y, 0.0, 1.0) +
- clamp(point.y - rect.w, 0.0, 1.0);
-}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
index 9d00899..2ebc954 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
@@ -20,22 +20,25 @@
'generate_class_script': '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/generate_shader_impl.py',
'shader_sources': [
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color.glsl',
- '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_between_rrects.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl',
- '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_rrect.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_texcoord.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_opacity_texcoord1d.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_color.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner2_color.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_rcorner_texcoord_color.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord.glsl',
- '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord_color_rrect.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/function_gaussian_integral.inc',
- '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rrect.inc',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/function_is_outside_rcorner.inc',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_offset.glsl',
- '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_texcoord.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner2.glsl',
+ '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner_texcoord.glsl',
'<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl',
],
},
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl
new file mode 100644
index 0000000..3d9ff46
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl
@@ -0,0 +1,28 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+uniform vec4 u_clip_adjustment;
+uniform mat3 u_view_matrix;
+attribute vec2 a_position;
+attribute vec4 a_rcorner;
+varying vec2 v_offset;
+varying vec4 v_rcorner;
+
+void main() {
+ vec3 pos2d = u_view_matrix * vec3(a_position, 1);
+ gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
+ u_clip_adjustment.zw, 0, pos2d.z);
+ v_offset = a_position;
+ v_rcorner = a_rcorner;
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_texcoord.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_texcoord.glsl
deleted file mode 100644
index df81d24..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_texcoord.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec2 a_offset;
-attribute vec2 a_texcoord;
-varying vec2 v_offset;
-varying vec2 v_texcoord;
-
-void main() {
- vec3 pos2d = u_view_matrix * vec3(a_position, 1);
- gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
- u_clip_adjustment.zw, 0, pos2d.z);
- v_offset = a_offset;
- v_texcoord = a_texcoord;
-}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner.glsl
new file mode 100644
index 0000000..a84d4f2
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner.glsl
@@ -0,0 +1,26 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+uniform vec4 u_clip_adjustment;
+uniform mat3 u_view_matrix;
+attribute vec2 a_position;
+attribute vec4 a_rcorner;
+varying vec4 v_rcorner;
+
+void main() {
+ vec3 pos2d = u_view_matrix * vec3(a_position, 1);
+ gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
+ u_clip_adjustment.zw, 0, pos2d.z);
+ v_rcorner = a_rcorner;
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner2.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner2.glsl
new file mode 100644
index 0000000..fa4d155
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner2.glsl
@@ -0,0 +1,28 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+uniform vec4 u_clip_adjustment;
+uniform mat3 u_view_matrix;
+attribute vec2 a_position;
+attribute vec4 a_rcorner_inner;
+attribute vec4 a_rcorner_outer;
+varying vec4 v_rcorner_inner;
+varying vec4 v_rcorner_outer;
+
+void main() {
+ vec3 pos2d = u_view_matrix * vec3(a_position, 1);
+ gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
+ u_clip_adjustment.zw, 0, pos2d.z);
+ v_rcorner_inner = a_rcorner_inner;
+ v_rcorner_outer = a_rcorner_outer;
+}
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner_texcoord.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner_texcoord.glsl
new file mode 100644
index 0000000..103d1ab
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_rcorner_texcoord.glsl
@@ -0,0 +1,29 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+uniform vec4 u_clip_adjustment;
+uniform mat3 u_view_matrix;
+attribute vec2 a_position;
+attribute vec4 a_rcorner;
+attribute vec2 a_texcoord;
+varying vec4 v_rcorner;
+varying vec2 v_texcoord;
+
+void main() {
+ vec3 pos2d = u_view_matrix * vec3(a_position, 1);
+ gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
+ u_clip_adjustment.zw, 0, pos2d.z);
+ v_rcorner = a_rcorner;
+ v_texcoord = a_texcoord;
+}
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index a3f66eb..760bb4d 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -216,6 +216,44 @@
RotateMatrix(static_cast<float>(M_PI) / 6.0f)));
}
+TEST_F(PixelTest, ScaledThenRotatedRectWithDifferentRoundedCorners) {
+ RoundedCorner top_left(6, 15);
+ RoundedCorner top_right(0, 0);
+ RoundedCorner bottom_right(6, 25);
+ RoundedCorner bottom_left(2, 25);
+
+ scoped_ptr<RoundedCorners> rounded_corners(
+ new RoundedCorners(top_left, top_right, bottom_right, bottom_left));
+
+ TestTree(new MatrixTransformNode(
+ new RectNode(RectF(-7, -25, 14, 50),
+ scoped_ptr<Brush>(
+ new SolidColorBrush(ColorRGBA(1, 1, 1, 1))),
+ rounded_corners.Pass()),
+ TranslateMatrix(100.0f, 100.0f) *
+ RotateMatrix(static_cast<float>(M_PI) / 3.0f) *
+ ScaleMatrix(-10.0f, 2.0f)));
+}
+
+TEST_F(PixelTest, RotatedThenScaledRectWithDifferentRoundedCorners) {
+ RoundedCorner top_left(4, 7);
+ RoundedCorner top_right(0, 0);
+ RoundedCorner bottom_right(10, 2);
+ RoundedCorner bottom_left(5, 3);
+
+ scoped_ptr<RoundedCorners> rounded_corners(
+ new RoundedCorners(top_left, top_right, bottom_right, bottom_left));
+
+ TestTree(new MatrixTransformNode(
+ new RectNode(RectF(-10, -7, 20, 14),
+ scoped_ptr<Brush>(
+ new SolidColorBrush(ColorRGBA(1, 1, 1, 1))),
+ rounded_corners.Pass()),
+ TranslateMatrix(100.0f, 100.0f) *
+ ScaleMatrix(6.0f, 9.0f) *
+ RotateMatrix(static_cast<float>(M_PI) / 6.0f)));
+}
+
TEST_F(PixelTest, RedRectWithDifferentRoundedCornersOnTopLeftOfSurface) {
RoundedCorner top_left(10, 10);
RoundedCorner top_right(20, 20);
@@ -1952,6 +1990,19 @@
new ImageNode(image)));
}
+TEST_F(PixelTest, ScaledThenRotatedRoundedCornersViewportOverImage) {
+ scoped_refptr<Image> image =
+ CreateColoredCheckersImage(GetResourceProvider(), output_surface_size());
+
+ TestTree(new MatrixTransformNode(
+ new FilterNode(
+ ViewportFilter(RectF(25, 5, 150, 10), RoundedCorners(25, 2)),
+ new ImageNode(image)),
+ TranslateMatrix(-30, 130) *
+ RotateMatrix(static_cast<float>(M_PI / 3.0f)) *
+ ScaleMatrix(1.0f, 10.0f)));
+}
+
TEST_F(PixelTest, RoundedCornersViewportOverWrappingImage) {
scoped_refptr<Image> image =
CreateColoredCheckersImage(GetResourceProvider(), output_surface_size());
@@ -2829,6 +2880,14 @@
Shadow(Vector2dF(0.0f, 0.0f), 100.0f, ColorRGBA(0, 0, 0, 1))));
}
+TEST_F(PixelTest, ScaledBoxShadowWithSpreadAndBlurCentered) {
+ TestTree(new MatrixTransformNode(CreateShadowRectWithBackground(
+ output_surface_size(), ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
+ ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f), RectF(50, 8, 100, 4),
+ Shadow(Vector2dF(0.0f, 0.0f), 3.0f, ColorRGBA(0, 0, 0, 1)), false, 5.0f),
+ ScaleMatrix(1.0f, 10.0f)));
+}
+
TEST_F(PixelTest, TransparentBoxShadowBlurOnGreenBackgroundCentered) {
TestTree(CreateShadowRectWithBackground(
output_surface_size(), ColorRGBA(0.3f, 0.8f, 0.3f, 1.0f),
@@ -2916,6 +2975,15 @@
RoundedCorners(25, 25)));
}
+TEST_F(PixelTest, ScaledBoxShadowEllipseWithOutset5pxSpreadAndRoundedCorners) {
+ TestTree(new MatrixTransformNode(CreateShadowRectWithBackground(
+ output_surface_size(), ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
+ ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f), RectF(6, 25, 2, 100),
+ Shadow(Vector2dF(0.0f, 0.0f), 0.0f, ColorRGBA(0, 0, 0, 1)), false, 5.0f,
+ RoundedCorners(1, 50)),
+ ScaleMatrix(15.0f, 1.0f)));
+}
+
TEST_F(PixelTest, BoxShadowCircleWithInset25pxSpread1pxBlurAndRoundedCorners) {
TestTree(CreateShadowRectWithBackground(
output_surface_size(), ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
@@ -3034,6 +3102,16 @@
}
TEST_F(PixelTest,
+ ScaledBoxShadowEllipseWithOutset25pxSpread3pxBlurAndRoundedCorners) {
+ TestTree(new MatrixTransformNode(CreateShadowRectWithBackground(
+ output_surface_size(), ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
+ ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f), RectF(20, 5, 140, 10),
+ Shadow(Vector2dF(8.0f, 1.0f), 3.0f, ColorRGBA(0, 0, 0, 1)), false, 4.0f,
+ RoundedCorners(70, 5)),
+ ScaleMatrix(1.0f, 10.0f)));
+}
+
+TEST_F(PixelTest,
BoxShadowEllipseWithInset25pxSpread50pxBlurAndRoundedCorners) {
TestTree(CreateShadowRectWithBackground(
output_surface_size(), ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 576969c..ce39cca 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -60,7 +60,7 @@
// on multiple threads simultaneously later.
SkSafeUnref(SkFontMgr::RefDefault());
-#if SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
decode_target_graphics_context_provider_.egl_display =
cobalt_context_->system_egl()->GetDisplay();
decode_target_graphics_context_provider_.egl_context =
@@ -68,7 +68,7 @@
decode_target_graphics_context_provider_.gles_context_runner =
&HardwareResourceProvider::GraphicsContextRunner;
decode_target_graphics_context_provider_.gles_context_runner_context = this;
-#endif // SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
}
HardwareResourceProvider::~HardwareResourceProvider() {
@@ -146,7 +146,7 @@
self_message_loop_));
}
-#if SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
namespace {
#if SB_API_VERSION < SB_DECODE_TARGET_PLANES_FOR_FORMAT
@@ -378,7 +378,7 @@
}
}
-#endif // SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
scoped_ptr<RawImageMemory> HardwareResourceProvider::AllocateRawImageMemory(
size_t size_in_bytes, size_t alignment) {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
index 33ed4d2..5ad0c4a 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
@@ -56,7 +56,6 @@
scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE;
#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 4
scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
SbDecodeTarget decode_target) OVERRIDE;
@@ -71,24 +70,6 @@
// Whether SbDecodeTargetIsSupported or not.
bool SupportsSbDecodeTarget() OVERRIDE { return true; }
-#elif SB_API_VERSION >= 3
-
- scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
- SbDecodeTarget decode_target) OVERRIDE {
- NOTREACHED()
- << "CreateImageFromSbDecodeTarget is not supported on EGL yet.";
- SbDecodeTargetDestroy(decode_target);
- return NULL;
- }
-
- // Return the associated SbDecodeTargetProvider with the ResourceProvider,
- // if it exists. Returns NULL if SbDecodeTarget is not supported.
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
-
- // Whether SbDecodeTargetIsSupported or not.
- bool SupportsSbDecodeTarget() OVERRIDE { return false; }
-
-#endif // SB_API_VERSION >= 4
#endif // SB_HAS(GRAPHICS)
scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory(
@@ -147,7 +128,7 @@
TextShaper text_shaper_;
int max_texture_size_;
-#if SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS)
static void GraphicsContextRunner(
SbDecodeTargetGraphicsContextProvider* graphics_context_provider,
SbDecodeTargetGlesContextRunnerTarget target_function,
@@ -155,7 +136,7 @@
SbDecodeTargetGraphicsContextProvider
decode_target_graphics_context_provider_;
-#endif // SB_API_VERSION >= 4 && SB_HAS(GRAPHICS)
+#endif // SB_HAS(GRAPHICS)
// We keep a handle to the message loop that this resource provider was
// created on. This message loop is used whenever we need to issue graphics
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
index 0f2159b..918a60e 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -949,6 +949,11 @@
SkiaBrushVisitor brush_visitor(&paint, *draw_state);
brush->Accept(&brush_visitor);
+ if (!draw_state->render_target->getTotalMatrix().preservesAxisAlignment()) {
+ // Enable anti-aliasing if we're rendering a rotated or skewed box.
+ paint.setAntiAlias(true);
+ }
+
draw_state->render_target->drawRect(
SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()), paint);
}
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
index 67e8e2d..492d3ab 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
@@ -47,7 +47,6 @@
scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE;
#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 4
scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
SbDecodeTarget decode_target) OVERRIDE {
NOTREACHED();
@@ -61,18 +60,6 @@
}
bool SupportsSbDecodeTarget() OVERRIDE { return false; }
-#elif SB_API_VERSION >= 3
- scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
- SbDecodeTarget decode_target) OVERRIDE {
- NOTREACHED();
- SbDecodeTargetDestroy(decode_target);
- return NULL;
- }
-
- SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
-
- bool SupportsSbDecodeTarget() OVERRIDE { return false; }
-#endif // SB_API_VERSION >= 4
#endif // SB_HAS(GRAPHICS)
scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory(
diff --git a/src/cobalt/renderer/rasterizer/testdata/RotatedThenScaledRectWithDifferentRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/RotatedThenScaledRectWithDifferentRoundedCorners-expected.png
new file mode 100644
index 0000000..b1725a0
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/RotatedThenScaledRectWithDifferentRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset25pxSpread3pxBlurAndRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset25pxSpread3pxBlurAndRoundedCorners-expected.png
new file mode 100644
index 0000000..94a9b2f
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset25pxSpread3pxBlurAndRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset5pxSpreadAndRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset5pxSpreadAndRoundedCorners-expected.png
new file mode 100644
index 0000000..26f22df
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowEllipseWithOutset5pxSpreadAndRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowWithSpreadAndBlurCentered-expected.png b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowWithSpreadAndBlurCentered-expected.png
new file mode 100644
index 0000000..9c2b5b6
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ScaledBoxShadowWithSpreadAndBlurCentered-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRectWithDifferentRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRectWithDifferentRoundedCorners-expected.png
new file mode 100644
index 0000000..9b62917
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRectWithDifferentRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRoundedCornersViewportOverImage-expected.png b/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRoundedCornersViewportOverImage-expected.png
new file mode 100644
index 0000000..55c2d7d
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ScaledThenRotatedRoundedCornersViewportOverImage-expected.png
Binary files differ
diff --git a/src/cobalt/script/error_report.h b/src/cobalt/script/error_report.h
new file mode 100644
index 0000000..82c1b12
--- /dev/null
+++ b/src/cobalt/script/error_report.h
@@ -0,0 +1,41 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_SCRIPT_ERROR_REPORT_H_
+#define COBALT_SCRIPT_ERROR_REPORT_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/script/value_handle.h"
+
+namespace cobalt {
+namespace script {
+
+struct ErrorReport {
+ public:
+ ErrorReport() : line_number(0), column_number(0), is_muted(false) {}
+
+ std::string message;
+ std::string filename;
+ uint32 line_number;
+ uint32 column_number;
+ scoped_ptr<script::ValueHandleHolder> error;
+ bool is_muted;
+};
+
+} // namespace script
+} // namespace cobalt
+
+#endif // COBALT_SCRIPT_ERROR_REPORT_H_
diff --git a/src/cobalt/script/fake_global_environment.h b/src/cobalt/script/fake_global_environment.h
index ca8c3fa..391c5e7 100644
--- a/src/cobalt/script/fake_global_environment.h
+++ b/src/cobalt/script/fake_global_environment.h
@@ -49,6 +49,8 @@
void EnableEval() OVERRIDE {}
void DisableJit() OVERRIDE {}
void SetReportEvalCallback(const base::Closure& /*report_eval*/) OVERRIDE {}
+ void SetReportErrorCallback(
+ const ReportErrorCallback& /*report_eval*/) OVERRIDE {}
void Bind(const std::string& /*identifier*/,
const scoped_refptr<Wrappable>& /*impl*/) OVERRIDE {}
ScriptValueFactory* script_value_factory() { return NULL; }
diff --git a/src/cobalt/script/global_environment.h b/src/cobalt/script/global_environment.h
index 794cc22..039f05d 100644
--- a/src/cobalt/script/global_environment.h
+++ b/src/cobalt/script/global_environment.h
@@ -19,6 +19,7 @@
#include "base/memory/ref_counted.h"
#include "base/optional.h"
+#include "cobalt/script/error_report.h"
#include "cobalt/script/opaque_handle.h"
#include "cobalt/script/script_value.h"
#include "cobalt/script/script_value_factory.h"
@@ -35,6 +36,9 @@
// Manages a handle to a JavaScript engine's global object.
class GlobalEnvironment : public base::RefCounted<GlobalEnvironment> {
public:
+ typedef base::Callback<bool(const ErrorReport& error_report)>
+ ReportErrorCallback;
+
// Create a new global object with bindings as defined for the definition of
// the GlobalInterface type. The IDL for this interface must have the
// PrimaryGlobal or Global extended attribute.
@@ -95,6 +99,10 @@
// constructor is used.
virtual void SetReportEvalCallback(const base::Closure& report_eval) = 0;
+ // Set a callback that will be fired whenever a JavaScript error occurs.
+ virtual void SetReportErrorCallback(
+ const ReportErrorCallback& report_error) = 0;
+
// Dynamically bind a cpp object to the javascript global object with the
// supplied identifier.
// This method is useful for testing and debug purposes, as well as for
diff --git a/src/cobalt/script/mozjs-45/mozjs_global_environment.cc b/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
index c850506..2673778 100644
--- a/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
@@ -339,6 +339,12 @@
report_eval_ = report_eval;
}
+void MozjsGlobalEnvironment::SetReportErrorCallback(
+ const ReportErrorCallback& report_error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ report_error_callback_ = report_error_callback;
+}
+
void MozjsGlobalEnvironment::Bind(const std::string& identifier,
const scoped_refptr<Wrappable>& impl) {
TRACK_MEMORY_SCOPE("Javascript");
@@ -456,19 +462,54 @@
void MozjsGlobalEnvironment::ReportError(const char* message,
JSErrorReport* report) {
- std::string error_message;
+ JS::RootedValue exception(context_);
+ ::JS_GetPendingException(context_, &exception);
+
+ // Note: we must do this before running any more code on context.
+ ::JS_ClearPendingException(context_);
+
+ // Populate the error report.
+ ErrorReport error_report;
if (report->errorNumber == JSMSG_CSP_BLOCKED_EVAL) {
- error_message = eval_disabled_message_.value_or(message);
+ error_report.message = eval_disabled_message_.value_or(message);
} else {
- error_message = message;
+ error_report.message = message;
+ }
+ error_report.filename =
+ report->filename ? report->filename : "<internal exception>";
+ error_report.line_number = report->lineno;
+ error_report.column_number = report->column;
+ // Let error object be the object that represents the error: in the case of
+ // an uncaught exception, that would be the object that was thrown; in the
+ // case of a JavaScript error that would be an Error object. If there is no
+ // corresponding object, then the null value must be used instead.
+ // https://www.w3.org/TR/html5/webappapis.html#runtime-script-errors
+ if (exception.isObject()) {
+ error_report.error.reset(
+ new MozjsValueHandleHolder(exception, context_, wrapper_factory()));
+ }
+ error_report.is_muted = report->isMuted;
+
+ // If this isn't simply a warning, and the error wasn't caused by JS running
+ // out of memory (in which case the callback will fail as well), then run
+ // the callback. In the case that it returns that the script was handled,
+ // simply return; the error should only be reported to the user if it wasn't
+ // handled.
+ if (!JSREPORT_IS_WARNING(report->flags) &&
+ report->errorNumber != JSMSG_OUT_OF_MEMORY &&
+ !report_error_callback_.is_null() &&
+ report_error_callback_.Run(error_report)) {
+ return;
}
+ // If the error is not handled, then the error may be reported to the user.
+ // https://www.w3.org/TR/html5/webappapis.html#runtime-script-errors-in-documents
if (last_error_message_) {
- *last_error_message_ = error_message;
+ *last_error_message_ = error_report.message;
} else {
- const char* filename = report->filename ? report->filename : "(none)";
- LOG(ERROR) << "JS Error: " << filename << ":" << report->lineno << ":"
- << report->column << ": " << error_message;
+ LOG(ERROR) << "JS Error: " << error_report.filename << ":"
+ << error_report.line_number << ":" << error_report.column_number
+ << ": " << error_report.message;
}
}
diff --git a/src/cobalt/script/mozjs-45/mozjs_global_environment.h b/src/cobalt/script/mozjs-45/mozjs_global_environment.h
index e34eabe..a7bccae 100644
--- a/src/cobalt/script/mozjs-45/mozjs_global_environment.h
+++ b/src/cobalt/script/mozjs-45/mozjs_global_environment.h
@@ -75,6 +75,9 @@
void SetReportEvalCallback(const base::Closure& report_eval) OVERRIDE;
+ void SetReportErrorCallback(
+ const ReportErrorCallback& report_error_callback) OVERRIDE;
+
void Bind(const std::string& identifier,
const scoped_refptr<Wrappable>& impl) OVERRIDE;
@@ -182,6 +185,7 @@
bool eval_enabled_;
base::optional<std::string> eval_disabled_message_;
base::Closure report_eval_;
+ ReportErrorCallback report_error_callback_;
friend class GlobalObjectProxy;
};
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.cc b/src/cobalt/script/mozjs/mozjs_global_environment.cc
index 71b593d..52af1fe 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.cc
@@ -344,6 +344,12 @@
report_eval_ = report_eval;
}
+void MozjsGlobalEnvironment::SetReportErrorCallback(
+ const ReportErrorCallback& report_error_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ report_error_callback_ = report_error_callback;
+}
+
void MozjsGlobalEnvironment::Bind(const std::string& identifier,
const scoped_refptr<Wrappable>& impl) {
TRACK_MEMORY_SCOPE("Javascript");
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.h b/src/cobalt/script/mozjs/mozjs_global_environment.h
index 4914e18..e221a5d 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.h
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.h
@@ -74,6 +74,9 @@
void SetReportEvalCallback(const base::Closure& report_eval) OVERRIDE;
+ void SetReportErrorCallback(
+ const ReportErrorCallback& report_error_callback) OVERRIDE;
+
void Bind(const std::string& identifier,
const scoped_refptr<Wrappable>& impl) OVERRIDE;
@@ -181,6 +184,7 @@
bool eval_enabled_;
base::optional<std::string> eval_disabled_message_;
base::Closure report_eval_;
+ ReportErrorCallback report_error_callback_;
friend class GlobalObjectProxy;
};
diff --git a/src/cobalt/script/script.gyp b/src/cobalt/script/script.gyp
index a7e9192..34a7c4e 100644
--- a/src/cobalt/script/script.gyp
+++ b/src/cobalt/script/script.gyp
@@ -21,6 +21,7 @@
'type': 'static_library',
'sources': [
'call_frame.h',
+ 'error_report.h',
'exception_message.cc',
'exception_message.h',
'execution_state.cc',
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 23dedfd..6fb7a34 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -290,14 +290,12 @@
const char* speech_api_key = "";
#if defined(OS_STARBOARD)
-#if SB_API_VERSION >= 2
const int kSpeechApiKeyLength = 100;
char buffer[kSpeechApiKeyLength] = {0};
bool result = SbSystemGetProperty(kSbSystemPropertySpeechApiKey, buffer,
SB_ARRAY_SIZE_INT(buffer));
SB_DCHECK(result);
speech_api_key = result ? buffer : "";
-#endif // SB_API_VERSION >= 2
#endif // defined(OS_STARBOARD)
up_url = AppendQueryParameter(up_url, "key", speech_api_key);
diff --git a/src/cobalt/speech/speech_configuration.h b/src/cobalt/speech/speech_configuration.h
index 8b9e0ba..c23ee5e 100644
--- a/src/cobalt/speech/speech_configuration.h
+++ b/src/cobalt/speech/speech_configuration.h
@@ -16,15 +16,14 @@
#define COBALT_SPEECH_SPEECH_CONFIGURATION_H_
#include "build/build_config.h"
-
-#if defined(OS_STARBOARD)
#include "starboard/configuration.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+
+#if SB_HAS(MICROPHONE)
#define SB_USE_SB_MICROPHONE 1
-#endif // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif // SB_HAS(MICROPHONE)
+
#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
#define SB_USE_SB_SPEECH_RECOGNIZER 1
#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
-#endif // defined(OS_STARBOARD)
#endif // COBALT_SPEECH_SPEECH_CONFIGURATION_H_
diff --git a/src/cobalt/speech/speech_synthesis.cc b/src/cobalt/speech/speech_synthesis.cc
index 5e3e1bb..fb34eaa 100644
--- a/src/cobalt/speech/speech_synthesis.cc
+++ b/src/cobalt/speech/speech_synthesis.cc
@@ -100,12 +100,6 @@
return;
}
-#if SB_API_VERSION < 4
- // DEPRECATED IN API VERSION 4
- std::string language =
- utterance->lang().empty() ? navigator_->language() : utterance->lang();
- SbSpeechSynthesisSetLanguage(language.c_str());
-#endif
SB_DLOG(INFO) << "Speaking: \"" << utterance->text() << "\" "
<< utterance->lang();
SbSpeechSynthesisSpeak(utterance->text().c_str());
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index e9daa78..88582da 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -15,6 +15,6 @@
#define COBALT_VERSION_H_
// Cobalt release number.
-#define COBALT_VERSION "12"
+#define COBALT_VERSION "13"
#endif // COBALT_VERSION_H_
diff --git a/src/cobalt/webdriver/server.cc b/src/cobalt/webdriver/server.cc
index 321c8c4..146b674 100644
--- a/src/cobalt/webdriver/server.cc
+++ b/src/cobalt/webdriver/server.cc
@@ -21,6 +21,8 @@
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/string_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
#include "net/base/tcp_listen_socket.h"
#include "net/server/http_server_request_info.h"
@@ -177,11 +179,21 @@
WebDriverServer::WebDriverServer(int port, const std::string& listen_ip,
const HandleRequestCallback& callback)
- : handle_request_callback_(callback) {
+ : handle_request_callback_(callback),
+ server_address_("WebDriver.Server",
+ "Address to communicate with WebDriver.") {
// Create http server
factory_.reset(new net::TCPListenSocketFactory(listen_ip, port));
server_ = new net::HttpServer(*factory_, this);
- LOG(INFO) << "Starting WebDriver server on port " << port;
+ GURL address;
+ int result = GetLocalAddress(&address);
+ if (result == net::OK) {
+ LOG(INFO) << "Starting WebDriver server on port " << port;
+ server_address_ = address.spec();
+ } else {
+ LOG(WARNING) << "Could not start WebDriver server";
+ server_address_ = "<NOT RUNNING>";
+ }
}
void WebDriverServer::OnHttpRequest(int connection_id,
@@ -217,5 +229,14 @@
parameters.Pass(), response_handler.Pass());
}
+int WebDriverServer::GetLocalAddress(GURL* out) const {
+ net::IPEndPoint ip_addr;
+ int result = server_->GetLocalAddress(&ip_addr);
+ if (result == net::OK) {
+ *out = GURL("http://" + ip_addr.ToString());
+ }
+ return result;
+}
+
} // namespace webdriver
} // namespace cobalt
diff --git a/src/cobalt/webdriver/server.h b/src/cobalt/webdriver/server.h
index 71a66fa..03ba2b4 100644
--- a/src/cobalt/webdriver/server.h
+++ b/src/cobalt/webdriver/server.h
@@ -22,7 +22,9 @@
#include "base/memory/scoped_ptr.h"
#include "base/threading/thread.h"
#include "base/values.h"
+#include "cobalt/base/c_val.h"
#include "cobalt/webdriver/protocol/server_status.h"
+#include "googleurl/src/gurl.h"
#include "net/base/stream_listen_socket.h"
#include "net/server/http_server.h"
@@ -92,10 +94,13 @@
void OnClose(int) OVERRIDE {} // NOLINT(readability/casting)
private:
+ int GetLocalAddress(GURL* out) const;
+
base::ThreadChecker thread_checker_;
HandleRequestCallback handle_request_callback_;
scoped_ptr<net::StreamListenSocketFactory> factory_;
scoped_refptr<net::HttpServer> server_;
+ base::CVal<std::string> server_address_;
};
} // namespace webdriver
diff --git a/src/media/audio/android/audio_manager_android.cc b/src/media/audio/android/audio_manager_android.cc
deleted file mode 100644
index 0e4d6ba..0000000
--- a/src/media/audio/android/audio_manager_android.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/android/audio_manager_android.h"
-
-#include "base/logging.h"
-#include "media/audio/android/opensles_input.h"
-#include "media/audio/android/opensles_output.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/fake_audio_input_stream.h"
-
-namespace media {
-
-// Maximum number of output streams that can be open simultaneously.
-static const int kMaxOutputStreams = 10;
-
-AudioManager* CreateAudioManager() {
- return new AudioManagerAndroid();
-}
-
-AudioManagerAndroid::AudioManagerAndroid() {
- SetMaxOutputStreamsAllowed(kMaxOutputStreams);
-}
-
-AudioManagerAndroid::~AudioManagerAndroid() {
- Shutdown();
-}
-
-bool AudioManagerAndroid::HasAudioOutputDevices() {
- return true;
-}
-
-bool AudioManagerAndroid::HasAudioInputDevices() {
- return false;
-}
-
-AudioOutputStream* AudioManagerAndroid::MakeLinearOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return new OpenSLESOutputStream(this, params);
-}
-
-AudioOutputStream* AudioManagerAndroid::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- return new OpenSLESOutputStream(this, params);
-}
-
-AudioInputStream* AudioManagerAndroid::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return new OpenSLESInputStream(this, params);
-}
-
-AudioInputStream* AudioManagerAndroid::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- return new OpenSLESInputStream(this, params);
-}
-
-} // namespace media
diff --git a/src/media/audio/android/audio_manager_android.h b/src/media/audio/android/audio_manager_android.h
deleted file mode 100644
index 8f14808..0000000
--- a/src/media/audio/android/audio_manager_android.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
-#define MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
-
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-// Android implemention of AudioManager.
-class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase {
- public:
- AudioManagerAndroid();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
-
- protected:
- virtual ~AudioManagerAndroid();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioManagerAndroid);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
diff --git a/src/media/audio/android/opensles_input.cc b/src/media/audio/android/opensles_input.cc
deleted file mode 100644
index 0df5bc1..0000000
--- a/src/media/audio/android/opensles_input.cc
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/android/opensles_input.h"
-
-#include "base/logging.h"
-#include "media/audio/android/audio_manager_android.h"
-
-namespace media {
-
-OpenSLESInputStream::OpenSLESInputStream(AudioManagerAndroid* audio_manager,
- const AudioParameters& params)
- : audio_manager_(audio_manager),
- callback_(NULL),
- recorder_(NULL),
- simple_buffer_queue_(NULL),
- active_queue_(0),
- buffer_size_bytes_(0),
- started_(false) {
- format_.formatType = SL_DATAFORMAT_PCM;
- format_.numChannels = static_cast<SLuint32>(params.channels());
- // Provides sampling rate in milliHertz to OpenSLES.
- format_.samplesPerSec = static_cast<SLuint32>(params.sample_rate() * 1000);
- format_.bitsPerSample = params.bits_per_sample();
- format_.containerSize = params.bits_per_sample();
- format_.endianness = SL_BYTEORDER_LITTLEENDIAN;
- if (format_.numChannels == 1)
- format_.channelMask = SL_SPEAKER_FRONT_CENTER;
- else if (format_.numChannels == 2)
- format_.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
- else
- NOTREACHED() << "Unsupported number of channels: " << format_.numChannels;
-
- buffer_size_bytes_ = params.GetBytesPerBuffer();
-
- memset(&audio_data_, 0, sizeof(audio_data_));
-}
-
-OpenSLESInputStream::~OpenSLESInputStream() {
- DCHECK(!recorder_object_.Get());
- DCHECK(!engine_object_.Get());
- DCHECK(!recorder_);
- DCHECK(!simple_buffer_queue_);
- DCHECK(!audio_data_[0]);
-}
-
-bool OpenSLESInputStream::Open() {
- if (engine_object_.Get())
- return false;
-
- if (!CreateRecorder())
- return false;
-
- SetupAudioBuffer();
-
- return true;
-}
-
-void OpenSLESInputStream::Start(AudioInputCallback* callback) {
- DCHECK(callback);
- DCHECK(recorder_);
- DCHECK(simple_buffer_queue_);
- if (started_)
- return;
-
- // Enable the flags before streaming.
- callback_ = callback;
- active_queue_ = 0;
- started_ = true;
-
- SLresult err = SL_RESULT_UNKNOWN_ERROR;
- // Enqueues |kNumOfQueuesInBuffer| zero buffers to get the ball rolling.
- for (int i = 0; i < kNumOfQueuesInBuffer - 1; ++i) {
- err = (*simple_buffer_queue_)->Enqueue(
- simple_buffer_queue_,
- audio_data_[i],
- buffer_size_bytes_);
- if (SL_RESULT_SUCCESS != err) {
- HandleError(err);
- return;
- }
- }
-
- // Start the recording by setting the state to |SL_RECORDSTATE_RECORDING|.
- err = (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- HandleError(err);
-}
-
-void OpenSLESInputStream::Stop() {
- if (!started_)
- return;
-
- // Stop recording by setting the record state to |SL_RECORDSTATE_STOPPED|.
- SLresult err = (*recorder_)->SetRecordState(recorder_,
- SL_RECORDSTATE_STOPPED);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(WARNING) << "SetRecordState() failed to set the state to stop";
- }
-
- // Clear the buffer queue to get rid of old data when resuming recording.
- err = (*simple_buffer_queue_)->Clear(simple_buffer_queue_);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(WARNING) << "Clear() failed to clear the buffer queue";
- }
-
- started_ = false;
-}
-
-void OpenSLESInputStream::Close() {
- // Stop the stream if it is still recording.
- Stop();
-
- // Explicitly free the player objects and invalidate their associated
- // interfaces. They have to be done in the correct order.
- recorder_object_.Reset();
- engine_object_.Reset();
- simple_buffer_queue_ = NULL;
- recorder_ = NULL;
-
- ReleaseAudioBuffer();
-
- audio_manager_->ReleaseInputStream(this);
-}
-
-double OpenSLESInputStream::GetMaxVolume() {
- NOTIMPLEMENTED();
- return 0.0;
-}
-
-void OpenSLESInputStream::SetVolume(double volume) {
- NOTIMPLEMENTED();
-}
-
-double OpenSLESInputStream::GetVolume() {
- NOTIMPLEMENTED();
- return 0.0;
-}
-
-void OpenSLESInputStream::SetAutomaticGainControl(bool enabled) {
- NOTIMPLEMENTED();
-}
-
-bool OpenSLESInputStream::GetAutomaticGainControl() {
- NOTIMPLEMENTED();
- return false;
-}
-
-bool OpenSLESInputStream::CreateRecorder() {
- // Initializes the engine object with specific option. After working with the
- // object, we need to free the object and its resources.
- SLEngineOption option[] = {
- { SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE) }
- };
- SLresult err = slCreateEngine(engine_object_.Receive(),
- 1,
- option,
- 0,
- NULL,
- NULL);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Realize the SL engine object in synchronous mode.
- err = engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Get the SL engine interface which is implicit.
- SLEngineItf engine;
- err = engine_object_->GetInterface(
- engine_object_.Get(), SL_IID_ENGINE, &engine);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Audio source configuration.
- SLDataLocator_IODevice mic_locator = {
- SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
- SL_DEFAULTDEVICEID_AUDIOINPUT, NULL
- };
- SLDataSource audio_source = { &mic_locator, NULL };
-
- // Audio sink configuration.
- SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
- SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // Locator type.
- static_cast<SLuint32>(kNumOfQueuesInBuffer) // Number of buffers.
- };
- SLDataSink audio_sink = { &buffer_queue, &format_ };
-
- // Create an audio recorder.
- const SLuint32 number_of_interfaces = 1;
- const SLInterfaceID interface_id[number_of_interfaces] = {
- SL_IID_ANDROIDSIMPLEBUFFERQUEUE
- };
- const SLboolean interface_required[number_of_interfaces] = {
- SL_BOOLEAN_TRUE
- };
- err = (*engine)->CreateAudioRecorder(engine,
- recorder_object_.Receive(),
- &audio_source,
- &audio_sink,
- number_of_interfaces,
- interface_id,
- interface_required);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(ERROR) << "CreateAudioRecorder failed with error code " << err;
- return false;
- }
-
- // Realize the recorder object in synchronous mode.
- err = recorder_object_->Realize(recorder_object_.Get(), SL_BOOLEAN_FALSE);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(ERROR) << "Recprder Realize() failed with error code " << err;
- return false;
- }
-
- // Get an implicit recorder interface.
- err = recorder_object_->GetInterface(recorder_object_.Get(),
- SL_IID_RECORD,
- &recorder_);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Get the simple buffer queue interface.
- err = recorder_object_->GetInterface(recorder_object_.Get(),
- SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
- &simple_buffer_queue_);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Register the input callback for the simple buffer queue.
- // This callback will be called when receiving new data from the device.
- err = (*simple_buffer_queue_)->RegisterCallback(simple_buffer_queue_,
- SimpleBufferQueueCallback,
- this);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
-
- return (SL_RESULT_SUCCESS == err);
-}
-
-void OpenSLESInputStream::SimpleBufferQueueCallback(
- SLAndroidSimpleBufferQueueItf buffer_queue, void* instance) {
- OpenSLESInputStream* stream =
- reinterpret_cast<OpenSLESInputStream*>(instance);
- stream->ReadBufferQueue();
-}
-
-void OpenSLESInputStream::ReadBufferQueue() {
- if (!started_)
- return;
-
- // Get the enqueued buffer from the soundcard.
- SLresult err = (*simple_buffer_queue_)->Enqueue(
- simple_buffer_queue_,
- audio_data_[active_queue_],
- buffer_size_bytes_);
- if (SL_RESULT_SUCCESS != err)
- HandleError(err);
-
- // TODO(xians): Get an accurate delay estimation.
- callback_->OnData(this,
- audio_data_[active_queue_],
- buffer_size_bytes_,
- buffer_size_bytes_,
- 0.0);
-
- active_queue_ = (active_queue_ + 1) % kNumOfQueuesInBuffer;
-}
-
-void OpenSLESInputStream::SetupAudioBuffer() {
- DCHECK(!audio_data_[0]);
- for (int i = 0; i < kNumOfQueuesInBuffer; ++i) {
- audio_data_[i] = new uint8[buffer_size_bytes_];
- }
-}
-
-void OpenSLESInputStream::ReleaseAudioBuffer() {
- if (audio_data_[0]) {
- for (int i = 0; i < kNumOfQueuesInBuffer; ++i) {
- delete [] audio_data_[i];
- audio_data_[i] = NULL;
- }
- }
-}
-
-void OpenSLESInputStream::HandleError(SLresult error) {
- DLOG(FATAL) << "OpenSLES error " << error;
- if (callback_)
- callback_->OnError(this, error);
-}
-
-} // namespace media
diff --git a/src/media/audio/android/opensles_input.h b/src/media/audio/android/opensles_input.h
deleted file mode 100644
index 0d18f9b..0000000
--- a/src/media/audio/android/opensles_input.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
-#define MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
-
-#include "base/compiler_specific.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/android/opensles_util.h"
-#include <SLES/OpenSLES_Android.h>
-
-namespace media {
-
-class AudioManagerAndroid;
-
-// Implements PCM audio input support for Android using the OpenSLES API.
-class OpenSLESInputStream : public AudioInputStream {
- public:
- static const int kNumOfQueuesInBuffer = 2;
-
- OpenSLESInputStream(AudioManagerAndroid* manager,
- const AudioParameters& params);
-
- virtual ~OpenSLESInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- private:
- bool CreateRecorder();
-
- static void SimpleBufferQueueCallback(
- SLAndroidSimpleBufferQueueItf buffer_queue, void* instance);
-
- void ReadBufferQueue();
-
- // Called in Open();
- void SetupAudioBuffer();
-
- // Called in Close();
- void ReleaseAudioBuffer();
-
- // If OpenSLES reports an error this function handles it and passes it to
- // the attached AudioInputCallback::OnError().
- void HandleError(SLresult error);
-
- AudioManagerAndroid* audio_manager_;
-
- AudioInputCallback* callback_;
-
- // Shared engine interfaces for the app.
- media::ScopedSLObjectItf recorder_object_;
- media::ScopedSLObjectItf engine_object_;
-
- SLRecordItf recorder_;
-
- // Buffer queue recorder interface.
- SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
-
- SLDataFormat_PCM format_;
-
- // Audio buffers that are allocated in the constructor based on
- // info from audio parameters.
- uint8* audio_data_[kNumOfQueuesInBuffer];
-
- int active_queue_;
- int buffer_size_bytes_;
-
- bool started_;
-
- DISALLOW_COPY_AND_ASSIGN(OpenSLESInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
diff --git a/src/media/audio/android/opensles_output.cc b/src/media/audio/android/opensles_output.cc
deleted file mode 100644
index 26ae25b..0000000
--- a/src/media/audio/android/opensles_output.cc
+++ /dev/null
@@ -1,311 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/android/opensles_output.h"
-
-#include "base/logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/android/audio_manager_android.h"
-
-namespace media {
-
-OpenSLESOutputStream::OpenSLESOutputStream(AudioManagerAndroid* manager,
- const AudioParameters& params)
- : audio_manager_(manager),
- callback_(NULL),
- player_(NULL),
- simple_buffer_queue_(NULL),
- active_queue_(0),
- buffer_size_bytes_(0),
- started_(false),
- volume_(1.0) {
- format_.formatType = SL_DATAFORMAT_PCM;
- format_.numChannels = static_cast<SLuint32>(params.channels());
- // Provides sampling rate in milliHertz to OpenSLES.
- format_.samplesPerSec = static_cast<SLuint32>(params.sample_rate() * 1000);
- format_.bitsPerSample = params.bits_per_sample();
- format_.containerSize = params.bits_per_sample();
- format_.endianness = SL_BYTEORDER_LITTLEENDIAN;
- if (format_.numChannels == 1)
- format_.channelMask = SL_SPEAKER_FRONT_CENTER;
- else if (format_.numChannels == 2)
- format_.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
- else
- NOTREACHED() << "Unsupported number of channels: " << format_.numChannels;
-
- buffer_size_bytes_ = params.GetBytesPerBuffer();
- audio_bus_ = AudioBus::Create(params);
-
- memset(&audio_data_, 0, sizeof(audio_data_));
-}
-
-OpenSLESOutputStream::~OpenSLESOutputStream() {
- DCHECK(!engine_object_.Get());
- DCHECK(!player_object_.Get());
- DCHECK(!output_mixer_.Get());
- DCHECK(!player_);
- DCHECK(!simple_buffer_queue_);
- DCHECK(!audio_data_[0]);
-}
-
-bool OpenSLESOutputStream::Open() {
- if (engine_object_.Get())
- return false;
-
- if (!CreatePlayer())
- return false;
-
- SetupAudioBuffer();
-
- return true;
-}
-
-void OpenSLESOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(callback);
- DCHECK(player_);
- DCHECK(simple_buffer_queue_);
- if (started_)
- return;
-
- // Enable the flags before streaming.
- callback_ = callback;
- active_queue_ = 0;
- started_ = true;
-
- // Avoid start-up glitches by filling up one buffer queue before starting
- // the stream.
- FillBufferQueue();
-
- // Start streaming data by setting the play state to |SL_PLAYSTATE_PLAYING|.
- SLresult err = (*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(WARNING) << "SetPlayState() failed to start playing";
- }
-}
-
-void OpenSLESOutputStream::Stop() {
- if (!started_)
- return;
-
- started_ = false;
- // Stop playing by setting the play state to |SL_PLAYSTATE_STOPPED|.
- SLresult err = (*player_)->SetPlayState(player_, SL_PLAYSTATE_STOPPED);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(WARNING) << "SetPlayState() failed to set the state to stop";
- }
-
- // Clear the buffer queue so that the old data won't be played when
- // resuming playing.
- err = (*simple_buffer_queue_)->Clear(simple_buffer_queue_);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(WARNING) << "Clear() failed to clear the buffer queue";
- }
-}
-
-void OpenSLESOutputStream::Close() {
- // Stop the stream if it is still playing.
- Stop();
-
- // Explicitly free the player objects and invalidate their associated
- // interfaces. They have to be done in the correct order.
- output_mixer_.Reset();
- player_object_.Reset();
- engine_object_.Reset();
- simple_buffer_queue_ = NULL;
- player_ = NULL;
-
- ReleaseAudioBuffer();
-
- audio_manager_->ReleaseOutputStream(this);
-}
-
-void OpenSLESOutputStream::SetVolume(double volume) {
- float volume_float = static_cast<float>(volume);
- if (volume_float < 0.0f || volume_float > 1.0f) {
- return;
- }
- volume_ = volume_float;
-}
-
-void OpenSLESOutputStream::GetVolume(double* volume) {
- *volume = static_cast<double>(volume_);
-}
-
-bool OpenSLESOutputStream::CreatePlayer() {
- // Initializes the engine object with specific option. After working with the
- // object, we need to free the object and its resources.
- SLEngineOption option[] = {
- { SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE) }
- };
- SLresult err = slCreateEngine(engine_object_.Receive(), 1, option, 0,
- NULL, NULL);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Realize the SL engine object in synchronous mode.
- err = engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Get the SL engine interface which is implicit.
- SLEngineItf engine;
- err = engine_object_->GetInterface(engine_object_.Get(),
- SL_IID_ENGINE,
- &engine);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Create ouput mixer object to be used by the player.
- // TODO(xians): Do we need the environmental reverb auxiliary effect?
- err = (*engine)->CreateOutputMix(engine,
- output_mixer_.Receive(),
- 0,
- NULL,
- NULL);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Realizing the output mix object in synchronous mode.
- err = output_mixer_->Realize(output_mixer_.Get(), SL_BOOLEAN_FALSE);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Audio source configuration.
- SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
- SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
- static_cast<SLuint32>(kNumOfQueuesInBuffer)
- };
- SLDataSource audio_source = { &simple_buffer_queue, &format_ };
-
- // Audio sink configuration.
- SLDataLocator_OutputMix locator_output_mix = {
- SL_DATALOCATOR_OUTPUTMIX, output_mixer_.Get()
- };
- SLDataSink audio_sink = { &locator_output_mix, NULL };
-
- // Create an audio player.
- const SLuint32 number_of_interfaces = 1;
- const SLInterfaceID interface_id[number_of_interfaces] = {
- SL_IID_BUFFERQUEUE
- };
- const SLboolean interface_required[number_of_interfaces] = {
- SL_BOOLEAN_TRUE
- };
- err = (*engine)->CreateAudioPlayer(engine,
- player_object_.Receive(),
- &audio_source,
- &audio_sink,
- number_of_interfaces,
- interface_id,
- interface_required);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(ERROR) << "CreateAudioPlayer() failed with error code " << err;
- return false;
- }
-
- // Realize the player object in synchronous mode.
- err = player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err) {
- DLOG(ERROR) << "Player Realize() failed with error code " << err;
- return false;
- }
-
- // Get an implicit player interface.
- err = player_object_->GetInterface(
- player_object_.Get(), SL_IID_PLAY, &player_);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Get the simple buffer queue interface.
- err = player_object_->GetInterface(player_object_.Get(),
- SL_IID_BUFFERQUEUE,
- &simple_buffer_queue_);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
- if (SL_RESULT_SUCCESS != err)
- return false;
-
- // Register the input callback for the simple buffer queue.
- // This callback will be called when the soundcard needs data.
- err = (*simple_buffer_queue_)->RegisterCallback(simple_buffer_queue_,
- SimpleBufferQueueCallback,
- this);
- DCHECK_EQ(SL_RESULT_SUCCESS, err);
-
- return (SL_RESULT_SUCCESS == err);
-}
-
-void OpenSLESOutputStream::SimpleBufferQueueCallback(
- SLAndroidSimpleBufferQueueItf buffer_queue, void* instance) {
- OpenSLESOutputStream* stream =
- reinterpret_cast<OpenSLESOutputStream*>(instance);
- stream->FillBufferQueue();
-}
-
-void OpenSLESOutputStream::FillBufferQueue() {
- if (!started_)
- return;
-
- // Read data from the registered client source.
- // TODO(xians): Get an accurate delay estimation.
- uint32 hardware_delay = buffer_size_bytes_;
- int frames_filled = callback_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(0, hardware_delay));
- int num_filled_bytes =
- frames_filled * audio_bus_->channels() * format_.bitsPerSample / 8;
- DCHECK_LE(static_cast<size_t>(num_filled_bytes), buffer_size_bytes_);
- // Note: If this ever changes to output raw float the data must be clipped and
- // sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, format_.bitsPerSample / 8, audio_data_[active_queue_]);
-
- // Perform in-place, software-volume adjustments.
- media::AdjustVolume(audio_data_[active_queue_],
- num_filled_bytes,
- format_.numChannels,
- format_.bitsPerSample / 8,
- volume_);
-
- // Enqueue the buffer for playback.
- SLresult err = (*simple_buffer_queue_)->Enqueue(
- simple_buffer_queue_,
- audio_data_[active_queue_],
- num_filled_bytes);
- if (SL_RESULT_SUCCESS != err)
- HandleError(err);
-
- active_queue_ = (active_queue_ + 1) % kNumOfQueuesInBuffer;
-}
-
-void OpenSLESOutputStream::SetupAudioBuffer() {
- DCHECK(!audio_data_[0]);
- for (int i = 0; i < kNumOfQueuesInBuffer; ++i) {
- audio_data_[i] = new uint8[buffer_size_bytes_];
- }
-}
-
-void OpenSLESOutputStream::ReleaseAudioBuffer() {
- if (audio_data_[0]) {
- for (int i = 0; i < kNumOfQueuesInBuffer; ++i) {
- delete [] audio_data_[i];
- audio_data_[i] = NULL;
- }
- }
-}
-
-void OpenSLESOutputStream::HandleError(SLresult error) {
- DLOG(FATAL) << "OpenSLES error " << error;
- if (callback_)
- callback_->OnError(this, error);
-}
-
-} // namespace media
diff --git a/src/media/audio/android/opensles_output.h b/src/media/audio/android/opensles_output.h
deleted file mode 100644
index 9ecfb6c..0000000
--- a/src/media/audio/android/opensles_output.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_
-#define MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_
-
-#include <vector>
-
-#include "base/compiler_specific.h"
-#include "media/audio/android/opensles_util.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include <SLES/OpenSLES_Android.h>
-
-namespace media {
-
-class AudioManagerAndroid;
-
-// Implements PCM audio output support for Android using the OpenSLES API.
-class OpenSLESOutputStream : public AudioOutputStream {
- public:
- static const int kNumOfQueuesInBuffer = 2;
-
- OpenSLESOutputStream(AudioManagerAndroid* manager,
- const AudioParameters& params);
-
- virtual ~OpenSLESOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- private:
- bool CreatePlayer();
-
- static void SimpleBufferQueueCallback(
- SLAndroidSimpleBufferQueueItf buffer_queue, void* instance);
-
- void FillBufferQueue();
-
- // Called in Open();
- void SetupAudioBuffer();
-
- // Called in Close();
- void ReleaseAudioBuffer();
-
- // If OpenSLES reports an error this function handles it and passes it to
- // the attached AudioOutputCallback::OnError().
- void HandleError(SLresult error);
-
- AudioManagerAndroid* audio_manager_;
-
- AudioSourceCallback* callback_;
-
- // Shared engine interfaces for the app.
- media::ScopedSLObjectItf engine_object_;
- media::ScopedSLObjectItf player_object_;
- media::ScopedSLObjectItf output_mixer_;
-
- SLPlayItf player_;
-
- // Buffer queue recorder interface.
- SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
-
- SLDataFormat_PCM format_;
-
- // Audio buffer arrays that are allocated in the constructor.
- uint8* audio_data_[kNumOfQueuesInBuffer];
-
- int active_queue_;
- size_t buffer_size_bytes_;
-
- bool started_;
-
- // Volume level from 0 to 1.
- float volume_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(OpenSLESOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
diff --git a/src/media/audio/android/opensles_util.h b/src/media/audio/android/opensles_util.h
deleted file mode 100644
index 4a028e2..0000000
--- a/src/media/audio/android/opensles_util.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
-#define MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
-
-#include "base/logging.h"
-#include <SLES/OpenSLES.h>
-
-namespace media {
-
-template <typename SLType, typename SLDerefType>
-class ScopedSLObject {
- public:
- ScopedSLObject() : obj_(NULL) {}
-
- ~ScopedSLObject() { Reset(); }
-
- SLType* Receive() {
- DCHECK(!obj_);
- return &obj_;
- }
-
- SLDerefType operator->() { return *obj_; }
-
- SLType Get() const { return obj_; }
-
- void Reset() {
- if (obj_) {
- (*obj_)->Destroy(obj_);
- obj_ = NULL;
- }
- }
-
- private:
- SLType obj_;
-};
-
-typedef ScopedSLObject<SLObjectItf, const SLObjectItf_*> ScopedSLObjectItf;
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
diff --git a/src/media/audio/async_socket_io_handler.h b/src/media/audio/async_socket_io_handler.h
deleted file mode 100644
index d17e3d3..0000000
--- a/src/media/audio/async_socket_io_handler.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ASYNC_SOCKET_IO_HANDLER_H_
-#define MEDIA_AUDIO_ASYNC_SOCKET_IO_HANDLER_H_
-
-#include "base/message_loop.h"
-#include "base/sync_socket.h"
-#include "base/threading/non_thread_safe.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// The message loop callback interface is different based on platforms.
-#if defined(OS_WIN)
-typedef MessageLoopForIO::IOHandler MessageLoopIOHandler;
-#elif defined(OS_POSIX)
-typedef MessageLoopForIO::Watcher MessageLoopIOHandler;
-#endif
-
-// Extends the CancelableSyncSocket class to allow reading from a socket
-// asynchronously on a TYPE_IO message loop thread. This makes it easy to share
-// a thread that uses a message loop (e.g. for IPC and other things) and not
-// require a separate thread to read from the socket.
-//
-// Example usage (also see the unit tests):
-//
-// class SocketReader {
-// public:
-// SocketReader(base::CancelableSyncSocket* socket)
-// : socket_(socket), buffer_() {
-// io_handler.Initialize(socket_->handle(),
-// base::Bind(&SocketReader::OnDataAvailable,
-// base::Unretained(this));
-// }
-//
-// void AsyncRead() {
-// CHECK(io_handler.Read(&buffer_[0], sizeof(buffer_)));
-// }
-//
-// private:
-// void OnDataAvailable(int bytes_read) {
-// if (ProcessData(&buffer_[0], bytes_read)) {
-// // Issue another read.
-// CHECK(io_handler.Read(&buffer_[0], sizeof(buffer_)));
-// }
-// }
-//
-// media::AsyncSocketIoHandler io_handler;
-// base::CancelableSyncSocket* socket_;
-// char buffer_[kBufferSize];
-// };
-//
-class MEDIA_EXPORT AsyncSocketIoHandler
- : public NON_EXPORTED_BASE(base::NonThreadSafe),
- public NON_EXPORTED_BASE(MessageLoopIOHandler) {
- public:
- AsyncSocketIoHandler();
- virtual ~AsyncSocketIoHandler();
-
- // Type definition for the callback. The parameter tells how many
- // bytes were read and is 0 if an error occurred.
- typedef base::Callback<void(int)> ReadCompleteCallback;
-
- // Initializes the AsyncSocketIoHandler by hooking it up to the current
- // thread's message loop (must be TYPE_IO), to do async reads from the socket
- // on the current thread. The |callback| will be invoked whenever a Read()
- // has completed.
- bool Initialize(base::SyncSocket::Handle socket,
- const ReadCompleteCallback& callback);
-
- // Attempts to read from the socket. The return value will be |false|
- // if an error occurred and |true| if data was read or a pending read
- // was issued. Regardless of async or sync operation, the
- // ReadCompleteCallback (see above) will be called when data is available.
- bool Read(char* buffer, int buffer_len);
-
- private:
-#if defined(OS_WIN)
- // Implementation of IOHandler on Windows.
- virtual void OnIOCompleted(MessageLoopForIO::IOContext* context,
- DWORD bytes_transfered,
- DWORD error) OVERRIDE;
-#elif defined(OS_POSIX)
- // Implementation of MessageLoopForIO::Watcher.
- virtual void OnFileCanWriteWithoutBlocking(int socket) OVERRIDE {}
- virtual void OnFileCanReadWithoutBlocking(int socket) OVERRIDE;
-
- void EnsureWatchingSocket();
-#endif
-
- base::SyncSocket::Handle socket_;
-#if defined(OS_WIN)
- MessageLoopForIO::IOContext* context_;
- bool is_pending_;
-#elif defined(OS_POSIX)
- MessageLoopForIO::FileDescriptorWatcher socket_watcher_;
- // |pending_buffer_| and |pending_buffer_len_| are valid only between
- // Read() and OnFileCanReadWithoutBlocking().
- char* pending_buffer_;
- int pending_buffer_len_;
- // |true| iff the message loop is watching the socket for IO events.
- bool is_watching_;
-#endif
- ReadCompleteCallback read_complete_;
-
- DISALLOW_COPY_AND_ASSIGN(AsyncSocketIoHandler);
-};
-
-} // namespace media.
-
-#endif // MEDIA_AUDIO_ASYNC_SOCKET_IO_HANDLER_H_
diff --git a/src/media/audio/async_socket_io_handler_posix.cc b/src/media/audio/async_socket_io_handler_posix.cc
deleted file mode 100644
index eeec7c1..0000000
--- a/src/media/audio/async_socket_io_handler_posix.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/async_socket_io_handler.h"
-
-#include <fcntl.h>
-
-#include "base/posix/eintr_wrapper.h"
-
-namespace media {
-
-AsyncSocketIoHandler::AsyncSocketIoHandler()
- : socket_(base::SyncSocket::kInvalidHandle),
- pending_buffer_(NULL),
- pending_buffer_len_(0),
- is_watching_(false) {
-}
-
-AsyncSocketIoHandler::~AsyncSocketIoHandler() {
- DCHECK(CalledOnValidThread());
-}
-
-void AsyncSocketIoHandler::OnFileCanReadWithoutBlocking(int socket) {
- DCHECK(CalledOnValidThread());
- DCHECK_EQ(socket, socket_);
- DCHECK(!read_complete_.is_null());
-
- if (pending_buffer_) {
- int bytes_read = HANDLE_EINTR(read(socket_, pending_buffer_,
- pending_buffer_len_));
- DCHECK_GT(bytes_read, 0);
- pending_buffer_ = NULL;
- pending_buffer_len_ = 0;
- read_complete_.Run(bytes_read > 0 ? bytes_read : 0);
- } else {
- // We're getting notifications that we can read from the socket while
- // we're not waiting for data. In order to not starve the message loop,
- // let's stop watching the fd and restart the watch when Read() is called.
- is_watching_ = false;
- socket_watcher_.StopWatchingFileDescriptor();
- }
-}
-
-bool AsyncSocketIoHandler::Read(char* buffer, int buffer_len) {
- DCHECK(CalledOnValidThread());
- DCHECK(!read_complete_.is_null());
- DCHECK(!pending_buffer_);
-
- EnsureWatchingSocket();
-
- int bytes_read = HANDLE_EINTR(read(socket_, buffer, buffer_len));
- if (bytes_read < 0) {
- if (errno == EAGAIN) {
- pending_buffer_ = buffer;
- pending_buffer_len_ = buffer_len;
- } else {
- NOTREACHED() << "read(): " << errno;
- return false;
- }
- } else {
- read_complete_.Run(bytes_read);
- }
- return true;
-}
-
-bool AsyncSocketIoHandler::Initialize(base::SyncSocket::Handle socket,
- const ReadCompleteCallback& callback) {
- DCHECK_EQ(socket_, base::SyncSocket::kInvalidHandle);
-
- DetachFromThread();
-
- socket_ = socket;
- read_complete_ = callback;
-
- // SyncSocket is blocking by default, so let's convert it to non-blocking.
- int value = fcntl(socket, F_GETFL);
- if (!(value & O_NONBLOCK)) {
- // Set the socket to be non-blocking so we can do async reads.
- if (fcntl(socket, F_SETFL, O_NONBLOCK) == -1) {
- NOTREACHED();
- return false;
- }
- }
-
- return true;
-}
-
-void AsyncSocketIoHandler::EnsureWatchingSocket() {
- DCHECK(CalledOnValidThread());
- if (!is_watching_ && socket_ != base::SyncSocket::kInvalidHandle) {
- is_watching_ = MessageLoopForIO::current()->WatchFileDescriptor(
- socket_, true, MessageLoopForIO::WATCH_READ, &socket_watcher_, this);
- }
-}
-
-} // namespace media.
diff --git a/src/media/audio/async_socket_io_handler_unittest.cc b/src/media/audio/async_socket_io_handler_unittest.cc
deleted file mode 100644
index c7fa47b..0000000
--- a/src/media/audio/async_socket_io_handler_unittest.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/async_socket_io_handler.h"
-
-#include "base/bind.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-const char kAsyncSocketIoTestString[] = "Hello, AsyncSocketIoHandler";
-const size_t kAsyncSocketIoTestStringLength =
- arraysize(kAsyncSocketIoTestString);
-
-class TestSocketReader {
- public:
- // Set |number_of_reads_before_quit| to >0 when you expect a specific number
- // of Read operations to complete. Once that number is reached, the current
- // message loop will be Quit(). Set |number_of_reads_before_quit| to -1 if
- // callbacks should not be counted.
- TestSocketReader(base::CancelableSyncSocket* socket,
- int number_of_reads_before_quit,
- bool issue_reads_from_callback)
- : socket_(socket), buffer_(),
- number_of_reads_before_quit_(number_of_reads_before_quit),
- callbacks_received_(0),
- issue_reads_from_callback_(issue_reads_from_callback) {
- io_handler.Initialize(socket_->handle(),
- base::Bind(&TestSocketReader::OnRead,
- base::Unretained(this)));
- }
- ~TestSocketReader() {}
-
- bool IssueRead() {
- return io_handler.Read(&buffer_[0], sizeof(buffer_));
- }
-
- const char* buffer() const { return &buffer_[0]; }
-
- int callbacks_received() const { return callbacks_received_; }
-
- private:
- void OnRead(int bytes_read) {
- EXPECT_GT(bytes_read, 0);
- ++callbacks_received_;
- if (number_of_reads_before_quit_ == callbacks_received_) {
- MessageLoop::current()->Quit();
- } else if (issue_reads_from_callback_) {
- IssueRead();
- }
- }
-
- media::AsyncSocketIoHandler io_handler;
- base::CancelableSyncSocket* socket_; // Ownership lies outside the class.
- char buffer_[kAsyncSocketIoTestStringLength];
- int number_of_reads_before_quit_;
- int callbacks_received_;
- bool issue_reads_from_callback_;
-};
-
-// Workaround to be able to use a base::Closure for sending data.
-// Send() returns int but a closure must return void.
-void SendData(base::CancelableSyncSocket* socket,
- const void* buffer,
- size_t length) {
- socket->Send(buffer, length);
-}
-
-} // end namespace.
-
-// Tests doing a pending read from a socket and use an IO handler to get
-// notified of data.
-TEST(AsyncSocketIoHandlerTest, AsynchronousReadWithMessageLoop) {
- MessageLoopForIO loop;
-
- base::CancelableSyncSocket pair[2];
- ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
-
- TestSocketReader reader(&pair[0], 1, false);
- EXPECT_TRUE(reader.IssueRead());
-
- pair[1].Send(kAsyncSocketIoTestString, kAsyncSocketIoTestStringLength);
- MessageLoop::current()->Run();
- EXPECT_EQ(strcmp(reader.buffer(), kAsyncSocketIoTestString), 0);
- EXPECT_EQ(1, reader.callbacks_received());
-}
-
-// Tests doing a read from a socket when we know that there is data in the
-// socket. Here we want to make sure that any async 'can read' notifications
-// won't trip us off and that the synchronous case works as well.
-TEST(AsyncSocketIoHandlerTest, SynchronousReadWithMessageLoop) {
- MessageLoopForIO loop;
-
- base::CancelableSyncSocket pair[2];
- ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
-
- TestSocketReader reader(&pair[0], -1, false);
-
- pair[1].Send(kAsyncSocketIoTestString, kAsyncSocketIoTestStringLength);
- MessageLoop::current()->PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- base::TimeDelta::FromMilliseconds(100));
- MessageLoop::current()->Run();
-
- EXPECT_TRUE(reader.IssueRead());
- EXPECT_EQ(strcmp(reader.buffer(), kAsyncSocketIoTestString), 0);
- // We've now verified that the read happened synchronously, but it's not
- // guaranteed that the callback has been issued since the callback will be
- // called asynchronously even though the read may have been done.
- // So we call RunUntilIdle() to allow any event notifications or APC's on
- // Windows, to execute before checking the count of how many callbacks we've
- // received.
- MessageLoop::current()->RunUntilIdle();
- EXPECT_EQ(1, reader.callbacks_received());
-}
-
-// Calls Read() from within a callback to test that simple read "loops" work.
-TEST(AsyncSocketIoHandlerTest, ReadFromCallback) {
- MessageLoopForIO loop;
-
- base::CancelableSyncSocket pair[2];
- ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
-
- const int kReadOperationCount = 10;
- TestSocketReader reader(&pair[0], kReadOperationCount, true);
- EXPECT_TRUE(reader.IssueRead());
-
- // Issue sends on an interval to satisfy the Read() requirements.
- int64 milliseconds = 0;
- for (int i = 0; i < kReadOperationCount; ++i) {
- MessageLoop::current()->PostDelayedTask(FROM_HERE,
- base::Bind(&SendData, &pair[1], kAsyncSocketIoTestString,
- kAsyncSocketIoTestStringLength),
- base::TimeDelta::FromMilliseconds(milliseconds));
- milliseconds += 10;
- }
-
- MessageLoop::current()->PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- base::TimeDelta::FromMilliseconds(100 + milliseconds));
-
- MessageLoop::current()->Run();
- EXPECT_EQ(kReadOperationCount, reader.callbacks_received());
-}
diff --git a/src/media/audio/async_socket_io_handler_win.cc b/src/media/audio/async_socket_io_handler_win.cc
deleted file mode 100644
index f83f405..0000000
--- a/src/media/audio/async_socket_io_handler_win.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/async_socket_io_handler.h"
-
-namespace media {
-
-AsyncSocketIoHandler::AsyncSocketIoHandler()
- : socket_(base::SyncSocket::kInvalidHandle),
- context_(NULL),
- is_pending_(false) {}
-
-AsyncSocketIoHandler::~AsyncSocketIoHandler() {
- // We need to be deleted on the correct thread to avoid racing with the
- // message loop thread.
- DCHECK(CalledOnValidThread());
-
- if (context_) {
- if (is_pending_) {
- // Make the context be deleted by the message pump when done.
- context_->handler = NULL;
- } else {
- delete context_;
- }
- }
-}
-
-// Implementation of IOHandler on Windows.
-void AsyncSocketIoHandler::OnIOCompleted(MessageLoopForIO::IOContext* context,
- DWORD bytes_transfered,
- DWORD error) {
- DCHECK(CalledOnValidThread());
- DCHECK_EQ(context_, context);
- DCHECK(!read_complete_.is_null());
- is_pending_ = false;
- read_complete_.Run(error == ERROR_SUCCESS ? bytes_transfered : 0);
-}
-
-bool AsyncSocketIoHandler::Read(char* buffer, int buffer_len) {
- DCHECK(CalledOnValidThread());
- DCHECK(!read_complete_.is_null());
- DCHECK(!is_pending_);
- DCHECK_NE(socket_, base::SyncSocket::kInvalidHandle);
-
- DWORD bytes_read = 0;
- BOOL ok = ::ReadFile(socket_, buffer, buffer_len, &bytes_read,
- &context_->overlapped);
- // The completion port will be signaled regardless of completing the read
- // straight away or asynchronously (ERROR_IO_PENDING). OnIOCompleted() will
- // be called regardless and we don't need to explicitly run the callback
- // in the case where ok is FALSE and GLE==ERROR_IO_PENDING.
- is_pending_ = !ok && (GetLastError() == ERROR_IO_PENDING);
- return ok || is_pending_;
-}
-
-bool AsyncSocketIoHandler::Initialize(base::SyncSocket::Handle socket,
- const ReadCompleteCallback& callback) {
- DCHECK(!context_);
- DCHECK_EQ(socket_, base::SyncSocket::kInvalidHandle);
-
- DetachFromThread();
-
- socket_ = socket;
- read_complete_ = callback;
-
- MessageLoopForIO::current()->RegisterIOHandler(socket, this);
-
- context_ = new MessageLoopForIO::IOContext();
- context_->handler = this;
- memset(&context_->overlapped, 0, sizeof(context_->overlapped));
-
- return true;
-}
-
-} // namespace media.
diff --git a/src/media/audio/audio_buffers_state.cc b/src/media/audio/audio_buffers_state.cc
deleted file mode 100644
index 6c4f950..0000000
--- a/src/media/audio/audio_buffers_state.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_buffers_state.h"
-
-namespace media {
-
-AudioBuffersState::AudioBuffersState()
- : pending_bytes(0),
- hardware_delay_bytes(0) {
-}
-
-AudioBuffersState::AudioBuffersState(int pending_bytes,
- int hardware_delay_bytes)
- : pending_bytes(pending_bytes),
- hardware_delay_bytes(hardware_delay_bytes) {
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_buffers_state.h b/src/media/audio/audio_buffers_state.h
deleted file mode 100644
index 79244ae..0000000
--- a/src/media/audio/audio_buffers_state.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_BUFFERS_STATE_H_
-#define MEDIA_AUDIO_AUDIO_BUFFERS_STATE_H_
-
-#include "media/base/media_export.h"
-
-namespace media {
-
-// AudioBuffersState struct stores current state of audio buffers.
-// It is used for audio synchronization.
-struct MEDIA_EXPORT AudioBuffersState {
- AudioBuffersState();
- AudioBuffersState(int pending_bytes, int hardware_delay_bytes);
-
- int total_bytes() {
- return pending_bytes + hardware_delay_bytes;
- }
-
- // Number of bytes we currently have in our software buffer.
- int pending_bytes;
-
- // Number of bytes that have been written to the device, but haven't
- // been played yet.
- int hardware_delay_bytes;
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_BUFFERS_STATE_H_
diff --git a/src/media/audio/audio_device_name.cc b/src/media/audio/audio_device_name.cc
deleted file mode 100644
index 02bb03f..0000000
--- a/src/media/audio/audio_device_name.cc
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/audio/audio_device_name.h"
-
-namespace media {
-
-AudioDeviceName::AudioDeviceName() {}
-
-AudioDeviceName::AudioDeviceName(const std::string& device_name,
- const std::string& unique_id)
- : device_name(device_name),
- unique_id(unique_id) {
-}
-
-} // namespace media
-
diff --git a/src/media/audio/audio_device_name.h b/src/media/audio/audio_device_name.h
deleted file mode 100644
index aa3cca0..0000000
--- a/src/media/audio/audio_device_name.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
-#define MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
-
-#include <list>
-#include <string>
-#include "media/base/media_export.h"
-
-namespace media {
-
-struct MEDIA_EXPORT AudioDeviceName {
- AudioDeviceName();
- AudioDeviceName(const std::string& device_name,
- const std::string& unique_id);
-
- std::string device_name; // Friendly name of the device.
- std::string unique_id; // Unique identifier for the device.
-};
-
-typedef std::list<AudioDeviceName> AudioDeviceNames;
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
diff --git a/src/media/audio/audio_device_thread.cc b/src/media/audio/audio_device_thread.cc
deleted file mode 100644
index 51d5ecd..0000000
--- a/src/media/audio/audio_device_thread.cc
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_device_thread.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "base/message_loop.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/thread_restrictions.h"
-#include "media/audio/audio_util.h"
-#include "media/base/audio_bus.h"
-
-using base::PlatformThread;
-
-namespace media {
-
-// The actual worker thread implementation. It's very bare bones and much
-// simpler than SimpleThread (no synchronization in Start, etc) and supports
-// joining the thread handle asynchronously via a provided message loop even
-// after the Thread object itself has been deleted.
-class AudioDeviceThread::Thread
- : public PlatformThread::Delegate,
- public base::RefCountedThreadSafe<AudioDeviceThread::Thread> {
- public:
- Thread(AudioDeviceThread::Callback* callback,
- base::SyncSocket::Handle socket,
- const char* thread_name);
-
- void Start();
-
- // Stops the thread. If |loop_for_join| is non-NULL, the function posts
- // a task to join (close) the thread handle later instead of waiting for
- // the thread. If loop_for_join is NULL, then the function waits
- // synchronously for the thread to terminate.
- void Stop(MessageLoop* loop_for_join);
-
- private:
- friend class base::RefCountedThreadSafe<AudioDeviceThread::Thread>;
- virtual ~Thread();
-
- // Overrides from PlatformThread::Delegate.
- virtual void ThreadMain() OVERRIDE;
-
- // Runs the loop that reads from the socket.
- void Run();
-
- private:
- base::PlatformThreadHandle thread_;
- AudioDeviceThread::Callback* callback_;
- base::CancelableSyncSocket socket_;
- base::Lock callback_lock_;
- const char* thread_name_;
-
- DISALLOW_COPY_AND_ASSIGN(Thread);
-};
-
-// AudioDeviceThread implementation
-
-AudioDeviceThread::AudioDeviceThread() {
-}
-
-AudioDeviceThread::~AudioDeviceThread() {
- DCHECK(!thread_);
-}
-
-void AudioDeviceThread::Start(AudioDeviceThread::Callback* callback,
- base::SyncSocket::Handle socket,
- const char* thread_name) {
- base::AutoLock auto_lock(thread_lock_);
- CHECK(thread_ == NULL);
- thread_ = new AudioDeviceThread::Thread(callback, socket, thread_name);
- thread_->Start();
-}
-
-void AudioDeviceThread::Stop(MessageLoop* loop_for_join) {
- base::AutoLock auto_lock(thread_lock_);
- if (thread_) {
- thread_->Stop(loop_for_join);
- thread_ = NULL;
- }
-}
-
-bool AudioDeviceThread::IsStopped() {
- base::AutoLock auto_lock(thread_lock_);
- return thread_ == NULL;
-}
-
-// AudioDeviceThread::Thread implementation
-AudioDeviceThread::Thread::Thread(AudioDeviceThread::Callback* callback,
- base::SyncSocket::Handle socket,
- const char* thread_name)
- : thread_(base::kNullThreadHandle),
- callback_(callback),
- socket_(socket),
- thread_name_(thread_name) {
-}
-
-AudioDeviceThread::Thread::~Thread() {
- DCHECK_EQ(thread_, base::kNullThreadHandle) << "Stop wasn't called";
-}
-
-void AudioDeviceThread::Thread::Start() {
- base::AutoLock auto_lock(callback_lock_);
- DCHECK_EQ(thread_, base::kNullThreadHandle);
- // This reference will be released when the thread exists.
- AddRef();
-
- PlatformThread::CreateWithPriority(0, this, &thread_,
- base::kThreadPriority_RealtimeAudio);
- CHECK(thread_ != base::kNullThreadHandle);
-}
-
-void AudioDeviceThread::Thread::Stop(MessageLoop* loop_for_join) {
- socket_.Shutdown();
-
- base::PlatformThreadHandle thread = base::kNullThreadHandle;
-
- { // NOLINT
- base::AutoLock auto_lock(callback_lock_);
- callback_ = NULL;
- std::swap(thread, thread_);
- }
-
- if (thread != base::kNullThreadHandle) {
- if (loop_for_join) {
- loop_for_join->PostTask(FROM_HERE,
- base::Bind(&base::PlatformThread::Join, thread));
- } else {
- base::PlatformThread::Join(thread);
- }
- }
-}
-
-void AudioDeviceThread::Thread::ThreadMain() {
- PlatformThread::SetName(thread_name_);
-
- // Singleton access is safe from this thread as long as callback is non-NULL.
- // The callback is the only point where the thread calls out to 'unknown' code
- // that might touch singletons and the lifetime of the callback is controlled
- // by another thread on which singleton access is OK as well.
- base::ThreadRestrictions::SetSingletonAllowed(true);
-
- { // NOLINT
- base::AutoLock auto_lock(callback_lock_);
- if (callback_)
- callback_->InitializeOnAudioThread();
- }
-
- Run();
-
- // Release the reference for the thread. Note that after this, the Thread
- // instance will most likely be deleted.
- Release();
-}
-
-void AudioDeviceThread::Thread::Run() {
- while (true) {
- int pending_data = 0;
- size_t bytes_read = socket_.Receive(&pending_data, sizeof(pending_data));
- if (bytes_read != sizeof(pending_data)) {
- DCHECK_EQ(bytes_read, 0U);
- break;
- }
-
- base::AutoLock auto_lock(callback_lock_);
- if (callback_)
- callback_->Process(pending_data);
- }
-}
-
-// AudioDeviceThread::Callback implementation
-
-AudioDeviceThread::Callback::Callback(
- const AudioParameters& audio_parameters,
- int input_channels,
- base::SharedMemoryHandle memory, int memory_length)
- : audio_parameters_(audio_parameters),
- input_channels_(input_channels),
- samples_per_ms_(audio_parameters.sample_rate() / 1000),
- bytes_per_ms_(audio_parameters.channels() *
- (audio_parameters_.bits_per_sample() / 8) *
- samples_per_ms_),
- shared_memory_(memory, false),
- memory_length_(memory_length) {
- CHECK_NE(bytes_per_ms_, 0); // Catch division by zero early.
- CHECK_NE(samples_per_ms_, 0);
-}
-
-AudioDeviceThread::Callback::~Callback() {}
-
-void AudioDeviceThread::Callback::InitializeOnAudioThread() {
- MapSharedMemory();
- DCHECK(shared_memory_.memory() != NULL);
-}
-
-} // namespace media.
diff --git a/src/media/audio/audio_device_thread.h b/src/media/audio/audio_device_thread.h
deleted file mode 100644
index 44dbc3a..0000000
--- a/src/media/audio/audio_device_thread.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
-#define MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/shared_memory.h"
-#include "base/sync_socket.h"
-#include "base/synchronization/lock.h"
-#include "media/base/media_export.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/shared_memory_util.h"
-
-class MessageLoop;
-
-namespace media {
-class AudioBus;
-
-// Data transfer between browser and render process uses a combination
-// of sync sockets and shared memory. To read from the socket and render
-// data, we use a worker thread, a.k.a. the AudioDeviceThread, which reads
-// data from the browser via the socket and fills the shared memory from the
-// audio thread via the AudioDeviceThread::Callback interface/class.
-// For more details see the documentation in audio_device.h.
-//
-// TODO(tommi): Multiple audio input/output device instances should be able to
-// share the same thread instead of spinning one per instance.
-class MEDIA_EXPORT AudioDeviceThread {
- public:
- // This is the callback interface/base class that Audio[Output|Input]Device
- // implements to render input/output data. The callbacks run on the
- // thread owned by AudioDeviceThread.
- class Callback {
- public:
- Callback(const AudioParameters& audio_parameters,
- int input_channels,
- base::SharedMemoryHandle memory,
- int memory_length);
- virtual ~Callback();
-
- // One time initialization for the callback object on the audio thread.
- void InitializeOnAudioThread();
-
- // Derived implementations must call shared_memory_.Map appropriately
- // before Process can be called.
- virtual void MapSharedMemory() = 0;
-
- // Called whenever we receive notifications about pending data.
- virtual void Process(int pending_data) = 0;
-
- protected:
- // Protected so that derived classes can access directly.
- // The variables are 'const' since values are calculated/set in the
- // constructor and must never change.
- const AudioParameters audio_parameters_;
- const int input_channels_;
- const int samples_per_ms_;
- const int bytes_per_ms_;
-
- base::SharedMemory shared_memory_;
- const int memory_length_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Callback);
- };
-
- AudioDeviceThread();
- ~AudioDeviceThread();
-
- // Starts the audio thread. The thread must not already be running.
- void Start(AudioDeviceThread::Callback* callback,
- base::SyncSocket::Handle socket,
- const char* thread_name);
-
- // This tells the audio thread to stop and clean up the data.
- // The method can stop the thread synchronously or asynchronously.
- // In the latter case, the thread will still be running after Stop()
- // returns, but the callback pointer is cleared so no further callbacks will
- // be made (IOW after Stop() returns, it is safe to delete the callback).
- // The |loop_for_join| parameter is required for asynchronous operation
- // in order to join the worker thread and close the thread handle later via a
- // posted task.
- // If set to NULL, function will wait for the thread to exit before returning.
- void Stop(MessageLoop* loop_for_join);
-
- // Returns true if the thread is stopped or stopping.
- bool IsStopped();
-
- private:
- // Our own private SimpleThread override. We implement this in a
- // private class so that we get the following benefits:
- // 1) AudioDeviceThread doesn't expose SimpleThread methods.
- // I.e. the caller can't call Start()/Stop() - which would be bad.
- // 2) We override ThreadMain to add additional on-thread initialization
- // while still synchronized with SimpleThread::Start() to provide
- // reliable initialization.
- class Thread;
-
- base::Lock thread_lock_;
- scoped_refptr<AudioDeviceThread::Thread> thread_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioDeviceThread);
-};
-
-} // namespace media.
-
-#endif // MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
diff --git a/src/media/audio/audio_input_controller.cc b/src/media/audio/audio_input_controller.cc
deleted file mode 100644
index 99bb420..0000000
--- a/src/media/audio/audio_input_controller.cc
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_input_controller.h"
-
-#include "base/bind.h"
-#include "base/threading/thread_restrictions.h"
-#include "media/base/limits.h"
-
-namespace {
-const int kMaxInputChannels = 2;
-const int kTimerResetIntervalSeconds = 1;
-#if defined(OS_IOS)
-// The first callback on iOS is received after the current background
-// audio has faded away.
-const int kTimerInitialIntervalSeconds = 4;
-#else
-const int kTimerInitialIntervalSeconds = 1;
-#endif // defined(OS_IOS)
-}
-
-namespace media {
-
-// static
-AudioInputController::Factory* AudioInputController::factory_ = NULL;
-
-AudioInputController::AudioInputController(EventHandler* handler,
- SyncWriter* sync_writer)
- : creator_loop_(base::MessageLoopProxy::current()),
- handler_(handler),
- stream_(NULL),
- data_is_active_(false),
- state_(kEmpty),
- sync_writer_(sync_writer),
- max_volume_(0.0) {
- DCHECK(creator_loop_);
-}
-
-AudioInputController::~AudioInputController() {
- DCHECK(kClosed == state_ || kCreated == state_ || kEmpty == state_);
-}
-
-// static
-scoped_refptr<AudioInputController> AudioInputController::Create(
- AudioManager* audio_manager,
- EventHandler* event_handler,
- const AudioParameters& params) {
- DCHECK(audio_manager);
-
- if (!params.IsValid() || (params.channels() > kMaxInputChannels))
- return NULL;
-
- if (factory_)
- return factory_->Create(audio_manager, event_handler, params);
-
- scoped_refptr<AudioInputController> controller(new AudioInputController(
- event_handler, NULL));
-
- controller->message_loop_ = audio_manager->GetMessageLoop();
-
- // Create and open a new audio input stream from the existing
- // audio-device thread. Use the default audio-input device.
- std::string device_id = AudioManagerBase::kDefaultDeviceId;
- if (!controller->message_loop_->PostTask(FROM_HERE,
- base::Bind(&AudioInputController::DoCreate, controller,
- base::Unretained(audio_manager), params, device_id))) {
- controller = NULL;
- }
-
- return controller;
-}
-
-// static
-scoped_refptr<AudioInputController> AudioInputController::CreateLowLatency(
- AudioManager* audio_manager,
- EventHandler* event_handler,
- const AudioParameters& params,
- const std::string& device_id,
- SyncWriter* sync_writer) {
- DCHECK(audio_manager);
- DCHECK(sync_writer);
-
- if (!params.IsValid() || (params.channels() > kMaxInputChannels))
- return NULL;
-
- // Create the AudioInputController object and ensure that it runs on
- // the audio-manager thread.
- scoped_refptr<AudioInputController> controller(new AudioInputController(
- event_handler, sync_writer));
- controller->message_loop_ = audio_manager->GetMessageLoop();
-
- // Create and open a new audio input stream from the existing
- // audio-device thread. Use the provided audio-input device.
- if (!controller->message_loop_->PostTask(FROM_HERE,
- base::Bind(&AudioInputController::DoCreate, controller,
- base::Unretained(audio_manager), params, device_id))) {
- controller = NULL;
- }
-
- return controller;
-}
-
-void AudioInputController::Record() {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioInputController::DoRecord, this));
-}
-
-void AudioInputController::Close(const base::Closure& closed_task) {
- DCHECK(!closed_task.is_null());
- DCHECK(creator_loop_->BelongsToCurrentThread());
-
- message_loop_->PostTaskAndReply(
- FROM_HERE, base::Bind(&AudioInputController::DoClose, this), closed_task);
-}
-
-void AudioInputController::SetVolume(double volume) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioInputController::DoSetVolume, this, volume));
-}
-
-void AudioInputController::SetAutomaticGainControl(bool enabled) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioInputController::DoSetAutomaticGainControl, this, enabled));
-}
-
-void AudioInputController::DoCreate(AudioManager* audio_manager,
- const AudioParameters& params,
- const std::string& device_id) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- stream_ = audio_manager->MakeAudioInputStream(params, device_id);
-
- if (!stream_) {
- // TODO(satish): Define error types.
- handler_->OnError(this, 0);
- return;
- }
-
- if (stream_ && !stream_->Open()) {
- stream_->Close();
- stream_ = NULL;
- // TODO(satish): Define error types.
- handler_->OnError(this, 0);
- return;
- }
-
- DCHECK(!no_data_timer_.get());
- // Create the data timer which will call DoCheckForNoData(). The timer
- // is started in DoRecord() and restarted in each DoCheckForNoData() callback.
- no_data_timer_.reset(new base::Timer(
- FROM_HERE, base::TimeDelta::FromSeconds(kTimerInitialIntervalSeconds),
- base::Bind(&AudioInputController::DoCheckForNoData,
- base::Unretained(this)), false));
- state_ = kCreated;
- handler_->OnCreated(this);
-}
-
-void AudioInputController::DoRecord() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ != kCreated)
- return;
-
- {
- base::AutoLock auto_lock(lock_);
- state_ = kRecording;
- }
-
- // Start the data timer. Once |kTimerResetIntervalSeconds| have passed,
- // a callback to DoCheckForNoData() is made.
- no_data_timer_->Reset();
-
- stream_->Start(this);
- handler_->OnRecording(this);
-}
-
-void AudioInputController::DoClose() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Delete the timer on the same thread that created it.
- no_data_timer_.reset();
-
- if (state_ != kClosed) {
- DoStopCloseAndClearStream(NULL);
- SetDataIsActive(false);
-
- if (LowLatencyMode()) {
- sync_writer_->Close();
- }
-
- state_ = kClosed;
- }
-}
-
-void AudioInputController::DoReportError(int code) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- handler_->OnError(this, code);
-}
-
-void AudioInputController::DoSetVolume(double volume) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_GE(volume, 0);
- DCHECK_LE(volume, 1.0);
-
- if (state_ != kCreated && state_ != kRecording)
- return;
-
- // Only ask for the maximum volume at first call and use cached value
- // for remaining function calls.
- if (!max_volume_) {
- max_volume_ = stream_->GetMaxVolume();
- }
-
- if (max_volume_ == 0.0) {
- DLOG(WARNING) << "Failed to access input volume control";
- return;
- }
-
- // Set the stream volume and scale to a range matched to the platform.
- stream_->SetVolume(max_volume_ * volume);
-}
-
-void AudioInputController::DoSetAutomaticGainControl(bool enabled) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_NE(state_, kRecording);
-
- // Ensure that the AGC state only can be modified before streaming starts.
- if (state_ != kCreated || state_ == kRecording)
- return;
-
- stream_->SetAutomaticGainControl(enabled);
-}
-
-void AudioInputController::DoCheckForNoData() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (!GetDataIsActive()) {
- // The data-is-active marker will be false only if it has been more than
- // one second since a data packet was recorded. This can happen if a
- // capture device has been removed or disabled.
- handler_->OnError(this, 0);
- return;
- }
-
- // Mark data as non-active. The flag will be re-enabled in OnData() each
- // time a data packet is received. Hence, under normal conditions, the
- // flag will only be disabled during a very short period.
- SetDataIsActive(false);
-
- // Restart the timer to ensure that we check the flag again in
- // |kTimerResetIntervalSeconds|.
- no_data_timer_->Start(
- FROM_HERE, base::TimeDelta::FromSeconds(kTimerResetIntervalSeconds),
- base::Bind(&AudioInputController::DoCheckForNoData,
- base::Unretained(this)));
-}
-
-void AudioInputController::OnData(AudioInputStream* stream, const uint8* data,
- uint32 size, uint32 hardware_delay_bytes,
- double volume) {
- {
- base::AutoLock auto_lock(lock_);
- if (state_ != kRecording)
- return;
- }
-
- // Mark data as active to ensure that the periodic calls to
- // DoCheckForNoData() does not report an error to the event handler.
- SetDataIsActive(true);
-
- // Use SyncSocket if we are in a low-latency mode.
- if (LowLatencyMode()) {
- sync_writer_->Write(data, size, volume);
- sync_writer_->UpdateRecordedBytes(hardware_delay_bytes);
- return;
- }
-
- handler_->OnData(this, data, size);
-}
-
-void AudioInputController::OnClose(AudioInputStream* stream) {
- DVLOG(1) << "AudioInputController::OnClose()";
- // TODO(satish): Sometimes the device driver closes the input stream without
- // us asking for it (may be if the device was unplugged?). Check how to handle
- // such cases here.
-}
-
-void AudioInputController::OnError(AudioInputStream* stream, int code) {
- // Handle error on the audio-manager thread.
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioInputController::DoReportError, this, code));
-}
-
-void AudioInputController::DoStopCloseAndClearStream(
- base::WaitableEvent *done) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Allow calling unconditionally and bail if we don't have a stream to close.
- if (stream_ != NULL) {
- stream_->Stop();
- stream_->Close();
- stream_ = NULL;
- }
-
- // Should be last in the method, do not touch "this" from here on.
- if (done != NULL)
- done->Signal();
-}
-
-void AudioInputController::SetDataIsActive(bool enabled) {
- base::subtle::Release_Store(&data_is_active_, enabled);
-}
-
-bool AudioInputController::GetDataIsActive() {
- return (base::subtle::Acquire_Load(&data_is_active_) != false);
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_controller.h b/src/media/audio/audio_input_controller.h
deleted file mode 100644
index 77e3e87..0000000
--- a/src/media/audio/audio_input_controller.h
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_
-#define MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_
-
-#include <string>
-#include "base/atomicops.h"
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/threading/thread.h"
-#include "base/timer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-
-// An AudioInputController controls an AudioInputStream and records data
-// from this input stream. The two main methods are Record() and Close() and
-// they are both executed on the audio thread which is injected by the two
-// alternative factory methods, Create() or CreateLowLatency().
-//
-// All public methods of AudioInputController are non-blocking.
-//
-// Here is a state diagram for the AudioInputController:
-//
-// .--> [ Closed / Error ] <--.
-// | |
-// | |
-// [ Created ] ----------> [ Recording ]
-// ^
-// |
-// *[ Empty ]
-//
-// * Initial state
-//
-// State sequences (assuming low-latency):
-//
-// [Creating Thread] [Audio Thread]
-//
-// User AudioInputController EventHandler
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-// CrateLowLatency() ==> DoCreate()
-// AudioManager::MakeAudioInputStream()
-// AudioInputStream::Open()
-// .- - - - - - - - - - - - -> OnError()
-// create the data timer
-// .-------------------------> OnCreated()
-// kCreated
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-// Record() ==> DoRecord()
-// AudioInputStream::Start()
-// .-------------------------> OnRecording()
-// start the data timer
-// kRecording
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-// Close() ==> DoClose()
-// delete the data timer
-// state_ = kClosed
-// AudioInputStream::Stop()
-// AudioInputStream::Close()
-// SyncWriter::Close()
-// Closure::Run() <-----------------.
-// (closure-task)
-//
-// The audio thread itself is owned by the AudioManager that the
-// AudioInputController holds a reference to. When performing tasks on the
-// audio thread, the controller must not add or release references to the
-// AudioManager or itself (since it in turn holds a reference to the manager).
-//
-namespace media {
-
-class MEDIA_EXPORT AudioInputController
- : public base::RefCountedThreadSafe<AudioInputController>,
- public AudioInputStream::AudioInputCallback {
- public:
- // An event handler that receives events from the AudioInputController. The
- // following methods are all called on the audio thread.
- class MEDIA_EXPORT EventHandler {
- public:
- virtual void OnCreated(AudioInputController* controller) = 0;
- virtual void OnRecording(AudioInputController* controller) = 0;
- virtual void OnError(AudioInputController* controller, int error_code) = 0;
- virtual void OnData(AudioInputController* controller, const uint8* data,
- uint32 size) = 0;
-
- protected:
- virtual ~EventHandler() {}
- };
-
- // A synchronous writer interface used by AudioInputController for
- // synchronous writing.
- class SyncWriter {
- public:
- virtual ~SyncWriter() {}
-
- // Notify the synchronous writer about the number of bytes in the
- // soundcard which has been recorded.
- virtual void UpdateRecordedBytes(uint32 bytes) = 0;
-
- // Write certain amount of data from |data|. This method returns
- // number of written bytes.
- virtual uint32 Write(const void* data, uint32 size, double volume) = 0;
-
- // Close this synchronous writer.
- virtual void Close() = 0;
- };
-
- // AudioInputController::Create() can use the currently registered Factory
- // to create the AudioInputController. Factory is intended for testing only.
- class Factory {
- public:
- virtual AudioInputController* Create(AudioManager* audio_manager,
- EventHandler* event_handler,
- AudioParameters params) = 0;
- protected:
- virtual ~Factory() {}
- };
-
- // Factory method for creating an AudioInputController.
- // The audio device will be created on the audio thread, and when that is
- // done, the event handler will receive an OnCreated() call from that same
- // thread.
- static scoped_refptr<AudioInputController> Create(
- AudioManager* audio_manager,
- EventHandler* event_handler,
- const AudioParameters& params);
-
- // Sets the factory used by the static method Create(). AudioInputController
- // does not take ownership of |factory|. A value of NULL results in an
- // AudioInputController being created directly.
- static void set_factory_for_testing(Factory* factory) { factory_ = factory; }
- AudioInputStream* stream_for_testing() { return stream_; }
-
- // Factory method for creating an AudioInputController for low-latency mode.
- // The audio device will be created on the audio thread, and when that is
- // done, the event handler will receive an OnCreated() call from that same
- // thread.
- static scoped_refptr<AudioInputController> CreateLowLatency(
- AudioManager* audio_manager,
- EventHandler* event_handler,
- const AudioParameters& params,
- const std::string& device_id,
- // External synchronous writer for audio controller.
- SyncWriter* sync_writer);
-
- // Starts recording using the created audio input stream.
- // This method is called on the creator thread.
- virtual void Record();
-
- // Closes the audio input stream. The state is changed and the resources
- // are freed on the audio thread. |closed_task| is then executed on the thread
- // that called Close().
- // Callbacks (EventHandler and SyncWriter) must exist until |closed_task|
- // is called.
- // It is safe to call this method more than once. Calls after the first one
- // will have no effect.
- // This method trampolines to the audio thread.
- virtual void Close(const base::Closure& closed_task);
-
- // Sets the capture volume of the input stream. The value 0.0 corresponds
- // to muted and 1.0 to maximum volume.
- virtual void SetVolume(double volume);
-
- // Sets the Automatic Gain Control (AGC) state of the input stream.
- // Changing the AGC state is not supported while recording is active.
- virtual void SetAutomaticGainControl(bool enabled);
-
- // AudioInputCallback implementation. Threading details depends on the
- // device-specific implementation.
- virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size,
- uint32 hardware_delay_bytes, double volume) OVERRIDE;
- virtual void OnClose(AudioInputStream* stream) OVERRIDE;
- virtual void OnError(AudioInputStream* stream, int code) OVERRIDE;
-
- bool LowLatencyMode() const { return sync_writer_ != NULL; }
-
- protected:
- friend class base::RefCountedThreadSafe<AudioInputController>;
-
- // Internal state of the source.
- enum State {
- kEmpty,
- kCreated,
- kRecording,
- kClosed,
- kError
- };
-
- AudioInputController(EventHandler* handler, SyncWriter* sync_writer);
- virtual ~AudioInputController();
-
- // Methods called on the audio thread (owned by the AudioManager).
- void DoCreate(AudioManager* audio_manager, const AudioParameters& params,
- const std::string& device_id);
- void DoRecord();
- void DoClose();
- void DoReportError(int code);
- void DoSetVolume(double volume);
- void DoSetAutomaticGainControl(bool enabled);
-
- // Method which ensures that OnError() is triggered when data recording
- // times out. Called on the audio thread.
- void DoCheckForNoData();
-
- // Helper method that stops, closes, and NULL:s |*stream_|.
- // Signals event when done if the event is not NULL.
- void DoStopCloseAndClearStream(base::WaitableEvent* done);
-
- void SetDataIsActive(bool enabled);
- bool GetDataIsActive();
-
- // Gives access to the message loop of the creating thread.
- scoped_refptr<base::MessageLoopProxy> creator_loop_;
-
- // The message loop of audio-manager thread that this object runs on.
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // Contains the AudioInputController::EventHandler which receives state
- // notifications from this class.
- EventHandler* handler_;
-
- // Pointer to the audio input stream object.
- AudioInputStream* stream_;
-
- // |no_data_timer_| is used to call OnError() when we stop receiving
- // OnData() calls without an OnClose() call. This can occur
- // when an audio input device is unplugged whilst recording on Windows.
- // See http://crbug.com/79936 for details.
- // This member is only touched by the audio thread.
- scoped_ptr<base::Timer> no_data_timer_;
-
- // This flag is used to signal that we are receiving OnData() calls, i.e,
- // that data is active. It can be touched by the audio thread and by the
- // low-level audio thread which calls OnData(). E.g. on Windows, the
- // low-level audio thread is called wasapi_capture_thread.
- base::subtle::Atomic32 data_is_active_;
-
- // |state_| is written on the audio thread and is read on the hardware audio
- // thread. These operations need to be locked. But lock is not required for
- // reading on the audio input controller thread.
- State state_;
-
- base::Lock lock_;
-
- // SyncWriter is used only in low-latency mode for synchronous writing.
- SyncWriter* sync_writer_;
-
- static Factory* factory_;
-
- double max_volume_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioInputController);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_
diff --git a/src/media/audio/audio_input_controller_unittest.cc b/src/media/audio/audio_input_controller_unittest.cc
deleted file mode 100644
index 0a2a39b..0000000
--- a/src/media/audio/audio_input_controller_unittest.cc
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/test/test_timeouts.h"
-#include "media/audio/audio_input_controller.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AtLeast;
-using ::testing::Exactly;
-using ::testing::InvokeWithoutArgs;
-using ::testing::NotNull;
-
-namespace media {
-
-static const int kSampleRate = AudioParameters::kAudioCDSampleRate;
-static const int kBitsPerSample = 16;
-static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
-static const int kSamplesPerPacket = kSampleRate / 10;
-
-// Posts MessageLoop::QuitClosure() on specified message loop.
-ACTION_P(QuitMessageLoop, loop_or_proxy) {
- loop_or_proxy->PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-// Posts MessageLoop::QuitClosure() on specified message loop after a certain
-// number of calls given by |limit|.
-ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop_or_proxy) {
- if (++*count >= limit) {
- loop_or_proxy->PostTask(FROM_HERE, MessageLoop::QuitClosure());
- }
-}
-
-// Closes AudioOutputController synchronously.
-static void CloseAudioController(AudioInputController* controller) {
- controller->Close(MessageLoop::QuitClosure());
- MessageLoop::current()->Run();
-}
-
-class MockAudioInputControllerEventHandler
- : public AudioInputController::EventHandler {
- public:
- MockAudioInputControllerEventHandler() {}
-
- MOCK_METHOD1(OnCreated, void(AudioInputController* controller));
- MOCK_METHOD1(OnRecording, void(AudioInputController* controller));
- MOCK_METHOD2(OnError, void(AudioInputController* controller, int error_code));
- MOCK_METHOD3(OnData, void(AudioInputController* controller,
- const uint8* data, uint32 size));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioInputControllerEventHandler);
-};
-
-// Test fixture.
-class AudioInputControllerTest : public testing::Test {
- public:
- AudioInputControllerTest() {}
- virtual ~AudioInputControllerTest() {}
-
- protected:
- MessageLoop message_loop_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioInputControllerTest);
-};
-
-// Test AudioInputController for create and close without recording audio.
-TEST_F(AudioInputControllerTest, CreateAndClose) {
- MockAudioInputControllerEventHandler event_handler;
-
- // OnCreated() will be posted once.
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .WillOnce(QuitMessageLoop(&message_loop_));
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioInputController> controller =
- AudioInputController::Create(audio_manager.get(), &event_handler, params);
- ASSERT_TRUE(controller.get());
-
- // Wait for OnCreated() to fire.
- message_loop_.Run();
-
- // Close the AudioInputController synchronously.
- CloseAudioController(controller);
-}
-
-// Test a normal call sequence of create, record and close.
-TEST_F(AudioInputControllerTest, RecordAndClose) {
- MockAudioInputControllerEventHandler event_handler;
- int count = 0;
-
- // OnCreated() will be called once.
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .Times(Exactly(1));
-
- // OnRecording() will be called only once.
- EXPECT_CALL(event_handler, OnRecording(NotNull()))
- .Times(Exactly(1));
-
- // OnData() shall be called ten times.
- EXPECT_CALL(event_handler, OnData(NotNull(), NotNull(), _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10,
- message_loop_.message_loop_proxy()));
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
-
- // Creating the AudioInputController should render an OnCreated() call.
- scoped_refptr<AudioInputController> controller =
- AudioInputController::Create(audio_manager.get(), &event_handler, params);
- ASSERT_TRUE(controller.get());
-
- // Start recording and trigger one OnRecording() call.
- controller->Record();
-
- // Record and wait until ten OnData() callbacks are received.
- message_loop_.Run();
-
- // Close the AudioInputController synchronously.
- CloseAudioController(controller);
-}
-
-// Test that the AudioInputController reports an error when the input stream
-// stops without an OnClose() callback. This can happen when the underlying
-// audio layer stops feeding data as a result of a removed microphone device.
-TEST_F(AudioInputControllerTest, RecordAndError) {
- MockAudioInputControllerEventHandler event_handler;
- int count = 0;
-
- // OnCreated() will be called once.
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .Times(Exactly(1));
-
- // OnRecording() will be called only once.
- EXPECT_CALL(event_handler, OnRecording(NotNull()))
- .Times(Exactly(1));
-
- // OnData() shall be called ten times.
- EXPECT_CALL(event_handler, OnData(NotNull(), NotNull(), _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10,
- message_loop_.message_loop_proxy()));
-
- // OnError() will be called after the data stream stops while the
- // controller is in a recording state.
- EXPECT_CALL(event_handler, OnError(NotNull(), 0))
- .Times(Exactly(1))
- .WillOnce(QuitMessageLoop(&message_loop_));
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
-
- // Creating the AudioInputController should render an OnCreated() call.
- scoped_refptr<AudioInputController> controller =
- AudioInputController::Create(audio_manager.get(), &event_handler, params);
- ASSERT_TRUE(controller.get());
-
- // Start recording and trigger one OnRecording() call.
- controller->Record();
-
- // Record and wait until ten OnData() callbacks are received.
- message_loop_.Run();
-
- // Stop the stream and verify that OnError() is posted.
- AudioInputStream* stream = controller->stream_for_testing();
- stream->Stop();
- message_loop_.Run();
-
- // Close the AudioInputController synchronously.
- CloseAudioController(controller);
-}
-
-// Test that AudioInputController rejects insanely large packet sizes.
-TEST_F(AudioInputControllerTest, SamplesPerPacketTooLarge) {
- // Create an audio device with a very large packet size.
- MockAudioInputControllerEventHandler event_handler;
-
- // OnCreated() shall not be called in this test.
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .Times(Exactly(0));
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket * 1000);
- scoped_refptr<AudioInputController> controller =
- AudioInputController::Create(audio_manager.get(), &event_handler, params);
- ASSERT_FALSE(controller);
-}
-
-// Test calling AudioInputController::Close multiple times.
-TEST_F(AudioInputControllerTest, CloseTwice) {
- MockAudioInputControllerEventHandler event_handler;
-
- // OnRecording() will be called only once.
- EXPECT_CALL(event_handler, OnCreated(NotNull()));
-
- // OnRecording() will be called only once.
- EXPECT_CALL(event_handler, OnRecording(NotNull()))
- .Times(Exactly(1));
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioInputController> controller =
- AudioInputController::Create(audio_manager.get(), &event_handler, params);
- ASSERT_TRUE(controller.get());
-
- controller->Record();
-
- controller->Close(MessageLoop::QuitClosure());
- MessageLoop::current()->Run();
-
- controller->Close(MessageLoop::QuitClosure());
- MessageLoop::current()->Run();
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_device.cc b/src/media/audio/audio_input_device.cc
deleted file mode 100644
index 9edf6db..0000000
--- a/src/media/audio/audio_input_device.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_input_device.h"
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/time.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/base/audio_bus.h"
-
-namespace media {
-
-// Takes care of invoking the capture callback on the audio thread.
-// An instance of this class is created for each capture stream in
-// OnLowLatencyCreated().
-class AudioInputDevice::AudioThreadCallback
- : public AudioDeviceThread::Callback {
- public:
- AudioThreadCallback(const AudioParameters& audio_parameters,
- base::SharedMemoryHandle memory,
- int memory_length,
- CaptureCallback* capture_callback);
- virtual ~AudioThreadCallback();
-
- virtual void MapSharedMemory() OVERRIDE;
-
- // Called whenever we receive notifications about pending data.
- virtual void Process(int pending_data) OVERRIDE;
-
- private:
- CaptureCallback* capture_callback_;
- scoped_ptr<AudioBus> audio_bus_;
- DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback);
-};
-
-AudioInputDevice::AudioInputDevice(
- AudioInputIPC* ipc,
- const scoped_refptr<base::MessageLoopProxy>& io_loop)
- : ScopedLoopObserver(io_loop),
- callback_(NULL),
- event_handler_(NULL),
- ipc_(ipc),
- stream_id_(0),
- session_id_(0),
- pending_device_ready_(false),
- agc_is_enabled_(false) {
- CHECK(ipc_);
-}
-
-void AudioInputDevice::Initialize(const AudioParameters& params,
- CaptureCallback* callback,
- CaptureEventHandler* event_handler) {
- DCHECK(!callback_);
- DCHECK(!event_handler_);
- audio_parameters_ = params;
- callback_ = callback;
- event_handler_ = event_handler;
-}
-
-void AudioInputDevice::SetDevice(int session_id) {
- DVLOG(1) << "SetDevice (session_id=" << session_id << ")";
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::SetSessionIdOnIOThread, this, session_id));
-}
-
-void AudioInputDevice::Start() {
- DVLOG(1) << "Start()";
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::InitializeOnIOThread, this));
-}
-
-void AudioInputDevice::Stop() {
- DVLOG(1) << "Stop()";
-
- {
- base::AutoLock auto_lock(audio_thread_lock_);
- audio_thread_.Stop(MessageLoop::current());
- }
-
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::ShutDownOnIOThread, this));
-}
-
-void AudioInputDevice::SetVolume(double volume) {
- if (volume < 0 || volume > 1.0) {
- DLOG(ERROR) << "Invalid volume value specified";
- return;
- }
-
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::SetVolumeOnIOThread, this, volume));
-}
-
-void AudioInputDevice::SetAutomaticGainControl(bool enabled) {
- DVLOG(1) << "SetAutomaticGainControl(enabled=" << enabled << ")";
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::SetAutomaticGainControlOnIOThread,
- this, enabled));
-}
-
-void AudioInputDevice::OnStreamCreated(
- base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) {
- DCHECK(message_loop()->BelongsToCurrentThread());
-#if defined(OS_WIN)
- DCHECK(handle);
- DCHECK(socket_handle);
-#elif defined(__LB_SHELL__) || defined(COBALT)
- DCHECK(handle.get());
-#else
- DCHECK_GE(handle.fd, 0);
- DCHECK_GE(socket_handle, 0);
-#endif
- DCHECK(length);
- DVLOG(1) << "OnStreamCreated (stream_id=" << stream_id_ << ")";
-
- // We should only get this callback if stream_id_ is valid. If it is not,
- // the IPC layer should have closed the shared memory and socket handles
- // for us and not invoked the callback. The basic assertion is that when
- // stream_id_ is 0 the AudioInputDevice instance is not registered as a
- // delegate and hence it should not receive callbacks.
- DCHECK(stream_id_);
-
- base::AutoLock auto_lock(audio_thread_lock_);
-
- DCHECK(audio_thread_.IsStopped());
- audio_callback_.reset(
- new AudioInputDevice::AudioThreadCallback(audio_parameters_, handle,
- length, callback_));
- audio_thread_.Start(audio_callback_.get(), socket_handle, "AudioInputDevice");
-
- MessageLoop::current()->PostTask(FROM_HERE,
- base::Bind(&AudioInputDevice::StartOnIOThread, this));
-}
-
-void AudioInputDevice::OnVolume(double volume) {
- NOTIMPLEMENTED();
-}
-
-void AudioInputDevice::OnStateChanged(
- AudioInputIPCDelegate::State state) {
- DCHECK(message_loop()->BelongsToCurrentThread());
-
- // Do nothing if the stream has been closed.
- if (!stream_id_)
- return;
-
- switch (state) {
- case AudioInputIPCDelegate::kStopped:
- // TODO(xians): Should we just call ShutDownOnIOThread here instead?
- ipc_->RemoveDelegate(stream_id_);
-
- audio_thread_.Stop(MessageLoop::current());
- audio_callback_.reset();
-
- if (event_handler_)
- event_handler_->OnDeviceStopped();
-
- stream_id_ = 0;
- pending_device_ready_ = false;
- break;
- case AudioInputIPCDelegate::kRecording:
- NOTIMPLEMENTED();
- break;
- case AudioInputIPCDelegate::kError:
- DLOG(WARNING) << "AudioInputDevice::OnStateChanged(kError)";
- // Don't dereference the callback object if the audio thread
- // is stopped or stopping. That could mean that the callback
- // object has been deleted.
- // TODO(tommi): Add an explicit contract for clearing the callback
- // object. Possibly require calling Initialize again or provide
- // a callback object via Start() and clear it in Stop().
- if (!audio_thread_.IsStopped())
- callback_->OnCaptureError();
- break;
- default:
- NOTREACHED();
- break;
- }
-}
-
-void AudioInputDevice::OnDeviceReady(const std::string& device_id) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- DVLOG(1) << "OnDeviceReady (device_id=" << device_id << ")";
-
- // Takes care of the case when Stop() is called before OnDeviceReady().
- if (!pending_device_ready_)
- return;
-
- // If AudioInputDeviceManager returns an empty string, it means no device
- // is ready for start.
- if (device_id.empty()) {
- ipc_->RemoveDelegate(stream_id_);
- stream_id_ = 0;
- } else {
- ipc_->CreateStream(stream_id_, audio_parameters_, device_id,
- agc_is_enabled_);
- }
-
- pending_device_ready_ = false;
- // Notify the client that the device has been started.
- if (event_handler_)
- event_handler_->OnDeviceStarted(device_id);
-}
-
-void AudioInputDevice::OnIPCClosed() {
- ipc_ = NULL;
-}
-
-AudioInputDevice::~AudioInputDevice() {
- // TODO(henrika): The current design requires that the user calls
- // Stop before deleting this class.
- CHECK_EQ(0, stream_id_);
-}
-
-void AudioInputDevice::InitializeOnIOThread() {
- DCHECK(message_loop()->BelongsToCurrentThread());
- // Make sure we don't call Start() more than once.
- DCHECK_EQ(0, stream_id_);
- if (stream_id_)
- return;
-
- stream_id_ = ipc_->AddDelegate(this);
- // If |session_id_| is not specified, it will directly create the stream;
- // otherwise it will send a AudioInputHostMsg_StartDevice msg to the browser
- // and create the stream when getting a OnDeviceReady() callback.
- if (!session_id_) {
- ipc_->CreateStream(stream_id_, audio_parameters_,
- AudioManagerBase::kDefaultDeviceId, agc_is_enabled_);
- } else {
- ipc_->StartDevice(stream_id_, session_id_);
- pending_device_ready_ = true;
- }
-}
-
-void AudioInputDevice::SetSessionIdOnIOThread(int session_id) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- session_id_ = session_id;
-}
-
-void AudioInputDevice::StartOnIOThread() {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (stream_id_)
- ipc_->RecordStream(stream_id_);
-}
-
-void AudioInputDevice::ShutDownOnIOThread() {
- DCHECK(message_loop()->BelongsToCurrentThread());
- // NOTE: |completion| may be NULL.
- // Make sure we don't call shutdown more than once.
- if (stream_id_) {
- if (ipc_) {
- ipc_->CloseStream(stream_id_);
- ipc_->RemoveDelegate(stream_id_);
- }
-
- stream_id_ = 0;
- session_id_ = 0;
- pending_device_ready_ = false;
- agc_is_enabled_ = false;
- }
-
- // We can run into an issue where ShutDownOnIOThread is called right after
- // OnStreamCreated is called in cases where Start/Stop are called before we
- // get the OnStreamCreated callback. To handle that corner case, we call
- // Stop(). In most cases, the thread will already be stopped.
- // Another situation is when the IO thread goes away before Stop() is called
- // in which case, we cannot use the message loop to close the thread handle
- // and can't not rely on the main thread existing either.
- base::ThreadRestrictions::ScopedAllowIO allow_io;
- audio_thread_.Stop(NULL);
- audio_callback_.reset();
-}
-
-void AudioInputDevice::SetVolumeOnIOThread(double volume) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (stream_id_)
- ipc_->SetVolume(stream_id_, volume);
-}
-
-void AudioInputDevice::SetAutomaticGainControlOnIOThread(bool enabled) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- DCHECK_EQ(0, stream_id_) <<
- "The AGC state can not be modified while capturing is active.";
- if (stream_id_)
- return;
-
- // We simply store the new AGC setting here. This value will be used when
- // a new stream is initialized and by GetAutomaticGainControl().
- agc_is_enabled_ = enabled;
-}
-
-void AudioInputDevice::WillDestroyCurrentMessageLoop() {
- LOG(ERROR) << "IO loop going away before the input device has been stopped";
- ShutDownOnIOThread();
-}
-
-// AudioInputDevice::AudioThreadCallback
-AudioInputDevice::AudioThreadCallback::AudioThreadCallback(
- const AudioParameters& audio_parameters,
- base::SharedMemoryHandle memory,
- int memory_length,
- CaptureCallback* capture_callback)
- : AudioDeviceThread::Callback(audio_parameters, 0, memory, memory_length),
- capture_callback_(capture_callback) {
- audio_bus_ = AudioBus::Create(audio_parameters_);
-}
-
-AudioInputDevice::AudioThreadCallback::~AudioThreadCallback() {
-}
-
-void AudioInputDevice::AudioThreadCallback::MapSharedMemory() {
- shared_memory_.Map(memory_length_);
-}
-
-void AudioInputDevice::AudioThreadCallback::Process(int pending_data) {
- // The shared memory represents parameters, size of the data buffer and the
- // actual data buffer containing audio data. Map the memory into this
- // structure and parse out parameters and the data area.
- AudioInputBuffer* buffer =
- reinterpret_cast<AudioInputBuffer*>(shared_memory_.memory());
- DCHECK_EQ(buffer->params.size,
- memory_length_ - sizeof(AudioInputBufferParameters));
- double volume = buffer->params.volume;
-
- int audio_delay_milliseconds = pending_data / bytes_per_ms_;
- int16* memory = reinterpret_cast<int16*>(&buffer->audio[0]);
- const int bytes_per_sample = sizeof(memory[0]);
-
- // Deinterleave each channel and convert to 32-bit floating-point
- // with nominal range -1.0 -> +1.0.
- audio_bus_->FromInterleaved(memory, audio_bus_->frames(), bytes_per_sample);
-
- // Deliver captured data to the client in floating point format
- // and update the audio-delay measurement.
- capture_callback_->Capture(audio_bus_.get(),
- audio_delay_milliseconds, volume);
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_device.h b/src/media/audio/audio_input_device.h
deleted file mode 100644
index edefdf1..0000000
--- a/src/media/audio/audio_input_device.h
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Low-latency audio capturing class utilizing audio input stream provided
-// by a server (browser) process by use of an IPC interface.
-//
-// Relationship of classes:
-//
-// AudioInputController AudioInputDevice
-// ^ ^
-// | |
-// v IPC v
-// AudioInputRendererHost <---------> AudioInputIPCDelegate
-// ^ (impl in AudioInputMessageFilter)
-// |
-// v
-// AudioInputDeviceManager
-//
-// Transportation of audio samples from the browser to the render process
-// is done by using shared memory in combination with a SyncSocket.
-// The AudioInputDevice user registers an AudioInputDevice::CaptureCallback by
-// calling Initialize(). The callback will be called with recorded audio from
-// the underlying audio layers.
-// The session ID is used by the AudioInputRendererHost to start the device
-// referenced by this ID.
-//
-// State sequences:
-//
-// Sequence where session_id has not been set using SetDevice():
-// ('<-' signifies callbacks, -> signifies calls made by AudioInputDevice)
-// Start -> InitializeOnIOThread -> CreateStream ->
-// <- OnStreamCreated <-
-// -> StartOnIOThread -> PlayStream ->
-//
-// Sequence where session_id has been set using SetDevice():
-// Start -> InitializeOnIOThread -> StartDevice ->
-// <- OnDeviceReady <-
-// -> CreateStream ->
-// <- OnStreamCreated <-
-// -> StartOnIOThread -> PlayStream ->
-//
-// AudioInputDevice::Capture => low latency audio transport on audio thread =>
-// |
-// Stop --> ShutDownOnIOThread ------> CloseStream -> Close
-//
-// This class depends on two threads to function:
-//
-// 1. An IO thread.
-// This thread is used to asynchronously process Start/Stop etc operations
-// that are available via the public interface. The public methods are
-// asynchronous and simply post a task to the IO thread to actually perform
-// the work.
-// 2. Audio transport thread.
-// Responsible for calling the CaptureCallback and feed audio samples from
-// the server side audio layer using a socket and shared memory.
-//
-// Implementation notes:
-// - The user must call Stop() before deleting the class instance.
-
-#ifndef MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
-#define MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
-
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/shared_memory.h"
-#include "media/audio/audio_device_thread.h"
-#include "media/audio/audio_input_ipc.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/scoped_loop_observer.h"
-#include "media/base/audio_capturer_source.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// TODO(henrika): This class is based on the AudioOutputDevice class and it has
-// many components in common. Investigate potential for re-factoring.
-// TODO(henrika): Add support for event handling (e.g. OnStateChanged,
-// OnCaptureStopped etc.) and ensure that we can deliver these notifications
-// to any clients using this class.
-class MEDIA_EXPORT AudioInputDevice
- : NON_EXPORTED_BASE(public AudioCapturerSource),
- NON_EXPORTED_BASE(public AudioInputIPCDelegate),
- NON_EXPORTED_BASE(public ScopedLoopObserver) {
- public:
- AudioInputDevice(AudioInputIPC* ipc,
- const scoped_refptr<base::MessageLoopProxy>& io_loop);
-
- // AudioCapturerSource implementation.
- virtual void Initialize(const AudioParameters& params,
- CaptureCallback* callback,
- CaptureEventHandler* event_handler) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void SetDevice(int session_id) OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
-
- protected:
- // Methods called on IO thread ----------------------------------------------
- // AudioInputIPCDelegate implementation.
- virtual void OnStreamCreated(base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) OVERRIDE;
- virtual void OnVolume(double volume) OVERRIDE;
- virtual void OnStateChanged(
- AudioInputIPCDelegate::State state) OVERRIDE;
- virtual void OnDeviceReady(const std::string& device_id) OVERRIDE;
- virtual void OnIPCClosed() OVERRIDE;
-
- friend class base::RefCountedThreadSafe<AudioInputDevice>;
- virtual ~AudioInputDevice();
-
- private:
- // Methods called on IO thread ----------------------------------------------
- // The following methods are tasks posted on the IO thread that needs to
- // be executed on that thread. They interact with AudioInputMessageFilter and
- // sends IPC messages on that thread.
- void InitializeOnIOThread();
- void SetSessionIdOnIOThread(int session_id);
- void StartOnIOThread();
- void ShutDownOnIOThread();
- void SetVolumeOnIOThread(double volume);
- void SetAutomaticGainControlOnIOThread(bool enabled);
-
- // MessageLoop::DestructionObserver implementation for the IO loop.
- // If the IO loop dies before we do, we shut down the audio thread from here.
- virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
-
- AudioParameters audio_parameters_;
-
- CaptureCallback* callback_;
- CaptureEventHandler* event_handler_;
-
- AudioInputIPC* ipc_;
-
- // Our stream ID on the message filter. Only modified on the IO thread.
- int stream_id_;
-
- // The media session ID used to identify which input device to be started.
- // Only modified on the IO thread.
- int session_id_;
-
- // State variable used to indicate it is waiting for a OnDeviceReady()
- // callback. Only modified on the IO thread.
- bool pending_device_ready_;
-
- // Stores the Automatic Gain Control state. Default is false.
- // Only modified on the IO thread.
- bool agc_is_enabled_;
-
- // Our audio thread callback class. See source file for details.
- class AudioThreadCallback;
-
- // In order to avoid a race between OnStreamCreated and Stop(), we use this
- // guard to control stopping and starting the audio thread.
- base::Lock audio_thread_lock_;
- AudioDeviceThread audio_thread_;
- scoped_ptr<AudioInputDevice::AudioThreadCallback> audio_callback_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(AudioInputDevice);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
diff --git a/src/media/audio/audio_input_device_unittest.cc b/src/media/audio/audio_input_device_unittest.cc
deleted file mode 100644
index dc211a4..0000000
--- a/src/media/audio/audio_input_device_unittest.cc
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/environment.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_manager_base.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/wavein_input_win.h"
-#endif
-
-namespace media {
-
-// Test fixture which allows us to override the default enumeration API on
-// Windows.
-class AudioInputDeviceTest
- : public ::testing::Test {
- protected:
- AudioInputDeviceTest()
- : audio_manager_(AudioManager::Create())
-#if defined(OS_WIN)
- , com_init_(base::win::ScopedCOMInitializer::kMTA)
-#endif
- {
- }
-
-#if defined(OS_WIN)
- bool SetMMDeviceEnumeration() {
- AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get());
- // Windows Wave is used as default if Windows XP was detected =>
- // return false since MMDevice is not supported on XP.
- if (amw->enumeration_type() == AudioManagerWin::kWaveEnumeration)
- return false;
-
- amw->SetEnumerationType(AudioManagerWin::kMMDeviceEnumeration);
- return true;
- }
-
- void SetWaveEnumeration() {
- AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get());
- amw->SetEnumerationType(AudioManagerWin::kWaveEnumeration);
- }
-
- std::string GetDeviceIdFromPCMWaveInAudioInputStream(
- const std::string& device_id) {
- AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get());
- AudioParameters parameters(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- AudioParameters::kAudioCDSampleRate, 16,
- 1024);
- scoped_ptr<PCMWaveInAudioInputStream> stream(
- static_cast<PCMWaveInAudioInputStream*>(
- amw->CreatePCMWaveInAudioInputStream(parameters, device_id)));
- return stream.get() ? stream->device_id_ : std::string();
- }
-#endif
-
- // Helper method which verifies that the device list starts with a valid
- // default record followed by non-default device names.
- static void CheckDeviceNames(const AudioDeviceNames& device_names) {
- if (!device_names.empty()) {
- AudioDeviceNames::const_iterator it = device_names.begin();
-
- // The first device in the list should always be the default device.
- EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceName),
- it->device_name);
- EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceId), it->unique_id);
- ++it;
-
- // Other devices should have non-empty name and id and should not contain
- // default name or id.
- while (it != device_names.end()) {
- EXPECT_FALSE(it->device_name.empty());
- EXPECT_FALSE(it->unique_id.empty());
- EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceName),
- it->device_name);
- EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceId),
- it->unique_id);
- ++it;
- }
- } else {
- // Log a warning so we can see the status on the build bots. No need to
- // break the test though since this does successfully test the code and
- // some failure cases.
- LOG(WARNING) << "No input devices detected";
- }
- }
-
- bool CanRunAudioTest() {
- return audio_manager_->HasAudioInputDevices();
- }
-
- scoped_ptr<AudioManager> audio_manager_;
-
-#if defined(OS_WIN)
- // The MMDevice API requires COM to be initialized on the current thread.
- base::win::ScopedCOMInitializer com_init_;
-#endif
-};
-
-// Test that devices can be enumerated.
-TEST_F(AudioInputDeviceTest, EnumerateDevices) {
- if (!CanRunAudioTest())
- return;
-
- AudioDeviceNames device_names;
- audio_manager_->GetAudioInputDeviceNames(&device_names);
- CheckDeviceNames(device_names);
-}
-
-// Run additional tests for Windows since enumeration can be done using
-// two different APIs. MMDevice is default for Vista and higher and Wave
-// is default for XP and lower.
-#if defined(OS_WIN)
-
-// Override default enumeration API and force usage of Windows MMDevice.
-// This test will only run on Windows Vista and higher.
-TEST_F(AudioInputDeviceTest, EnumerateDevicesWinMMDevice) {
- if (!CanRunAudioTest())
- return;
-
- AudioDeviceNames device_names;
- if (!SetMMDeviceEnumeration()) {
- // Usage of MMDevice will fail on XP and lower.
- LOG(WARNING) << "MM device enumeration is not supported.";
- return;
- }
- audio_manager_->GetAudioInputDeviceNames(&device_names);
- CheckDeviceNames(device_names);
-}
-
-// Override default enumeration API and force usage of Windows Wave.
-// This test will run on Windows XP, Windows Vista and Windows 7.
-TEST_F(AudioInputDeviceTest, EnumerateDevicesWinWave) {
- if (!CanRunAudioTest())
- return;
-
- AudioDeviceNames device_names;
- SetWaveEnumeration();
- audio_manager_->GetAudioInputDeviceNames(&device_names);
- CheckDeviceNames(device_names);
-}
-
-TEST_F(AudioInputDeviceTest, WinXPDeviceIdUnchanged) {
- if (!CanRunAudioTest())
- return;
-
- AudioDeviceNames xp_device_names;
- SetWaveEnumeration();
- audio_manager_->GetAudioInputDeviceNames(&xp_device_names);
- CheckDeviceNames(xp_device_names);
-
- // Device ID should remain unchanged, including the default device ID.
- for (AudioDeviceNames::iterator i = xp_device_names.begin();
- i != xp_device_names.end(); ++i) {
- EXPECT_EQ(i->unique_id,
- GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id));
- }
-}
-
-TEST_F(AudioInputDeviceTest, ConvertToWinXPDeviceId) {
- if (!CanRunAudioTest())
- return;
-
- if (!SetMMDeviceEnumeration()) {
- // Usage of MMDevice will fail on XP and lower.
- LOG(WARNING) << "MM device enumeration is not supported.";
- return;
- }
-
- AudioDeviceNames device_names;
- audio_manager_->GetAudioInputDeviceNames(&device_names);
- CheckDeviceNames(device_names);
-
- for (AudioDeviceNames::iterator i = device_names.begin();
- i != device_names.end(); ++i) {
- std::string converted_id =
- GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id);
- if (i == device_names.begin()) {
- // The first in the list is the default device ID, which should not be
- // changed when passed to PCMWaveInAudioInputStream.
- EXPECT_EQ(i->unique_id, converted_id);
- } else {
- // MMDevice-style device IDs should be converted to WaveIn-style device
- // IDs.
- EXPECT_NE(i->unique_id, converted_id);
- }
- }
-}
-
-#endif
-
-} // namespace media
diff --git a/src/media/audio/audio_input_ipc.cc b/src/media/audio/audio_input_ipc.cc
deleted file mode 100644
index 69253b0..0000000
--- a/src/media/audio/audio_input_ipc.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_input_ipc.h"
-
-namespace media {
-
-AudioInputIPCDelegate::~AudioInputIPCDelegate() {}
-
-AudioInputIPC::~AudioInputIPC() {}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_ipc.h b/src/media/audio/audio_input_ipc.h
deleted file mode 100644
index eb4e72d..0000000
--- a/src/media/audio/audio_input_ipc.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
-#define MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
-
-#include "base/shared_memory.h"
-#include "base/sync_socket.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Contains IPC notifications for the state of the server side
-// (AudioInputController) audio state changes and when an AudioInputController
-// has been created. Implemented by AudioInputDevice.
-class MEDIA_EXPORT AudioInputIPCDelegate {
- public:
- // Valid states for the input stream.
- enum State {
- kRecording,
- kStopped,
- kError
- };
-
- // Called when an AudioInputController has been created.
- // The shared memory |handle| points to a memory section that's used to
- // transfer data between the AudioInputDevice and AudioInputController
- // objects. The implementation of OnStreamCreated takes ownership.
- // The |socket_handle| is used by the AudioInputController to signal
- // notifications that more data is available and can optionally provide
- // parameter changes back. The AudioInputDevice must read from this socket
- // and process the shared memory whenever data is read from the socket.
- virtual void OnStreamCreated(base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) = 0;
-
- // Called when state of an audio stream has changed.
- virtual void OnStateChanged(State state) = 0;
-
- // Called when the input stream volume has changed.
- virtual void OnVolume(double volume) = 0;
-
- // Called when a device has been started on the server side.
- // If the device could not be started, |device_id| will be empty.
- virtual void OnDeviceReady(const std::string& device_id) = 0;
-
- // Called when the AudioInputIPC object is going away and/or when the
- // IPC channel has been closed and no more IPC requests can be made.
- // Implementations must clear any references to the AudioInputIPC object
- // at this point.
- virtual void OnIPCClosed() = 0;
-
- protected:
- virtual ~AudioInputIPCDelegate();
-};
-
-// Provides IPC functionality for an AudioInputDevice. The implementation
-// should asynchronously deliver the messages to an AudioInputController object
-// (or create one in the case of CreateStream()), that may live in a separate
-// process.
-class MEDIA_EXPORT AudioInputIPC {
- public:
- // Registers an AudioInputIPCDelegate and returns a |stream_id| that
- // must be used with all other IPC functions in this interface.
- virtual int AddDelegate(AudioInputIPCDelegate* delegate) = 0;
-
- // Unregisters a delegate that was previously registered via a call to
- // AddDelegate(). The audio stream should be in a closed state prior to
- // calling this function.
- virtual void RemoveDelegate(int stream_id) = 0;
-
- // Sends a request to create an AudioInputController object in the peer
- // process, identify it by |stream_id| and configure it to use the specified
- // audio |params|. Once the stream has been created, the implementation must
- // generate a notification to the AudioInputIPCDelegate and call
- // OnStreamCreated().
- virtual void CreateStream(int stream_id, const AudioParameters& params,
- const std::string& device_id, bool automatic_gain_control) = 0;
-
- // Starts the device on the server side. Once the device has started,
- // or failed to start, a callback to
- // AudioInputIPCDelegate::OnDeviceReady() must be made.
- virtual void StartDevice(int stream_id, int session_id) = 0;
-
- // Corresponds to a call to AudioInputController::Record() on the server side.
- virtual void RecordStream(int stream_id) = 0;
-
- // Sets the volume of the audio stream.
- virtual void SetVolume(int stream_id, double volume) = 0;
-
- // Closes the audio stream and deletes the matching AudioInputController
- // instance. Prior to deleting the AudioInputController object, a call to
- // AudioInputController::Close must be made.
- virtual void CloseStream(int stream_id) = 0;
-
- protected:
- virtual ~AudioInputIPC();
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
diff --git a/src/media/audio/audio_input_stream_impl.cc b/src/media/audio/audio_input_stream_impl.cc
deleted file mode 100644
index f68317c..0000000
--- a/src/media/audio/audio_input_stream_impl.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "media/audio/audio_input_stream_impl.h"
-
-namespace media {
-
-static const int kMinIntervalBetweenVolumeUpdatesMs = 1000;
-
-AudioInputStreamImpl::AudioInputStreamImpl()
- : agc_is_enabled_(false),
- max_volume_(0.0),
- normalized_volume_(0.0) {
-}
-
-AudioInputStreamImpl::~AudioInputStreamImpl() {}
-
-void AudioInputStreamImpl::SetAutomaticGainControl(bool enabled) {
- agc_is_enabled_ = enabled;
-}
-
-bool AudioInputStreamImpl::GetAutomaticGainControl() {
- return agc_is_enabled_;
-}
-
-void AudioInputStreamImpl::UpdateAgcVolume() {
- base::AutoLock lock(lock_);
-
- // We take new volume samples once every second when the AGC is enabled.
- // To ensure that a new setting has an immediate effect, the new volume
- // setting is cached here. It will ensure that the next OnData() callback
- // will contain a new valid volume level. If this approach was not taken,
- // we could report invalid volume levels to the client for a time period
- // of up to one second.
- if (agc_is_enabled_) {
- GetNormalizedVolume();
- }
-}
-
-void AudioInputStreamImpl::QueryAgcVolume(double* normalized_volume) {
- base::AutoLock lock(lock_);
-
- // Only modify the |volume| output reference if AGC is enabled and if
- // more than one second has passed since the volume was updated the last time.
- if (agc_is_enabled_) {
- base::Time now = base::Time::Now();
- if ((now - last_volume_update_time_).InMilliseconds() >
- kMinIntervalBetweenVolumeUpdatesMs) {
- GetNormalizedVolume();
- last_volume_update_time_ = now;
- }
- *normalized_volume = normalized_volume_;
- }
-}
-
-void AudioInputStreamImpl::GetNormalizedVolume() {
- if (max_volume_ == 0.0) {
- // Cach the maximum volume if this is the first time we ask for it.
- max_volume_ = GetMaxVolume();
- }
-
- if (max_volume_ != 0.0) {
- // Retrieve the current volume level by asking the audio hardware.
- // Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux.
- normalized_volume_ = GetVolume() / max_volume_;
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_stream_impl.h b/src/media/audio/audio_input_stream_impl.h
deleted file mode 100644
index 64980a9..0000000
--- a/src/media/audio/audio_input_stream_impl.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_
-#define MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_
-
-#include "base/compiler_specific.h"
-#include "base/synchronization/lock.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-
-namespace media {
-
-// AudioInputStreamImpl implements platform-independent parts of the
-// AudioInputStream interface. Each platform dependent implementation
-// should derive from this class.
-// TODO(henrika): we can probably break out more parts from our current
-// AudioInputStream implementation and move out to this class.
-class MEDIA_EXPORT AudioInputStreamImpl : public AudioInputStream {
- public:
- AudioInputStreamImpl();
- virtual ~AudioInputStreamImpl();
-
- // Sets the automatic gain control (AGC) to on or off. When AGC is enabled,
- // the microphone volume is queried periodically and the volume level is
- // provided in each AudioInputCallback::OnData() callback and fed to the
- // render-side AGC.
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
-
- // Gets the current automatic gain control state.
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- protected:
- // Stores a new volume level by asking the audio hardware.
- // This method only has an effect if AGC is enabled.
- void UpdateAgcVolume();
-
- // Gets the latest stored volume level if AGC is enabled and if
- // more than one second has passed since the volume was updated the last time.
- void QueryAgcVolume(double* normalized_volume);
-
- private:
- // Takes a volume sample and stores it in |normalized_volume_|.
- void GetNormalizedVolume();
-
- // True when automatic gain control is enabled, false otherwise.
- // Guarded by |lock_|.
- bool agc_is_enabled_;
-
- // Stores the maximum volume which is used for normalization to a volume
- // range of [0.0, 1.0].
- double max_volume_;
-
- // Contains last result of internal call to GetVolume(). We save resources
- // but not querying the capture volume for each callback. Guarded by |lock_|.
- // The range is normalized to [0.0, 1.0].
- double normalized_volume_;
-
- // Protects |agc_is_enabled_| and |volume_| .
- base::Lock lock_;
-
- // Keeps track of the last time the microphone volume level was queried.
- base::Time last_volume_update_time_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioInputStreamImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_INPUT_STREAM_IMPL_H_
diff --git a/src/media/audio/audio_input_unittest.cc b/src/media/audio/audio_input_unittest.cc
deleted file mode 100644
index 5a02323..0000000
--- a/src/media/audio/audio_input_unittest.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/environment.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/threading/platform_thread.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kSamplingRate = 8000;
-static const int kSamplesPerPacket = kSamplingRate / 20;
-
-// This class allows to find out if the callbacks are occurring as
-// expected and if any error has been reported.
-class TestInputCallback : public AudioInputStream::AudioInputCallback {
- public:
- explicit TestInputCallback(int max_data_bytes)
- : callback_count_(0),
- had_error_(0),
- max_data_bytes_(max_data_bytes) {
- }
- virtual void OnData(AudioInputStream* stream, const uint8* data,
- uint32 size, uint32 hardware_delay_bytes, double volume) {
- ++callback_count_;
- // Read the first byte to make sure memory is good.
- if (size) {
- ASSERT_LE(static_cast<int>(size), max_data_bytes_);
- int value = data[0];
- EXPECT_GE(value, 0);
- }
- }
- virtual void OnClose(AudioInputStream* stream) {}
- virtual void OnError(AudioInputStream* stream, int code) {
- ++had_error_;
- }
- // Returns how many times OnData() has been called.
- int callback_count() const {
- return callback_count_;
- }
- // Returns how many times the OnError callback was called.
- int had_error() const {
- return had_error_;
- }
-
- private:
- int callback_count_;
- int had_error_;
- int max_data_bytes_;
-};
-
-static bool CanRunAudioTests(AudioManager* audio_man) {
- bool has_input = audio_man->HasAudioInputDevices();
-
- if (!has_input)
- LOG(WARNING) << "No input devices detected";
-
- return has_input;
-}
-
-static AudioInputStream* CreateTestAudioInputStream(AudioManager* audio_man) {
- AudioInputStream* ais = audio_man->MakeAudioInputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- kSamplingRate, 16, kSamplesPerPacket),
- AudioManagerBase::kDefaultDeviceId);
- EXPECT_TRUE(NULL != ais);
- return ais;
-}
-
-// Test that AudioInputStream rejects out of range parameters.
-TEST(AudioInputTest, SanityOnMakeParams) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!CanRunAudioTests(audio_man.get()))
- return;
-
- AudioParameters::Format fmt = AudioParameters::AUDIO_PCM_LINEAR;
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_7_1, 8000, 16,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 1024 * 1024, 16,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, 80,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, 80,
- 1000 * kSamplesPerPacket),
- AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_UNSUPPORTED, 8000, 16,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, -8000, 16,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, -16,
- kSamplesPerPacket), AudioManagerBase::kDefaultDeviceId));
- EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, 16, -1024),
- AudioManagerBase::kDefaultDeviceId));
-}
-
-// Test create and close of an AudioInputStream without recording audio.
-TEST(AudioInputTest, CreateAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!CanRunAudioTests(audio_man.get()))
- return;
- AudioInputStream* ais = CreateTestAudioInputStream(audio_man.get());
- ais->Close();
-}
-
-// Test create, open and close of an AudioInputStream without recording audio.
-TEST(AudioInputTest, OpenAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!CanRunAudioTests(audio_man.get()))
- return;
- AudioInputStream* ais = CreateTestAudioInputStream(audio_man.get());
- EXPECT_TRUE(ais->Open());
- ais->Close();
-}
-
-// Test create, open, stop and close of an AudioInputStream without recording.
-TEST(AudioInputTest, OpenStopAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!CanRunAudioTests(audio_man.get()))
- return;
- AudioInputStream* ais = CreateTestAudioInputStream(audio_man.get());
- EXPECT_TRUE(ais->Open());
- ais->Stop();
- ais->Close();
-}
-
-// Test a normal recording sequence using an AudioInputStream.
-TEST(AudioInputTest, Record) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!CanRunAudioTests(audio_man.get()))
- return;
- MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
- AudioInputStream* ais = CreateTestAudioInputStream(audio_man.get());
- EXPECT_TRUE(ais->Open());
-
- TestInputCallback test_callback(kSamplesPerPacket * 4);
- ais->Start(&test_callback);
- // Verify at least 500ms worth of audio was recorded, after giving sufficient
- // extra time.
- message_loop.PostDelayedTask(
- FROM_HERE,
- MessageLoop::QuitClosure(),
- base::TimeDelta::FromMilliseconds(690));
- message_loop.Run();
- EXPECT_GE(test_callback.callback_count(), 1);
- EXPECT_FALSE(test_callback.had_error());
-
- ais->Stop();
- ais->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_input_volume_unittest.cc b/src/media/audio/audio_input_volume_unittest.cc
deleted file mode 100644
index 8f754cc..0000000
--- a/src/media/audio/audio_input_volume_unittest.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <cmath>
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/audio_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/win/core_audio_util_win.h"
-#endif
-
-namespace media {
-
-class AudioInputVolumeTest : public ::testing::Test {
- protected:
- AudioInputVolumeTest()
- : audio_manager_(AudioManager::Create())
-#if defined(OS_WIN)
- , com_init_(base::win::ScopedCOMInitializer::kMTA)
-#endif
- {
- }
-
- bool CanRunAudioTests() {
-#if defined(OS_WIN)
- // TODO(henrika): add support for volume control on Windows XP as well.
- // For now, we might as well signal false already here to avoid running
- // these tests on Windows XP.
- if (!CoreAudioUtil::IsSupported())
- return false;
-#endif
- if (!audio_manager_.get())
- return false;
-
- return audio_manager_->HasAudioInputDevices();
- }
-
- // Helper method which checks if the stream has volume support.
- bool HasDeviceVolumeControl(AudioInputStream* stream) {
- if (!stream)
- return false;
-
- return (stream->GetMaxVolume() != 0.0);
- }
-
- AudioInputStream* CreateAndOpenStream(const std::string& device_id) {
- AudioParameters::Format format = AudioParameters::AUDIO_PCM_LOW_LATENCY;
- ChannelLayout channel_layout =
- media::GetAudioInputHardwareChannelLayout(device_id);
- int bits_per_sample = 16;
- int sample_rate =
- static_cast<int>(media::GetAudioInputHardwareSampleRate(device_id));
- int samples_per_packet = 0;
-#if defined(OS_MACOSX)
- samples_per_packet = (sample_rate / 100);
-#elif defined(OS_LINUX) || defined(OS_OPENBSD)
- samples_per_packet = (sample_rate / 100);
-#elif defined(OS_WIN)
- if (sample_rate == 44100)
- samples_per_packet = 448;
- else
- samples_per_packet = (sample_rate / 100);
-#else
-#error Unsupported platform
-#endif
- AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
- AudioParameters(format, channel_layout, sample_rate, bits_per_sample,
- samples_per_packet),
- device_id);
- EXPECT_TRUE(NULL != ais);
-
-#if defined(OS_LINUX) || defined(OS_OPENBSD)
- // Some linux devices do not support our settings, we may fail to open
- // those devices.
- if (!ais->Open()) {
- // Default device should always be able to be opened.
- EXPECT_TRUE(AudioManagerBase::kDefaultDeviceId != device_id);
- ais->Close();
- ais = NULL;
- }
-#elif defined(OS_WIN) || defined(OS_MACOSX)
- EXPECT_TRUE(ais->Open());
-#endif
-
- return ais;
- }
-
- scoped_ptr<AudioManager> audio_manager_;
-
-#if defined(OS_WIN)
- base::win::ScopedCOMInitializer com_init_;
-#endif
-};
-
-TEST_F(AudioInputVolumeTest, InputVolumeTest) {
- if (!CanRunAudioTests())
- return;
-
- // Retrieve a list of all available input devices.
- AudioDeviceNames device_names;
- audio_manager_->GetAudioInputDeviceNames(&device_names);
- if (device_names.empty()) {
- LOG(WARNING) << "Could not find any available input device";
- return;
- }
-
- // Scan all available input devices and repeat the same test for all of them.
- for (AudioDeviceNames::const_iterator it = device_names.begin();
- it != device_names.end();
- ++it) {
- AudioInputStream* ais = CreateAndOpenStream(it->unique_id);
- if (!ais) {
- DLOG(WARNING) << "Failed to open stream for device " << it->unique_id;
- continue;
- }
-
- if (!HasDeviceVolumeControl(ais)) {
- DLOG(WARNING) << "Device: " << it->unique_id
- << ", does not have volume control.";
- ais->Close();
- continue;
- }
-
- double max_volume = ais->GetMaxVolume();
- EXPECT_GT(max_volume, 0.0);
-
- // Store the current input-device volume level.
- double original_volume = ais->GetVolume();
- EXPECT_GE(original_volume, 0.0);
-#if defined(OS_WIN) || defined(OS_MACOSX)
- // Note that |original_volume| can be higher than |max_volume| on Linux.
- EXPECT_LE(original_volume, max_volume);
-#endif
-
- // Set the volume to the maxiumum level..
- ais->SetVolume(max_volume);
- double current_volume = ais->GetVolume();
- EXPECT_EQ(max_volume, current_volume);
-
- // Set the volume to the mininum level (=0).
- double new_volume = 0.0;
- ais->SetVolume(new_volume);
- current_volume = ais->GetVolume();
- EXPECT_EQ(new_volume, current_volume);
-
- // Set the volume to the mid level (50% of max).
- // Verify that the absolute error is small enough.
- new_volume = max_volume / 2;
- ais->SetVolume(new_volume);
- current_volume = ais->GetVolume();
- EXPECT_LT(current_volume, max_volume);
- EXPECT_GT(current_volume, 0);
- EXPECT_NEAR(current_volume, new_volume, 0.25 * max_volume);
-
- // Restores the volume to the original value.
- ais->SetVolume(original_volume);
- current_volume = ais->GetVolume();
- EXPECT_EQ(original_volume, current_volume);
-
- ais->Close();
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_io.h b/src/media/audio/audio_io.h
deleted file mode 100644
index e7b9a36..0000000
--- a/src/media/audio/audio_io.h
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_IO_H_
-#define MEDIA_AUDIO_AUDIO_IO_H_
-
-#include "base/basictypes.h"
-#include "media/audio/audio_buffers_state.h"
-#include "media/base/audio_bus.h"
-
-// Low-level audio output support. To make sound there are 3 objects involved:
-// - AudioSource : produces audio samples on a pull model. Implements
-// the AudioSourceCallback interface.
-// - AudioOutputStream : uses the AudioSource to render audio on a given
-// channel, format and sample frequency configuration. Data from the
-// AudioSource is delivered in a 'pull' model.
-// - AudioManager : factory for the AudioOutputStream objects, manager
-// of the hardware resources and mixer control.
-//
-// The number and configuration of AudioOutputStream does not need to match the
-// physically available hardware resources. For example you can have:
-//
-// MonoPCMSource1 --> MonoPCMStream1 --> | | --> audio left channel
-// StereoPCMSource -> StereoPCMStream -> | mixer |
-// MonoPCMSource2 --> MonoPCMStream2 --> | | --> audio right channel
-//
-// This facility's objective is mix and render audio with low overhead using
-// the OS basic audio support, abstracting as much as possible the
-// idiosyncrasies of each platform. Non-goals:
-// - Positional, 3d audio
-// - Dependence on non-default libraries such as DirectX 9, 10, XAudio
-// - Digital signal processing or effects
-// - Extra features if a specific hardware is installed (EAX, X-fi)
-//
-// The primary client of this facility is audio coming from several tabs.
-// Specifically for this case we avoid supporting complex formats such as MP3
-// or WMA. Complex format decoding should be done by the renderers.
-
-
-// Models an audio stream that gets rendered to the audio hardware output.
-// Because we support more audio streams than physically available channels
-// a given AudioOutputStream might or might not talk directly to hardware.
-// An audio stream allocates several buffers for audio data and calls
-// AudioSourceCallback::OnMoreData() periodically to fill these buffers,
-// as the data is written to the audio device. Size of each packet is determined
-// by |samples_per_packet| specified in AudioParameters when the stream is
-// created.
-
-namespace media {
-
-class MEDIA_EXPORT AudioOutputStream {
- public:
- // Audio sources must implement AudioSourceCallback. This interface will be
- // called in a random thread which very likely is a high priority thread. Do
- // not rely on using this thread TLS or make calls that alter the thread
- // itself such as creating Windows or initializing COM.
- class MEDIA_EXPORT AudioSourceCallback {
- public:
- // Provide more data by fully filling |dest|. The source will return
- // the number of frames it filled. |buffers_state| contains current state
- // of the buffers, and can be used by the source to calculate delay.
- virtual int OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) = 0;
-
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) = 0;
-
- // There was an error while playing a buffer. Audio source cannot be
- // destroyed yet. No direct action needed by the AudioStream, but it is
- // a good place to stop accumulating sound data since is is likely that
- // playback will not continue. |code| is an error code that is platform
- // specific.
- virtual void OnError(AudioOutputStream* stream, int code) = 0;
-
- // Deprecated. DO NOT USE. Waits until data becomes available. Used only
- // by Windows' WaveOut clients which may be extremely laggy. Will yield the
- // current thread until the renderer client has written its audio data or
- // 1.5 seconds have elapsed.
- virtual void WaitTillDataReady() {}
-
- protected:
- virtual ~AudioSourceCallback() {}
- };
-
- virtual ~AudioOutputStream() {}
-
- // Open the stream. false is returned if the stream cannot be opened. Open()
- // must always be followed by a call to Close() even if Open() fails.
- virtual bool Open() = 0;
-
- // Starts playing audio and generating AudioSourceCallback::OnMoreData().
- // Since implementor of AudioOutputStream may have internal buffers, right
- // after calling this method initial buffers are fetched.
- //
- // The output stream does not take ownership of this callback.
- virtual void Start(AudioSourceCallback* callback) = 0;
-
- // Stops playing audio. Effect might not be instantaneous as the hardware
- // might have locked audio data that is processing.
- virtual void Stop() = 0;
-
- // Sets the relative volume, with range [0.0, 1.0] inclusive.
- virtual void SetVolume(double volume) = 0;
-
- // Gets the relative volume, with range [0.0, 1.0] inclusive.
- virtual void GetVolume(double* volume) = 0;
-
- // Close the stream. This also generates AudioSourceCallback::OnClose().
- // After calling this method, the object should not be used anymore.
- virtual void Close() = 0;
-};
-
-// Models an audio sink receiving recorded audio from the audio driver.
-class MEDIA_EXPORT AudioInputStream {
- public:
- class MEDIA_EXPORT AudioInputCallback {
- public:
- // Called by the audio recorder when a full packet of audio data is
- // available. This is called from a special audio thread and the
- // implementation should return as soon as possible.
- virtual void OnData(AudioInputStream* stream, const uint8* src,
- uint32 size, uint32 hardware_delay_bytes,
- double volume) = 0;
-
- // The stream is done with this callback, the last call received by this
- // audio sink.
- virtual void OnClose(AudioInputStream* stream) = 0;
-
- // There was an error while recording audio. The audio sink cannot be
- // destroyed yet. No direct action needed by the AudioInputStream, but it
- // is a good place to stop accumulating sound data since is is likely that
- // recording will not continue. |code| is an error code that is platform
- // specific.
- virtual void OnError(AudioInputStream* stream, int code) = 0;
-
- protected:
- virtual ~AudioInputCallback() {}
- };
-
- virtual ~AudioInputStream() {}
-
- // Open the stream and prepares it for recording. Call Start() to actually
- // begin recording.
- virtual bool Open() = 0;
-
- // Starts recording audio and generating AudioInputCallback::OnData().
- // The input stream does not take ownership of this callback.
- virtual void Start(AudioInputCallback* callback) = 0;
-
- // Stops recording audio. Effect might not be instantaneous as there could be
- // pending audio callbacks in the queue which will be issued first before
- // recording stops.
- virtual void Stop() = 0;
-
- // Close the stream. This also generates AudioInputCallback::OnClose(). This
- // should be the last call made on this object.
- virtual void Close() = 0;
-
- // Returns the maximum microphone analog volume or 0.0 if device does not
- // have volume control.
- virtual double GetMaxVolume() = 0;
-
- // Sets the microphone analog volume, with range [0, max_volume] inclusive.
- virtual void SetVolume(double volume) = 0;
-
- // Returns the microphone analog volume, with range [0, max_volume] inclusive.
- virtual double GetVolume() = 0;
-
- // Sets the Automatic Gain Control (AGC) state.
- virtual void SetAutomaticGainControl(bool enabled) = 0;
-
- // Returns the Automatic Gain Control (AGC) state.
- virtual bool GetAutomaticGainControl() = 0;
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_IO_H_
diff --git a/src/media/audio/audio_low_latency_input_output_unittest.cc b/src/media/audio/audio_low_latency_input_output_unittest.cc
deleted file mode 100644
index 463321a..0000000
--- a/src/media/audio/audio_low_latency_input_output_unittest.cc
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/environment.h"
-#include "base/file_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/path_service.h"
-#include "base/synchronization/lock.h"
-#include "base/test/test_timeouts.h"
-#include "base/time.h"
-#include "build/build_config.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/audio_util.h"
-#include "media/base/seekable_buffer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(OS_LINUX) || defined(OS_OPENBSD)
-#include "media/audio/linux/audio_manager_linux.h"
-#elif defined(OS_MACOSX)
-#include "media/audio/mac/audio_manager_mac.h"
-#elif defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#elif defined(OS_ANDROID)
-#include "media/audio/android/audio_manager_android.h"
-#endif
-
-namespace media {
-
-#if defined(OS_LINUX) || defined(OS_OPENBSD)
-typedef AudioManagerLinux AudioManagerAnyPlatform;
-#elif defined(OS_MACOSX)
-typedef AudioManagerMac AudioManagerAnyPlatform;
-#elif defined(OS_WIN)
-typedef AudioManagerWin AudioManagerAnyPlatform;
-#elif defined(OS_ANDROID)
-typedef AudioManagerAndroid AudioManagerAnyPlatform;
-#endif
-
-// Limits the number of delay measurements we can store in an array and
-// then write to file at end of the WASAPIAudioInputOutputFullDuplex test.
-static const size_t kMaxDelayMeasurements = 1000;
-
-// Name of the output text file. The output file will be stored in the
-// directory containing media_unittests.exe.
-// Example: \src\build\Debug\audio_delay_values_ms.txt.
-// See comments for the WASAPIAudioInputOutputFullDuplex test for more details
-// about the file format.
-static const char* kDelayValuesFileName = "audio_delay_values_ms.txt";
-
-// Contains delay values which are reported during the full-duplex test.
-// Total delay = |buffer_delay_ms| + |input_delay_ms| + |output_delay_ms|.
-struct AudioDelayState {
- AudioDelayState()
- : delta_time_ms(0),
- buffer_delay_ms(0),
- input_delay_ms(0),
- output_delay_ms(0) {
- }
-
- // Time in milliseconds since last delay report. Typical value is ~10 [ms].
- int delta_time_ms;
-
- // Size of internal sync buffer. Typical value is ~0 [ms].
- int buffer_delay_ms;
-
- // Reported capture/input delay. Typical value is ~10 [ms].
- int input_delay_ms;
-
- // Reported render/output delay. Typical value is ~40 [ms].
- int output_delay_ms;
-};
-
-// This class mocks the platform specific audio manager and overrides
-// the GetMessageLoop() method to ensure that we can run our tests on
-// the main thread instead of the audio thread.
-class MockAudioManager : public AudioManagerAnyPlatform {
- public:
- MockAudioManager() {}
- virtual ~MockAudioManager() {}
-
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE {
- return MessageLoop::current()->message_loop_proxy();
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioManager);
-};
-
-// Test fixture class.
-class AudioLowLatencyInputOutputTest : public testing::Test {
- protected:
- AudioLowLatencyInputOutputTest() {}
-
- virtual ~AudioLowLatencyInputOutputTest() {}
-
- AudioManager* audio_manager() { return &mock_audio_manager_; }
- MessageLoopForUI* message_loop() { return &message_loop_; }
-
- // Convenience method which ensures that we are not running on the build
- // bots and that at least one valid input and output device can be found.
- bool CanRunAudioTests() {
- bool input = audio_manager()->HasAudioInputDevices();
- bool output = audio_manager()->HasAudioOutputDevices();
- LOG_IF(WARNING, !input) << "No input device detected.";
- LOG_IF(WARNING, !output) << "No output device detected.";
- return input && output;
- }
-
- private:
- MessageLoopForUI message_loop_;
- MockAudioManager mock_audio_manager_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest);
-};
-
-// This audio source/sink implementation should be used for manual tests
-// only since delay measurements are stored on an output text file.
-// All incoming/recorded audio packets are stored in an intermediate media
-// buffer which the renderer reads from when it needs audio for playout.
-// The total effect is that recorded audio is played out in loop back using
-// a sync buffer as temporary storage.
-class FullDuplexAudioSinkSource
- : public AudioInputStream::AudioInputCallback,
- public AudioOutputStream::AudioSourceCallback {
- public:
- FullDuplexAudioSinkSource(int sample_rate,
- int samples_per_packet,
- int channels)
- : sample_rate_(sample_rate),
- samples_per_packet_(samples_per_packet),
- channels_(channels),
- input_elements_to_write_(0),
- output_elements_to_write_(0),
- previous_write_time_(base::Time::Now()) {
- // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM).
- frame_size_ = (16 / 8) * channels_;
-
- // Start with the smallest possible buffer size. It will be increased
- // dynamically during the test if required.
- buffer_.reset(
- new media::SeekableBuffer(0, samples_per_packet_ * frame_size_));
-
- frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_);
- delay_states_.reset(new AudioDelayState[kMaxDelayMeasurements]);
- }
-
- virtual ~FullDuplexAudioSinkSource() {
- // Get complete file path to output file in the directory containing
- // media_unittests.exe. Example: src/build/Debug/audio_delay_values_ms.txt.
- FilePath file_name;
- EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name));
- file_name = file_name.AppendASCII(kDelayValuesFileName);
-
- FILE* text_file = file_util::OpenFile(file_name, "wt");
- DLOG_IF(ERROR, !text_file) << "Failed to open log file.";
- LOG(INFO) << ">> Output file " << file_name.value() << " has been created.";
-
- // Write the array which contains time-stamps, buffer size and
- // audio delays values to a text file.
- size_t elements_written = 0;
- while (elements_written <
- std::min(input_elements_to_write_, output_elements_to_write_)) {
- const AudioDelayState state = delay_states_[elements_written];
- fprintf(text_file, "%d %d %d %d\n",
- state.delta_time_ms,
- state.buffer_delay_ms,
- state.input_delay_ms,
- state.output_delay_ms);
- ++elements_written;
- }
-
- file_util::CloseFile(text_file);
- }
-
- // AudioInputStream::AudioInputCallback.
- virtual void OnData(AudioInputStream* stream,
- const uint8* src, uint32 size,
- uint32 hardware_delay_bytes,
- double volume) OVERRIDE {
- base::AutoLock lock(lock_);
-
- // Update three components in the AudioDelayState for this recorded
- // audio packet.
- base::Time now_time = base::Time::Now();
- int diff = (now_time - previous_write_time_).InMilliseconds();
- previous_write_time_ = now_time;
- if (input_elements_to_write_ < kMaxDelayMeasurements) {
- delay_states_[input_elements_to_write_].delta_time_ms = diff;
- delay_states_[input_elements_to_write_].buffer_delay_ms =
- BytesToMilliseconds(buffer_->forward_bytes());
- delay_states_[input_elements_to_write_].input_delay_ms =
- BytesToMilliseconds(hardware_delay_bytes);
- ++input_elements_to_write_;
- }
-
- // Store the captured audio packet in a seekable media buffer.
- if (!buffer_->Append(src, size)) {
- // An attempt to write outside the buffer limits has been made.
- // Double the buffer capacity to ensure that we have a buffer large
- // enough to handle the current sample test scenario.
- buffer_->set_forward_capacity(2 * buffer_->forward_capacity());
- buffer_->Clear();
- }
- }
-
- virtual void OnClose(AudioInputStream* stream) OVERRIDE {}
- virtual void OnError(AudioInputStream* stream, int code) OVERRIDE {}
-
- // AudioOutputStream::AudioSourceCallback.
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState buffers_state) OVERRIDE {
- base::AutoLock lock(lock_);
-
- // Update one component in the AudioDelayState for the packet
- // which is about to be played out.
- if (output_elements_to_write_ < kMaxDelayMeasurements) {
- int output_delay_bytes = buffers_state.hardware_delay_bytes;
-#if defined(OS_WIN)
- // Special fix for Windows in combination with Wave where the
- // pending bytes field of the audio buffer state is used to
- // report the delay.
- if (!CoreAudioUtil::IsSupported()) {
- output_delay_bytes = buffers_state.pending_bytes;
- }
-#endif
- delay_states_[output_elements_to_write_].output_delay_ms =
- BytesToMilliseconds(output_delay_bytes);
- ++output_elements_to_write_;
- }
-
- int size;
- const uint8* source;
- // Read the data from the seekable media buffer which contains
- // captured data at the same size and sample rate as the output side.
- if (buffer_->GetCurrentChunk(&source, &size) && size > 0) {
- EXPECT_EQ(channels_, audio_bus->channels());
- size = std::min(audio_bus->frames() * frame_size_, size);
- EXPECT_EQ(static_cast<size_t>(size) % sizeof(*audio_bus->channel(0)), 0U);
- audio_bus->FromInterleaved(
- source, size / frame_size_, frame_size_ / channels_);
- buffer_->Seek(size);
- return size / frame_size_;
- }
-
- return 0;
- }
-
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE {
- NOTREACHED();
- return 0;
- }
-
- virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE {}
- virtual void WaitTillDataReady() OVERRIDE {}
-
- protected:
- // Converts from bytes to milliseconds taking the sample rate and size
- // of an audio frame into account.
- int BytesToMilliseconds(uint32 delay_bytes) const {
- return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5);
- }
-
- private:
- base::Lock lock_;
- scoped_ptr<media::SeekableBuffer> buffer_;
- int sample_rate_;
- int samples_per_packet_;
- int channels_;
- int frame_size_;
- double frames_to_ms_;
- scoped_array<AudioDelayState> delay_states_;
- size_t input_elements_to_write_;
- size_t output_elements_to_write_;
- base::Time previous_write_time_;
-};
-
-class AudioInputStreamTraits {
- public:
- typedef AudioInputStream StreamType;
-
- static int HardwareSampleRate() {
- return static_cast<int>(media::GetAudioInputHardwareSampleRate(
- AudioManagerBase::kDefaultDeviceId));
- }
-
- // TODO(henrika): add support for GetAudioInputHardwareBufferSize in media.
- static int HardwareBufferSize() {
- return static_cast<int>(media::GetAudioHardwareBufferSize());
- }
-
- static StreamType* CreateStream(AudioManager* audio_manager,
- const AudioParameters& params) {
- return audio_manager->MakeAudioInputStream(params,
- AudioManagerBase::kDefaultDeviceId);
- }
-};
-
-class AudioOutputStreamTraits {
- public:
- typedef AudioOutputStream StreamType;
-
- static int HardwareSampleRate() {
- return static_cast<int>(media::GetAudioHardwareSampleRate());
- }
-
- static int HardwareBufferSize() {
- return static_cast<int>(media::GetAudioHardwareBufferSize());
- }
-
- static StreamType* CreateStream(AudioManager* audio_manager,
- const AudioParameters& params) {
- return audio_manager->MakeAudioOutputStream(params);
- }
-};
-
-// Traits template holding a trait of StreamType. It encapsulates
-// AudioInputStream and AudioOutputStream stream types.
-template <typename StreamTraits>
-class StreamWrapper {
- public:
- typedef typename StreamTraits::StreamType StreamType;
-
- explicit StreamWrapper(AudioManager* audio_manager)
- :
-#if defined(OS_WIN)
- com_init_(base::win::ScopedCOMInitializer::kMTA),
-#endif
- audio_manager_(audio_manager),
- format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
-#if defined(OS_ANDROID)
- channel_layout_(CHANNEL_LAYOUT_MONO),
-#else
- channel_layout_(CHANNEL_LAYOUT_STEREO),
-#endif
- bits_per_sample_(16) {
- // Use the preferred sample rate.
- sample_rate_ = StreamTraits::HardwareSampleRate();
-
- // Use the preferred buffer size. Note that the input side uses the same
- // size as the output side in this implementation.
- samples_per_packet_ = StreamTraits::HardwareBufferSize();
- }
-
- virtual ~StreamWrapper() {}
-
- // Creates an Audio[Input|Output]Stream stream object using default
- // parameters.
- StreamType* Create() {
- return CreateStream();
- }
-
- int channels() const {
- return ChannelLayoutToChannelCount(channel_layout_);
- }
- int bits_per_sample() const { return bits_per_sample_; }
- int sample_rate() const { return sample_rate_; }
- int samples_per_packet() const { return samples_per_packet_; }
-
- private:
- StreamType* CreateStream() {
- StreamType* stream = StreamTraits::CreateStream(audio_manager_,
- AudioParameters(format_, channel_layout_, sample_rate_,
- bits_per_sample_, samples_per_packet_));
- EXPECT_TRUE(stream);
- return stream;
- }
-
-#if defined(OS_WIN)
- base::win::ScopedCOMInitializer com_init_;
-#endif
-
- AudioManager* audio_manager_;
- AudioParameters::Format format_;
- ChannelLayout channel_layout_;
- int bits_per_sample_;
- int sample_rate_;
- int samples_per_packet_;
-};
-
-typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper;
-typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper;
-
-// This test is intended for manual tests and should only be enabled
-// when it is required to make a real-time test of audio in full duplex and
-// at the same time create a text file which contains measured delay values.
-// The file can later be analyzed off line using e.g. MATLAB.
-// MATLAB example:
-// D=load('audio_delay_values_ms.txt');
-// x=cumsum(D(:,1));
-// plot(x, D(:,2), x, D(:,3), x, D(:,4), x, D(:,2)+D(:,3)+D(:,4));
-// axis([0, max(x), 0, max(D(:,2)+D(:,3)+D(:,4))+10]);
-// legend('buffer delay','input delay','output delay','total delay');
-// xlabel('time [msec]')
-// ylabel('delay [msec]')
-// title('Full-duplex audio delay measurement');
-TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) {
- if (!CanRunAudioTests())
- return;
-
- AudioInputStreamWrapper aisw(audio_manager());
- AudioInputStream* ais = aisw.Create();
- EXPECT_TRUE(ais);
-
- AudioOutputStreamWrapper aosw(audio_manager());
- AudioOutputStream* aos = aosw.Create();
- EXPECT_TRUE(aos);
-
- // This test only supports identical parameters in both directions.
- // TODO(henrika): it is possible to cut delay here by using different
- // buffer sizes for input and output.
- if (aisw.sample_rate() != aosw.sample_rate() ||
- aisw.samples_per_packet() != aosw.samples_per_packet() ||
- aisw.channels()!= aosw.channels() ||
- aisw.bits_per_sample() != aosw.bits_per_sample()) {
- LOG(ERROR) << "This test requires symmetric input and output parameters. "
- "Ensure that sample rate and number of channels are identical in "
- "both directions";
- aos->Close();
- ais->Close();
- return;
- }
-
- EXPECT_TRUE(ais->Open());
- EXPECT_TRUE(aos->Open());
-
- FullDuplexAudioSinkSource full_duplex(
- aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels());
-
- LOG(INFO) << ">> You should now be able to hear yourself in loopback...";
- DLOG(INFO) << " sample_rate : " << aisw.sample_rate();
- DLOG(INFO) << " samples_per_packet: " << aisw.samples_per_packet();
- DLOG(INFO) << " channels : " << aisw.channels();
-
- ais->Start(&full_duplex);
- aos->Start(&full_duplex);
-
- // Wait for approximately 10 seconds. The user shall hear his own voice
- // in loop back during this time. At the same time, delay recordings are
- // performed and stored in the output text file.
- message_loop()->PostDelayedTask(FROM_HERE,
- MessageLoop::QuitClosure(), TestTimeouts::action_timeout());
- message_loop()->Run();
-
- aos->Stop();
- ais->Stop();
-
- // All Close() operations that run on the mocked audio thread,
- // should be synchronous and not post additional close tasks to
- // mocked the audio thread. Hence, there is no need to call
- // message_loop()->RunUntilIdle() after the Close() methods.
- aos->Close();
- ais->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_manager.cc b/src/media/audio/audio_manager.cc
deleted file mode 100644
index 9372d08..0000000
--- a/src/media/audio/audio_manager.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_manager.h"
-
-#include "base/at_exit.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/logging.h"
-#include "base/message_loop.h"
-
-namespace media {
-
-// Forward declaration of the platform specific AudioManager factory function.
-AudioManager* CreateAudioManager();
-
-AudioManager::AudioManager() {
-}
-
-AudioManager::~AudioManager() {
-}
-
-// static
-AudioManager* AudioManager::Create() {
- return CreateAudioManager();
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_manager.h b/src/media/audio/audio_manager.h
deleted file mode 100644
index ca4c468..0000000
--- a/src/media/audio/audio_manager.h
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_MANAGER_H_
-#define MEDIA_AUDIO_AUDIO_MANAGER_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/string16.h"
-#include "media/audio/audio_device_name.h"
-#include "media/audio/audio_parameters.h"
-
-class MessageLoop;
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class AudioInputStream;
-class AudioOutputStream;
-
-// Manages all audio resources. In particular it owns the AudioOutputStream
-// objects. Provides some convenience functions that avoid the need to provide
-// iterators over the existing streams.
-class MEDIA_EXPORT AudioManager {
- public:
- virtual ~AudioManager();
-
- // Use to construct the audio manager.
- // NOTE: There should only be one instance.
- static AudioManager* Create();
-
- // Returns true if the OS reports existence of audio devices. This does not
- // guarantee that the existing devices support all formats and sample rates.
- virtual bool HasAudioOutputDevices() = 0;
-
- // Returns true if the OS reports existence of audio recording devices. This
- // does not guarantee that the existing devices support all formats and
- // sample rates.
- virtual bool HasAudioInputDevices() = 0;
-
- // Returns a human readable string for the model/make of the active audio
- // input device for this computer.
- virtual string16 GetAudioInputDeviceModel() = 0;
-
- // Returns true if the platform specific audio input settings UI is known
- // and can be shown.
- virtual bool CanShowAudioInputSettings() = 0;
-
- // Opens the platform default audio input settings UI.
- // Note: This could invoke an external application/preferences pane, so
- // ideally must not be called from the UI thread or other time sensitive
- // threads to avoid blocking the rest of the application.
- virtual void ShowAudioInputSettings() = 0;
-
- // Appends a list of available input devices. It is not guaranteed that
- // all the devices in the list support all formats and sample rates for
- // recording.
- virtual void GetAudioInputDeviceNames(AudioDeviceNames* device_names) = 0;
-
- // Factory for all the supported stream formats. |params| defines parameters
- // of the audio stream to be created.
- //
- // |params.sample_per_packet| is the requested buffer allocation which the
- // audio source thinks it can usually fill without blocking. Internally two
- // or three buffers are created, one will be locked for playback and one will
- // be ready to be filled in the call to AudioSourceCallback::OnMoreData().
- //
- // Returns NULL if the combination of the parameters is not supported, or if
- // we have reached some other platform specific limit.
- //
- // |params.format| can be set to AUDIO_PCM_LOW_LATENCY and that has two
- // effects:
- // 1- Instead of triple buffered the audio will be double buffered.
- // 2- A low latency driver or alternative audio subsystem will be used when
- // available.
- //
- // Do not free the returned AudioOutputStream. It is owned by AudioManager.
- virtual AudioOutputStream* MakeAudioOutputStream(
- const AudioParameters& params) = 0;
-
- // Creates new audio output proxy. A proxy implements
- // AudioOutputStream interface, but unlike regular output stream
- // created with MakeAudioOutputStream() it opens device only when a
- // sound is actually playing.
- virtual AudioOutputStream* MakeAudioOutputStreamProxy(
- const AudioParameters& params) = 0;
-
- // Factory to create audio recording streams.
- // |channels| can be 1 or 2.
- // |sample_rate| is in hertz and can be any value supported by the platform.
- // |bits_per_sample| can be any value supported by the platform.
- // |samples_per_packet| is in hertz as well and can be 0 to |sample_rate|,
- // with 0 suggesting that the implementation use a default value for that
- // platform.
- // Returns NULL if the combination of the parameters is not supported, or if
- // we have reached some other platform specific limit.
- //
- // Do not free the returned AudioInputStream. It is owned by AudioManager.
- // When you are done with it, call |Stop()| and |Close()| to release it.
- virtual AudioInputStream* MakeAudioInputStream(
- const AudioParameters& params, const std::string& device_id) = 0;
-
- // Used to determine if something else is currently making use of audio input.
- virtual bool IsRecordingInProcess() = 0;
-
- // Returns message loop used for audio IO.
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() = 0;
-
- // Allows clients to listen for device state changes; e.g. preferred sample
- // rate or channel layout changes. The typical response to receiving this
- // callback is to recreate the stream.
- class AudioDeviceListener {
- public:
- virtual void OnDeviceChange() = 0;
- };
-
- virtual void AddOutputDeviceChangeListener(AudioDeviceListener* listener) = 0;
- virtual void RemoveOutputDeviceChangeListener(
- AudioDeviceListener* listener) = 0;
-
- protected:
- AudioManager();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioManager);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_MANAGER_H_
diff --git a/src/media/audio/audio_manager_base.cc b/src/media/audio/audio_manager_base.cc
deleted file mode 100644
index 6333039..0000000
--- a/src/media/audio/audio_manager_base.cc
+++ /dev/null
@@ -1,397 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_manager_base.h"
-
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/message_loop_proxy.h"
-#include "base/threading/thread.h"
-#include "media/audio/audio_output_dispatcher_impl.h"
-#include "media/audio/audio_output_proxy.h"
-#include "media/audio/audio_output_resampler.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/fake_audio_input_stream.h"
-#include "media/audio/fake_audio_output_stream.h"
-#include "media/audio/virtual_audio_input_stream.h"
-#include "media/audio/virtual_audio_output_stream.h"
-#include "media/base/media_switches.h"
-
-// TODO(dalecurtis): Temporarily disabled while switching pipeline to use float,
-// http://crbug.com/114700
-#if defined(ENABLE_AUDIO_MIXER)
-#include "media/audio/audio_output_mixer.h"
-#endif
-
-namespace media {
-
-static const int kStreamCloseDelaySeconds = 5;
-
-// Default maximum number of output streams that can be open simultaneously
-// for all platforms.
-static const int kDefaultMaxOutputStreams = 16;
-
-// Default maximum number of input streams that can be open simultaneously
-// for all platforms.
-static const int kDefaultMaxInputStreams = 16;
-
-static const int kMaxInputChannels = 2;
-
-const char AudioManagerBase::kDefaultDeviceName[] = "Default";
-const char AudioManagerBase::kDefaultDeviceId[] = "default";
-
-AudioManagerBase::AudioManagerBase()
- : num_active_input_streams_(0),
- max_num_output_streams_(kDefaultMaxOutputStreams),
- max_num_input_streams_(kDefaultMaxInputStreams),
- num_output_streams_(0),
- num_input_streams_(0),
- audio_thread_(new base::Thread("AudioThread")),
- virtual_audio_input_stream_(NULL) {
-#if defined(OS_WIN)
- audio_thread_->init_com_with_mta(true);
-#endif
- CHECK(audio_thread_->Start());
- message_loop_ = audio_thread_->message_loop_proxy();
-}
-
-AudioManagerBase::~AudioManagerBase() {
- // The platform specific AudioManager implementation must have already
- // stopped the audio thread. Otherwise, we may destroy audio streams before
- // stopping the thread, resulting an unexpected behavior.
- // This way we make sure activities of the audio streams are all stopped
- // before we destroy them.
- CHECK(!audio_thread_.get());
- // All the output streams should have been deleted.
- DCHECK_EQ(0, num_output_streams_);
- // All the input streams should have been deleted.
- DCHECK_EQ(0, num_input_streams_);
-}
-
-string16 AudioManagerBase::GetAudioInputDeviceModel() {
- return string16();
-}
-
-scoped_refptr<base::MessageLoopProxy> AudioManagerBase::GetMessageLoop() {
- return message_loop_;
-}
-
-AudioOutputStream* AudioManagerBase::MakeAudioOutputStream(
- const AudioParameters& params) {
- if (!params.IsValid()) {
- DLOG(ERROR) << "Audio parameters are invalid";
- return NULL;
- }
-
- // Limit the number of audio streams opened. This is to prevent using
- // excessive resources for a large number of audio streams. More
- // importantly it prevents instability on certain systems.
- // See bug: http://crbug.com/30242.
- if (num_output_streams_ >= max_num_output_streams_) {
- DLOG(ERROR) << "Number of opened output audio streams "
- << num_output_streams_
- << " exceed the max allowed number "
- << max_num_output_streams_;
- return NULL;
- }
-
- // If there are no audio output devices we should use a FakeAudioOutputStream
- // to ensure video playback continues to work.
- bool audio_output_disabled =
- params.format() == AudioParameters::AUDIO_FAKE ||
- !HasAudioOutputDevices();
-
- AudioOutputStream* stream = NULL;
- if (virtual_audio_input_stream_) {
-#if defined(OS_IOS)
- // We do not currently support iOS. It does not link.
- NOTIMPLEMENTED();
- return NULL;
-#else
- stream = VirtualAudioOutputStream::MakeStream(this, params, message_loop_,
- virtual_audio_input_stream_);
-#endif
- } else if (audio_output_disabled) {
- stream = FakeAudioOutputStream::MakeFakeStream(this, params);
- } else if (params.format() == AudioParameters::AUDIO_PCM_LINEAR) {
- stream = MakeLinearOutputStream(params);
- } else if (params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
- stream = MakeLowLatencyOutputStream(params);
- }
-
- if (stream)
- ++num_output_streams_;
-
- return stream;
-}
-
-AudioInputStream* AudioManagerBase::MakeAudioInputStream(
- const AudioParameters& params, const std::string& device_id) {
- if (!params.IsValid() || (params.channels() > kMaxInputChannels) ||
- device_id.empty()) {
- DLOG(ERROR) << "Audio parameters are invalid for device " << device_id;
- return NULL;
- }
-
- if (num_input_streams_ >= max_num_input_streams_) {
- DLOG(ERROR) << "Number of opened input audio streams "
- << num_input_streams_
- << " exceed the max allowed number " << max_num_input_streams_;
- return NULL;
- }
-
- AudioInputStream* stream = NULL;
- if (params.format() == AudioParameters::AUDIO_VIRTUAL) {
-#if defined(OS_IOS)
- // We do not currently support iOS.
- NOTIMPLEMENTED();
- return NULL;
-#else
- // TODO(justinlin): Currently, audio mirroring will only work for the first
- // request. Subsequent requests will not get audio.
- if (!virtual_audio_input_stream_) {
- virtual_audio_input_stream_ =
- VirtualAudioInputStream::MakeStream(this, params, message_loop_);
- stream = virtual_audio_input_stream_;
- DVLOG(1) << "Virtual audio input stream created.";
-
- // Make all current output streams recreate themselves as
- // VirtualAudioOutputStreams that will attach to the above
- // VirtualAudioInputStream.
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioManagerBase::NotifyAllOutputDeviceChangeListeners,
- base::Unretained(this)));
- } else {
- stream = NULL;
- }
-#endif
- } else if (params.format() == AudioParameters::AUDIO_FAKE) {
- stream = FakeAudioInputStream::MakeFakeStream(this, params);
- } else if (params.format() == AudioParameters::AUDIO_PCM_LINEAR) {
- stream = MakeLinearInputStream(params, device_id);
- } else if (params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
- stream = MakeLowLatencyInputStream(params, device_id);
- }
-
- if (stream)
- ++num_input_streams_;
-
- return stream;
-}
-
-AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
- const AudioParameters& params) {
-#if defined(OS_IOS)
- // IOS implements audio input only.
- NOTIMPLEMENTED();
- return NULL;
-#else
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- bool use_audio_output_resampler =
- !CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDisableAudioOutputResampler) &&
- params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY;
-
- // If we're not using AudioOutputResampler our output parameters are the same
- // as our input parameters.
- AudioParameters output_params = params;
- if (use_audio_output_resampler) {
- output_params = GetPreferredLowLatencyOutputStreamParameters(params);
-
- // Ensure we only pass on valid output parameters.
- if (!output_params.IsValid()) {
- // We've received invalid audio output parameters, so switch to a mock
- // output device based on the input parameters. This may happen if the OS
- // provided us junk values for the hardware configuration.
- LOG(ERROR) << "Invalid audio output parameters received; using fake "
- << "audio path. Channels: " << output_params.channels() << ", "
- << "Sample Rate: " << output_params.sample_rate() << ", "
- << "Bits Per Sample: " << output_params.bits_per_sample()
- << ", Frames Per Buffer: "
- << output_params.frames_per_buffer();
-
- // Tell the AudioManager to create a fake output device.
- output_params = AudioParameters(
- AudioParameters::AUDIO_FAKE, params.channel_layout(),
- params.sample_rate(), params.bits_per_sample(),
- params.frames_per_buffer());
- }
- }
-
- std::pair<AudioParameters, AudioParameters> dispatcher_key =
- std::make_pair(params, output_params);
- AudioOutputDispatchersMap::iterator it =
- output_dispatchers_.find(dispatcher_key);
- if (it != output_dispatchers_.end())
- return new AudioOutputProxy(it->second);
-
- base::TimeDelta close_delay =
- base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
-
- if (use_audio_output_resampler &&
- output_params.format() != AudioParameters::AUDIO_FAKE) {
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputResampler(this, params, output_params, close_delay);
- output_dispatchers_[dispatcher_key] = dispatcher;
- return new AudioOutputProxy(dispatcher);
- }
-
-#if defined(ENABLE_AUDIO_MIXER)
- // TODO(dalecurtis): Browser side mixing has a couple issues that must be
- // fixed before it can be turned on by default: http://crbug.com/138098 and
- // http://crbug.com/140247
- if (cmd_line->HasSwitch(switches::kEnableAudioMixer)) {
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputMixer(this, params, close_delay);
- output_dispatchers_[dispatcher_key] = dispatcher;
- return new AudioOutputProxy(dispatcher);
- }
-#endif
-
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputDispatcherImpl(this, output_params, close_delay);
- output_dispatchers_[dispatcher_key] = dispatcher;
- return new AudioOutputProxy(dispatcher);
-#endif // defined(OS_IOS)
-}
-
-bool AudioManagerBase::CanShowAudioInputSettings() {
- return false;
-}
-
-void AudioManagerBase::ShowAudioInputSettings() {
-}
-
-void AudioManagerBase::GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) {
-}
-
-void AudioManagerBase::ReleaseOutputStream(AudioOutputStream* stream) {
- DCHECK(stream);
- // TODO(xians) : Have a clearer destruction path for the AudioOutputStream.
- // For example, pass the ownership to AudioManager so it can delete the
- // streams.
- num_output_streams_--;
- delete stream;
-}
-
-void AudioManagerBase::ReleaseInputStream(AudioInputStream* stream) {
- DCHECK(stream);
- // TODO(xians) : Have a clearer destruction path for the AudioInputStream.
-
- if (virtual_audio_input_stream_ == stream) {
- DVLOG(1) << "Virtual audio input stream stopping.";
- virtual_audio_input_stream_->Stop();
- virtual_audio_input_stream_ = NULL;
-
- // Make all VirtualAudioOutputStreams unregister from the
- // VirtualAudioInputStream and recreate themselves as regular audio streams
- // to return sound to hardware.
- NotifyAllOutputDeviceChangeListeners();
- }
-
- num_input_streams_--;
- delete stream;
-}
-
-void AudioManagerBase::IncreaseActiveInputStreamCount() {
- base::AtomicRefCountInc(&num_active_input_streams_);
-}
-
-void AudioManagerBase::DecreaseActiveInputStreamCount() {
- DCHECK(IsRecordingInProcess());
- base::AtomicRefCountDec(&num_active_input_streams_);
-}
-
-bool AudioManagerBase::IsRecordingInProcess() {
- return !base::AtomicRefCountIsZero(&num_active_input_streams_);
-}
-
-void AudioManagerBase::Shutdown() {
- // To avoid running into deadlocks while we stop the thread, shut it down
- // via a local variable while not holding the audio thread lock.
- scoped_ptr<base::Thread> audio_thread;
- {
- base::AutoLock lock(audio_thread_lock_);
- audio_thread_.swap(audio_thread);
- }
-
- if (!audio_thread.get())
- return;
-
- CHECK_NE(MessageLoop::current(), audio_thread->message_loop());
-
- // We must use base::Unretained since Shutdown might have been called from
- // the destructor and we can't alter the refcount of the object at that point.
- audio_thread->message_loop()->PostTask(FROM_HERE, base::Bind(
- &AudioManagerBase::ShutdownOnAudioThread,
- base::Unretained(this)));
-
- // Stop() will wait for any posted messages to be processed first.
- audio_thread->Stop();
-}
-
-void AudioManagerBase::ShutdownOnAudioThread() {
-// IOS implements audio input only.
-#if defined(OS_IOS)
- return;
-#else
- // This should always be running on the audio thread, but since we've cleared
- // the audio_thread_ member pointer when we get here, we can't verify exactly
- // what thread we're running on. The method is not public though and only
- // called from one place, so we'll leave it at that.
- AudioOutputDispatchersMap::iterator it = output_dispatchers_.begin();
- for (; it != output_dispatchers_.end(); ++it) {
- scoped_refptr<AudioOutputDispatcher>& dispatcher = (*it).second;
- if (dispatcher) {
- dispatcher->Shutdown();
- // All AudioOutputProxies must have been freed before Shutdown is called.
- // If they still exist, things will go bad. They have direct pointers to
- // both physical audio stream objects that belong to the dispatcher as
- // well as the message loop of the audio thread that will soon go away.
- // So, better crash now than later.
- DCHECK(dispatcher->HasOneRef()) << "AudioOutputProxies are still alive";
- dispatcher = NULL;
- }
- }
-
- output_dispatchers_.clear();
-#endif // defined(OS_IOS)
-}
-
-AudioParameters AudioManagerBase::GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) {
-#if defined(OS_IOS)
- // IOS implements audio input only.
- NOTIMPLEMENTED();
- return AudioParameters();
-#else
- // TODO(dalecurtis): This should include bits per channel and channel layout
- // eventually.
- return AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, input_params.channel_layout(),
- GetAudioHardwareSampleRate(), 16, GetAudioHardwareBufferSize());
-#endif // defined(OS_IOS)
-}
-
-void AudioManagerBase::AddOutputDeviceChangeListener(
- AudioDeviceListener* listener) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- output_listeners_.AddObserver(listener);
-}
-
-void AudioManagerBase::RemoveOutputDeviceChangeListener(
- AudioDeviceListener* listener) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- output_listeners_.RemoveObserver(listener);
-}
-
-void AudioManagerBase::NotifyAllOutputDeviceChangeListeners() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DVLOG(1) << "Firing OnDeviceChange() notifications.";
- FOR_EACH_OBSERVER(AudioDeviceListener, output_listeners_, OnDeviceChange());
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_manager_base.h b/src/media/audio/audio_manager_base.h
deleted file mode 100644
index 83ad98b..0000000
--- a/src/media/audio/audio_manager_base.h
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
-#define MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
-
-#include <map>
-#include <string>
-#include <utility>
-
-#include "base/atomic_ref_count.h"
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/observer_list.h"
-#include "base/synchronization/lock.h"
-#include "media/audio/audio_manager.h"
-
-#if defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#endif
-
-namespace base {
-class Thread;
-}
-
-namespace media {
-
-class AudioOutputDispatcher;
-class VirtualAudioInputStream;
-
-// AudioManagerBase provides AudioManager functions common for all platforms.
-class MEDIA_EXPORT AudioManagerBase : public AudioManager {
- public:
- // Name of the generic "default" device.
- static const char kDefaultDeviceName[];
- // Unique Id of the generic "default" device.
- static const char kDefaultDeviceId[];
-
- virtual ~AudioManagerBase();
-
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE;
-
- virtual string16 GetAudioInputDeviceModel() OVERRIDE;
-
- virtual bool CanShowAudioInputSettings() OVERRIDE;
- virtual void ShowAudioInputSettings() OVERRIDE;
-
- virtual void GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) OVERRIDE;
-
- virtual AudioOutputStream* MakeAudioOutputStream(
- const AudioParameters& params) OVERRIDE;
-
- virtual AudioInputStream* MakeAudioInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
-
- virtual AudioOutputStream* MakeAudioOutputStreamProxy(
- const AudioParameters& params) OVERRIDE;
-
- virtual bool IsRecordingInProcess() OVERRIDE;
-
- // Called internally by the audio stream when it has been closed.
- virtual void ReleaseOutputStream(AudioOutputStream* stream);
- virtual void ReleaseInputStream(AudioInputStream* stream);
-
- void IncreaseActiveInputStreamCount();
- void DecreaseActiveInputStreamCount();
-
- // Creates the output stream for the |AUDIO_PCM_LINEAR| format. The legacy
- // name is also from |AUDIO_PCM_LINEAR|.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) = 0;
-
- // Creates the output stream for the |AUDIO_PCM_LOW_LATENCY| format.
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) = 0;
-
- // Creates the input stream for the |AUDIO_PCM_LINEAR| format. The legacy
- // name is also from |AUDIO_PCM_LINEAR|.
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) = 0;
-
- // Creates the input stream for the |AUDIO_PCM_LOW_LATENCY| format.
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) = 0;
-
- // Returns the preferred hardware audio output parameters for opening output
- // streams in the |AUDIO_PCM_LOW_LATENCY| format.
- // TODO(dalecurtis): Retrieve the |channel_layout| value from hardware instead
- // of accepting the value.
- // TODO(dalecurtis): Each AudioManager should implement their own version, see
- // http://crbug.com/137326
- virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params);
-
- // Listeners will be notified on the AudioManager::GetMessageLoop() loop.
- virtual void AddOutputDeviceChangeListener(
- AudioDeviceListener* listener) OVERRIDE;
- virtual void RemoveOutputDeviceChangeListener(
- AudioDeviceListener* listener) OVERRIDE;
-
- protected:
- AudioManagerBase();
-
- // TODO(dalecurtis): This must change to map both input and output parameters
- // to a single dispatcher, otherwise on a device state change we'll just get
- // the exact same invalid dispatcher.
- typedef std::map<std::pair<AudioParameters, AudioParameters>,
- scoped_refptr<AudioOutputDispatcher> >
- AudioOutputDispatchersMap;
-
- // Shuts down the audio thread and releases all the audio output dispatchers
- // on the audio thread. All audio streams should be freed before Shutdown()
- // is called. This must be called in the destructor of every AudioManagerBase
- // implementation.
- void Shutdown();
-
- void SetMaxOutputStreamsAllowed(int max) { max_num_output_streams_ = max; }
-
- // Called by each platform specific AudioManager to notify output state change
- // listeners that a state change has occurred. Must be called from the audio
- // thread.
- void NotifyAllOutputDeviceChangeListeners();
-
- // Map of cached AudioOutputDispatcher instances. Must only be touched
- // from the audio thread (no locking).
- AudioOutputDispatchersMap output_dispatchers_;
-
- private:
- // Called by Shutdown().
- void ShutdownOnAudioThread();
-
- // Counts the number of active input streams to find out if something else
- // is currently recording in Chrome.
- base::AtomicRefCount num_active_input_streams_;
-
- // Max number of open output streams, modified by
- // SetMaxOutputStreamsAllowed().
- int max_num_output_streams_;
-
- // Max number of open input streams.
- int max_num_input_streams_;
-
- // Number of currently open output streams.
- int num_output_streams_;
-
- // Number of currently open input streams.
- int num_input_streams_;
-
- // Track output state change listeners.
- ObserverList<AudioDeviceListener> output_listeners_;
-
- // Thread used to interact with audio streams created by this audio manager.
- scoped_ptr<base::Thread> audio_thread_;
- mutable base::Lock audio_thread_lock_;
-
- // The message loop of the audio thread this object runs on. Used for internal
- // tasks which run on the audio thread even after Shutdown() has been started
- // and GetMessageLoop() starts returning NULL.
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // Currently active VirtualAudioInputStream. When this is set, we will
- // create all audio output streams as virtual streams so as to redirect audio
- // data to this virtual input stream.
- VirtualAudioInputStream* virtual_audio_input_stream_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerBase);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
diff --git a/src/media/audio/audio_output_controller.cc b/src/media/audio/audio_output_controller.cc
deleted file mode 100644
index 50850c9..0000000
--- a/src/media/audio/audio_output_controller.cc
+++ /dev/null
@@ -1,400 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_controller.h"
-
-#include "base/bind.h"
-#include "base/debug/trace_event.h"
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/time.h"
-#include "build/build_config.h"
-#include "media/audio/shared_memory_util.h"
-
-using base::Time;
-using base::TimeDelta;
-using base::WaitableEvent;
-
-namespace media {
-
-// Polling-related constants.
-const int AudioOutputController::kPollNumAttempts = 3;
-const int AudioOutputController::kPollPauseInMilliseconds = 3;
-
-AudioOutputController::AudioOutputController(AudioManager* audio_manager,
- EventHandler* handler,
- const AudioParameters& params,
- SyncReader* sync_reader)
- : audio_manager_(audio_manager),
- handler_(handler),
- stream_(NULL),
- volume_(1.0),
- state_(kEmpty),
- sync_reader_(sync_reader),
- message_loop_(audio_manager->GetMessageLoop()),
- number_polling_attempts_left_(0),
- params_(params),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)) {
-}
-
-AudioOutputController::~AudioOutputController() {
- DCHECK_EQ(kClosed, state_);
-
- if (message_loop_->BelongsToCurrentThread()) {
- DoStopCloseAndClearStream(NULL);
- } else {
- // http://crbug.com/120973
- base::ThreadRestrictions::ScopedAllowWait allow_wait;
- WaitableEvent completion(true /* manual reset */,
- false /* initial state */);
- message_loop_->PostTask(FROM_HERE,
- base::Bind(&AudioOutputController::DoStopCloseAndClearStream,
- base::Unretained(this),
- &completion));
- completion.Wait();
- }
-}
-
-// static
-scoped_refptr<AudioOutputController> AudioOutputController::Create(
- AudioManager* audio_manager,
- EventHandler* event_handler,
- const AudioParameters& params,
- SyncReader* sync_reader) {
- DCHECK(audio_manager);
- DCHECK(sync_reader);
-
- if (!params.IsValid() || !audio_manager)
- return NULL;
-
- // Starts the audio controller thread.
- scoped_refptr<AudioOutputController> controller(new AudioOutputController(
- audio_manager, event_handler, params, sync_reader));
-
- controller->message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoCreate, controller));
-
- return controller;
-}
-
-void AudioOutputController::Play() {
- DCHECK(message_loop_);
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoPlay, this));
-}
-
-void AudioOutputController::Pause() {
- DCHECK(message_loop_);
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoPause, this));
-}
-
-void AudioOutputController::Flush() {
- DCHECK(message_loop_);
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoFlush, this));
-}
-
-void AudioOutputController::Close(const base::Closure& closed_task) {
- DCHECK(!closed_task.is_null());
- DCHECK(message_loop_);
- message_loop_->PostTaskAndReply(FROM_HERE, base::Bind(
- &AudioOutputController::DoClose, this), closed_task);
-}
-
-void AudioOutputController::SetVolume(double volume) {
- DCHECK(message_loop_);
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoSetVolume, this, volume));
-}
-
-void AudioOutputController::DoCreate() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Close() can be called before DoCreate() is executed.
- if (state_ == kClosed)
- return;
- DCHECK(state_ == kEmpty || state_ == kRecreating) << state_;
-
- DoStopCloseAndClearStream(NULL);
- stream_ = audio_manager_->MakeAudioOutputStreamProxy(params_);
- if (!stream_) {
- state_ = kError;
-
- // TODO(hclam): Define error types.
- handler_->OnError(this, 0);
- return;
- }
-
- if (!stream_->Open()) {
- state_ = kError;
- DoStopCloseAndClearStream(NULL);
-
- // TODO(hclam): Define error types.
- handler_->OnError(this, 0);
- return;
- }
-
- // Everything started okay, so register for state change callbacks if we have
- // not already done so.
- if (state_ != kRecreating)
- audio_manager_->AddOutputDeviceChangeListener(this);
-
- // We have successfully opened the stream. Set the initial volume.
- stream_->SetVolume(volume_);
-
- // Finally set the state to kCreated.
- State original_state = state_;
- state_ = kCreated;
-
- // And then report we have been created if we haven't done so already.
- if (original_state != kRecreating)
- handler_->OnCreated(this);
-}
-
-void AudioOutputController::DoPlay() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // We can start from created or paused state.
- if (state_ != kCreated && state_ != kPaused) {
- // If a pause is pending drop it. Otherwise the controller might hang since
- // the corresponding play event has already occurred.
- if (state_ == kPausedWhenStarting)
- state_ = kStarting;
- return;
- }
-
- state_ = kStarting;
-
- // Ask for first packet.
- sync_reader_->UpdatePendingBytes(0);
-
- // Cannot start stream immediately, should give renderer some time
- // to deliver data.
- // TODO(vrk): The polling here and in WaitTillDataReady() is pretty clunky.
- // Refine the API such that polling is no longer needed. (crbug.com/112196)
- number_polling_attempts_left_ = kPollNumAttempts;
- message_loop_->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AudioOutputController::PollAndStartIfDataReady,
- weak_this_.GetWeakPtr()),
- TimeDelta::FromMilliseconds(kPollPauseInMilliseconds));
-}
-
-void AudioOutputController::PollAndStartIfDataReady() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Being paranoid: do nothing if state unexpectedly changed.
- if ((state_ != kStarting) && (state_ != kPausedWhenStarting))
- return;
-
- bool pausing = (state_ == kPausedWhenStarting);
- // If we are ready to start the stream, start it.
- // Of course we may have to stop it immediately...
- if (--number_polling_attempts_left_ == 0 ||
- pausing ||
- sync_reader_->DataReady()) {
- StartStream();
- if (pausing) {
- DoPause();
- }
- } else {
- message_loop_->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AudioOutputController::PollAndStartIfDataReady,
- weak_this_.GetWeakPtr()),
- TimeDelta::FromMilliseconds(kPollPauseInMilliseconds));
- }
-}
-
-void AudioOutputController::StartStream() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- state_ = kPlaying;
-
- // We start the AudioOutputStream lazily.
- stream_->Start(this);
-
- // Tell the event handler that we are now playing.
- handler_->OnPlaying(this);
-}
-
-void AudioOutputController::DoPause() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (stream_) {
- // Then we stop the audio device. This is not the perfect solution
- // because it discards all the internal buffer in the audio device.
- // TODO(hclam): Actually pause the audio device.
- stream_->Stop();
- }
-
- switch (state_) {
- case kStarting:
- // We were asked to pause while starting. There is delayed task that will
- // try starting playback, and there is no way to remove that task from the
- // queue. If we stop now that task will be executed anyway.
- // Delay pausing, let delayed task to do pause after it start playback.
- state_ = kPausedWhenStarting;
- break;
- case kPlaying:
- state_ = kPaused;
-
- // Send a special pause mark to the low-latency audio thread.
- sync_reader_->UpdatePendingBytes(kPauseMark);
-
- handler_->OnPaused(this);
- break;
- default:
- return;
- }
-}
-
-void AudioOutputController::DoFlush() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // TODO(hclam): Actually flush the audio device.
-}
-
-void AudioOutputController::DoClose() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ != kClosed) {
- DoStopCloseAndClearStream(NULL);
- sync_reader_->Close();
- state_ = kClosed;
- }
-}
-
-void AudioOutputController::DoSetVolume(double volume) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Saves the volume to a member first. We may not be able to set the volume
- // right away but when the stream is created we'll set the volume.
- volume_ = volume;
-
- switch (state_) {
- case kCreated:
- case kStarting:
- case kPausedWhenStarting:
- case kPlaying:
- case kPaused:
- stream_->SetVolume(volume_);
- break;
- default:
- return;
- }
-}
-
-void AudioOutputController::DoReportError(int code) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (state_ != kClosed)
- handler_->OnError(this, code);
-}
-
-int AudioOutputController::OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) {
- return OnMoreIOData(NULL, dest, buffers_state);
-}
-
-int AudioOutputController::OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) {
- TRACE_EVENT0("audio", "AudioOutputController::OnMoreIOData");
-
- {
- // Check state and do nothing if we are not playing.
- // We are on the hardware audio thread, so lock is needed.
- base::AutoLock auto_lock(lock_);
- if (state_ != kPlaying) {
- return 0;
- }
- }
-
- int frames = sync_reader_->Read(source, dest);
- sync_reader_->UpdatePendingBytes(
- buffers_state.total_bytes() + frames * params_.GetBytesPerFrame());
- return frames;
-}
-
-void AudioOutputController::WaitTillDataReady() {
-#if defined(OS_WIN) || defined(OS_MACOSX)
- base::Time start = base::Time::Now();
- // Wait for up to 1.5 seconds for DataReady(). 1.5 seconds was chosen because
- // it's larger than the playback time of the WaveOut buffer size using the
- // minimum supported sample rate: 4096 / 3000 = ~1.4 seconds. Even a client
- // expecting real time playout should be able to fill in this time.
- const base::TimeDelta max_wait = base::TimeDelta::FromMilliseconds(1500);
- while (!sync_reader_->DataReady() &&
- ((base::Time::Now() - start) < max_wait)) {
- base::PlatformThread::YieldCurrentThread();
- }
-#else
- // WaitTillDataReady() is deprecated and should not be used.
- CHECK(false);
-#endif
-}
-
-void AudioOutputController::OnError(AudioOutputStream* stream, int code) {
- // Handle error on the audio controller thread.
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputController::DoReportError, this, code));
-}
-
-void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Allow calling unconditionally and bail if we don't have a stream_ to close.
- if (stream_) {
- stream_->Stop();
- stream_->Close();
- stream_ = NULL;
-
- audio_manager_->RemoveOutputDeviceChangeListener(this);
- audio_manager_ = NULL;
-
- weak_this_.InvalidateWeakPtrs();
- }
-
- // Should be last in the method, do not touch "this" from here on.
- if (done)
- done->Signal();
-}
-
-void AudioOutputController::OnDeviceChange() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // We should always have a stream by this point.
- CHECK(stream_);
-
- // Preserve the original state and shutdown the stream.
- State original_state = state_;
- stream_->Stop();
- stream_->Close();
- stream_ = NULL;
-
- // Recreate the stream, exit if we ran into an error.
- state_ = kRecreating;
- DoCreate();
- if (!stream_ || state_ == kError)
- return;
-
- // Get us back to the original state or an equivalent state.
- switch (original_state) {
- case kStarting:
- case kPlaying:
- DoPlay();
- return;
- case kCreated:
- case kPausedWhenStarting:
- case kPaused:
- // From the outside these three states are equivalent.
- return;
- default:
- NOTREACHED() << "Invalid original state.";
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_controller.h b/src/media/audio/audio_output_controller.h
deleted file mode 100644
index 762a948..0000000
--- a/src/media/audio/audio_output_controller.h
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_CONTROLLER_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_CONTROLLER_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/synchronization/lock.h"
-#include "media/audio/audio_buffers_state.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "media/base/media_export.h"
-
-namespace base {
-class WaitableEvent;
-} // namespace base
-
-class MessageLoop;
-
-// An AudioOutputController controls an AudioOutputStream and provides data
-// to this output stream. It has an important function that it executes
-// audio operations like play, pause, stop, etc. on a separate thread,
-// namely the audio manager thread.
-//
-// All the public methods of AudioOutputController are non-blocking.
-// The actual operations are performed on the audio manager thread.
-//
-// Here is a state diagram for the AudioOutputController:
-//
-// .-----------------------> [ Closed / Error ] <------.
-// | ^ |
-// | | |
-// [ Created ] --> [ Starting ] --> [ Playing ] --> [ Paused ]
-// ^ | ^ | ^
-// | | | | |
-// | | `----------------' |
-// | V |
-// | [ PausedWhenStarting ] ------------------------'
-// |
-// *[ Empty ]
-//
-// * Initial state
-//
-// At any time after reaching the Created state but before Closed / Error, the
-// AudioOutputController may be notified of a device change via OnDeviceChange()
-// and transition to the Recreating state. If OnDeviceChange() completes
-// successfully the state will transition back to an equivalent pre-call state.
-// E.g., if the state was Paused or PausedWhenStarting, the new state will be
-// Created, since these states are all functionally equivalent and require a
-// Play() call to continue to the next state.
-//
-// The AudioOutputStream can request data from the AudioOutputController via the
-// AudioSourceCallback interface. AudioOutputController uses the SyncReader
-// passed to it via construction to synchronously fulfill this read request.
-//
-// Since AudioOutputController uses AudioManager's message loop the controller
-// uses WeakPtr to allow safe cancellation of pending tasks.
-//
-
-namespace media {
-
-class MEDIA_EXPORT AudioOutputController
- : public base::RefCountedThreadSafe<AudioOutputController>,
- public AudioOutputStream::AudioSourceCallback,
- NON_EXPORTED_BASE(public AudioManager::AudioDeviceListener) {
- public:
- // An event handler that receives events from the AudioOutputController. The
- // following methods are called on the audio manager thread.
- class MEDIA_EXPORT EventHandler {
- public:
- virtual void OnCreated(AudioOutputController* controller) = 0;
- virtual void OnPlaying(AudioOutputController* controller) = 0;
- virtual void OnPaused(AudioOutputController* controller) = 0;
- virtual void OnError(AudioOutputController* controller, int error_code) = 0;
-
- protected:
- virtual ~EventHandler() {}
- };
-
- // A synchronous reader interface used by AudioOutputController for
- // synchronous reading.
- // TODO(crogers): find a better name for this class and the Read() method
- // now that it can handle synchronized I/O.
- class SyncReader {
- public:
- virtual ~SyncReader() {}
-
- // Notify the synchronous reader the number of bytes in the
- // AudioOutputController not yet played. This is used by SyncReader to
- // prepare more data and perform synchronization.
- virtual void UpdatePendingBytes(uint32 bytes) = 0;
-
- // Attempt to completely fill |dest|, return the actual number of
- // frames that could be read.
- // |source| may optionally be provided for input data.
- virtual int Read(AudioBus* source, AudioBus* dest) = 0;
-
- // Close this synchronous reader.
- virtual void Close() = 0;
-
- // Check if data is ready.
- virtual bool DataReady() = 0;
- };
-
- // Factory method for creating an AudioOutputController.
- // This also creates and opens an AudioOutputStream on the audio manager
- // thread, and if this is successful, the |event_handler| will receive an
- // OnCreated() call from the same audio manager thread. |audio_manager| must
- // outlive AudioOutputController.
- static scoped_refptr<AudioOutputController> Create(
- AudioManager* audio_manager, EventHandler* event_handler,
- const AudioParameters& params, SyncReader* sync_reader);
-
- // Methods to control playback of the stream.
-
- // Starts the playback of this audio output stream.
- void Play();
-
- // Pause this audio output stream.
- void Pause();
-
- // Discard all audio data buffered in this output stream. This method only
- // has effect when the stream is paused.
- void Flush();
-
- // Closes the audio output stream. The state is changed and the resources
- // are freed on the audio manager thread. closed_task is executed after that.
- // Callbacks (EventHandler and SyncReader) must exist until closed_task is
- // called.
- //
- // It is safe to call this method more than once. Calls after the first one
- // will have no effect.
- void Close(const base::Closure& closed_task);
-
- // Sets the volume of the audio output stream.
- void SetVolume(double volume);
-
- // AudioSourceCallback implementation.
- virtual int OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE;
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE;
- virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
- virtual void WaitTillDataReady() OVERRIDE;
-
- // AudioDeviceListener implementation. When called AudioOutputController will
- // shutdown the existing |stream_|, transition to the kRecreating state,
- // create a new stream, and then transition back to an equivalent state prior
- // to being called.
- virtual void OnDeviceChange() OVERRIDE;
-
- protected:
- // Internal state of the source.
- enum State {
- kEmpty,
- kCreated,
- kPlaying,
- kStarting,
- kPausedWhenStarting,
- kPaused,
- kClosed,
- kError,
- kRecreating,
- };
-
- friend class base::RefCountedThreadSafe<AudioOutputController>;
- virtual ~AudioOutputController();
-
- private:
- // We are polling sync reader if data became available.
- static const int kPollNumAttempts;
- static const int kPollPauseInMilliseconds;
-
- AudioOutputController(AudioManager* audio_manager, EventHandler* handler,
- const AudioParameters& params, SyncReader* sync_reader);
-
- // The following methods are executed on the audio manager thread.
- void DoCreate();
- void DoPlay();
- void PollAndStartIfDataReady();
- void DoPause();
- void DoFlush();
- void DoClose();
- void DoSetVolume(double volume);
- void DoReportError(int code);
-
- // Helper method that starts physical stream.
- void StartStream();
-
- // Helper method that stops, closes, and NULLs |*stream_|.
- // Signals event when done if it is not NULL.
- void DoStopCloseAndClearStream(base::WaitableEvent *done);
-
- AudioManager* audio_manager_;
-
- // |handler_| may be called only if |state_| is not kClosed.
- EventHandler* handler_;
- AudioOutputStream* stream_;
-
- // The current volume of the audio stream.
- double volume_;
-
- // |state_| is written on the audio manager thread and is read on the
- // hardware audio thread. These operations need to be locked. But lock
- // is not required for reading on the audio manager thread.
- State state_;
-
- // The |lock_| must be acquired whenever we access |state_| from a thread
- // other than the audio manager thread.
- base::Lock lock_;
-
- // SyncReader is used only in low latency mode for synchronous reading.
- SyncReader* sync_reader_;
-
- // The message loop of audio manager thread that this object runs on.
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // When starting stream we wait for data to become available.
- // Number of times left.
- int number_polling_attempts_left_;
-
- AudioParameters params_;
-
- // Used to post delayed tasks to ourselves that we can cancel.
- // We don't want the tasks to hold onto a reference as it will slow down
- // shutdown and force it to wait for the most delayed task.
- // Also, if we're shutting down, we do not want to poll for more data.
- base::WeakPtrFactory<AudioOutputController> weak_this_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputController);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_CONTROLLER_H_
diff --git a/src/media/audio/audio_output_controller_unittest.cc b/src/media/audio/audio_output_controller_unittest.cc
deleted file mode 100644
index fe29ce5..0000000
--- a/src/media/audio/audio_output_controller_unittest.cc
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/environment.h"
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/audio/audio_output_controller.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// TODO(vrk): These tests need to be rewritten! (crbug.com/112500)
-
-using ::testing::_;
-using ::testing::AtLeast;
-using ::testing::DoAll;
-using ::testing::Exactly;
-using ::testing::InvokeWithoutArgs;
-using ::testing::NotNull;
-using ::testing::Return;
-
-namespace media {
-
-static const int kSampleRate = AudioParameters::kAudioCDSampleRate;
-static const int kBitsPerSample = 16;
-static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
-static const int kSamplesPerPacket = kSampleRate / 10;
-static const int kHardwareBufferSize = kSamplesPerPacket *
- ChannelLayoutToChannelCount(kChannelLayout) * kBitsPerSample / 8;
-
-class MockAudioOutputControllerEventHandler
- : public AudioOutputController::EventHandler {
- public:
- MockAudioOutputControllerEventHandler() {}
-
- MOCK_METHOD1(OnCreated, void(AudioOutputController* controller));
- MOCK_METHOD1(OnPlaying, void(AudioOutputController* controller));
- MOCK_METHOD1(OnPaused, void(AudioOutputController* controller));
- MOCK_METHOD2(OnError, void(AudioOutputController* controller,
- int error_code));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerEventHandler);
-};
-
-class MockAudioOutputControllerSyncReader
- : public AudioOutputController::SyncReader {
- public:
- MockAudioOutputControllerSyncReader() {}
-
- MOCK_METHOD1(UpdatePendingBytes, void(uint32 bytes));
- MOCK_METHOD2(Read, int(AudioBus* source, AudioBus* dest));
- MOCK_METHOD0(Close, void());
- MOCK_METHOD0(DataReady, bool());
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerSyncReader);
-};
-
-ACTION_P(SignalEvent, event) {
- event->Signal();
-}
-
-// Custom action to clear a memory buffer.
-ACTION(ClearBuffer) {
- arg1->Zero();
-}
-
-// Closes AudioOutputController synchronously.
-static void CloseAudioController(AudioOutputController* controller) {
- controller->Close(MessageLoop::QuitClosure());
- MessageLoop::current()->Run();
-}
-
-class AudioOutputControllerTest : public testing::Test {
- public:
- AudioOutputControllerTest() {}
- virtual ~AudioOutputControllerTest() {}
-
- protected:
- MessageLoopForIO message_loop_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioOutputControllerTest);
-};
-
-TEST_F(AudioOutputControllerTest, CreateAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!audio_manager->HasAudioOutputDevices())
- return;
-
- MockAudioOutputControllerEventHandler event_handler;
-
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .Times(1);
-
- MockAudioOutputControllerSyncReader sync_reader;
- EXPECT_CALL(sync_reader, Close());
-
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioOutputController> controller =
- AudioOutputController::Create(
- audio_manager.get(), &event_handler, params, &sync_reader);
- ASSERT_TRUE(controller.get());
-
- // Close the controller immediately.
- CloseAudioController(controller);
-}
-
-TEST_F(AudioOutputControllerTest, PlayPauseClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!audio_manager->HasAudioOutputDevices())
- return;
-
- MockAudioOutputControllerEventHandler event_handler;
- base::WaitableEvent event(false, false);
- base::WaitableEvent pause_event(false, false);
-
- // If OnCreated is called then signal the event.
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
-
- // OnPlaying() will be called only once.
- EXPECT_CALL(event_handler, OnPlaying(NotNull()));
-
- MockAudioOutputControllerSyncReader sync_reader;
- EXPECT_CALL(sync_reader, UpdatePendingBytes(_))
- .Times(AtLeast(2));
- EXPECT_CALL(sync_reader, Read(_, _))
- .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event),
- Return(4)));
- EXPECT_CALL(sync_reader, DataReady())
- .WillRepeatedly(Return(true));
- EXPECT_CALL(event_handler, OnPaused(NotNull()))
- .WillOnce(InvokeWithoutArgs(&pause_event, &base::WaitableEvent::Signal));
- EXPECT_CALL(sync_reader, Close());
-
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioOutputController> controller =
- AudioOutputController::Create(
- audio_manager.get(), &event_handler, params, &sync_reader);
- ASSERT_TRUE(controller.get());
-
- // Wait for OnCreated() to be called.
- event.Wait();
-
- ASSERT_FALSE(pause_event.IsSignaled());
- controller->Play();
- controller->Pause();
- pause_event.Wait();
-
- // Now stop the controller.
- CloseAudioController(controller);
-}
-
-TEST_F(AudioOutputControllerTest, HardwareBufferTooLarge) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!audio_manager->HasAudioOutputDevices())
- return;
-
- // Create an audio device with a very large hardware buffer size.
- MockAudioOutputControllerEventHandler event_handler;
-
- MockAudioOutputControllerSyncReader sync_reader;
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
- kSampleRate, kBitsPerSample,
- kSamplesPerPacket * 1000);
- scoped_refptr<AudioOutputController> controller =
- AudioOutputController::Create(
- audio_manager.get(), &event_handler, params, &sync_reader);
-
- // Use assert because we don't stop the device and assume we can't
- // create one.
- ASSERT_FALSE(controller);
-}
-
-TEST_F(AudioOutputControllerTest, PlayPausePlayClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!audio_manager->HasAudioOutputDevices())
- return;
-
- MockAudioOutputControllerEventHandler event_handler;
- base::WaitableEvent event(false, false);
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
-
- // OnPlaying() will be called only once.
- base::WaitableEvent play_event(false, false);
- EXPECT_CALL(event_handler, OnPlaying(NotNull()))
- .WillOnce(InvokeWithoutArgs(&play_event, &base::WaitableEvent::Signal));
-
- // OnPaused() should never be called since the pause during kStarting is
- // dropped when the second play comes in.
- EXPECT_CALL(event_handler, OnPaused(NotNull()))
- .Times(0);
-
- MockAudioOutputControllerSyncReader sync_reader;
- EXPECT_CALL(sync_reader, UpdatePendingBytes(_))
- .Times(AtLeast(1));
- EXPECT_CALL(sync_reader, Read(_, _))
- .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), Return(4)));
- EXPECT_CALL(sync_reader, DataReady())
- .WillRepeatedly(Return(true));
- EXPECT_CALL(sync_reader, Close());
-
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioOutputController> controller =
- AudioOutputController::Create(
- audio_manager.get(), &event_handler, params, &sync_reader);
- ASSERT_TRUE(controller.get());
-
- // Wait for OnCreated() to be called.
- event.Wait();
-
- ASSERT_FALSE(play_event.IsSignaled());
- controller->Play();
- controller->Pause();
- controller->Play();
- play_event.Wait();
-
- // Now stop the controller.
- CloseAudioController(controller);
-}
-
-// Ensure state change events are handled.
-TEST_F(AudioOutputControllerTest, PlayStateChangeClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!audio_manager->HasAudioOutputDevices())
- return;
-
- MockAudioOutputControllerEventHandler event_handler;
- base::WaitableEvent event(false, false);
- EXPECT_CALL(event_handler, OnCreated(NotNull()))
- .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
-
- // OnPlaying() will be called once normally and once after being recreated.
- base::WaitableEvent play_event(false, false);
- EXPECT_CALL(event_handler, OnPlaying(NotNull()))
- .Times(2)
- .WillRepeatedly(InvokeWithoutArgs(
- &play_event, &base::WaitableEvent::Signal));
-
- // OnPaused() should not be called during the state change event.
- EXPECT_CALL(event_handler, OnPaused(NotNull()))
- .Times(0);
-
- MockAudioOutputControllerSyncReader sync_reader;
- EXPECT_CALL(sync_reader, UpdatePendingBytes(_))
- .Times(AtLeast(1));
- EXPECT_CALL(sync_reader, Read(_, _))
- .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), Return(4)));
- EXPECT_CALL(sync_reader, DataReady())
- .WillRepeatedly(Return(true));
- EXPECT_CALL(sync_reader, Close());
-
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
- kSampleRate, kBitsPerSample, kSamplesPerPacket);
- scoped_refptr<AudioOutputController> controller =
- AudioOutputController::Create(
- audio_manager.get(), &event_handler, params, &sync_reader);
- ASSERT_TRUE(controller.get());
-
- // Wait for OnCreated() to be called.
- event.Wait();
-
- ASSERT_FALSE(play_event.IsSignaled());
- controller->Play();
- play_event.Wait();
-
- // Force a state change and wait for the stream to come back to playing state.
- play_event.Reset();
- audio_manager->GetMessageLoop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputController::OnDeviceChange, controller));
- play_event.Wait();
-
- // Now stop the controller.
- CloseAudioController(controller);
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_device.cc b/src/media/audio/audio_output_device.cc
deleted file mode 100644
index 8ad93ff..0000000
--- a/src/media/audio/audio_output_device.cc
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_device.h"
-
-#include "base/debug/trace_event.h"
-#include "base/message_loop.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/time.h"
-#include "media/audio/audio_output_controller.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/shared_memory_util.h"
-#include "media/base/limits.h"
-
-namespace media {
-
-// Takes care of invoking the render callback on the audio thread.
-// An instance of this class is created for each capture stream in
-// OnStreamCreated().
-class AudioOutputDevice::AudioThreadCallback
- : public AudioDeviceThread::Callback {
- public:
- AudioThreadCallback(const AudioParameters& audio_parameters,
- int input_channels,
- base::SharedMemoryHandle memory,
- int memory_length,
- AudioRendererSink::RenderCallback* render_callback);
- virtual ~AudioThreadCallback();
-
- virtual void MapSharedMemory() OVERRIDE;
-
- // Called whenever we receive notifications about pending data.
- virtual void Process(int pending_data) OVERRIDE;
-
- private:
- AudioRendererSink::RenderCallback* render_callback_;
- scoped_ptr<AudioBus> input_bus_;
- scoped_ptr<AudioBus> output_bus_;
- DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback);
-};
-
-AudioOutputDevice::AudioOutputDevice(
- AudioOutputIPC* ipc,
- const scoped_refptr<base::MessageLoopProxy>& io_loop)
- : ScopedLoopObserver(io_loop),
- input_channels_(0),
- callback_(NULL),
- ipc_(ipc),
- state_(IDLE),
- play_on_start_(true),
- stopping_hack_(false) {
- CHECK(ipc_);
- stream_id_ = ipc_->AddDelegate(this);
-}
-
-void AudioOutputDevice::Initialize(const AudioParameters& params,
- RenderCallback* callback) {
- DCHECK(!callback_) << "Calling Initialize() twice?";
- audio_parameters_ = params;
- callback_ = callback;
-}
-
-void AudioOutputDevice::InitializeIO(const AudioParameters& params,
- int input_channels,
- RenderCallback* callback) {
- DCHECK_GE(input_channels, 0);
- DCHECK_LT(input_channels, limits::kMaxChannels);
- input_channels_ = input_channels;
- Initialize(params, callback);
-}
-
-AudioOutputDevice::~AudioOutputDevice() {
- // The current design requires that the user calls Stop() before deleting
- // this class.
- DCHECK(audio_thread_.IsStopped());
-
- if (ipc_)
- ipc_->RemoveDelegate(stream_id_);
-}
-
-void AudioOutputDevice::Start() {
- DCHECK(callback_) << "Initialize hasn't been called";
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this,
- audio_parameters_, input_channels_));
-}
-
-void AudioOutputDevice::Stop() {
- {
- base::AutoLock auto_lock(audio_thread_lock_);
- audio_thread_.Stop(MessageLoop::current());
- stopping_hack_ = true;
- }
-
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputDevice::ShutDownOnIOThread, this));
-}
-
-void AudioOutputDevice::Play() {
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputDevice::PlayOnIOThread, this));
-}
-
-void AudioOutputDevice::Pause(bool flush) {
- message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputDevice::PauseOnIOThread, this, flush));
-}
-
-bool AudioOutputDevice::SetVolume(double volume) {
- if (volume < 0 || volume > 1.0)
- return false;
-
- if (!message_loop()->PostTask(FROM_HERE,
- base::Bind(&AudioOutputDevice::SetVolumeOnIOThread, this, volume))) {
- return false;
- }
-
- return true;
-}
-
-void AudioOutputDevice::CreateStreamOnIOThread(const AudioParameters& params,
- int input_channels) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (state_ == IDLE) {
- state_ = CREATING_STREAM;
- ipc_->CreateStream(stream_id_, params, input_channels);
- }
-}
-
-void AudioOutputDevice::PlayOnIOThread() {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (state_ == PAUSED) {
- ipc_->PlayStream(stream_id_);
- state_ = PLAYING;
- play_on_start_ = false;
- } else {
- play_on_start_ = true;
- }
-}
-
-void AudioOutputDevice::PauseOnIOThread(bool flush) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (state_ == PLAYING) {
- ipc_->PauseStream(stream_id_);
- if (flush)
- ipc_->FlushStream(stream_id_);
- state_ = PAUSED;
- } else {
- // Note that |flush| isn't relevant here since this is the case where
- // the stream is first starting.
- }
- play_on_start_ = false;
-}
-
-void AudioOutputDevice::ShutDownOnIOThread() {
- DCHECK(message_loop()->BelongsToCurrentThread());
-
- // Make sure we don't call shutdown more than once.
- if (state_ >= CREATING_STREAM) {
- ipc_->CloseStream(stream_id_);
- state_ = IDLE;
- }
-
- // We can run into an issue where ShutDownOnIOThread is called right after
- // OnStreamCreated is called in cases where Start/Stop are called before we
- // get the OnStreamCreated callback. To handle that corner case, we call
- // Stop(). In most cases, the thread will already be stopped.
- //
- // Another situation is when the IO thread goes away before Stop() is called
- // in which case, we cannot use the message loop to close the thread handle
- // and can't rely on the main thread existing either.
- base::AutoLock auto_lock_(audio_thread_lock_);
- base::ThreadRestrictions::ScopedAllowIO allow_io;
- audio_thread_.Stop(NULL);
- audio_callback_.reset();
- stopping_hack_ = false;
-}
-
-void AudioOutputDevice::SetVolumeOnIOThread(double volume) {
- DCHECK(message_loop()->BelongsToCurrentThread());
- if (state_ >= CREATING_STREAM)
- ipc_->SetVolume(stream_id_, volume);
-}
-
-void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) {
- DCHECK(message_loop()->BelongsToCurrentThread());
-
- // Do nothing if the stream has been closed.
- if (state_ < CREATING_STREAM)
- return;
-
- if (state == AudioOutputIPCDelegate::kError) {
- DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)";
- // Don't dereference the callback object if the audio thread
- // is stopped or stopping. That could mean that the callback
- // object has been deleted.
- // TODO(tommi): Add an explicit contract for clearing the callback
- // object. Possibly require calling Initialize again or provide
- // a callback object via Start() and clear it in Stop().
- if (!audio_thread_.IsStopped())
- callback_->OnRenderError();
- }
-}
-
-void AudioOutputDevice::OnStreamCreated(
- base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) {
- DCHECK(message_loop()->BelongsToCurrentThread());
-#if defined(OS_WIN)
- DCHECK(handle);
- DCHECK(socket_handle);
-#elif defined(__LB_SHELL__) || defined(COBALT)
- DCHECK(handle.get());
-#else
- DCHECK_GE(handle.fd, 0);
- DCHECK_GE(socket_handle, 0);
-#endif
-
- if (state_ != CREATING_STREAM)
- return;
-
- // We can receive OnStreamCreated() on the IO thread after the client has
- // called Stop() but before ShutDownOnIOThread() is processed. In such a
- // situation |callback_| might point to freed memory. Instead of starting
- // |audio_thread_| do nothing and wait for ShutDownOnIOThread() to get called.
- //
- // TODO(scherkus): The real fix is to have sane ownership semantics. The fact
- // that |callback_| (which should own and outlive this object!) can point to
- // freed memory is a mess. AudioRendererSink should be non-refcounted so that
- // owners (WebRtcAudioDeviceImpl, AudioRendererImpl, etc...) can Stop() and
- // delete as they see fit. AudioOutputDevice should internally use WeakPtr
- // to handle teardown and thread hopping. See http://crbug.com/151051 for
- // details.
- base::AutoLock auto_lock(audio_thread_lock_);
- if (stopping_hack_)
- return;
-
- DCHECK(audio_thread_.IsStopped());
- audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback(
- audio_parameters_, input_channels_, handle, length, callback_));
- audio_thread_.Start(audio_callback_.get(), socket_handle,
- "AudioOutputDevice");
- state_ = PAUSED;
-
- // We handle the case where Play() and/or Pause() may have been called
- // multiple times before OnStreamCreated() gets called.
- if (play_on_start_)
- PlayOnIOThread();
-}
-
-void AudioOutputDevice::OnIPCClosed() {
- DCHECK(message_loop()->BelongsToCurrentThread());
- state_ = IPC_CLOSED;
- ipc_ = NULL;
-}
-
-void AudioOutputDevice::WillDestroyCurrentMessageLoop() {
- LOG(ERROR) << "IO loop going away before the audio device has been stopped";
- ShutDownOnIOThread();
-}
-
-// AudioOutputDevice::AudioThreadCallback
-
-AudioOutputDevice::AudioThreadCallback::AudioThreadCallback(
- const AudioParameters& audio_parameters,
- int input_channels,
- base::SharedMemoryHandle memory,
- int memory_length,
- AudioRendererSink::RenderCallback* render_callback)
- : AudioDeviceThread::Callback(audio_parameters,
- input_channels,
- memory,
- memory_length),
- render_callback_(render_callback) {
-}
-
-AudioOutputDevice::AudioThreadCallback::~AudioThreadCallback() {
-}
-
-void AudioOutputDevice::AudioThreadCallback::MapSharedMemory() {
- shared_memory_.Map(TotalSharedMemorySizeInBytes(memory_length_));
-
- // Calculate output and input memory size.
- int output_memory_size = AudioBus::CalculateMemorySize(audio_parameters_);
- int frames = audio_parameters_.frames_per_buffer();
- int input_memory_size =
- AudioBus::CalculateMemorySize(input_channels_, frames);
-
- int io_size = output_memory_size + input_memory_size;
-
- DCHECK_EQ(memory_length_, io_size);
-
- output_bus_ =
- AudioBus::WrapMemory(audio_parameters_, shared_memory_.memory());
-
- if (input_channels_ > 0) {
- // The input data is after the output data.
- char* input_data =
- static_cast<char*>(shared_memory_.memory()) + output_memory_size;
- input_bus_ =
- AudioBus::WrapMemory(input_channels_, frames, input_data);
- }
-}
-
-// Called whenever we receive notifications about pending data.
-void AudioOutputDevice::AudioThreadCallback::Process(int pending_data) {
- if (pending_data == kPauseMark) {
- memset(shared_memory_.memory(), 0, memory_length_);
- SetActualDataSizeInBytes(&shared_memory_, memory_length_, 0);
- return;
- }
-
- // Convert the number of pending bytes in the render buffer
- // into milliseconds.
- int audio_delay_milliseconds = pending_data / bytes_per_ms_;
-
- TRACE_EVENT0("audio", "AudioOutputDevice::FireRenderCallback");
-
- // Update the audio-delay measurement then ask client to render audio. Since
- // |output_bus_| is wrapping the shared memory the Render() call is writing
- // directly into the shared memory.
- size_t num_frames = audio_parameters_.frames_per_buffer();
-
- if (input_bus_.get() && input_channels_ > 0) {
- render_callback_->RenderIO(input_bus_.get(),
- output_bus_.get(),
- audio_delay_milliseconds);
- } else {
- num_frames = render_callback_->Render(output_bus_.get(),
- audio_delay_milliseconds);
- }
-
- // Let the host know we are done.
- // TODO(dalecurtis): Technically this is not always correct. Due to channel
- // padding for alignment, there may be more data available than this. We're
- // relying on AudioSyncReader::Read() to parse this with that in mind. Rename
- // these methods to Set/GetActualFrameCount().
- SetActualDataSizeInBytes(
- &shared_memory_, memory_length_,
- num_frames * sizeof(*output_bus_->channel(0)) * output_bus_->channels());
-}
-
-} // namespace media.
diff --git a/src/media/audio/audio_output_device.h b/src/media/audio/audio_output_device.h
deleted file mode 100644
index 6650028..0000000
--- a/src/media/audio/audio_output_device.h
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Audio rendering unit utilizing audio output stream provided by browser
-// process through IPC.
-//
-// Relationship of classes.
-//
-// AudioOutputController AudioOutputDevice
-// ^ ^
-// | |
-// v IPC v
-// AudioRendererHost <---------> AudioOutputIPC (AudioMessageFilter)
-//
-// Transportation of audio samples from the render to the browser process
-// is done by using shared memory in combination with a sync socket pair
-// to generate a low latency transport. The AudioOutputDevice user registers an
-// AudioOutputDevice::RenderCallback at construction and will be polled by the
-// AudioOutputDevice for audio to be played out by the underlying audio layers.
-//
-// State sequences.
-//
-// Task [IO thread] IPC [IO thread]
-//
-// Start -> CreateStreamOnIOThread -----> CreateStream ------>
-// <- OnStreamCreated <- AudioMsg_NotifyStreamCreated <-
-// ---> PlayOnIOThread -----------> PlayStream -------->
-//
-// Optionally Play() / Pause() sequences may occur:
-// Play -> PlayOnIOThread --------------> PlayStream --------->
-// Pause -> PauseOnIOThread ------------> PauseStream -------->
-// (note that Play() / Pause() sequences before OnStreamCreated are
-// deferred until OnStreamCreated, with the last valid state being used)
-//
-// AudioOutputDevice::Render => audio transport on audio thread =>
-// |
-// Stop --> ShutDownOnIOThread --------> CloseStream -> Close
-//
-// This class utilizes several threads during its lifetime, namely:
-// 1. Creating thread.
-// Must be the main render thread.
-// 2. Control thread (may be the main render thread or another thread).
-// The methods: Start(), Stop(), Play(), Pause(), SetVolume()
-// must be called on the same thread.
-// 3. IO thread (internal implementation detail - not exposed to public API)
-// The thread within which this class receives all the IPC messages and
-// IPC communications can only happen in this thread.
-// 4. Audio transport thread (See AudioDeviceThread).
-// Responsible for calling the AudioThreadCallback implementation that in
-// turn calls AudioRendererSink::RenderCallback which feeds audio samples to
-// the audio layer in the browser process using sync sockets and shared
-// memory.
-//
-// Implementation notes:
-// - The user must call Stop() before deleting the class instance.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/shared_memory.h"
-#include "media/base/media_export.h"
-#include "media/audio/audio_device_thread.h"
-#include "media/audio/audio_output_ipc.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/scoped_loop_observer.h"
-#include "media/base/audio_renderer_sink.h"
-
-namespace media {
-
-class MEDIA_EXPORT AudioOutputDevice
- : NON_EXPORTED_BASE(public AudioRendererSink),
- public AudioOutputIPCDelegate,
- NON_EXPORTED_BASE(public ScopedLoopObserver) {
- public:
- // AudioRendererSink implementation.
- virtual void Initialize(const AudioParameters& params,
- RenderCallback* callback) OVERRIDE;
- virtual void InitializeIO(const AudioParameters& params,
- int input_channels,
- RenderCallback* callback) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Play() OVERRIDE;
- virtual void Pause(bool flush) OVERRIDE;
- virtual bool SetVolume(double volume) OVERRIDE;
-
- // Methods called on IO thread ----------------------------------------------
- // AudioOutputIPCDelegate methods.
- virtual void OnStateChanged(AudioOutputIPCDelegate::State state) OVERRIDE;
- virtual void OnStreamCreated(base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) OVERRIDE;
- virtual void OnIPCClosed() OVERRIDE;
-
- // Creates an uninitialized AudioOutputDevice. Clients must call Initialize()
- // before using.
- AudioOutputDevice(AudioOutputIPC* ipc,
- const scoped_refptr<base::MessageLoopProxy>& io_loop);
-
- protected:
- // Magic required by ref_counted.h to avoid any code deleting the object
- // accidentally while there are references to it.
- friend class base::RefCountedThreadSafe<AudioOutputDevice>;
- virtual ~AudioOutputDevice();
-
- // Accessors for subclasses (via IO thread only).
- int stream_id() const { return stream_id_; }
- AudioOutputIPC* audio_output_ipc() const { return ipc_; }
-
- private:
- // Note: The ordering of members in this enum is critical to correct behavior!
- enum State {
- IPC_CLOSED, // No more IPCs can take place.
- IDLE, // Not started.
- CREATING_STREAM, // Waiting for OnStreamCreated() to be called back.
- PAUSED, // Paused. OnStreamCreated() has been called. Can Play()/Stop().
- PLAYING, // Playing back. Can Pause()/Stop().
- };
-
- // Methods called on IO thread ----------------------------------------------
- // The following methods are tasks posted on the IO thread that needs to
- // be executed on that thread. They interact with AudioMessageFilter and
- // sends IPC messages on that thread.
- void CreateStreamOnIOThread(const AudioParameters& params,
- int input_channels);
- void PlayOnIOThread();
- void PauseOnIOThread(bool flush);
- void ShutDownOnIOThread();
- void SetVolumeOnIOThread(double volume);
-
- // MessageLoop::DestructionObserver implementation for the IO loop.
- // If the IO loop dies before we do, we shut down the audio thread from here.
- virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
-
- AudioParameters audio_parameters_;
-
- // The number of optional synchronized input channels having the same
- // sample-rate and buffer-size as specified in audio_parameters_.
- int input_channels_;
-
- RenderCallback* callback_;
-
- // A pointer to the IPC layer that takes care of sending requests over to
- // the AudioRendererHost.
- AudioOutputIPC* ipc_;
-
- // Our stream ID on the message filter. Only accessed on the IO thread.
- // Must only be modified on the IO thread.
- int stream_id_;
-
- // Current state (must only be accessed from the IO thread). See comments for
- // State enum above.
- State state_;
-
- // State of Play() / Pause() calls before OnStreamCreated() is called.
- bool play_on_start_;
-
- // Our audio thread callback class. See source file for details.
- class AudioThreadCallback;
-
- // In order to avoid a race between OnStreamCreated and Stop(), we use this
- // guard to control stopping and starting the audio thread.
- base::Lock audio_thread_lock_;
- AudioDeviceThread audio_thread_;
- scoped_ptr<AudioOutputDevice::AudioThreadCallback> audio_callback_;
-
- // Temporary hack to ignore OnStreamCreated() due to the user calling Stop()
- // so we don't start the audio thread pointing to a potentially freed
- // |callback_|.
- //
- // TODO(scherkus): Replace this by changing AudioRendererSink to either accept
- // the callback via Start(). See http://crbug.com/151051 for details.
- bool stopping_hack_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputDevice);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
diff --git a/src/media/audio/audio_output_device_unittest.cc b/src/media/audio/audio_output_device_unittest.cc
deleted file mode 100644
index 70e2a49..0000000
--- a/src/media/audio/audio_output_device_unittest.cc
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <vector>
-
-#include "base/at_exit.h"
-#include "base/message_loop.h"
-#include "base/process_util.h"
-#include "base/shared_memory.h"
-#include "base/sync_socket.h"
-#include "base/test/test_timeouts.h"
-#include "media/audio/audio_output_device.h"
-#include "media/audio/sample_rates.h"
-#include "media/audio/shared_memory_util.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gmock_mutant.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::CancelableSyncSocket;
-using base::SharedMemory;
-using base::SyncSocket;
-using testing::_;
-using testing::DoAll;
-using testing::Invoke;
-using testing::Return;
-using testing::WithArgs;
-using testing::StrictMock;
-using testing::Values;
-
-namespace media {
-
-namespace {
-
-class MockRenderCallback : public AudioRendererSink::RenderCallback {
- public:
- MockRenderCallback() {}
- virtual ~MockRenderCallback() {}
-
- MOCK_METHOD2(Render, int(AudioBus* dest, int audio_delay_milliseconds));
- MOCK_METHOD3(RenderIO, void(AudioBus* source,
- AudioBus* dest,
- int audio_delay_milliseconds));
- MOCK_METHOD0(OnRenderError, void());
-};
-
-class MockAudioOutputIPC : public AudioOutputIPC {
- public:
- MockAudioOutputIPC() {}
- virtual ~MockAudioOutputIPC() {}
-
- MOCK_METHOD1(AddDelegate, int(AudioOutputIPCDelegate* delegate));
- MOCK_METHOD1(RemoveDelegate, void(int stream_id));
-
- MOCK_METHOD3(CreateStream,
- void(int stream_id, const AudioParameters& params, int input_channels));
- MOCK_METHOD1(PlayStream, void(int stream_id));
- MOCK_METHOD1(CloseStream, void(int stream_id));
- MOCK_METHOD2(SetVolume, void(int stream_id, double volume));
- MOCK_METHOD1(PauseStream, void(int stream_id));
- MOCK_METHOD1(FlushStream, void(int stream_id));
-};
-
-// Creates a copy of a SyncSocket handle that we can give to AudioOutputDevice.
-// On Windows this means duplicating the pipe handle so that AudioOutputDevice
-// can call CloseHandle() (since ownership has been transferred), but on other
-// platforms, we just copy the same socket handle since AudioOutputDevice on
-// those platforms won't actually own the socket (FileDescriptor.auto_close is
-// false).
-bool DuplicateSocketHandle(SyncSocket::Handle socket_handle,
- SyncSocket::Handle* copy) {
-#if defined(OS_WIN)
- HANDLE process = GetCurrentProcess();
- ::DuplicateHandle(process, socket_handle, process, copy,
- 0, FALSE, DUPLICATE_SAME_ACCESS);
- return *copy != NULL;
-#else
- *copy = socket_handle;
- return *copy != -1;
-#endif
-}
-
-ACTION_P2(SendPendingBytes, socket, pending_bytes) {
- socket->Send(&pending_bytes, sizeof(pending_bytes));
-}
-
-// Used to terminate a loop from a different thread than the loop belongs to.
-// |loop| should be a MessageLoopProxy.
-ACTION_P(QuitLoop, loop) {
- loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-} // namespace.
-
-class AudioOutputDeviceTest
- : public testing::Test,
- public testing::WithParamInterface<bool> {
- public:
- AudioOutputDeviceTest();
- ~AudioOutputDeviceTest();
-
- void StartAudioDevice();
- void CreateStream();
- void ExpectRenderCallback();
- void WaitUntilRenderCallback();
- void StopAudioDevice();
-
- protected:
- // Used to clean up TLS pointers that the test(s) will initialize.
- // Must remain the first member of this class.
- base::ShadowingAtExitManager at_exit_manager_;
- MessageLoopForIO io_loop_;
- const AudioParameters default_audio_parameters_;
- StrictMock<MockRenderCallback> callback_;
- StrictMock<MockAudioOutputIPC> audio_output_ipc_;
- scoped_refptr<AudioOutputDevice> audio_device_;
-
- private:
- int CalculateMemorySize();
-
- const bool synchronized_io_;
- const int input_channels_;
- SharedMemory shared_memory_;
- CancelableSyncSocket browser_socket_;
- CancelableSyncSocket renderer_socket_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputDeviceTest);
-};
-
-static const int kStreamId = 123;
-
-int AudioOutputDeviceTest::CalculateMemorySize() {
- // Calculate output and input memory size.
- int output_memory_size =
- AudioBus::CalculateMemorySize(default_audio_parameters_);
-
- int frames = default_audio_parameters_.frames_per_buffer();
- int input_memory_size =
- AudioBus::CalculateMemorySize(input_channels_, frames);
-
- int io_buffer_size = output_memory_size + input_memory_size;
-
- // This is where it gets a bit hacky. The shared memory contract between
- // AudioOutputDevice and its browser side counter part includes a bit more
- // than just the audio data, so we must call TotalSharedMemorySizeInBytes()
- // to get the actual size needed to fit the audio data plus the extra data.
- return TotalSharedMemorySizeInBytes(io_buffer_size);
-}
-
-AudioOutputDeviceTest::AudioOutputDeviceTest()
- : default_audio_parameters_(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO,
- 48000, 16, 1024),
- synchronized_io_(GetParam()),
- input_channels_(synchronized_io_ ? 2 : 0) {
- EXPECT_CALL(audio_output_ipc_, AddDelegate(_))
- .WillOnce(Return(kStreamId));
-
- audio_device_ = new AudioOutputDevice(
- &audio_output_ipc_, io_loop_.message_loop_proxy());
-
- if (synchronized_io_) {
- audio_device_->InitializeIO(default_audio_parameters_,
- input_channels_,
- &callback_);
- } else {
- audio_device_->Initialize(default_audio_parameters_,
- &callback_);
- }
- io_loop_.RunUntilIdle();
-}
-
-AudioOutputDeviceTest::~AudioOutputDeviceTest() {
- EXPECT_CALL(audio_output_ipc_, RemoveDelegate(kStreamId));
-
- audio_device_ = NULL;
-}
-
-void AudioOutputDeviceTest::StartAudioDevice() {
- audio_device_->Start();
-
- EXPECT_CALL(audio_output_ipc_, CreateStream(kStreamId, _, _));
-
- io_loop_.RunUntilIdle();
-}
-
-void AudioOutputDeviceTest::CreateStream() {
- const int kMemorySize = CalculateMemorySize();
-
- ASSERT_TRUE(shared_memory_.CreateAndMapAnonymous(kMemorySize));
- memset(shared_memory_.memory(), 0xff, kMemorySize);
-
- ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket_,
- &renderer_socket_));
-
- // Create duplicates of the handles we pass to AudioOutputDevice since
- // ownership will be transferred and AudioOutputDevice is responsible for
- // freeing.
- SyncSocket::Handle audio_device_socket = SyncSocket::kInvalidHandle;
- ASSERT_TRUE(DuplicateSocketHandle(renderer_socket_.handle(),
- &audio_device_socket));
- base::SharedMemoryHandle duplicated_memory_handle;
- ASSERT_TRUE(shared_memory_.ShareToProcess(base::GetCurrentProcessHandle(),
- &duplicated_memory_handle));
-
- audio_device_->OnStreamCreated(duplicated_memory_handle, audio_device_socket,
- PacketSizeInBytes(kMemorySize));
- io_loop_.RunUntilIdle();
-}
-
-void AudioOutputDeviceTest::ExpectRenderCallback() {
- // We should get a 'play' notification when we call OnStreamCreated().
- // Respond by asking for some audio data. This should ask our callback
- // to provide some audio data that AudioOutputDevice then writes into the
- // shared memory section.
- const int kMemorySize = CalculateMemorySize();
-
- EXPECT_CALL(audio_output_ipc_, PlayStream(kStreamId))
- .WillOnce(SendPendingBytes(&browser_socket_, kMemorySize));
-
- // We expect calls to our audio renderer callback, which returns the number
- // of frames written to the memory section.
- // Here's the second place where it gets hacky: There's no way for us to
- // know (without using a sleep loop!) when the AudioOutputDevice has finished
- // writing the interleaved audio data into the shared memory section.
- // So, for the sake of this test, we consider the call to Render a sign
- // of success and quit the loop.
- if (synchronized_io_) {
- // For synchronized I/O, we expect RenderIO().
- EXPECT_CALL(callback_, RenderIO(_, _, _))
- .WillOnce(QuitLoop(io_loop_.message_loop_proxy()));
- } else {
- // For output only we expect Render().
- const int kNumberOfFramesToProcess = 0;
- EXPECT_CALL(callback_, Render(_, _))
- .WillOnce(DoAll(
- QuitLoop(io_loop_.message_loop_proxy()),
- Return(kNumberOfFramesToProcess)));
- }
-}
-
-void AudioOutputDeviceTest::WaitUntilRenderCallback() {
- // Don't hang the test if we never get the Render() callback.
- io_loop_.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- io_loop_.Run();
-}
-
-void AudioOutputDeviceTest::StopAudioDevice() {
- audio_device_->Stop();
-
- EXPECT_CALL(audio_output_ipc_, CloseStream(kStreamId));
-
- io_loop_.RunUntilIdle();
-}
-
-TEST_P(AudioOutputDeviceTest, Initialize) {
- // Tests that the object can be constructed, initialized and destructed
- // without having ever been started/stopped.
-}
-
-// Calls Start() followed by an immediate Stop() and check for the basic message
-// filter messages being sent in that case.
-TEST_P(AudioOutputDeviceTest, StartStop) {
- StartAudioDevice();
- StopAudioDevice();
-}
-
-// AudioOutputDevice supports multiple start/stop sequences.
-TEST_P(AudioOutputDeviceTest, StartStopStartStop) {
- StartAudioDevice();
- StopAudioDevice();
- StartAudioDevice();
- StopAudioDevice();
-}
-
-// Simulate receiving OnStreamCreated() prior to processing ShutDownOnIOThread()
-// on the IO loop.
-TEST_P(AudioOutputDeviceTest, StopBeforeRender) {
- StartAudioDevice();
-
- // Call Stop() but don't run the IO loop yet.
- audio_device_->Stop();
-
- // Expect us to shutdown IPC but not to render anything despite the stream
- // getting created.
- EXPECT_CALL(audio_output_ipc_, CloseStream(kStreamId));
- CreateStream();
-}
-
-// Full test with output only.
-TEST_P(AudioOutputDeviceTest, CreateStream) {
- StartAudioDevice();
- ExpectRenderCallback();
- CreateStream();
- WaitUntilRenderCallback();
- StopAudioDevice();
-}
-
-INSTANTIATE_TEST_CASE_P(Render, AudioOutputDeviceTest, Values(false));
-INSTANTIATE_TEST_CASE_P(RenderIO, AudioOutputDeviceTest, Values(true));
-
-} // namespace media.
diff --git a/src/media/audio/audio_output_dispatcher.cc b/src/media/audio/audio_output_dispatcher.cc
deleted file mode 100644
index bfd3fb8..0000000
--- a/src/media/audio/audio_output_dispatcher.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_dispatcher.h"
-
-#include "base/message_loop.h"
-
-namespace media {
-
-AudioOutputDispatcher::AudioOutputDispatcher(
- AudioManager* audio_manager,
- const AudioParameters& params)
- : audio_manager_(audio_manager),
- message_loop_(MessageLoop::current()),
- params_(params) {
- // We expect to be instantiated on the audio thread. Otherwise the
- // message_loop_ member will point to the wrong message loop!
- DCHECK(audio_manager->GetMessageLoop()->BelongsToCurrentThread());
-}
-
-AudioOutputDispatcher::~AudioOutputDispatcher() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_dispatcher.h b/src/media/audio/audio_output_dispatcher.h
deleted file mode 100644
index 6f8d86e..0000000
--- a/src/media/audio/audio_output_dispatcher.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// AudioOutputDispatcher is a single-threaded base class that dispatches
-// creation and deletion of audio output streams. AudioOutputProxy objects use
-// this class to allocate and recycle actual audio output streams. When playback
-// is started, the proxy calls StartStream() to get an output stream that it
-// uses to play audio. When playback is stopped, the proxy returns the stream
-// back to the dispatcher by calling StopStream().
-//
-// AudioManagerBase creates one specialization of AudioOutputDispatcher on the
-// audio thread for each possible set of audio parameters. I.e streams with
-// different parameters are managed independently. The AudioOutputDispatcher
-// instance is then deleted on the audio thread when the AudioManager shuts
-// down.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/timer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_parameters.h"
-
-class MessageLoop;
-
-namespace media {
-
-class AudioOutputProxy;
-
-class MEDIA_EXPORT AudioOutputDispatcher
- : public base::RefCountedThreadSafe<AudioOutputDispatcher> {
- public:
- AudioOutputDispatcher(AudioManager* audio_manager,
- const AudioParameters& params);
-
- // Called by AudioOutputProxy to open the stream.
- // Returns false, if it fails to open it.
- virtual bool OpenStream() = 0;
-
- // Called by AudioOutputProxy when the stream is started.
- // Uses |callback| to get source data and report errors, if any.
- // Does *not* take ownership of this callback.
- // Returns true if started successfully, false otherwise.
- virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) = 0;
-
- // Called by AudioOutputProxy when the stream is stopped.
- // Ownership of the |stream_proxy| is passed to the dispatcher.
- virtual void StopStream(AudioOutputProxy* stream_proxy) = 0;
-
- // Called by AudioOutputProxy when the volume is set.
- virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) = 0;
-
- // Called by AudioOutputProxy when the stream is closed.
- virtual void CloseStream(AudioOutputProxy* stream_proxy) = 0;
-
- // Called on the audio thread when the AudioManager is shutting down.
- virtual void Shutdown() = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<AudioOutputDispatcher>;
- friend class AudioOutputProxyTest;
-
- virtual ~AudioOutputDispatcher();
-
- // A no-reference-held pointer (we don't want circular references) back to the
- // AudioManager that owns this object.
- AudioManager* audio_manager_;
- MessageLoop* message_loop_;
- AudioParameters params_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioOutputDispatcher);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
diff --git a/src/media/audio/audio_output_dispatcher_impl.cc b/src/media/audio/audio_output_dispatcher_impl.cc
deleted file mode 100644
index d254278..0000000
--- a/src/media/audio/audio_output_dispatcher_impl.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_dispatcher_impl.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/compiler_specific.h"
-#include "base/message_loop.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_output_proxy.h"
-#include "media/audio/audio_util.h"
-
-namespace media {
-
-AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
- AudioManager* audio_manager,
- const AudioParameters& params,
- const base::TimeDelta& close_delay)
- : AudioOutputDispatcher(audio_manager, params),
- pause_delay_(base::TimeDelta::FromMilliseconds(
- 2 * params.frames_per_buffer() *
- base::Time::kMillisecondsPerSecond / params.sample_rate())),
- paused_proxies_(0),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
- close_timer_(FROM_HERE,
- close_delay,
- weak_this_.GetWeakPtr(),
- &AudioOutputDispatcherImpl::ClosePendingStreams) {
-}
-
-AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
- DCHECK(proxy_to_physical_map_.empty());
- DCHECK(idle_streams_.empty());
- DCHECK(pausing_streams_.empty());
-}
-
-bool AudioOutputDispatcherImpl::OpenStream() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- paused_proxies_++;
-
- // Ensure that there is at least one open stream.
- if (idle_streams_.empty() && !CreateAndOpenStream()) {
- paused_proxies_--;
- return false;
- }
-
- close_timer_.Reset();
- return true;
-}
-
-bool AudioOutputDispatcherImpl::StartStream(
- AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (idle_streams_.empty() && !CreateAndOpenStream())
- return false;
-
- AudioOutputStream* physical_stream = idle_streams_.back();
- DCHECK(physical_stream);
- idle_streams_.pop_back();
-
- DCHECK_GT(paused_proxies_, 0u);
- --paused_proxies_;
-
- close_timer_.Reset();
-
- // Schedule task to allocate streams for other proxies if we need to.
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &AudioOutputDispatcherImpl::OpenTask, weak_this_.GetWeakPtr()));
-
- double volume = 0;
- stream_proxy->GetVolume(&volume);
- physical_stream->SetVolume(volume);
- physical_stream->Start(callback);
- proxy_to_physical_map_[stream_proxy] = physical_stream;
- return true;
-}
-
-void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
- DCHECK(it != proxy_to_physical_map_.end());
- AudioOutputStream* physical_stream = it->second;
- proxy_to_physical_map_.erase(it);
-
- physical_stream->Stop();
-
- ++paused_proxies_;
-
- pausing_streams_.push_front(physical_stream);
-
- // Don't recycle stream until two buffers worth of time has elapsed.
- message_loop_->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AudioOutputDispatcherImpl::StopStreamTask,
- weak_this_.GetWeakPtr()),
- pause_delay_);
-}
-
-void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
- if (it != proxy_to_physical_map_.end()) {
- AudioOutputStream* physical_stream = it->second;
- physical_stream->SetVolume(volume);
- }
-}
-
-void AudioOutputDispatcherImpl::StopStreamTask() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (pausing_streams_.empty())
- return;
-
- AudioOutputStream* stream = pausing_streams_.back();
- pausing_streams_.pop_back();
- idle_streams_.push_back(stream);
- close_timer_.Reset();
-}
-
-void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- while (!pausing_streams_.empty()) {
- idle_streams_.push_back(pausing_streams_.back());
- pausing_streams_.pop_back();
- }
-
- DCHECK_GT(paused_proxies_, 0u);
- paused_proxies_--;
-
- while (idle_streams_.size() > paused_proxies_) {
- idle_streams_.back()->Close();
- idle_streams_.pop_back();
- }
-}
-
-void AudioOutputDispatcherImpl::Shutdown() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // Cancel any pending tasks to close paused streams or create new ones.
- weak_this_.InvalidateWeakPtrs();
-
- // No AudioOutputProxy objects should hold a reference to us when we get
- // to this stage.
- DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
-
- AudioOutputStreamList::iterator it = idle_streams_.begin();
- for (; it != idle_streams_.end(); ++it)
- (*it)->Close();
- idle_streams_.clear();
-
- it = pausing_streams_.begin();
- for (; it != pausing_streams_.end(); ++it)
- (*it)->Close();
- pausing_streams_.clear();
-}
-
-bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_);
- if (!stream)
- return false;
-
- if (!stream->Open()) {
- stream->Close();
- return false;
- }
- idle_streams_.push_back(stream);
- return true;
-}
-
-void AudioOutputDispatcherImpl::OpenTask() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- // Make sure that we have at least one stream allocated if there
- // are paused streams.
- if (paused_proxies_ > 0 && idle_streams_.empty() &&
- pausing_streams_.empty()) {
- CreateAndOpenStream();
- }
-
- close_timer_.Reset();
-}
-
-// This method is called by |close_timer_|.
-void AudioOutputDispatcherImpl::ClosePendingStreams() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- while (!idle_streams_.empty()) {
- idle_streams_.back()->Close();
- idle_streams_.pop_back();
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_dispatcher_impl.h b/src/media/audio/audio_output_dispatcher_impl.h
deleted file mode 100644
index 0eaa651..0000000
--- a/src/media/audio/audio_output_dispatcher_impl.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// AudioOutputDispatcherImpl is an implementation of AudioOutputDispatcher.
-//
-// To avoid opening and closing audio devices more frequently than necessary,
-// each dispatcher has a pool of inactive physical streams. A stream is closed
-// only if it hasn't been used for a certain period of time (specified via the
-// constructor).
-//
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
-
-#include <list>
-#include <map>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/timer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_output_dispatcher.h"
-#include "media/audio/audio_parameters.h"
-
-class MessageLoop;
-
-namespace media {
-
-class AudioOutputProxy;
-
-class MEDIA_EXPORT AudioOutputDispatcherImpl : public AudioOutputDispatcher {
- public:
- // |close_delay_ms| specifies delay after the stream is paused until
- // the audio device is closed.
- AudioOutputDispatcherImpl(AudioManager* audio_manager,
- const AudioParameters& params,
- const base::TimeDelta& close_delay);
-
- // Opens a new physical stream if there are no pending streams in
- // |idle_streams_|. Do not call Close() or Stop() if this method fails.
- virtual bool OpenStream() OVERRIDE;
-
- // If there are pending streams in |idle_streams_| then it reuses one of
- // them, otherwise creates a new one.
- virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) OVERRIDE;
-
- // Holds the physical stream temporarily in |pausing_streams_| and then
- // |stream| is added to the pool of pending streams (i.e. |idle_streams_|).
- virtual void StopStream(AudioOutputProxy* stream_proxy) OVERRIDE;
-
- virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) OVERRIDE;
-
- virtual void CloseStream(AudioOutputProxy* stream_proxy) OVERRIDE;
-
- virtual void Shutdown() OVERRIDE;
-
- private:
- typedef std::map<AudioOutputProxy*, AudioOutputStream*> AudioStreamMap;
- friend class base::RefCountedThreadSafe<AudioOutputDispatcherImpl>;
- virtual ~AudioOutputDispatcherImpl();
-
- friend class AudioOutputProxyTest;
-
- // Creates a new physical output stream, opens it and pushes to
- // |idle_streams_|. Returns false if the stream couldn't be created or
- // opened.
- bool CreateAndOpenStream();
-
- // A task scheduled by StartStream(). Opens a new stream and puts
- // it in |idle_streams_|.
- void OpenTask();
-
- // Before a stream is reused, it should sit idle for a bit. This task is
- // called once that time has elapsed.
- void StopStreamTask();
-
- // Called by |close_timer_|. Closes all pending streams.
- void ClosePendingStreams();
-
- base::TimeDelta pause_delay_;
- size_t paused_proxies_;
- typedef std::list<AudioOutputStream*> AudioOutputStreamList;
- AudioOutputStreamList idle_streams_;
- AudioOutputStreamList pausing_streams_;
-
- // Used to post delayed tasks to ourselves that we cancel inside Shutdown().
- base::WeakPtrFactory<AudioOutputDispatcherImpl> weak_this_;
- base::DelayTimer<AudioOutputDispatcherImpl> close_timer_;
-
- AudioStreamMap proxy_to_physical_map_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputDispatcherImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
diff --git a/src/media/audio/audio_output_ipc.cc b/src/media/audio/audio_output_ipc.cc
deleted file mode 100644
index 233a3b8..0000000
--- a/src/media/audio/audio_output_ipc.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_ipc.h"
-
-namespace media {
-
-AudioOutputIPCDelegate::~AudioOutputIPCDelegate() {}
-
-AudioOutputIPC::~AudioOutputIPC() {}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_ipc.h b/src/media/audio/audio_output_ipc.h
deleted file mode 100644
index 8543cdc..0000000
--- a/src/media/audio/audio_output_ipc.h
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
-
-#include "base/shared_memory.h"
-#include "base/sync_socket.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Contains IPC notifications for the state of the server side
-// (AudioOutputController) audio state changes and when an AudioOutputController
-// has been created. Implemented by AudioOutputDevice.
-class MEDIA_EXPORT AudioOutputIPCDelegate {
- public:
- // Current status of the audio output stream in the browser process. Browser
- // sends information about the current playback state and error to the
- // renderer process using this type.
- enum State {
- kPlaying,
- kPaused,
- kError
- };
-
- // Called when state of an audio stream has changed.
- virtual void OnStateChanged(State state) = 0;
-
- // Called when an audio stream has been created.
- // The shared memory |handle| points to a memory section that's used to
- // transfer audio buffers from the AudioOutputIPCDelegate back to the
- // AudioRendererHost. The implementation of OnStreamCreated takes ownership.
- // The |socket_handle| is used by AudioRendererHost to signal requests for
- // audio data to be written into the shared memory. The AudioOutputIPCDelegate
- // must read from this socket and provide audio whenever data (search for
- // "pending_bytes") is received.
- virtual void OnStreamCreated(base::SharedMemoryHandle handle,
- base::SyncSocket::Handle socket_handle,
- int length) = 0;
-
- // Called when the AudioOutputIPC object is going away and/or when the IPC
- // channel has been closed and no more ipc requests can be made.
- // Implementations must clear any references to the AudioOutputIPC object
- // at this point.
- virtual void OnIPCClosed() = 0;
-
- protected:
- virtual ~AudioOutputIPCDelegate();
-};
-
-// Provides IPC functionality for an AudioOutputDevice. The implementation
-// should asynchronously deliver the messages to an AudioOutputController object
-// (or create one in the case of CreateStream()), that may live in a separate
-// process.
-class MEDIA_EXPORT AudioOutputIPC {
- public:
- // Registers an AudioOutputIPCDelegate and returns a |stream_id| that must
- // be used with all other IPC functions in this interface.
- virtual int AddDelegate(AudioOutputIPCDelegate* delegate) = 0;
-
- // Unregisters a delegate that was previously registered via a call to
- // AddDelegate(). The audio stream should be in a closed state prior to
- // calling this function.
- virtual void RemoveDelegate(int stream_id) = 0;
-
- // Sends a request to create an AudioOutputController object in the peer
- // process, identify it by |stream_id| and configure it to use the specified
- // audio |params| and number of synchronized input channels.
- // Once the stream has been created, the implementation must
- // generate a notification to the AudioOutputIPCDelegate and call
- // OnStreamCreated().
- virtual void CreateStream(int stream_id,
- const AudioParameters& params,
- int input_channels) = 0;
-
- // Starts playing the stream. This should generate a call to
- // AudioOutputController::Play().
- virtual void PlayStream(int stream_id) = 0;
-
- // Pauses an audio stream. This should generate a call to
- // AudioOutputController::Pause().
- virtual void PauseStream(int stream_id) = 0;
-
- // "Flushes" the audio device. This should generate a call to
- // AudioOutputController::Flush().
- // TODO(tommi): This is currently neither implemented nor called. Remove?
- virtual void FlushStream(int stream_id) = 0;
-
- // Closes the audio stream and deletes the matching AudioOutputController
- // instance. Prior to deleting the AudioOutputController object, a call to
- // AudioOutputController::Close must be made.
- virtual void CloseStream(int stream_id) = 0;
-
- // Sets the volume of the audio stream.
- virtual void SetVolume(int stream_id, double volume) = 0;
-
- protected:
- virtual ~AudioOutputIPC();
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
diff --git a/src/media/audio/audio_output_mixer.cc b/src/media/audio/audio_output_mixer.cc
deleted file mode 100644
index edce4ea..0000000
--- a/src/media/audio/audio_output_mixer.cc
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_mixer.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/compiler_specific.h"
-#include "base/message_loop.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_output_proxy.h"
-#include "media/audio/audio_util.h"
-
-namespace media {
-
-AudioOutputMixer::AudioOutputMixer(AudioManager* audio_manager,
- const AudioParameters& params,
- const base::TimeDelta& close_delay)
- : AudioOutputDispatcher(audio_manager, params),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
- close_timer_(FROM_HERE,
- close_delay,
- weak_this_.GetWeakPtr(),
- &AudioOutputMixer::ClosePhysicalStream),
- pending_bytes_(0) {
- // TODO(enal): align data.
- mixer_data_.reset(new uint8[params_.GetBytesPerBuffer()]);
-}
-
-AudioOutputMixer::~AudioOutputMixer() {
-}
-
-bool AudioOutputMixer::OpenStream() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (physical_stream_.get())
- return true;
- AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_);
- if (!stream)
- return false;
- if (!stream->Open()) {
- stream->Close();
- return false;
- }
- pending_bytes_ = 0; // Just in case.
- physical_stream_.reset(stream);
- close_timer_.Reset();
- return true;
-}
-
-bool AudioOutputMixer::StartStream(
- AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // May need to re-open the physical stream if no active proxies and
- // enough time had pass.
- OpenStream();
- if (!physical_stream_.get())
- return false;
-
- double volume = 0.0;
- stream_proxy->GetVolume(&volume);
- bool should_start = proxies_.empty();
- {
- base::AutoLock lock(lock_);
- ProxyData* proxy_data = &proxies_[stream_proxy];
- proxy_data->audio_source_callback = callback;
- proxy_data->volume = volume;
- proxy_data->pending_bytes = 0;
- }
- // We cannot start physical stream under the lock,
- // OnMoreData() would try acquiring it...
- if (should_start) {
- physical_stream_->SetVolume(1.0);
- physical_stream_->Start(this);
- }
- return true;
-}
-
-void AudioOutputMixer::StopStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // Because of possible deadlock we cannot stop physical stream under the lock
- // (physical_stream_->Stop() can call OnError(), and it acquires the lock to
- // iterate through proxies), so acquire the lock, update proxy list, release
- // the lock, and only then stop physical stream if necessary.
- bool stop_physical_stream = false;
- {
- base::AutoLock lock(lock_);
- ProxyMap::iterator it = proxies_.find(stream_proxy);
- if (it != proxies_.end()) {
- proxies_.erase(it);
- stop_physical_stream = proxies_.empty();
- }
- }
- if (physical_stream_.get()) {
- if (stop_physical_stream) {
- physical_stream_->Stop();
- pending_bytes_ = 0; // Just in case.
- }
- close_timer_.Reset();
- }
-}
-
-void AudioOutputMixer::StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- ProxyMap::iterator it = proxies_.find(stream_proxy);
-
- // Do nothing if stream is not currently playing.
- if (it != proxies_.end()) {
- base::AutoLock lock(lock_);
- it->second.volume = volume;
- }
-}
-
-void AudioOutputMixer::CloseStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- StopStream(stream_proxy);
-}
-
-void AudioOutputMixer::Shutdown() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // Cancel any pending tasks to close physical stream.
- weak_this_.InvalidateWeakPtrs();
-
- while (!proxies_.empty()) {
- CloseStream(proxies_.begin()->first);
- }
- ClosePhysicalStream();
-
- // No AudioOutputProxy objects should hold a reference to us when we get
- // to this stage.
- DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
-}
-
-void AudioOutputMixer::ClosePhysicalStream() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (proxies_.empty() && physical_stream_.get() != NULL)
- physical_stream_.release()->Close();
-}
-
-// AudioSourceCallback implementation.
-uint32 AudioOutputMixer::OnMoreData(uint8* dest,
- uint32 max_size,
- AudioBuffersState buffers_state) {
- max_size = std::min(max_size,
- static_cast<uint32>(params_.GetBytesPerBuffer()));
- // TODO(enal): consider getting rid of lock as it is in time-critical code.
- // E.g. swap |proxies_| with local variable, and merge 2 lists
- // at the end. That would speed things up but complicate stopping
- // the stream.
- base::AutoLock lock(lock_);
-
- DCHECK_GE(pending_bytes_, buffers_state.pending_bytes);
- if (proxies_.empty()) {
- pending_bytes_ = buffers_state.pending_bytes;
- return 0;
- }
- uint32 actual_total_size = 0;
- uint32 bytes_per_sample = params_.bits_per_sample() >> 3;
-
- // Go through all the streams, getting data for every one of them
- // and mixing it into destination.
- // Minor optimization: for the first stream we are writing data directly into
- // destination. This way we don't have to mix the data when there is only one
- // active stream, and net win in other cases, too.
- bool first_stream = true;
- uint8* actual_dest = dest;
- for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
- ProxyData* proxy_data = &it->second;
-
- // If proxy's pending bytes are the same as pending bytes for combined
- // stream, both are either pre-buffering or in the steady state. In either
- // case new pending bytes for proxy is the same as new pending bytes for
- // combined stream.
- // Note: use >= instead of ==, that way is safer.
- if (proxy_data->pending_bytes >= pending_bytes_)
- proxy_data->pending_bytes = buffers_state.pending_bytes;
-
- // Note: there is no way we can deduce hardware_delay_bytes for the
- // particular proxy stream. Use zero instead.
- uint32 actual_size = proxy_data->audio_source_callback->OnMoreData(
- actual_dest,
- max_size,
- AudioBuffersState(proxy_data->pending_bytes, 0));
- if (actual_size == 0)
- continue;
- double volume = proxy_data->volume;
-
- // Different handling for first and all subsequent streams.
- if (first_stream) {
- if (volume != 1.0) {
- media::AdjustVolume(actual_dest,
- actual_size,
- params_.channels(),
- bytes_per_sample,
- volume);
- }
- if (actual_size < max_size)
- memset(dest + actual_size, 0, max_size - actual_size);
- first_stream = false;
- actual_dest = mixer_data_.get();
- actual_total_size = actual_size;
- } else {
- media::MixStreams(dest,
- actual_dest,
- actual_size,
- bytes_per_sample,
- volume);
- actual_total_size = std::max(actual_size, actual_total_size);
- }
- }
-
- // Now go through all proxies once again and increase pending_bytes
- // for each proxy. Could not do it earlier because we did not know
- // actual_total_size.
- for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
- it->second.pending_bytes += actual_total_size;
- }
- pending_bytes_ = buffers_state.pending_bytes + actual_total_size;
-
- return actual_total_size;
-}
-
-void AudioOutputMixer::OnError(AudioOutputStream* stream, int code) {
- base::AutoLock lock(lock_);
- for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
- it->second.audio_source_callback->OnError(it->first, code);
- }
-}
-
-void AudioOutputMixer::WaitTillDataReady() {
- base::AutoLock lock(lock_);
- for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
- it->second.audio_source_callback->WaitTillDataReady();
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_mixer.h b/src/media/audio/audio_output_mixer.h
deleted file mode 100644
index 4ddeeef..0000000
--- a/src/media/audio/audio_output_mixer.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// AudioOutputMixer is a class that implements browser-side audio mixer.
-// AudioOutputMixer implements both AudioOutputDispatcher and
-// AudioSourceCallback interfaces.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
-
-#include <map>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/timer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_output_dispatcher.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class MEDIA_EXPORT AudioOutputMixer
- : public AudioOutputDispatcher,
- public AudioOutputStream::AudioSourceCallback {
- public:
- AudioOutputMixer(AudioManager* audio_manager,
- const AudioParameters& params,
- const base::TimeDelta& close_delay);
-
- // AudioOutputDispatcher interface.
- virtual bool OpenStream() OVERRIDE;
- virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void StopStream(AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) OVERRIDE;
- virtual void CloseStream(AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void Shutdown() OVERRIDE;
-
- // AudioSourceCallback interface.
- virtual uint32 OnMoreData(uint8* dest,
- uint32 max_size,
- AudioBuffersState buffers_state) OVERRIDE;
- virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
- virtual void WaitTillDataReady() OVERRIDE;
-
- private:
- friend class base::RefCountedThreadSafe<AudioOutputMixer>;
- virtual ~AudioOutputMixer();
-
- // Called by |close_timer_|. Closes physical stream.
- void ClosePhysicalStream();
-
- // The |lock_| must be acquired whenever we modify |proxies_| in the audio
- // manager thread or accessing it in the hardware audio thread. Read in the
- // audio manager thread is safe.
- base::Lock lock_;
-
- // List of audio output proxies currently being played.
- // For every proxy we store aux structure containing data necessary for
- // mixing.
- struct ProxyData {
- AudioOutputStream::AudioSourceCallback* audio_source_callback;
- double volume;
- int pending_bytes;
- };
- typedef std::map<AudioOutputProxy*, ProxyData> ProxyMap;
- ProxyMap proxies_;
-
- // Physical stream for this mixer.
- scoped_ptr<AudioOutputStream> physical_stream_;
-
- // Temporary buffer used when mixing. Allocated in the constructor
- // to avoid constant allocation/deallocation in the callback.
- scoped_array<uint8> mixer_data_;
-
- // Used to post delayed tasks to ourselves that we cancel inside Shutdown().
- base::WeakPtrFactory<AudioOutputMixer> weak_this_;
- base::DelayTimer<AudioOutputMixer> close_timer_;
-
- // Size of data in all in-flight buffers.
- int pending_bytes_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputMixer);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
diff --git a/src/media/audio/audio_output_proxy.cc b/src/media/audio/audio_output_proxy.cc
deleted file mode 100644
index 3609079..0000000
--- a/src/media/audio/audio_output_proxy.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_proxy.h"
-
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_output_dispatcher.h"
-
-namespace media {
-
-AudioOutputProxy::AudioOutputProxy(AudioOutputDispatcher* dispatcher)
- : dispatcher_(dispatcher),
- state_(kCreated),
- volume_(1.0) {
-}
-
-AudioOutputProxy::~AudioOutputProxy() {
- DCHECK(CalledOnValidThread());
- DCHECK(state_ == kCreated || state_ == kClosed) << "State is: " << state_;
-}
-
-bool AudioOutputProxy::Open() {
- DCHECK(CalledOnValidThread());
- DCHECK_EQ(state_, kCreated);
-
- if (!dispatcher_->OpenStream()) {
- state_ = kOpenError;
- return false;
- }
-
- state_ = kOpened;
- return true;
-}
-
-void AudioOutputProxy::Start(AudioSourceCallback* callback) {
- DCHECK(CalledOnValidThread());
- DCHECK_EQ(state_, kOpened);
-
- if (!dispatcher_->StartStream(callback, this)) {
- state_ = kStartError;
- callback->OnError(this, 0);
- return;
- }
- state_ = kPlaying;
-}
-
-void AudioOutputProxy::Stop() {
- DCHECK(CalledOnValidThread());
- if (state_ != kPlaying)
- return;
-
- dispatcher_->StopStream(this);
- state_ = kOpened;
-}
-
-void AudioOutputProxy::SetVolume(double volume) {
- DCHECK(CalledOnValidThread());
- volume_ = volume;
- dispatcher_->StreamVolumeSet(this, volume);
-}
-
-void AudioOutputProxy::GetVolume(double* volume) {
- DCHECK(CalledOnValidThread());
- *volume = volume_;
-}
-
-void AudioOutputProxy::Close() {
- DCHECK(CalledOnValidThread());
- DCHECK(state_ == kCreated || state_ == kOpenError || state_ == kOpened ||
- state_ == kStartError);
-
- // kStartError means OpenStream() succeeded and the stream must be closed
- // before destruction.
- if (state_ != kCreated && state_ != kOpenError)
- dispatcher_->CloseStream(this);
-
- state_ = kClosed;
-
- // Delete the object now like is done in the Close() implementation of
- // physical stream objects. If we delete the object via DeleteSoon, we
- // unnecessarily complicate the Shutdown procedure of the
- // dispatcher+audio manager.
- delete this;
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_proxy.h b/src/media/audio/audio_output_proxy.h
deleted file mode 100644
index 86dab51..0000000
--- a/src/media/audio/audio_output_proxy.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/memory/ref_counted.h"
-#include "base/threading/non_thread_safe.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioOutputDispatcher;
-
-// AudioOutputProxy is an audio otput stream that uses resources more
-// efficiently than a regular audio output stream: it opens audio
-// device only when sound is playing, i.e. between Start() and Stop()
-// (there is still one physical stream per each audio output proxy in
-// playing state).
-//
-// AudioOutputProxy uses AudioOutputDispatcher to open and close
-// physical output streams.
-class MEDIA_EXPORT AudioOutputProxy
- : public AudioOutputStream,
- public NON_EXPORTED_BASE(base::NonThreadSafe) {
- public:
- // Caller keeps ownership of |dispatcher|.
- explicit AudioOutputProxy(AudioOutputDispatcher* dispatcher);
-
- // AudioOutputStream interface.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
- virtual void Close() OVERRIDE;
-
- private:
- enum State {
- kCreated,
- kOpened,
- kPlaying,
- kClosed,
- kOpenError,
- kStartError,
- };
-
- virtual ~AudioOutputProxy();
-
- scoped_refptr<AudioOutputDispatcher> dispatcher_;
- State state_;
-
- // Need to save volume here, so that we can restore it in case the stream
- // is stopped, and then started again.
- double volume_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputProxy);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
diff --git a/src/media/audio/audio_output_proxy_unittest.cc b/src/media/audio/audio_output_proxy_unittest.cc
deleted file mode 100644
index 6c13856..0000000
--- a/src/media/audio/audio_output_proxy_unittest.cc
+++ /dev/null
@@ -1,900 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-
-#include "base/command_line.h"
-#include "base/message_loop.h"
-#include "base/message_loop_proxy.h"
-#include "media/audio/audio_output_dispatcher_impl.h"
-#include "media/audio/audio_output_proxy.h"
-#include "media/audio/audio_output_resampler.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/fake_audio_output_stream.h"
-#include "media/base/media_switches.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// TODO(dalecurtis): Temporarily disabled while switching pipeline to use float,
-// http://crbug.com/114700
-#if defined(ENABLE_AUDIO_MIXER)
-#include "media/audio/audio_output_mixer.h"
-#endif
-
-using ::testing::_;
-using ::testing::AllOf;
-using ::testing::DoAll;
-using ::testing::Field;
-using ::testing::Mock;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::SetArrayArgument;
-using media::AudioBus;
-using media::AudioBuffersState;
-using media::AudioInputStream;
-using media::AudioManager;
-using media::AudioManagerBase;
-using media::AudioOutputDispatcher;
-using media::AudioOutputProxy;
-using media::AudioOutputStream;
-using media::AudioParameters;
-using media::FakeAudioOutputStream;
-
-namespace {
-
-static const int kTestCloseDelayMs = 100;
-
-// Used in the test where we don't want a stream to be closed unexpectedly.
-static const int kTestBigCloseDelaySeconds = 1000;
-
-// Delay between callbacks to AudioSourceCallback::OnMoreData.
-static const int kOnMoreDataCallbackDelayMs = 10;
-
-// Let start run long enough for many OnMoreData callbacks to occur.
-static const int kStartRunTimeMs = kOnMoreDataCallbackDelayMs * 10;
-
-class MockAudioOutputStream : public AudioOutputStream {
- public:
- MockAudioOutputStream(AudioManagerBase* manager,
- const AudioParameters& params)
- : start_called_(false),
- stop_called_(false),
- params_(params),
- fake_output_stream_(
- FakeAudioOutputStream::MakeFakeStream(manager, params_)) {
- }
-
- void Start(AudioSourceCallback* callback) {
- start_called_ = true;
- fake_output_stream_->Start(callback);
- }
-
- void Stop() {
- stop_called_ = true;
- fake_output_stream_->Stop();
- }
-
- ~MockAudioOutputStream() {}
-
- bool start_called() { return start_called_; }
- bool stop_called() { return stop_called_; }
-
- MOCK_METHOD0(Open, bool());
- MOCK_METHOD1(SetVolume, void(double volume));
- MOCK_METHOD1(GetVolume, void(double* volume));
- MOCK_METHOD0(Close, void());
-
- private:
- bool start_called_;
- bool stop_called_;
- AudioParameters params_;
- scoped_ptr<AudioOutputStream> fake_output_stream_;
-};
-
-class MockAudioManager : public AudioManagerBase {
- public:
- MockAudioManager() {}
- virtual ~MockAudioManager() {
- Shutdown();
- }
-
- MOCK_METHOD0(HasAudioOutputDevices, bool());
- MOCK_METHOD0(HasAudioInputDevices, bool());
- MOCK_METHOD0(GetAudioInputDeviceModel, string16());
- MOCK_METHOD1(MakeAudioOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD1(MakeAudioOutputStreamProxy, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD2(MakeAudioInputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
- MOCK_METHOD0(CanShowAudioInputSettings, bool());
- MOCK_METHOD0(ShowAudioInputSettings, void());
- MOCK_METHOD0(GetMessageLoop, scoped_refptr<base::MessageLoopProxy>());
- MOCK_METHOD1(GetAudioInputDeviceNames, void(
- media::AudioDeviceNames* device_name));
- MOCK_METHOD0(IsRecordingInProcess, bool());
-
- MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD1(MakeLowLatencyOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD2(MakeLinearInputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
- MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
-};
-
-class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
- public:
- int OnMoreData(AudioBus* audio_bus, AudioBuffersState buffers_state) {
- audio_bus->Zero();
- return audio_bus->frames();
- }
- int OnMoreIOData(AudioBus* source, AudioBus* dest,
- AudioBuffersState buffers_state) {
- return OnMoreData(dest, buffers_state);
- }
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-} // namespace
-
-namespace media {
-
-class AudioOutputProxyTest : public testing::Test {
- protected:
- virtual void SetUp() {
- EXPECT_CALL(manager_, GetMessageLoop())
- .WillRepeatedly(Return(message_loop_.message_loop_proxy()));
- InitDispatcher(base::TimeDelta::FromMilliseconds(kTestCloseDelayMs));
- }
-
- virtual void TearDown() {
- // All paused proxies should have been closed at this point.
- EXPECT_EQ(0u, dispatcher_impl_->paused_proxies_);
-
- // This is necessary to free all proxy objects that have been
- // closed by the test.
- message_loop_.RunUntilIdle();
- }
-
- virtual void InitDispatcher(base::TimeDelta close_delay) {
- // Use a low sample rate and large buffer size when testing otherwise the
- // FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
- // RunUntilIdle() will never terminate.
- params_ = AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO, 8000, 16, 2048);
- dispatcher_impl_ = new AudioOutputDispatcherImpl(&manager(),
- params_,
- close_delay);
-#if defined(ENABLE_AUDIO_MIXER)
- mixer_ = new AudioOutputMixer(&manager(), params_, close_delay);
-#endif
-
- // Necessary to know how long the dispatcher will wait before posting
- // StopStreamTask.
- pause_delay_ = dispatcher_impl_->pause_delay_;
- }
-
- virtual void OnStart() {}
-
- MockAudioManager& manager() {
- return manager_;
- }
-
- // Wait for the close timer to fire.
- void WaitForCloseTimer(const int timer_delay_ms) {
- message_loop_.RunUntilIdle(); // OpenTask() may reset the timer.
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(timer_delay_ms) * 2);
- message_loop_.RunUntilIdle();
- }
-
- // Methods that do actual tests.
- void OpenAndClose(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy->Open());
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
- }
-
- // Create a stream, and then calls Start() and Stop().
- void StartAndStop(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy->Open());
-
- proxy->Start(&callback_);
- OnStart();
- proxy->Stop();
-
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
- EXPECT_TRUE(stream.stop_called());
- EXPECT_TRUE(stream.start_called());
- }
-
- // Verify that the stream is closed after Stop is called.
- void CloseAfterStop(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy->Open());
-
- proxy->Start(&callback_);
- OnStart();
- proxy->Stop();
-
- // Wait for StopStream() to post StopStreamTask().
- base::PlatformThread::Sleep(pause_delay_ * 2);
- WaitForCloseTimer(kTestCloseDelayMs);
-
- // Verify expectation before calling Close().
- Mock::VerifyAndClear(&stream);
-
- proxy->Close();
- EXPECT_TRUE(stream.stop_called());
- EXPECT_TRUE(stream.start_called());
- }
-
- // Create two streams, but don't start them. Only one device must be open.
- void TwoStreams(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
- proxy1->Close();
- proxy2->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
- EXPECT_FALSE(stream.stop_called());
- EXPECT_FALSE(stream.start_called());
- }
-
- // Open() method failed.
- void OpenFailed(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(false));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher);
- EXPECT_FALSE(proxy->Open());
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
- EXPECT_FALSE(stream.stop_called());
- EXPECT_FALSE(stream.start_called());
- }
-
- void CreateAndWait(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy->Open());
-
- // Simulate a delay.
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(kTestCloseDelayMs) * 2);
- message_loop_.RunUntilIdle();
-
- // Verify expectation before calling Close().
- Mock::VerifyAndClear(&stream);
-
- proxy->Close();
- EXPECT_FALSE(stream.stop_called());
- EXPECT_FALSE(stream.start_called());
- }
-
- void TwoStreams_OnePlaying(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream1(&manager_, params_);
- MockAudioOutputStream stream2(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream1))
- .WillOnce(Return(&stream2));
-
- EXPECT_CALL(stream1, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream1, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream1, Close())
- .Times(1);
-
- EXPECT_CALL(stream2, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream2, Close())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
-
- proxy1->Start(&callback_);
- message_loop_.RunUntilIdle();
- OnStart();
- proxy1->Stop();
-
- proxy1->Close();
- proxy2->Close();
- EXPECT_TRUE(stream1.stop_called());
- EXPECT_TRUE(stream1.start_called());
- EXPECT_FALSE(stream2.stop_called());
- EXPECT_FALSE(stream2.start_called());
- }
-
- void TwoStreams_BothPlaying(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream1(&manager_, params_);
- MockAudioOutputStream stream2(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream1))
- .WillOnce(Return(&stream2));
-
- EXPECT_CALL(stream1, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream1, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream1, Close())
- .Times(1);
-
- EXPECT_CALL(stream2, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream2, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream2, Close())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
-
- proxy1->Start(&callback_);
- proxy2->Start(&callback_);
- OnStart();
- proxy1->Stop();
- proxy2->Stop();
-
- proxy1->Close();
- proxy2->Close();
- EXPECT_TRUE(stream1.stop_called());
- EXPECT_TRUE(stream1.start_called());
- EXPECT_TRUE(stream2.stop_called());
- EXPECT_TRUE(stream2.start_called());
- }
-
- void StartFailed(AudioOutputDispatcher* dispatcher) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_impl_);
- EXPECT_TRUE(proxy->Open());
-
- // Simulate a delay.
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(kTestCloseDelayMs) * 2);
- message_loop_.RunUntilIdle();
-
- // Verify expectation before calling Close().
- Mock::VerifyAndClear(&stream);
-
- // |stream| is closed at this point. Start() should reopen it again.
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(reinterpret_cast<AudioOutputStream*>(NULL)));
-
- EXPECT_CALL(callback_, OnError(_, _))
- .Times(1);
-
- proxy->Start(&callback_);
-
- Mock::VerifyAndClear(&callback_);
-
- proxy->Close();
- }
-
- MessageLoop message_loop_;
- scoped_refptr<AudioOutputDispatcherImpl> dispatcher_impl_;
-#if defined(ENABLE_AUDIO_MIXER)
- scoped_refptr<AudioOutputMixer> mixer_;
-#endif
- base::TimeDelta pause_delay_;
- MockAudioManager manager_;
- MockAudioSourceCallback callback_;
- AudioParameters params_;
-};
-
-class AudioOutputResamplerTest : public AudioOutputProxyTest {
- public:
- virtual void TearDown() {
- AudioOutputProxyTest::TearDown();
- }
-
- virtual void InitDispatcher(base::TimeDelta close_delay) {
- AudioOutputProxyTest::InitDispatcher(close_delay);
- // Use a low sample rate and large buffer size when testing otherwise the
- // FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
- // RunUntilIdle() will never terminate.
- resampler_params_ = AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
- 16000, 16, 1024);
- resampler_ = new AudioOutputResampler(
- &manager(), params_, resampler_params_, close_delay);
- }
-
- virtual void OnStart() {
- // Let start run for a bit.
- message_loop_.RunUntilIdle();
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(kStartRunTimeMs));
- }
-
- protected:
- AudioParameters resampler_params_;
- scoped_refptr<AudioOutputResampler> resampler_;
-};
-
-TEST_F(AudioOutputProxyTest, CreateAndClose) {
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_impl_);
- proxy->Close();
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, CreateAndClose_Mixer) {
- AudioOutputProxy* proxy = new AudioOutputProxy(mixer_);
- proxy->Close();
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, CreateAndClose) {
- AudioOutputProxy* proxy = new AudioOutputProxy(resampler_);
- proxy->Close();
-}
-
-TEST_F(AudioOutputProxyTest, OpenAndClose) {
- OpenAndClose(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, OpenAndClose_Mixer) {
- OpenAndClose(mixer_);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, OpenAndClose) {
- OpenAndClose(resampler_);
-}
-
-// Create a stream, and verify that it is closed after kTestCloseDelayMs.
-// if it doesn't start playing.
-TEST_F(AudioOutputProxyTest, CreateAndWait) {
- CreateAndWait(dispatcher_impl_);
-}
-
-// Create a stream, and verify that it is closed after kTestCloseDelayMs.
-// if it doesn't start playing.
-TEST_F(AudioOutputResamplerTest, CreateAndWait) {
- CreateAndWait(resampler_);
-}
-
-TEST_F(AudioOutputProxyTest, StartAndStop) {
- StartAndStop(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, StartAndStop_Mixer) {
- StartAndStop(mixer_);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, StartAndStop) {
- StartAndStop(resampler_);
-}
-
-TEST_F(AudioOutputProxyTest, CloseAfterStop) {
- CloseAfterStop(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, CloseAfterStop_Mixer) {
- CloseAfterStop(mixer_);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, CloseAfterStop) {
- CloseAfterStop(resampler_);
-}
-
-TEST_F(AudioOutputProxyTest, TwoStreams) {
- TwoStreams(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, TwoStreams_Mixer) {
- TwoStreams(mixer_);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, TwoStreams) {
- TwoStreams(resampler_);
-}
-
-// Two streams: verify that second stream is allocated when the first
-// starts playing.
-TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying) {
- InitDispatcher(base::TimeDelta::FromSeconds(kTestBigCloseDelaySeconds));
- TwoStreams_OnePlaying(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-// Two streams: verify that only one device will be created.
-TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying_Mixer) {
- MockAudioOutputStream stream(&manager_, params_);
-
- InitDispatcher(base::TimeDelta::FromMilliseconds(kTestCloseDelayMs));
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
-
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Start(_))
- .Times(1);
- EXPECT_CALL(stream, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream, Stop())
- .Times(1);
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(mixer_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
-
- proxy1->Start(&callback_);
- proxy1->Stop();
-
- proxy1->Close();
- proxy2->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, TwoStreams_OnePlaying) {
- InitDispatcher(base::TimeDelta::FromSeconds(kTestBigCloseDelaySeconds));
- TwoStreams_OnePlaying(resampler_);
-}
-
-// Two streams, both are playing. Dispatcher should not open a third stream.
-TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
- InitDispatcher(base::TimeDelta::FromSeconds(kTestBigCloseDelaySeconds));
- TwoStreams_BothPlaying(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-// Two streams, both are playing. Still have to use single device.
-// Also verifies that every proxy stream gets its own pending_bytes.
-TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying_Mixer) {
- MockAudioOutputStream stream(&manager_, params_);
-
- InitDispatcher(base::TimeDelta::FromMilliseconds(kTestCloseDelayMs));
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
-
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Start(_))
- .Times(1);
- EXPECT_CALL(stream, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream, Stop())
- .Times(1);
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(mixer_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
-
- proxy1->Start(&callback_);
-
- // Mute the proxy. Resulting stream should still have correct length.
- proxy1->SetVolume(0.0);
-
- uint8 zeroes[4] = {0, 0, 0, 0};
- uint8 buf1[4] = {0};
- EXPECT_CALL(callback_,
- OnMoreData(NotNull(), 4,
- AllOf(Field(&AudioBuffersState::pending_bytes, 0),
- Field(&AudioBuffersState::hardware_delay_bytes, 0))))
- .WillOnce(DoAll(SetArrayArgument<0>(zeroes, zeroes + sizeof(zeroes)),
- Return(4)));
- mixer_->OnMoreData(buf1, sizeof(buf1), AudioBuffersState(0, 0));
- proxy2->Start(&callback_);
- uint8 buf2[4] = {0};
- EXPECT_CALL(callback_,
- OnMoreData(NotNull(), 4,
- AllOf(Field(&AudioBuffersState::pending_bytes, 4),
- Field(&AudioBuffersState::hardware_delay_bytes, 0))))
- .WillOnce(DoAll(SetArrayArgument<0>(zeroes, zeroes + sizeof(zeroes)),
- Return(4)));
- EXPECT_CALL(callback_,
- OnMoreData(NotNull(), 4,
- AllOf(Field(&AudioBuffersState::pending_bytes, 0),
- Field(&AudioBuffersState::hardware_delay_bytes, 0))))
- .WillOnce(DoAll(SetArrayArgument<0>(zeroes, zeroes + sizeof(zeroes)),
- Return(4)));
- mixer_->OnMoreData(buf2, sizeof(buf2), AudioBuffersState(4, 0));
- proxy1->Stop();
- proxy2->Stop();
-
- proxy1->Close();
- proxy2->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, TwoStreams_BothPlaying) {
- InitDispatcher(base::TimeDelta::FromSeconds(kTestBigCloseDelaySeconds));
- TwoStreams_BothPlaying(resampler_);
-}
-
-TEST_F(AudioOutputProxyTest, OpenFailed) {
- OpenFailed(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-TEST_F(AudioOutputProxyTest, OpenFailed_Mixer) {
- OpenFailed(mixer_);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, OpenFailed) {
- CommandLine::ForCurrentProcess()->AppendSwitch(
- switches::kDisableAudioFallback);
- OpenFailed(resampler_);
-}
-
-// Start() method failed.
-TEST_F(AudioOutputProxyTest, StartFailed) {
- StartFailed(dispatcher_impl_);
-}
-
-#if defined(ENABLE_AUDIO_MIXER)
-// Start() method failed.
-TEST_F(AudioOutputProxyTest, StartFailed_Mixer) {
- MockAudioOutputStream stream(&manager_, params_);
-
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
- EXPECT_CALL(stream, Start(_))
- .Times(1);
- EXPECT_CALL(stream, SetVolume(_))
- .Times(1);
- EXPECT_CALL(stream, Stop())
- .Times(1);
-
- AudioOutputProxy* proxy1 = new AudioOutputProxy(mixer_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
- proxy1->Start(&callback_);
- proxy1->Stop();
- proxy1->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-
- // Verify expectation before continueing.
- Mock::VerifyAndClear(&stream);
-
- // |stream| is closed at this point. Start() should reopen it again.
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(reinterpret_cast<AudioOutputStream*>(NULL)));
-
- EXPECT_CALL(callback_, OnError(_, _))
- .Times(1);
-
- proxy2->Start(&callback_);
-
- Mock::VerifyAndClear(&callback_);
-
- proxy2->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-#endif
-
-TEST_F(AudioOutputResamplerTest, StartFailed) {
- StartFailed(resampler_);
-}
-
-// Simulate AudioOutputStream::Create() failure with a low latency stream and
-// ensure AudioOutputResampler falls back to the high latency path.
-TEST_F(AudioOutputResamplerTest, LowLatencyCreateFailedFallback) {
- MockAudioOutputStream stream(&manager_, params_);
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .Times(2)
- .WillOnce(Return(static_cast<AudioOutputStream*>(NULL)))
- .WillRepeatedly(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(resampler_);
- EXPECT_TRUE(proxy->Open());
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-
-// Simulate AudioOutputStream::Open() failure with a low latency stream and
-// ensure AudioOutputResampler falls back to the high latency path.
-TEST_F(AudioOutputResamplerTest, LowLatencyOpenFailedFallback) {
- MockAudioOutputStream failed_stream(&manager_, params_);
- MockAudioOutputStream okay_stream(&manager_, params_);
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .Times(2)
- .WillOnce(Return(&failed_stream))
- .WillRepeatedly(Return(&okay_stream));
- EXPECT_CALL(failed_stream, Open())
- .WillOnce(Return(false));
- EXPECT_CALL(failed_stream, Close())
- .Times(1);
- EXPECT_CALL(okay_stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(okay_stream, Close())
- .Times(1);
-
- AudioOutputProxy* proxy = new AudioOutputProxy(resampler_);
- EXPECT_TRUE(proxy->Open());
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-
-// Simulate failures to open both the low latency and the fallback high latency
-// stream and ensure AudioOutputResampler terminates normally.
-TEST_F(AudioOutputResamplerTest, LowLatencyFallbackFailed) {
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .Times(2)
- .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
-
- AudioOutputProxy* proxy = new AudioOutputProxy(resampler_);
- EXPECT_FALSE(proxy->Open());
- proxy->Close();
- WaitForCloseTimer(kTestCloseDelayMs);
-}
-
-// Simulate an eventual OpenStream() failure; i.e. successful OpenStream() calls
-// eventually followed by one which fails; root cause of http://crbug.com/150619
-TEST_F(AudioOutputResamplerTest, LowLatencyOpenEventuallyFails) {
- MockAudioOutputStream stream1(&manager_, params_);
- MockAudioOutputStream stream2(&manager_, params_);
- MockAudioOutputStream stream3(&manager_, params_);
-
- // Setup the mock such that all three streams are successfully created.
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream1))
- .WillOnce(Return(&stream2))
- .WillOnce(Return(&stream3))
- .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
-
- // Stream1 should be able to successfully open and start.
- EXPECT_CALL(stream1, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream1, Close())
- .Times(1);
- EXPECT_CALL(stream1, SetVolume(_))
- .Times(1);
-
- // Stream2 should also be able to successfully open and start.
- EXPECT_CALL(stream2, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream2, Close())
- .Times(1);
- EXPECT_CALL(stream2, SetVolume(_))
- .Times(1);
-
- // Stream3 should fail on Open() (yet still be closed since
- // MakeAudioOutputStream returned a valid AudioOutputStream object).
- EXPECT_CALL(stream3, Open())
- .WillOnce(Return(false));
- EXPECT_CALL(stream3, Close())
- .Times(1);
-
- // Open and start the first proxy and stream.
- AudioOutputProxy* proxy1 = new AudioOutputProxy(resampler_);
- EXPECT_TRUE(proxy1->Open());
- proxy1->Start(&callback_);
- OnStart();
-
- // Open and start the second proxy and stream.
- AudioOutputProxy* proxy2 = new AudioOutputProxy(resampler_);
- EXPECT_TRUE(proxy2->Open());
- proxy2->Start(&callback_);
- OnStart();
-
- // Attempt to open the third stream which should fail.
- AudioOutputProxy* proxy3 = new AudioOutputProxy(resampler_);
- EXPECT_FALSE(proxy3->Open());
-
- // Perform the required Stop()/Close() shutdown dance for each proxy. Under
- // the hood each proxy should correctly call CloseStream() if OpenStream()
- // succeeded or not.
- proxy3->Stop();
- proxy3->Close();
- proxy2->Stop();
- proxy2->Close();
- proxy1->Stop();
- proxy1->Close();
-
- // Wait for all of the messages to fly and then verify stream behavior.
- WaitForCloseTimer(kTestCloseDelayMs);
- EXPECT_TRUE(stream1.stop_called());
- EXPECT_TRUE(stream1.start_called());
- EXPECT_TRUE(stream2.stop_called());
- EXPECT_TRUE(stream2.start_called());
- EXPECT_FALSE(stream3.stop_called());
- EXPECT_FALSE(stream3.start_called());
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_resampler.cc b/src/media/audio/audio_output_resampler.cc
deleted file mode 100644
index 4734e40..0000000
--- a/src/media/audio/audio_output_resampler.cc
+++ /dev/null
@@ -1,397 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_output_resampler.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/command_line.h"
-#include "base/compiler_specific.h"
-#include "base/message_loop.h"
-#include "base/metrics/histogram.h"
-#include "base/time.h"
-#include "build/build_config.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_output_dispatcher_impl.h"
-#include "media/audio/audio_output_proxy.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/sample_rates.h"
-#include "media/base/audio_converter.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-#if defined(OS_WIN)
-#include "media/audio/win/core_audio_util_win.h"
-#endif
-
-namespace media {
-
-class OnMoreDataConverter
- : public AudioOutputStream::AudioSourceCallback,
- public AudioConverter::InputCallback {
- public:
- OnMoreDataConverter(const AudioParameters& input_params,
- const AudioParameters& output_params);
- virtual ~OnMoreDataConverter();
-
- // AudioSourceCallback interface.
- virtual int OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE;
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE;
- virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
- virtual void WaitTillDataReady() OVERRIDE;
-
- // Sets |source_callback_|. If this is not a new object, then Stop() must be
- // called before Start().
- void Start(AudioOutputStream::AudioSourceCallback* callback);
-
- // Clears |source_callback_| and flushes the resampler.
- void Stop();
-
- private:
- // AudioConverter::InputCallback implementation.
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE;
-
- // Ratio of input bytes to output bytes used to correct playback delay with
- // regard to buffering and resampling.
- double io_ratio_;
-
- // Source callback and associated lock.
- base::Lock source_lock_;
- AudioOutputStream::AudioSourceCallback* source_callback_;
-
- // |source| passed to OnMoreIOData() which should be passed downstream.
- AudioBus* source_bus_;
-
- // Last AudioBuffersState object received via OnMoreData(), used to correct
- // playback delay by ProvideInput() and passed on to |source_callback_|.
- AudioBuffersState current_buffers_state_;
-
- const int input_bytes_per_second_;
-
- // Handles resampling, buffering, and channel mixing between input and output
- // parameters.
- AudioConverter audio_converter_;
-
- // If we're using WaveOut on Windows' we always have to wait for DataReady()
- // before calling |source_callback_|.
- bool waveout_wait_hack_;
-
- DISALLOW_COPY_AND_ASSIGN(OnMoreDataConverter);
-};
-
-// Record UMA statistics for hardware output configuration.
-static void RecordStats(const AudioParameters& output_params) {
- UMA_HISTOGRAM_ENUMERATION(
- "Media.HardwareAudioBitsPerChannel", output_params.bits_per_sample(),
- limits::kMaxBitsPerSample);
- UMA_HISTOGRAM_ENUMERATION(
- "Media.HardwareAudioChannelLayout", output_params.channel_layout(),
- CHANNEL_LAYOUT_MAX);
- UMA_HISTOGRAM_ENUMERATION(
- "Media.HardwareAudioChannelCount", output_params.channels(),
- limits::kMaxChannels);
-
- AudioSampleRate asr = media::AsAudioSampleRate(output_params.sample_rate());
- if (asr != kUnexpectedAudioSampleRate) {
- UMA_HISTOGRAM_ENUMERATION(
- "Media.HardwareAudioSamplesPerSecond", asr, kUnexpectedAudioSampleRate);
- } else {
- UMA_HISTOGRAM_COUNTS(
- "Media.HardwareAudioSamplesPerSecondUnexpected",
- output_params.sample_rate());
- }
-}
-
-// Record UMA statistics for hardware output configuration after fallback.
-static void RecordFallbackStats(const AudioParameters& output_params) {
- UMA_HISTOGRAM_BOOLEAN("Media.FallbackToHighLatencyAudioPath", true);
- UMA_HISTOGRAM_ENUMERATION(
- "Media.FallbackHardwareAudioBitsPerChannel",
- output_params.bits_per_sample(), limits::kMaxBitsPerSample);
- UMA_HISTOGRAM_ENUMERATION(
- "Media.FallbackHardwareAudioChannelLayout",
- output_params.channel_layout(), CHANNEL_LAYOUT_MAX);
- UMA_HISTOGRAM_ENUMERATION(
- "Media.FallbackHardwareAudioChannelCount",
- output_params.channels(), limits::kMaxChannels);
-
- AudioSampleRate asr = media::AsAudioSampleRate(output_params.sample_rate());
- if (asr != kUnexpectedAudioSampleRate) {
- UMA_HISTOGRAM_ENUMERATION(
- "Media.FallbackHardwareAudioSamplesPerSecond",
- asr, kUnexpectedAudioSampleRate);
- } else {
- UMA_HISTOGRAM_COUNTS(
- "Media.FallbackHardwareAudioSamplesPerSecondUnexpected",
- output_params.sample_rate());
- }
-}
-
-// Converts low latency based |output_params| into high latency appropriate
-// output parameters in error situations.
-static AudioParameters SetupFallbackParams(
- const AudioParameters& input_params, const AudioParameters& output_params) {
- // Choose AudioParameters appropriate for opening the device in high latency
- // mode. |kMinLowLatencyFrameSize| is arbitrarily based on Pepper Flash's
- // MAXIMUM frame size for low latency.
- static const int kMinLowLatencyFrameSize = 2048;
- int frames_per_buffer = std::min(
- std::max(input_params.frames_per_buffer(), kMinLowLatencyFrameSize),
- static_cast<int>(
- GetHighLatencyOutputBufferSize(input_params.sample_rate())));
-
- return AudioParameters(
- AudioParameters::AUDIO_PCM_LINEAR, input_params.channel_layout(),
- input_params.sample_rate(), input_params.bits_per_sample(),
- frames_per_buffer);
-}
-
-AudioOutputResampler::AudioOutputResampler(AudioManager* audio_manager,
- const AudioParameters& input_params,
- const AudioParameters& output_params,
- const base::TimeDelta& close_delay)
- : AudioOutputDispatcher(audio_manager, input_params),
- close_delay_(close_delay),
- output_params_(output_params),
- streams_opened_(false) {
- DCHECK(input_params.IsValid());
- DCHECK(output_params.IsValid());
- DCHECK_EQ(output_params_.format(), AudioParameters::AUDIO_PCM_LOW_LATENCY);
-
- // Record UMA statistics for the hardware configuration.
- RecordStats(output_params);
-
- Initialize();
-}
-
-AudioOutputResampler::~AudioOutputResampler() {
- DCHECK(callbacks_.empty());
-}
-
-void AudioOutputResampler::Initialize() {
- DCHECK(!streams_opened_);
- DCHECK(callbacks_.empty());
- dispatcher_ = new AudioOutputDispatcherImpl(
- audio_manager_, output_params_, close_delay_);
-}
-
-bool AudioOutputResampler::OpenStream() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (dispatcher_->OpenStream()) {
- // Only record the UMA statistic if we didn't fallback during construction
- // and only for the first stream we open.
- if (!streams_opened_ &&
- output_params_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
- UMA_HISTOGRAM_BOOLEAN("Media.FallbackToHighLatencyAudioPath", false);
- }
- streams_opened_ = true;
- return true;
- }
-
- // If we've already tried to open the stream in high latency mode or we've
- // successfully opened a stream previously, there's nothing more to be done.
- if (output_params_.format() == AudioParameters::AUDIO_PCM_LINEAR ||
- streams_opened_ || !callbacks_.empty()) {
- return false;
- }
-
- DCHECK_EQ(output_params_.format(), AudioParameters::AUDIO_PCM_LOW_LATENCY);
-
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDisableAudioFallback)) {
- LOG(ERROR) << "Open failed and automatic fallback to high latency audio "
- << "path is disabled, aborting.";
- return false;
- }
-
- DLOG(ERROR) << "Unable to open audio device in low latency mode. Falling "
- << "back to high latency audio output.";
-
- // Record UMA statistics about the hardware which triggered the failure so
- // we can debug and triage later.
- RecordFallbackStats(output_params_);
- output_params_ = SetupFallbackParams(params_, output_params_);
- Initialize();
-
- // Retry, if this fails, there's nothing left to do but report the error back.
- return dispatcher_->OpenStream();
-}
-
-bool AudioOutputResampler::StartStream(
- AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- OnMoreDataConverter* resampler_callback = NULL;
- CallbackMap::iterator it = callbacks_.find(stream_proxy);
- if (it == callbacks_.end()) {
- resampler_callback = new OnMoreDataConverter(params_, output_params_);
- callbacks_[stream_proxy] = resampler_callback;
- } else {
- resampler_callback = it->second;
- }
- resampler_callback->Start(callback);
- return dispatcher_->StartStream(resampler_callback, stream_proxy);
-}
-
-void AudioOutputResampler::StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- dispatcher_->StreamVolumeSet(stream_proxy, volume);
-}
-
-void AudioOutputResampler::StopStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- dispatcher_->StopStream(stream_proxy);
-
- // Now that StopStream() has completed the underlying physical stream should
- // be stopped and no longer calling OnMoreData(), making it safe to Stop() the
- // OnMoreDataConverter.
- CallbackMap::iterator it = callbacks_.find(stream_proxy);
- if (it != callbacks_.end())
- it->second->Stop();
-}
-
-void AudioOutputResampler::CloseStream(AudioOutputProxy* stream_proxy) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- dispatcher_->CloseStream(stream_proxy);
-
- // We assume that StopStream() is always called prior to CloseStream(), so
- // that it is safe to delete the OnMoreDataConverter here.
- CallbackMap::iterator it = callbacks_.find(stream_proxy);
- if (it != callbacks_.end()) {
- delete it->second;
- callbacks_.erase(it);
- }
-}
-
-void AudioOutputResampler::Shutdown() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // No AudioOutputProxy objects should hold a reference to us when we get
- // to this stage.
- DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
-
- dispatcher_->Shutdown();
- DCHECK(callbacks_.empty());
-}
-
-OnMoreDataConverter::OnMoreDataConverter(const AudioParameters& input_params,
- const AudioParameters& output_params)
- : source_callback_(NULL),
- source_bus_(NULL),
- input_bytes_per_second_(input_params.GetBytesPerSecond()),
- audio_converter_(input_params, output_params, false),
- waveout_wait_hack_(false) {
- io_ratio_ =
- static_cast<double>(input_params.GetBytesPerSecond()) /
- output_params.GetBytesPerSecond();
-
- // TODO(dalecurtis): We should require all render side clients to use a
- // buffer size that's a multiple of the hardware buffer size scaled by the
- // request_sample_rate / hw_sample_rate. Doing so ensures each hardware
- // request for audio data results in only a single render side callback and
- // would allow us to remove this hack. See http://crbug.com/162207.
-#if defined(OS_WIN)
- waveout_wait_hack_ =
- output_params.format() == AudioParameters::AUDIO_PCM_LINEAR ||
- !CoreAudioUtil::IsSupported();
-#endif
-}
-
-OnMoreDataConverter::~OnMoreDataConverter() {}
-
-void OnMoreDataConverter::Start(
- AudioOutputStream::AudioSourceCallback* callback) {
- base::AutoLock auto_lock(source_lock_);
- DCHECK(!source_callback_);
- source_callback_ = callback;
-
- // While AudioConverter can handle multiple inputs, we're using it only with
- // a single input currently. Eventually this may be the basis for a browser
- // side mixer.
- audio_converter_.AddInput(this);
-}
-
-void OnMoreDataConverter::Stop() {
- base::AutoLock auto_lock(source_lock_);
- source_callback_ = NULL;
- audio_converter_.RemoveInput(this);
-}
-
-int OnMoreDataConverter::OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) {
- return OnMoreIOData(NULL, dest, buffers_state);
-}
-
-int OnMoreDataConverter::OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) {
- base::AutoLock auto_lock(source_lock_);
- // While we waited for |source_lock_| the callback might have been cleared.
- if (!source_callback_) {
- dest->Zero();
- return dest->frames();
- }
-
- source_bus_ = source;
- current_buffers_state_ = buffers_state;
- audio_converter_.Convert(dest);
-
- // Always return the full number of frames requested, ProvideInput_Locked()
- // will pad with silence if it wasn't able to acquire enough data.
- return dest->frames();
-}
-
-double OnMoreDataConverter::ProvideInput(AudioBus* dest,
- base::TimeDelta buffer_delay) {
- source_lock_.AssertAcquired();
-
- // Adjust playback delay to include |buffer_delay|.
- // TODO(dalecurtis): Stop passing bytes around, it doesn't make sense since
- // AudioBus is just float data. Use TimeDelta instead.
- AudioBuffersState new_buffers_state;
- new_buffers_state.pending_bytes =
- io_ratio_ * (current_buffers_state_.total_bytes() +
- buffer_delay.InSecondsF() * input_bytes_per_second_);
-
- if (waveout_wait_hack_)
- source_callback_->WaitTillDataReady();
-
- // Retrieve data from the original callback.
- int frames = source_callback_->OnMoreIOData(
- source_bus_, dest, new_buffers_state);
-
- // |source_bus_| should only be provided once.
- // TODO(dalecurtis, crogers): This is not a complete fix. If ProvideInput()
- // is called multiple times, we need to do something more clever here.
- source_bus_ = NULL;
-
- // Zero any unfilled frames if anything was filled, otherwise we'll just
- // return a volume of zero and let AudioConverter drop the output.
- if (frames > 0 && frames < dest->frames())
- dest->ZeroFramesPartial(frames, dest->frames() - frames);
-
- // TODO(dalecurtis): Return the correct volume here.
- return frames > 0 ? 1 : 0;
-}
-
-void OnMoreDataConverter::OnError(AudioOutputStream* stream, int code) {
- base::AutoLock auto_lock(source_lock_);
- if (source_callback_)
- source_callback_->OnError(stream, code);
-}
-
-void OnMoreDataConverter::WaitTillDataReady() {
- base::AutoLock auto_lock(source_lock_);
- if (source_callback_)
- source_callback_->WaitTillDataReady();
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_output_resampler.h b/src/media/audio/audio_output_resampler.h
deleted file mode 100644
index 057cf34..0000000
--- a/src/media/audio/audio_output_resampler.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
-#define MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
-
-#include <map>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_output_dispatcher.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class OnMoreDataConverter;
-
-// AudioOutputResampler is a browser-side resampling and buffering solution
-// which ensures audio data is always output at given parameters. See the
-// AudioConverter class for details on the conversion process.
-//
-// AOR works by intercepting the AudioSourceCallback provided to StartStream()
-// and redirecting it through an AudioConverter instance. AudioBuffersState is
-// adjusted for buffer delay caused by the conversion process.
-//
-// AOR will automatically fall back from AUDIO_PCM_LOW_LATENCY to
-// AUDIO_PCM_LINEAR if the output device fails to open at the requested output
-// parameters.
-//
-// TODO(dalecurtis): Ideally the low latency path will be as reliable as the
-// high latency path once we have channel mixing and support querying for the
-// hardware's configured bit depth. Monitor the UMA stats for fallback and
-// remove fallback support once it's stable. http://crbug.com/148418
-class MEDIA_EXPORT AudioOutputResampler : public AudioOutputDispatcher {
- public:
- AudioOutputResampler(AudioManager* audio_manager,
- const AudioParameters& input_params,
- const AudioParameters& output_params,
- const base::TimeDelta& close_delay);
-
- // AudioOutputDispatcher interface.
- virtual bool OpenStream() OVERRIDE;
- virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
- AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void StopStream(AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
- double volume) OVERRIDE;
- virtual void CloseStream(AudioOutputProxy* stream_proxy) OVERRIDE;
- virtual void Shutdown() OVERRIDE;
-
- private:
- friend class base::RefCountedThreadSafe<AudioOutputResampler>;
- virtual ~AudioOutputResampler();
-
- // Used to initialize and reinitialize |dispatcher_|.
- void Initialize();
-
- // Dispatcher to proxy all AudioOutputDispatcher calls too.
- scoped_refptr<AudioOutputDispatcher> dispatcher_;
-
- // Map of outstanding OnMoreDataConverter objects. A new object is created
- // on every StartStream() call and destroyed on CloseStream().
- typedef std::map<AudioOutputProxy*, OnMoreDataConverter*> CallbackMap;
- CallbackMap callbacks_;
-
- // Used by AudioOutputDispatcherImpl; kept so we can reinitialize on the fly.
- base::TimeDelta close_delay_;
-
- // AudioParameters used to setup the output stream.
- AudioParameters output_params_;
-
- // Whether any streams have been opened through |dispatcher_|, if so we can't
- // fallback on future OpenStream() failures.
- bool streams_opened_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioOutputResampler);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
diff --git a/src/media/audio/audio_parameters.cc b/src/media/audio/audio_parameters.cc
deleted file mode 100644
index 0d9263f..0000000
--- a/src/media/audio/audio_parameters.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_parameters.h"
-
-#include "media/base/limits.h"
-
-namespace media {
-
-AudioParameters::AudioParameters()
- : format_(AUDIO_PCM_LINEAR),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- sample_rate_(0),
- bits_per_sample_(0),
- frames_per_buffer_(0),
- channels_(0) {
-}
-
-AudioParameters::AudioParameters(Format format, ChannelLayout channel_layout,
- int sample_rate, int bits_per_sample,
- int frames_per_buffer)
- : format_(format),
- channel_layout_(channel_layout),
- sample_rate_(sample_rate),
- bits_per_sample_(bits_per_sample),
- frames_per_buffer_(frames_per_buffer),
- channels_(ChannelLayoutToChannelCount(channel_layout)) {
-}
-
-void AudioParameters::Reset(Format format, ChannelLayout channel_layout,
- int sample_rate, int bits_per_sample,
- int frames_per_buffer) {
- format_ = format;
- channel_layout_ = channel_layout;
- sample_rate_ = sample_rate;
- bits_per_sample_ = bits_per_sample;
- frames_per_buffer_ = frames_per_buffer;
- channels_ = ChannelLayoutToChannelCount(channel_layout);
-}
-
-bool AudioParameters::IsValid() const {
- return (format_ >= AUDIO_PCM_LINEAR) &&
- (format_ < AUDIO_LAST_FORMAT) &&
- (channels_ > 0) &&
- (channels_ <= media::limits::kMaxChannels) &&
- (channel_layout_ > CHANNEL_LAYOUT_UNSUPPORTED) &&
- (channel_layout_ < CHANNEL_LAYOUT_MAX) &&
- (sample_rate_ >= media::limits::kMinSampleRate) &&
- (sample_rate_ <= media::limits::kMaxSampleRate) &&
- (bits_per_sample_ > 0) &&
- (bits_per_sample_ <= media::limits::kMaxBitsPerSample) &&
- (frames_per_buffer_ > 0) &&
- (frames_per_buffer_ <= media::limits::kMaxSamplesPerPacket);
-}
-
-int AudioParameters::GetBytesPerBuffer() const {
- return frames_per_buffer_ * GetBytesPerFrame();
-}
-
-int AudioParameters::GetBytesPerSecond() const {
- return sample_rate_ * GetBytesPerFrame();
-}
-
-int AudioParameters::GetBytesPerFrame() const {
- return channels_ * bits_per_sample_ / 8;
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_parameters.h b/src/media/audio/audio_parameters.h
deleted file mode 100644
index 0225468..0000000
--- a/src/media/audio/audio_parameters.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_PARAMETERS_H_
-#define MEDIA_AUDIO_AUDIO_PARAMETERS_H_
-
-#include "base/basictypes.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-struct MEDIA_EXPORT AudioInputBufferParameters {
- double volume;
- uint32 size;
-};
-
-// Use a struct-in-struct approach to ensure that we can calculate the required
-// size as sizeof(AudioInputBufferParameters) + #(bytes in audio buffer) without
-// using packing.
-struct MEDIA_EXPORT AudioInputBuffer {
- AudioInputBufferParameters params;
- int8 audio[1];
-};
-
-class MEDIA_EXPORT AudioParameters {
- public:
- enum Format {
- AUDIO_PCM_LINEAR = 0, // PCM is 'raw' amplitude samples.
- AUDIO_PCM_LOW_LATENCY, // Linear PCM, low latency requested.
- AUDIO_FAKE, // Creates a fake AudioOutputStream object.
- AUDIO_VIRTUAL, // Creates a VirtualAudioInputStream object.
- // Applies to input streams only.
- AUDIO_LAST_FORMAT // Only used for validation of format.
- };
-
- enum {
- // Telephone quality sample rate, mostly for speech-only audio.
- kTelephoneSampleRate = 8000,
- // CD sampling rate is 44.1 KHz or conveniently 2x2x3x3x5x5x7x7.
- kAudioCDSampleRate = 44100,
- };
-
- AudioParameters();
- AudioParameters(Format format, ChannelLayout channel_layout,
- int sample_rate, int bits_per_sample,
- int frames_per_buffer);
- void Reset(Format format, ChannelLayout channel_layout,
- int sample_rate, int bits_per_sample,
- int frames_per_buffer);
-
- // Checks that all values are in the expected range. All limits are specified
- // in media::Limits.
- bool IsValid() const;
-
- // Returns size of audio buffer in bytes.
- int GetBytesPerBuffer() const;
-
- // Returns the number of bytes representing one second of audio.
- int GetBytesPerSecond() const;
-
- // Returns the number of bytes representing a frame of audio.
- int GetBytesPerFrame() const;
-
- Format format() const { return format_; }
- ChannelLayout channel_layout() const { return channel_layout_; }
- int sample_rate() const { return sample_rate_; }
- int bits_per_sample() const { return bits_per_sample_; }
- int frames_per_buffer() const { return frames_per_buffer_; }
- int channels() const { return channels_; }
-
- private:
- Format format_; // Format of the stream.
- ChannelLayout channel_layout_; // Order of surround sound channels.
- int sample_rate_; // Sampling frequency/rate.
- int bits_per_sample_; // Number of bits per sample.
- int frames_per_buffer_; // Number of frames in a buffer.
-
- int channels_; // Number of channels. Value set based on
- // |channel_layout|.
-};
-
-// Comparison is useful when AudioParameters is used with std structures.
-inline bool operator<(const AudioParameters& a, const AudioParameters& b) {
- if (a.format() != b.format())
- return a.format() < b.format();
- if (a.channels() != b.channels())
- return a.channels() < b.channels();
- if (a.sample_rate() != b.sample_rate())
- return a.sample_rate() < b.sample_rate();
- if (a.bits_per_sample() != b.bits_per_sample())
- return a.bits_per_sample() < b.bits_per_sample();
- return a.frames_per_buffer() < b.frames_per_buffer();
-}
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_PARAMETERS_H_
diff --git a/src/media/audio/audio_parameters_unittest.cc b/src/media/audio/audio_parameters_unittest.cc
deleted file mode 100644
index fd42e14..0000000
--- a/src/media/audio/audio_parameters_unittest.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/string_number_conversions.h"
-#include "media/audio/audio_parameters.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-TEST(AudioParameters, Constructor_Default) {
- AudioParameters::Format expected_format = AudioParameters::AUDIO_PCM_LINEAR;
- int expected_bits = 0;
- int expected_channels = 0;
- ChannelLayout expected_channel_layout = CHANNEL_LAYOUT_NONE;
- int expected_rate = 0;
- int expected_samples = 0;
-
- AudioParameters params;
-
- EXPECT_EQ(expected_format, params.format());
- EXPECT_EQ(expected_bits, params.bits_per_sample());
- EXPECT_EQ(expected_channels, params.channels());
- EXPECT_EQ(expected_channel_layout, params.channel_layout());
- EXPECT_EQ(expected_rate, params.sample_rate());
- EXPECT_EQ(expected_samples, params.frames_per_buffer());
-}
-
-TEST(AudioParameters, Constructor_ParameterValues) {
- AudioParameters::Format expected_format =
- AudioParameters::AUDIO_PCM_LOW_LATENCY;
- int expected_bits = 16;
- int expected_channels = 6;
- ChannelLayout expected_channel_layout = CHANNEL_LAYOUT_5_1;
- int expected_rate = 44100;
- int expected_samples = 880;
-
- AudioParameters params(expected_format, expected_channel_layout,
- expected_rate, expected_bits, expected_samples);
-
- EXPECT_EQ(expected_format, params.format());
- EXPECT_EQ(expected_bits, params.bits_per_sample());
- EXPECT_EQ(expected_channels, params.channels());
- EXPECT_EQ(expected_channel_layout, params.channel_layout());
- EXPECT_EQ(expected_rate, params.sample_rate());
- EXPECT_EQ(expected_samples, params.frames_per_buffer());
-}
-
-TEST(AudioParameters, GetBytesPerBuffer) {
- EXPECT_EQ(100, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_MONO, 1000, 8, 100)
- .GetBytesPerBuffer());
- EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_MONO, 1000, 16, 100)
- .GetBytesPerBuffer());
- EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO, 1000, 8, 100)
- .GetBytesPerBuffer());
- EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_MONO, 1000, 8, 200)
- .GetBytesPerBuffer());
- EXPECT_EQ(800, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO, 1000, 16, 200)
- .GetBytesPerBuffer());
-}
-
-TEST(AudioParameters, GetBytesPerSecond) {
- EXPECT_EQ(0, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_NONE, 0, 0, 0)
- .GetBytesPerSecond());
- EXPECT_EQ(0, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO, 0, 0, 0)
- .GetBytesPerSecond());
- EXPECT_EQ(0, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_NONE, 100, 0, 0)
- .GetBytesPerSecond());
- EXPECT_EQ(0, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_NONE, 0, 8, 0)
- .GetBytesPerSecond());
- EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_STEREO, 100, 8, 0)
- .GetBytesPerSecond());
-}
-
-TEST(AudioParameters, Compare) {
- AudioParameters values[] = {
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 1000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 1000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 1000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 1000, 16, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 2000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 2000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 2000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 2000, 16, 200),
-
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 1000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 1000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 1000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 1000, 16, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 2000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 2000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 2000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 2000, 16, 200),
-
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 1000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 1000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 1000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 1000, 16, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 2000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 2000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 2000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_MONO,
- 2000, 16, 200),
-
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 1000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 1000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 1000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 1000, 16, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 2000, 8, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 2000, 8, 200),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 2000, 16, 100),
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, 2000, 16, 200),
- };
-
- for (size_t i = 0; i < arraysize(values); ++i) {
- for (size_t j = 0; j < arraysize(values); ++j) {
- SCOPED_TRACE("i=" + base::IntToString(i) + " j=" + base::IntToString(j));
- EXPECT_EQ(i < j, values[i] < values[j]);
- }
-
- // Verify that a value is never less than itself.
- EXPECT_FALSE(values[i] < values[i]);
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/audio_util.cc b/src/media/audio/audio_util.cc
deleted file mode 100644
index e91610f..0000000
--- a/src/media/audio/audio_util.cc
+++ /dev/null
@@ -1,381 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Software adjust volume of samples, allows each audio stream its own
-// volume without impacting master volume for chrome and other applications.
-
-// Implemented as templates to allow 8, 16 and 32 bit implementations.
-// 8 bit is unsigned and biased by 128.
-
-// TODO(vrk): This file has been running pretty wild and free, and it's likely
-// that a lot of the functions can be simplified and made more elegant. Revisit
-// after other audio cleanup is done. (crbug.com/120319)
-
-#include "media/audio/audio_util.h"
-
-#include <algorithm>
-#include <limits>
-
-#include "base/basictypes.h"
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/string_number_conversions.h"
-#include "base/time.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_bus.h"
-#include "media/base/media_switches.h"
-
-#if defined(OS_MACOSX)
-#include "media/audio/mac/audio_low_latency_input_mac.h"
-#include "media/audio/mac/audio_low_latency_output_mac.h"
-#elif defined(OS_WIN)
-#include "base/win/windows_version.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/win/audio_low_latency_input_win.h"
-#include "media/audio/win/audio_low_latency_output_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "media/base/limits.h"
-#endif
-
-namespace media {
-
-// Returns user buffer size as specified on the command line or 0 if no buffer
-// size has been specified.
-static int GetUserBufferSize() {
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- int buffer_size = 0;
- std::string buffer_size_str(cmd_line->GetSwitchValueASCII(
- switches::kAudioBufferSize));
- if (base::StringToInt(buffer_size_str, &buffer_size) && buffer_size > 0) {
- return buffer_size;
- }
-
- return 0;
-}
-
-// TODO(fbarchard): Convert to intrinsics for better efficiency.
-template<class Fixed>
-static int ScaleChannel(int channel, int volume) {
- return static_cast<int>((static_cast<Fixed>(channel) * volume) >> 16);
-}
-
-template<class Format, class Fixed, int bias>
-static void AdjustVolume(Format* buf_out,
- int sample_count,
- int fixed_volume) {
- for (int i = 0; i < sample_count; ++i) {
- buf_out[i] = static_cast<Format>(ScaleChannel<Fixed>(buf_out[i] - bias,
- fixed_volume) + bias);
- }
-}
-
-template<class Fixed, int min_value, int max_value>
-static int AddSaturated(int val, int adder) {
- Fixed sum = static_cast<Fixed>(val) + static_cast<Fixed>(adder);
- if (sum > max_value)
- return max_value;
- if (sum < min_value)
- return min_value;
- return static_cast<int>(sum);
-}
-
-// AdjustVolume() does an in place audio sample change.
-bool AdjustVolume(void* buf,
- size_t buflen,
- int channels,
- int bytes_per_sample,
- float volume) {
- DCHECK(buf);
- if (volume < 0.0f || volume > 1.0f)
- return false;
- if (volume == 1.0f) {
- return true;
- } else if (volume == 0.0f) {
- memset(buf, 0, buflen);
- return true;
- }
- if (channels > 0 && channels <= 8 && bytes_per_sample > 0) {
- int sample_count = buflen / bytes_per_sample;
- const int fixed_volume = static_cast<int>(volume * 65536);
- if (bytes_per_sample == 1) {
- AdjustVolume<uint8, int32, 128>(reinterpret_cast<uint8*>(buf),
- sample_count,
- fixed_volume);
- return true;
- } else if (bytes_per_sample == 2) {
- AdjustVolume<int16, int32, 0>(reinterpret_cast<int16*>(buf),
- sample_count,
- fixed_volume);
- return true;
- } else if (bytes_per_sample == 4) {
- AdjustVolume<int32, int64, 0>(reinterpret_cast<int32*>(buf),
- sample_count,
- fixed_volume);
- return true;
- }
- }
- return false;
-}
-
-// TODO(enal): use template specialization and size-specific intrinsics.
-// Call is on the time-critical path, and by using SSE/AVX
-// instructions we can speed things up by ~4-8x, more for the case
-// when we have to adjust volume as well.
-template<class Format, class Fixed, int min_value, int max_value, int bias>
-static void MixStreams(Format* dst, Format* src, int count, float volume) {
- if (volume == 0.0f)
- return;
- if (volume == 1.0f) {
- // Most common case -- no need to adjust volume.
- for (int i = 0; i < count; ++i) {
- Fixed value = AddSaturated<Fixed, min_value, max_value>(dst[i] - bias,
- src[i] - bias);
- dst[i] = static_cast<Format>(value + bias);
- }
- } else {
- // General case -- have to adjust volume before mixing.
- const int fixed_volume = static_cast<int>(volume * 65536);
- for (int i = 0; i < count; ++i) {
- Fixed adjusted_src = ScaleChannel<Fixed>(src[i] - bias, fixed_volume);
- Fixed value = AddSaturated<Fixed, min_value, max_value>(dst[i] - bias,
- adjusted_src);
- dst[i] = static_cast<Format>(value + bias);
- }
- }
-}
-
-void MixStreams(void* dst,
- void* src,
- size_t buflen,
- int bytes_per_sample,
- float volume) {
- DCHECK(dst);
- DCHECK(src);
- DCHECK_GE(volume, 0.0f);
- DCHECK_LE(volume, 1.0f);
- switch (bytes_per_sample) {
- case 1:
- MixStreams<uint8, int32, kint8min, kint8max, 128>(
- static_cast<uint8*>(dst),
- static_cast<uint8*>(src),
- buflen,
- volume);
- break;
- case 2:
- DCHECK_EQ(0u, buflen % 2);
- MixStreams<int16, int32, kint16min, kint16max, 0>(
- static_cast<int16*>(dst),
- static_cast<int16*>(src),
- buflen / 2,
- volume);
- break;
- case 4:
- DCHECK_EQ(0u, buflen % 4);
- MixStreams<int32, int64, kint32min, kint32max, 0>(
- static_cast<int32*>(dst),
- static_cast<int32*>(src),
- buflen / 4,
- volume);
- break;
- default:
- NOTREACHED() << "Illegal bytes per sample";
- break;
- }
-}
-
-int GetAudioHardwareSampleRate() {
-#if defined(OS_MACOSX)
- // Hardware sample-rate on the Mac can be configured, so we must query.
- return AUAudioOutputStream::HardwareSampleRate();
-#elif defined(OS_WIN)
- if (!CoreAudioUtil::IsSupported()) {
- // Fall back to Windows Wave implementation on Windows XP or lower
- // and use 48kHz as default input sample rate.
- return 48000;
- }
-
- // TODO(crogers): tune this rate for best possible WebAudio performance.
- // WebRTC works well at 48kHz and a buffer size of 480 samples will be used
- // for this case. Note that exclusive mode is experimental.
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
- // This sample rate will be combined with a buffer size of 256 samples
- // (see GetAudioHardwareBufferSize()), which corresponds to an output
- // delay of ~5.33ms.
- return 48000;
- }
-
- // Hardware sample-rate on Windows can be configured, so we must query.
- // TODO(henrika): improve possibility to specify an audio endpoint.
- // Use the default device (same as for Wave) for now to be compatible
- // or possibly remove the ERole argument completely until it is in use.
- return WASAPIAudioOutputStream::HardwareSampleRate(eConsole);
-#elif defined(OS_ANDROID)
- return 16000;
-#else
- // Hardware for Linux is nearly always 48KHz.
- // TODO(crogers) : return correct value in rare non-48KHz cases.
- return 48000;
-#endif
-}
-
-int GetAudioInputHardwareSampleRate(const std::string& device_id) {
- // TODO(henrika): add support for device selection on all platforms.
- // Only exists on Windows today.
-#if defined(OS_MACOSX)
- return AUAudioInputStream::HardwareSampleRate();
-#elif defined(OS_WIN)
- if (!CoreAudioUtil::IsSupported()) {
- return 48000;
- }
- return WASAPIAudioInputStream::HardwareSampleRate(device_id);
-#elif defined(OS_ANDROID)
- return 16000;
-#else
- return 48000;
-#endif
-}
-
-size_t GetAudioHardwareBufferSize() {
- int user_buffer_size = GetUserBufferSize();
- if (user_buffer_size)
- return user_buffer_size;
-
- // The sizes here were determined by experimentation and are roughly
- // the lowest value (for low latency) that still allowed glitch-free
- // audio under high loads.
- //
- // For Mac OS X and Windows the chromium audio backend uses a low-latency
- // Core Audio API, so a low buffer size is possible. For Linux, further
- // tuning may be needed.
-#if defined(OS_MACOSX)
- return 128;
-#elif defined(OS_WIN)
- // Buffer size to use when a proper size can't be determined from the system.
- static const int kFallbackBufferSize = 4096;
-
- if (!CoreAudioUtil::IsSupported()) {
- // Fall back to Windows Wave implementation on Windows XP or lower
- // and assume 48kHz as default sample rate.
- return kFallbackBufferSize;
- }
-
- // TODO(crogers): tune this size to best possible WebAudio performance.
- // WebRTC always uses 10ms for Windows and does not call this method.
- // Note that exclusive mode is experimental.
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
- return 256;
- }
-
- // TODO(henrika): remove when the --enable-webaudio-input flag is no longer
- // utilized.
- if (cmd_line->HasSwitch(switches::kEnableWebAudioInput)) {
- AudioParameters params;
- HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
- ¶ms);
- return FAILED(hr) ? kFallbackBufferSize : params.frames_per_buffer();
- }
-
- // This call must be done on a COM thread configured as MTA.
- // TODO(tommi): http://code.google.com/p/chromium/issues/detail?id=103835.
- int mixing_sample_rate =
- WASAPIAudioOutputStream::HardwareSampleRate(eConsole);
-
- // Windows will return a sample rate of 0 when no audio output is available
- // (i.e. via RemoteDesktop with remote audio disabled), but we should never
- // return a buffer size of zero.
- if (mixing_sample_rate == 0)
- return kFallbackBufferSize;
-
- // Use different buffer sizes depening on the sample rate . The existing
- // WASAPI implementation is tuned to provide the most stable callback
- // sequence using these combinations.
- if (mixing_sample_rate % 11025 == 0)
- // Use buffer size of ~10.15873 ms.
- return (112 * (mixing_sample_rate / 11025));
-
- if (mixing_sample_rate % 8000 == 0)
- // Use buffer size of 10ms.
- return (80 * (mixing_sample_rate / 8000));
-
- // Ensure we always return a buffer size which is somewhat appropriate.
- LOG(ERROR) << "Unknown sample rate " << mixing_sample_rate << " detected.";
- if (mixing_sample_rate > limits::kMinSampleRate)
- return (mixing_sample_rate / 100);
- return kFallbackBufferSize;
-#else
- return 2048;
-#endif
-}
-
-ChannelLayout GetAudioInputHardwareChannelLayout(const std::string& device_id) {
- // TODO(henrika): add support for device selection on all platforms.
- // Only exists on Windows today.
-#if defined(OS_MACOSX)
- return CHANNEL_LAYOUT_MONO;
-#elif defined(OS_WIN)
- if (!CoreAudioUtil::IsSupported()) {
- // Fall back to Windows Wave implementation on Windows XP or lower and
- // use stereo by default.
- return CHANNEL_LAYOUT_STEREO;
- }
- return WASAPIAudioInputStream::HardwareChannelCount(device_id) == 1 ?
- CHANNEL_LAYOUT_MONO : CHANNEL_LAYOUT_STEREO;
-#else
- return CHANNEL_LAYOUT_STEREO;
-#endif
-}
-
-// Computes a buffer size based on the given |sample_rate|. Must be used in
-// conjunction with AUDIO_PCM_LINEAR.
-size_t GetHighLatencyOutputBufferSize(int sample_rate) {
- int user_buffer_size = GetUserBufferSize();
- if (user_buffer_size)
- return user_buffer_size;
-
- // TODO(vrk/crogers): The buffer sizes that this function computes is probably
- // overly conservative. However, reducing the buffer size to 2048-8192 bytes
- // caused crbug.com/108396. This computation should be revisited while making
- // sure crbug.com/108396 doesn't happen again.
-
- // The minimum number of samples in a hardware packet.
- // This value is selected so that we can handle down to 5khz sample rate.
- static const size_t kMinSamplesPerHardwarePacket = 1024;
-
- // The maximum number of samples in a hardware packet.
- // This value is selected so that we can handle up to 192khz sample rate.
- static const size_t kMaxSamplesPerHardwarePacket = 64 * 1024;
-
- // This constant governs the hardware audio buffer size, this value should be
- // chosen carefully.
- // This value is selected so that we have 8192 samples for 48khz streams.
- static const size_t kMillisecondsPerHardwarePacket = 170;
-
- // Select the number of samples that can provide at least
- // |kMillisecondsPerHardwarePacket| worth of audio data.
- size_t samples = kMinSamplesPerHardwarePacket;
- while (samples <= kMaxSamplesPerHardwarePacket &&
- samples * base::Time::kMillisecondsPerSecond <
- sample_rate * kMillisecondsPerHardwarePacket) {
- samples *= 2;
- }
- return samples;
-}
-
-#if defined(OS_WIN)
-
-int NumberOfWaveOutBuffers() {
- // Use 4 buffers for Vista, 3 for everyone else:
- // - The entire Windows audio stack was rewritten for Windows Vista and wave
- // out performance was degraded compared to XP.
- // - The regression was fixed in Windows 7 and most configurations will work
- // with 2, but some (e.g., some Sound Blasters) still need 3.
- // - Some XP configurations (even multi-processor ones) also need 3.
- return (base::win::GetVersion() == base::win::VERSION_VISTA) ? 4 : 3;
-}
-
-#endif
-
-} // namespace media
diff --git a/src/media/audio/audio_util.h b/src/media/audio/audio_util.h
deleted file mode 100644
index 18cda41..0000000
--- a/src/media/audio/audio_util.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_AUDIO_UTIL_H_
-#define MEDIA_AUDIO_AUDIO_UTIL_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-
-namespace base {
-class SharedMemory;
-}
-
-namespace media {
-class AudioBus;
-
-// For all audio functions 3 audio formats are supported:
-// 8 bits unsigned 0 to 255.
-// 16 bit signed (little endian).
-// 32 bit signed (little endian)
-
-// AdjustVolume() does a software volume adjustment of a sample buffer.
-// The samples are multiplied by the volume, which should range from
-// 0.0 (mute) to 1.0 (full volume).
-// Using software allows each audio and video to have its own volume without
-// affecting the master volume.
-// In the future the function may be used to adjust the sample format to
-// simplify hardware requirements and to support a wider variety of input
-// formats.
-// The buffer is modified in-place to avoid memory management, as this
-// function may be called in performance critical code.
-MEDIA_EXPORT bool AdjustVolume(void* buf,
- size_t buflen,
- int channels,
- int bytes_per_sample,
- float volume);
-
-// MixStreams() mixes 2 audio streams with same sample rate and number of
-// samples, adjusting volume on one of them.
-// Dst += Src * volume.
-MEDIA_EXPORT void MixStreams(void* dst,
- void* src,
- size_t buflen,
- int bytes_per_sample,
- float volume);
-
-// Returns the default audio output hardware sample-rate.
-MEDIA_EXPORT int GetAudioHardwareSampleRate();
-
-// Returns the audio input hardware sample-rate for the specified device.
-MEDIA_EXPORT int GetAudioInputHardwareSampleRate(
- const std::string& device_id);
-
-// Returns the optimal low-latency buffer size for the audio hardware.
-// This is the smallest buffer size the system can comfortably render
-// at without glitches. The buffer size is in sample-frames.
-MEDIA_EXPORT size_t GetAudioHardwareBufferSize();
-
-// Returns the channel layout for the specified audio input device.
-MEDIA_EXPORT ChannelLayout GetAudioInputHardwareChannelLayout(
- const std::string& device_id);
-
-// Computes a buffer size based on the given |sample_rate|. Must be used in
-// conjunction with AUDIO_PCM_LINEAR.
-MEDIA_EXPORT size_t GetHighLatencyOutputBufferSize(int sample_rate);
-
-#if defined(OS_WIN)
-
-// Returns number of buffers to be used by wave out.
-MEDIA_EXPORT int NumberOfWaveOutBuffers();
-
-#endif // defined(OS_WIN)
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_AUDIO_UTIL_H_
diff --git a/src/media/audio/audio_util_unittest.cc b/src/media/audio/audio_util_unittest.cc
deleted file mode 100644
index 2643b99..0000000
--- a/src/media/audio/audio_util_unittest.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "media/audio/audio_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// Number of samples in each audio array.
-static const size_t kNumberOfSamples = 4;
-
-namespace media {
-
-TEST(AudioUtilTest, AdjustVolume_u8) {
- // Test AdjustVolume() on 8 bit samples.
- uint8 samples_u8[kNumberOfSamples] = { 4, 0x40, 0x80, 0xff };
- uint8 expected_u8[kNumberOfSamples] = { (4 - 128) / 2 + 128,
- (0x40 - 128) / 2 + 128,
- (0x80 - 128) / 2 + 128,
- (0xff - 128) / 2 + 128 };
- bool result_u8 = media::AdjustVolume(samples_u8, sizeof(samples_u8),
- 1, // channels.
- sizeof(samples_u8[0]),
- 0.5f);
- EXPECT_TRUE(result_u8);
- int expected_test = memcmp(samples_u8, expected_u8, sizeof(expected_u8));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, AdjustVolume_s16) {
- // Test AdjustVolume() on 16 bit samples.
- int16 samples_s16[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int16 expected_s16[kNumberOfSamples] = { -1, 0x10, -8192, 30 };
- bool result_s16 = media::AdjustVolume(samples_s16, sizeof(samples_s16),
- 2, // channels.
- sizeof(samples_s16[0]),
- 0.25f);
- EXPECT_TRUE(result_s16);
- int expected_test = memcmp(samples_s16, expected_s16, sizeof(expected_s16));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, AdjustVolume_s16_zero) {
- // Test AdjustVolume() on 16 bit samples.
- int16 samples_s16[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int16 expected_s16[kNumberOfSamples] = { 0, 0, 0, 0 };
- bool result_s16 = media::AdjustVolume(samples_s16, sizeof(samples_s16),
- 2, // channels.
- sizeof(samples_s16[0]),
- 0.0f);
- EXPECT_TRUE(result_s16);
- int expected_test = memcmp(samples_s16, expected_s16, sizeof(expected_s16));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, AdjustVolume_s16_one) {
- // Test AdjustVolume() on 16 bit samples.
- int16 samples_s16[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int16 expected_s16[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- bool result_s16 = media::AdjustVolume(samples_s16, sizeof(samples_s16),
- 2, // channels.
- sizeof(samples_s16[0]),
- 1.0f);
- EXPECT_TRUE(result_s16);
- int expected_test = memcmp(samples_s16, expected_s16, sizeof(expected_s16));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, AdjustVolume_s32) {
- // Test AdjustVolume() on 32 bit samples.
- int32 samples_s32[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int32 expected_s32[kNumberOfSamples] = { -1, 0x10, -8192, 30 };
- bool result_s32 = media::AdjustVolume(samples_s32, sizeof(samples_s32),
- 4, // channels.
- sizeof(samples_s32[0]),
- 0.25f);
- EXPECT_TRUE(result_s32);
- int expected_test = memcmp(samples_s32, expected_s32, sizeof(expected_s32));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_u8_QuarterVolume) {
- // Test MixStreams() on 8 bit samples.
- uint8 dst_u8[kNumberOfSamples] = { 14, 0x44, 0x80, 0xff };
- uint8 src_u8[kNumberOfSamples] = { 4, 0x40, 0x80, 0xff };
- uint8 expected_u8[kNumberOfSamples] = { 0, /* saturation */
- (0x44 - 128) + (0x40 - 128) / 4 + 128,
- (0x80 - 128) + (0x80 - 128) / 4 + 128,
- 0xff /* saturation */ };
- media::MixStreams(dst_u8, src_u8, sizeof(dst_u8), sizeof(src_u8[0]), 0.25f);
- int expected_test = memcmp(dst_u8, expected_u8, sizeof(expected_u8));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_u8_FullVolume) {
- // Test MixStreams() on 8 bit samples.
- uint8 dst_u8[kNumberOfSamples] = { 44, 0x44, 0x80, 0xff };
- uint8 src_u8[kNumberOfSamples] = { 4, 0x40, 0x80, 0xff };
- uint8 expected_u8[kNumberOfSamples] = { 0, /* saturation */
- (0x44 - 128) + (0x40 - 128) + 128,
- (0x80 - 128) + (0x80 - 128) + 128,
- 0xff /* saturation */ };
- media::MixStreams(dst_u8, src_u8, sizeof(dst_u8), sizeof(src_u8[0]), 1.0f);
- int expected_test = memcmp(dst_u8, expected_u8, sizeof(expected_u8));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_s16_QuarterVolume) {
- // Test MixStreams() on 16 bit samples.
- int16 dst_s16[kNumberOfSamples] = { -4, 0x40, -32760, 32760 };
- int16 src_s16[kNumberOfSamples] = { -4, 0x40, -123, 123 };
- int16 expected_s16[kNumberOfSamples] = { -5, 0x50, -32768, 32767 };
- media::MixStreams(dst_s16,
- src_s16,
- sizeof(dst_s16),
- sizeof(src_s16[0]),
- 0.25f);
- int expected_test = memcmp(dst_s16, expected_s16, sizeof(expected_s16));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_s16_FullVolume) {
- // Test MixStreams() on 16 bit samples.
- int16 dst_s16[kNumberOfSamples] = { -4, 0x40, -32760, 32760 };
- int16 src_s16[kNumberOfSamples] = { -4, 0x40, -123, 123 };
- int16 expected_s16[kNumberOfSamples] = { -8, 0x80, -32768, 32767 };
- media::MixStreams(dst_s16,
- src_s16,
- sizeof(dst_s16),
- sizeof(src_s16[0]),
- 1.0f);
- int expected_test = memcmp(dst_s16, expected_s16, sizeof(expected_s16));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_s32_QuarterVolume) {
- // Test MixStreams() on 32 bit samples.
- int32 dst_s32[kNumberOfSamples] = { -4, 0x40, -32768, 2147483640 };
- int32 src_s32[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int32 expected_s32[kNumberOfSamples] = { -5, 0x50, -40960, 2147483647 };
- media::MixStreams(dst_s32,
- src_s32,
- sizeof(dst_s32),
- sizeof(src_s32[0]),
- 0.25f);
- int expected_test = memcmp(dst_s32, expected_s32, sizeof(expected_s32));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(AudioUtilTest, MixStreams_s32_FullVolume) {
- // Test MixStreams() on 32 bit samples.
- int32 dst_s32[kNumberOfSamples] = { -4, 0x40, -32768, 2147483640 };
- int32 src_s32[kNumberOfSamples] = { -4, 0x40, -32768, 123 };
- int32 expected_s32[kNumberOfSamples] = { -8, 0x80, -65536, 2147483647 };
- media::MixStreams(dst_s32,
- src_s32,
- sizeof(dst_s32),
- sizeof(src_s32[0]),
- 1.0);
- int expected_test = memcmp(dst_s32, expected_s32, sizeof(expected_s32));
- EXPECT_EQ(0, expected_test);
-}
-
-} // namespace media
diff --git a/src/media/audio/cross_process_notification.cc b/src/media/audio/cross_process_notification.cc
deleted file mode 100644
index 1806f77..0000000
--- a/src/media/audio/cross_process_notification.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/cross_process_notification.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-
-CrossProcessNotification::CrossProcessNotification() {}
-
-CrossProcessNotification::WaitForMultiple::WaitForMultiple(
- const Notifications* notifications) {
- Reset(notifications);
-}
-
-int CrossProcessNotification::WaitForMultiple::Wait() {
- DCHECK(CalledOnValidThread());
- int ret = WaitMultiple(*notifications_, wait_offset_);
- wait_offset_ = (ret + 1) % notifications_->size();
- return ret;
-}
-
-void CrossProcessNotification::WaitForMultiple::Reset(
- const Notifications* notifications) {
- DCHECK(CalledOnValidThread());
- wait_offset_ = 0;
- notifications_ = notifications;
- DCHECK(!notifications_->empty());
-}
diff --git a/src/media/audio/cross_process_notification.h b/src/media/audio/cross_process_notification.h
deleted file mode 100644
index cae7435..0000000
--- a/src/media/audio/cross_process_notification.h
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_CROSS_PROCESS_NOTIFICATION_H_
-#define MEDIA_AUDIO_CROSS_PROCESS_NOTIFICATION_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/process.h"
-#include "base/threading/non_thread_safe.h"
-#include "media/base/media_export.h"
-
-#if defined(OS_WIN)
-#include "base/win/scoped_handle.h"
-#else
-#include "base/file_descriptor_posix.h"
-#include "base/sync_socket.h"
-#endif
-
-// A mechanism to synchronize access to a shared resource between two parties
-// when the usage pattern resembles that of two players playing a game of chess.
-// Each end has an instance of CrossProcessNotification and calls Signal() when
-// it has finished using the shared resource.
-// Before accessing the resource, it must call Wait() in order to know when the
-// other end has called Signal().
-//
-// Here's some pseudo code for how this class can be used:
-//
-// This method is used by both processes as it's a general way to use the
-// shared resource and then grant the privilege to the other process:
-//
-// void WriteToSharedMemory(CrossProcessNotification* notification,
-// SharedMemory* mem,
-// const char my_char) {
-// notification->Wait(); // Wait for the other process to yield access.
-// reinterpret_cast<char*>(mem->memory())[0] = my_char;
-// notification->Signal(); // Grant the other process access.
-// }
-//
-// Process A:
-//
-// class A {
-// public:
-// void Initialize(base::ProcessHandle process_b) {
-// mem_.CreateNamed("foo", false, 1024);
-//
-// CrossProcessNotification other;
-// CHECK(CrossProcessNotification::InitializePair(¬ification_, &other));
-// CrossProcessNotification::IPCHandle handle_1, handle_2;
-// CHECK(other.ShareToProcess(process_b, &handle_1, &handle_2));
-// // This could be implemented by using some IPC mechanism
-// // such as MessageLoop.
-// SendToProcessB(mem_, handle_1, handle_2);
-// // Allow process B the first chance to write to the memory:
-// notification_.Signal();
-// // Once B is done, we'll write 'A' to the shared memory.
-// WriteToSharedMemory(¬ification_, &mem_, 'A');
-// }
-//
-// CrossProcessNotification notification_;
-// SharedMemory mem_;
-// };
-//
-// Process B:
-//
-// class B {
-// public:
-// // Called when we receive the IPC message from A.
-// void Initialize(SharedMemoryHandle mem,
-// CrossProcessNotification::IPCHandle handle_1,
-// CrossProcessNotification::IPCHandle handle_2) {
-// mem_.reset(new SharedMemory(mem, false));
-// notification_.reset(new CrossProcessNotification(handle_1, handle_2));
-// WriteToSharedMemory(¬ification_, &mem_, 'B');
-// }
-//
-// CrossProcessNotification notification_;
-// scoped_ptr<SharedMemory> mem_;
-// };
-//
-class MEDIA_EXPORT CrossProcessNotification {
- public:
-#if defined(OS_WIN)
- typedef HANDLE IPCHandle;
-#else
- typedef base::FileDescriptor IPCHandle;
-#endif
-
- typedef std::vector<CrossProcessNotification*> Notifications;
-
- // Default ctor. Initializes a NULL notification. User must call
- // InitializePair() to initialize the instance along with a connected one.
- CrossProcessNotification();
-
- // Ctor for the user that does not call InitializePair but instead receives
- // handles from the one that did. These handles come from a call to
- // ShareToProcess.
- CrossProcessNotification(IPCHandle handle_1, IPCHandle handle_2);
- ~CrossProcessNotification();
-
- // Raises a signal that the shared resource now can be accessed by the other
- // party.
- // NOTE: Calling Signal() more than once without calling Wait() in between
- // is not a supported scenario and will result in undefined behavior (and
- // different depending on platform).
- void Signal();
-
- // Waits for the other party to finish using the shared resource.
- // NOTE: As with Signal(), you must not call Wait() more than once without
- // calling Signal() in between.
- void Wait();
-
- bool IsValid() const;
-
- // Copies the internal handles to the output parameters, |handle_1| and
- // |handle_2|. The operation can fail, so the caller must be prepared to
- // handle that case.
- bool ShareToProcess(base::ProcessHandle process, IPCHandle* handle_1,
- IPCHandle* handle_2);
-
- // Initializes a pair of CrossProcessNotification instances. Note that this
- // can fail (e.g. due to EMFILE on Linux).
- static bool InitializePair(CrossProcessNotification* a,
- CrossProcessNotification* b);
-
- // Use an instance of this class when you have to repeatedly wait for multiple
- // notifications on the same thread. The class will store information about
- // which notification was last signaled and try to distribute the signals so
- // that all notifications get a chance to be processed in times of high load
- // and a busy one won't starve the others.
- // TODO(tommi): Support a way to abort the wait.
- class MEDIA_EXPORT WaitForMultiple :
- public NON_EXPORTED_BASE(base::NonThreadSafe) {
- public:
- // Caller must make sure that the lifetime of the array is greater than
- // that of the WaitForMultiple instance.
- explicit WaitForMultiple(const Notifications* notifications);
-
- // Waits for any of the notifications to be signaled. Returns the 0 based
- // index of a signaled notification.
- int Wait();
-
- // Call when the array changes. This should be called on the same thread
- // as Wait() is called on and the array must never change while a Wait()
- // is in progress.
- void Reset(const Notifications* notifications);
-
- private:
- const Notifications* notifications_;
- size_t wait_offset_;
- };
-
- private:
- // Only called by the WaitForMultiple class. See documentation
- // for WaitForMultiple and comments inside WaitMultiple for details.
- static int WaitMultiple(const Notifications& notifications,
- size_t wait_offset);
-
-#if defined(OS_WIN)
- base::win::ScopedHandle mine_;
- base::win::ScopedHandle other_;
-#else
- typedef base::CancelableSyncSocket SocketClass;
- SocketClass socket_;
-#endif
-
- DISALLOW_COPY_AND_ASSIGN(CrossProcessNotification);
-};
-
-#endif // MEDIA_AUDIO_CROSS_PROCESS_NOTIFICATION_H_
diff --git a/src/media/audio/cross_process_notification_posix.cc b/src/media/audio/cross_process_notification_posix.cc
deleted file mode 100644
index 070ef06..0000000
--- a/src/media/audio/cross_process_notification_posix.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/cross_process_notification.h"
-
-#include <errno.h>
-#include <sys/poll.h>
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/file_descriptor_posix.h"
-
-CrossProcessNotification::~CrossProcessNotification() {}
-
-CrossProcessNotification::CrossProcessNotification(IPCHandle handle_1,
- IPCHandle handle_2)
- : socket_(handle_1.fd) {
- DCHECK_NE(handle_1.fd, -1);
- DCHECK_EQ(handle_2.fd, -1);
- DCHECK(IsValid());
-}
-
-void CrossProcessNotification::Signal() {
- DCHECK(IsValid());
- char signal = 1;
- size_t bytes = socket_.Send(&signal, sizeof(signal));
- DCHECK_EQ(bytes, 1U) << "errno: " << errno;
-}
-
-void CrossProcessNotification::Wait() {
- DCHECK(IsValid());
- char signal = 0;
- size_t bytes = socket_.Receive(&signal, sizeof(signal));
- DCHECK_EQ(bytes, 1U) << "errno: " << errno;
- DCHECK_EQ(signal, 1);
-}
-
-bool CrossProcessNotification::IsValid() const {
- return socket_.handle() != SocketClass::kInvalidHandle;
-}
-
-bool CrossProcessNotification::ShareToProcess(base::ProcessHandle process,
- IPCHandle* handle_1,
- IPCHandle* handle_2) {
- DCHECK(IsValid());
- handle_1->fd = socket_.handle();
- handle_1->auto_close = false;
- handle_2->fd = -1;
- return true;
-}
-
-// static
-bool CrossProcessNotification::InitializePair(CrossProcessNotification* a,
- CrossProcessNotification* b) {
- DCHECK(!a->IsValid());
- DCHECK(!b->IsValid());
-
- bool ok = SocketClass::CreatePair(&a->socket_, &b->socket_);
-
- DLOG_IF(WARNING, !ok) << "failed to create socket: " << errno;
- DCHECK(!ok || a->IsValid());
- DCHECK(!ok || b->IsValid());
- return ok;
-}
-
-// static
-int CrossProcessNotification::WaitMultiple(const Notifications& notifications,
- size_t wait_offset) {
- DCHECK_LT(wait_offset, notifications.size());
-
- for (size_t i = 0; i < notifications.size(); ++i) {
- DCHECK(notifications[i]->IsValid());
- }
-
- // Below, we always check the |revents| of the first socket in the array
- // and return the index of that socket if set. This can cause sockets
- // that come later in the array to starve when the first sockets are
- // very busy. So to avoid the starving problem, we use the |wait_offset|
- // variable to split up the array so that the last socket to be signaled
- // becomes the last socket in the array and all the other sockets will have
- // priority the next time WaitMultiple is called.
- scoped_array<struct pollfd> sockets(new struct pollfd[notifications.size()]);
- memset(&sockets[0], 0, notifications.size() * sizeof(sockets[0]));
- size_t index = 0;
- for (size_t i = wait_offset; i < notifications.size(); ++i) {
- struct pollfd& fd = sockets[index++];
- fd.events = POLLIN;
- fd.fd = notifications[i]->socket_.handle();
- }
-
- for (size_t i = 0; i < wait_offset; ++i) {
- struct pollfd& fd = sockets[index++];
- fd.events = POLLIN;
- fd.fd = notifications[i]->socket_.handle();
- }
- DCHECK_EQ(index, notifications.size());
-
- int err = poll(&sockets[0], notifications.size(), -1);
- if (err != -1) {
- for (size_t i = 0; i < notifications.size(); ++i) {
- if (sockets[i].revents) {
- size_t ret = (i + wait_offset) % notifications.size();
- DCHECK_EQ(sockets[i].fd, notifications[ret]->socket_.handle());
- notifications[ret]->Wait();
- return ret;
- }
- }
- }
- // Either poll() failed or we failed to find a single socket that was
- // signaled. Either way continuing will result in undefined behavior.
- LOG(FATAL) << "poll() failed: " << errno;
- return -1;
-}
diff --git a/src/media/audio/cross_process_notification_unittest.cc b/src/media/audio/cross_process_notification_unittest.cc
deleted file mode 100644
index d1fbead..0000000
--- a/src/media/audio/cross_process_notification_unittest.cc
+++ /dev/null
@@ -1,461 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/shared_memory.h"
-#include "base/stl_util.h"
-#include "base/test/multiprocess_test.h"
-#include "base/threading/platform_thread.h"
-#include "media/audio/cross_process_notification.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/multiprocess_func_list.h"
-
-#include <utility> // NOLINT
-
-namespace {
-
-// Initializes (ctor) and deletes (dtor) two vectors of pairs of
-// CrossProcessNotification instances.
-class NotificationsOwner {
- public:
- // Attempts to create up to |number_of_pairs| number of pairs. Call size()
- // after construction to find out how many pairs were actually created.
- explicit NotificationsOwner(size_t number_of_pairs) {
- CreateMultiplePairs(number_of_pairs);
- }
- ~NotificationsOwner() {
- STLDeleteElements(&a_);
- STLDeleteElements(&b_);
- }
-
- size_t size() const {
- DCHECK_EQ(a_.size(), b_.size());
- return a_.size();
- }
-
- const CrossProcessNotification::Notifications& a() { return a_; }
- const CrossProcessNotification::Notifications& b() { return b_; }
-
- private:
- void CreateMultiplePairs(size_t count) {
- a_.resize(count);
- b_.resize(count);
- size_t i = 0;
- for (; i < count; ++i) {
- a_[i] = new CrossProcessNotification();
- b_[i] = new CrossProcessNotification();
- if (!CrossProcessNotification::InitializePair(a_[i], b_[i])) {
- LOG(WARNING) << "InitializePair failed at " << i;
- delete a_[i];
- delete b_[i];
- break;
- }
- }
- a_.resize(i);
- b_.resize(i);
- }
-
- CrossProcessNotification::Notifications a_;
- CrossProcessNotification::Notifications b_;
-};
-
-// A simple thread that we'll run two instances of. Both threads get a pointer
-// to the same |shared_data| and use a CrossProcessNotification to control when
-// each thread can read/write.
-class SingleNotifierWorker : public base::PlatformThread::Delegate {
- public:
- SingleNotifierWorker(size_t* shared_data, size_t repeats,
- CrossProcessNotification* notifier)
- : shared_data_(shared_data), repeats_(repeats),
- notifier_(notifier) {
- }
- virtual ~SingleNotifierWorker() {}
-
- // base::PlatformThread::Delegate:
- virtual void ThreadMain() OVERRIDE {
- for (size_t i = 0; i < repeats_; ++i) {
- notifier_->Wait();
- ++(*shared_data_);
- notifier_->Signal();
- }
- }
-
- private:
- size_t* shared_data_;
- size_t repeats_;
- CrossProcessNotification* notifier_;
- DISALLOW_COPY_AND_ASSIGN(SingleNotifierWorker);
-};
-
-// Similar to SingleNotifierWorker, except each instance of this class will
-// have >1 instances of CrossProcessNotification to Wait/Signal and an equal
-// amount of |shared_data| that the notifiers control access to.
-class MultiNotifierWorker : public base::PlatformThread::Delegate {
- public:
- MultiNotifierWorker(size_t* shared_data, size_t repeats,
- const CrossProcessNotification::Notifications* notifiers)
- : shared_data_(shared_data), repeats_(repeats),
- notifiers_(notifiers) {
- }
- virtual ~MultiNotifierWorker() {}
-
- // base::PlatformThread::Delegate:
- virtual void ThreadMain() OVERRIDE {
- CrossProcessNotification::WaitForMultiple waiter(notifiers_);
- for (size_t i = 0; i < repeats_; ++i) {
- int signaled = waiter.Wait();
- ++shared_data_[signaled];
- (*notifiers_)[signaled]->Signal();
- }
- }
-
- private:
- size_t* shared_data_;
- size_t repeats_;
- const CrossProcessNotification::Notifications* notifiers_;
- DISALLOW_COPY_AND_ASSIGN(MultiNotifierWorker);
-};
-
-// A fixed array of bool flags. Each flag uses 1 bit. Use sizeof(FlagArray)
-// to determine how much memory you need. The number of flags will therefore
-// be sizeof(FlagArray) * 8.
-// We use 'struct' to signify that this structures represents compiler
-// independent structured data. I.e. you must be able to map this class
-// to a piece of shared memory of size sizeof(FlagArray) and be able to
-// use the class. No vtables etc.
-// TODO(tommi): Move this to its own header when we start using it for signaling
-// audio devices. As is, it's just here for perf comparison against the
-// "multiple notifiers" approach.
-struct FlagArray {
- public:
- FlagArray() : flags_() {}
-
- bool is_set(size_t index) const {
- return (flags_[index >> 5] & (1 << (index & 31)));
- }
-
- void set(size_t index) {
- flags_[index >> 5] |= (1U << (static_cast<uint32>(index) & 31));
- }
-
- void clear(size_t index) {
- flags_[index >> 5] &= ~(1U << (static_cast<uint32>(index) & 31));
- }
-
- // Returns the number of flags that can be set/checked.
- size_t size() const { return sizeof(flags_) * 8; }
-
- private:
- // 256 * 32 = 8192 flags in 1KB.
- uint32 flags_[256];
- DISALLOW_COPY_AND_ASSIGN(FlagArray);
-};
-
-class MultiNotifierWorkerFlagArray : public base::PlatformThread::Delegate {
- public:
- MultiNotifierWorkerFlagArray(size_t count, FlagArray* signals,
- size_t* shared_data, size_t repeats,
- CrossProcessNotification* notifier)
- : count_(count), signals_(signals), shared_data_(shared_data),
- repeats_(repeats), notifier_(notifier) {
- }
- virtual ~MultiNotifierWorkerFlagArray() {}
-
- // base::PlatformThread::Delegate:
- virtual void ThreadMain() OVERRIDE {
- for (size_t i = 0; i < repeats_; ++i) {
- notifier_->Wait();
- for (size_t s = 0; s < count_; ++s) {
- if (signals_->is_set(s)) {
- ++shared_data_[s];
- // We don't clear the flag here but simply leave it signaled because
- // we want the other thread to also increment this variable.
- }
- }
- notifier_->Signal();
- }
- }
-
- private:
- size_t count_;
- FlagArray* signals_;
- size_t* shared_data_;
- size_t repeats_;
- CrossProcessNotification* notifier_;
- DISALLOW_COPY_AND_ASSIGN(MultiNotifierWorkerFlagArray);
-};
-
-} // end namespace
-
-TEST(CrossProcessNotification, FlagArray) {
- FlagArray flags;
- EXPECT_GT(flags.size(), 1000U);
- for (size_t i = 0; i < flags.size(); ++i) {
- EXPECT_FALSE(flags.is_set(i));
- flags.set(i);
- EXPECT_TRUE(flags.is_set(i));
- flags.clear(i);
- EXPECT_FALSE(flags.is_set(i));
- }
-}
-
-// Initializes two notifiers, signals the each one and make sure the others
-// wait is satisfied.
-TEST(CrossProcessNotification, Basic) {
- CrossProcessNotification a, b;
- ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
- EXPECT_TRUE(a.IsValid());
- EXPECT_TRUE(b.IsValid());
-
- a.Signal();
- b.Wait();
-
- b.Signal();
- a.Wait();
-}
-
-// Spins two worker threads, each with their own CrossProcessNotification
-// that they use to read and write from a shared memory buffer.
-// Disabled as it trips of the TSAN bot (false positive since TSAN doesn't
-// recognize sockets as being a synchronization primitive).
-TEST(CrossProcessNotification, DISABLED_TwoThreads) {
- CrossProcessNotification a, b;
- ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
-
- size_t data = 0;
- const size_t kRepeats = 10000;
- SingleNotifierWorker worker1(&data, kRepeats, &a);
- SingleNotifierWorker worker2(&data, kRepeats, &b);
- base::PlatformThreadHandle thread1, thread2;
- base::PlatformThread::Create(0, &worker1, &thread1);
- base::PlatformThread::Create(0, &worker2, &thread2);
-
- // Start the first thread. They should ping pong a few times and take turns
- // incrementing the shared variable and never step on each other's toes.
- a.Signal();
-
- base::PlatformThread::Join(thread1);
- base::PlatformThread::Join(thread2);
-
- EXPECT_EQ(kRepeats * 2, data);
-}
-
-// Uses a pair of threads to access up to 1000 pieces of synchronized shared
-// data. On regular dev machines, the number of notifiers should be 1000, but on
-// mac and linux bots, the number will be smaller due to the RLIMIT_NOFILE
-// limit. Specifically, linux will have this limit at 1024 which means for this
-// test that the max number of notifiers will be in the range 500-512. On Mac
-// the limit is 256, so |count| will be ~120. Oh, and raising the limit via
-// setrlimit() won't work.
-// DISABLED since the distribution won't be accurate when run on valgrind.
-TEST(CrossProcessNotification, DISABLED_ThousandNotifiersTwoThreads) {
- const size_t kCount = 1000;
- NotificationsOwner pairs(kCount);
- size_t data[kCount] = {0};
- // We use a multiple of the count so that the division in the check below
- // will be nice and round.
- size_t repeats = pairs.size() * 1;
-
- MultiNotifierWorker worker_1(&data[0], repeats, &pairs.a());
- MultiNotifierWorker worker_2(&data[0], repeats, &pairs.b());
- base::PlatformThreadHandle thread_1, thread_2;
- base::PlatformThread::Create(0, &worker_1, &thread_1);
- base::PlatformThread::Create(0, &worker_2, &thread_2);
-
- for (size_t i = 0; i < pairs.size(); ++i)
- pairs.a()[i]->Signal();
-
- base::PlatformThread::Join(thread_1);
- base::PlatformThread::Join(thread_2);
-
- size_t expected_total = pairs.size() * 2;
- size_t total = 0;
- for (size_t i = 0; i < pairs.size(); ++i) {
- // The CrossProcessNotification::WaitForMultiple class should have ensured
- // that all notifiers had the same quality of service.
- EXPECT_EQ(expected_total / pairs.size(), data[i]);
- total += data[i];
- }
- EXPECT_EQ(expected_total, total);
-}
-
-// Functionally equivalent (as far as the shared data goes) to the
-// ThousandNotifiersTwoThreads test but uses a single pair of notifiers +
-// FlagArray for the 1000 signals. This approach is significantly faster.
-// Disabled as it trips of the TSAN bot - "Possible data race during write of
-// size 4" (the flag array).
-TEST(CrossProcessNotification, DISABLED_TwoNotifiersTwoThreads1000Signals) {
- CrossProcessNotification a, b;
- ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
-
- const size_t kCount = 1000;
- FlagArray signals;
- ASSERT_GE(signals.size(), kCount);
- size_t data[kCount] = {0};
-
- // Since this algorithm checks all events each time the notifier is
- // signaled, |repeat| doesn't mean the same thing here as it does in
- // ThousandNotifiersTwoThreads. 1 repeat here is the same as kCount
- // repeats in ThousandNotifiersTwoThreads.
- size_t repeats = 1;
- MultiNotifierWorkerFlagArray worker1(kCount, &signals, &data[0], repeats, &a);
- MultiNotifierWorkerFlagArray worker2(kCount, &signals, &data[0], repeats, &b);
- base::PlatformThreadHandle thread1, thread2;
- base::PlatformThread::Create(0, &worker1, &thread1);
- base::PlatformThread::Create(0, &worker2, &thread2);
-
- for (size_t i = 0; i < kCount; ++i)
- signals.set(i);
- a.Signal();
-
- base::PlatformThread::Join(thread1);
- base::PlatformThread::Join(thread2);
-
- size_t expected_total = kCount * 2;
- size_t total = 0;
- for (size_t i = 0; i < kCount; ++i) {
- // Since for each signal, we process all signaled events, the shared data
- // variables should all be equal.
- EXPECT_EQ(expected_total / kCount, data[i]);
- total += data[i];
- }
- EXPECT_EQ(expected_total, total);
-}
-
-// Test the maximum number of notifiers without spinning further wait
-// threads on Windows. This test assumes we can always create 64 pairs and
-// bails if we can't.
-TEST(CrossProcessNotification, MultipleWaits64) {
- const size_t kCount = 64;
- NotificationsOwner pairs(kCount);
- ASSERT_TRUE(pairs.size() == kCount);
-
- CrossProcessNotification::WaitForMultiple waiter(&pairs.b());
- for (size_t i = 0; i < kCount; ++i) {
- pairs.a()[i]->Signal();
- int index = waiter.Wait();
- EXPECT_EQ(i, static_cast<size_t>(index));
- }
-}
-
-// Tests waiting for more notifiers than the OS supports on one thread.
-// The test will create at most 1000 pairs, but on mac/linux bots the actual
-// number will be lower. See comment about the RLIMIT_NOFILE limit above for
-// more details.
-// DISABLED since the distribution won't be accurate when run on valgrind.
-TEST(CrossProcessNotification, DISABLED_MultipleWaits1000) {
- // A 1000 notifiers requires 16 threads on Windows, including the current
- // one, to perform the wait operation.
- const size_t kCount = 1000;
- NotificationsOwner pairs(kCount);
-
- for (size_t i = 0; i < pairs.size(); ++i) {
- pairs.a()[i]->Signal();
- // To disable the load distribution algorithm and force the extra worker
- // thread(s) to catch the signaled event, we define the |waiter| inside
- // the loop.
- CrossProcessNotification::WaitForMultiple waiter(&pairs.b());
- int index = waiter.Wait();
- EXPECT_EQ(i, static_cast<size_t>(index));
- }
-}
-
-class CrossProcessNotificationMultiProcessTest : public base::MultiProcessTest {
-};
-
-namespace {
-
-// A very crude IPC mechanism that we use to set up the spawned child process
-// and the parent process.
-struct CrudeIpc {
- uint8 ready;
- CrossProcessNotification::IPCHandle handle_1;
- CrossProcessNotification::IPCHandle handle_2;
-};
-
-#if defined(OS_POSIX)
-const int kPosixChildSharedMem = 30;
-#else
-const char kSharedMemName[] = "CrossProcessNotificationMultiProcessTest";
-#endif
-
-const size_t kSharedMemSize = 1024;
-
-} // namespace
-
-// The main routine of the child process. Waits for the parent process
-// to copy handles over to the child and then uses a CrossProcessNotification to
-// wait and signal to the parent process.
-MULTIPROCESS_TEST_MAIN(CrossProcessNotificationChildMain) {
-#if defined(OS_POSIX)
- base::SharedMemory mem(
- base::SharedMemoryHandle(kPosixChildSharedMem, true /* auto close */),
- false);
-#else
- base::SharedMemory mem;
- CHECK(mem.CreateNamed(kSharedMemName, true, kSharedMemSize));
-#endif
-
- CHECK(mem.Map(kSharedMemSize));
- CrudeIpc* ipc = reinterpret_cast<CrudeIpc*>(mem.memory());
-
- while (!ipc->ready)
- base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
-
- CrossProcessNotification notifier(ipc->handle_1, ipc->handle_2);
- notifier.Wait();
- notifier.Signal();
-
- return 0;
-}
-
-// Spawns a new process and hands a CrossProcessNotification instance to the
-// new process. Once that's done, it waits for the child process to signal
-// it's end and quits.
-TEST_F(CrossProcessNotificationMultiProcessTest, Basic) {
- CrossProcessNotification a, b;
- ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
- EXPECT_TRUE(a.IsValid());
- EXPECT_TRUE(b.IsValid());
-
- base::SharedMemory mem;
-
-#if defined(OS_POSIX)
- ASSERT_TRUE(mem.CreateAndMapAnonymous(kSharedMemSize));
-#else
- mem.Delete(kSharedMemName); // In case a previous run was unsuccessful.
- ASSERT_TRUE(mem.CreateNamed(kSharedMemName, false, kSharedMemSize));
- ASSERT_TRUE(mem.Map(kSharedMemSize));
-#endif
-
- CrudeIpc* ipc = reinterpret_cast<CrudeIpc*>(mem.memory());
- ipc->ready = false;
-
-#if defined(OS_POSIX)
- const int kPosixChildSocket = 20;
- EXPECT_TRUE(b.ShareToProcess(
- base::kNullProcessHandle, &ipc->handle_1, &ipc->handle_2));
- base::FileHandleMappingVector fd_mapping_vec;
- fd_mapping_vec.push_back(std::make_pair(ipc->handle_1.fd, kPosixChildSocket));
- fd_mapping_vec.push_back(
- std::make_pair(mem.handle().fd, kPosixChildSharedMem));
- ipc->handle_1.fd = kPosixChildSocket;
- base::ProcessHandle process = SpawnChild("CrossProcessNotificationChildMain",
- fd_mapping_vec, false);
-#else
- base::ProcessHandle process = SpawnChild("CrossProcessNotificationChildMain",
- false);
- EXPECT_TRUE(b.ShareToProcess(process, &ipc->handle_1, &ipc->handle_2));
-#endif
-
- ipc->ready = true;
-
- a.Signal();
- a.Wait();
-
- int exit_code = -1;
- base::WaitForExitCode(process, &exit_code);
- EXPECT_EQ(0, exit_code);
-}
diff --git a/src/media/audio/cross_process_notification_win.cc b/src/media/audio/cross_process_notification_win.cc
deleted file mode 100644
index b454cbf..0000000
--- a/src/media/audio/cross_process_notification_win.cc
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/cross_process_notification.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/threading/platform_thread.h"
-#include "base/win/scoped_handle.h"
-
-CrossProcessNotification::~CrossProcessNotification() {}
-
-CrossProcessNotification::CrossProcessNotification(IPCHandle handle_1,
- IPCHandle handle_2)
- : mine_(handle_1), other_(handle_2) {
- DCHECK(IsValid());
-}
-
-void CrossProcessNotification::Signal() {
- DCHECK(IsValid());
- DCHECK_EQ(::WaitForSingleObject(mine_, 0), static_cast<DWORD>(WAIT_TIMEOUT))
- << "Are you calling Signal() without calling Wait() first?";
- BOOL ok = ::SetEvent(mine_);
- CHECK(ok);
-}
-
-void CrossProcessNotification::Wait() {
- DCHECK(IsValid());
- DWORD wait = ::WaitForSingleObject(other_, INFINITE);
- DCHECK_EQ(wait, WAIT_OBJECT_0);
- BOOL ok = ::ResetEvent(other_);
- CHECK(ok);
-}
-
-bool CrossProcessNotification::IsValid() const {
- return mine_.IsValid() && other_.IsValid();
-}
-
-bool CrossProcessNotification::ShareToProcess(base::ProcessHandle process,
- IPCHandle* handle_1,
- IPCHandle* handle_2) {
- DCHECK(IsValid());
- HANDLE our_process = ::GetCurrentProcess();
- if (!::DuplicateHandle(our_process, mine_, process, handle_1, 0, FALSE,
- DUPLICATE_SAME_ACCESS)) {
- return false;
- }
-
- if (!::DuplicateHandle(our_process, other_, process, handle_2, 0, FALSE,
- DUPLICATE_SAME_ACCESS)) {
- // In case we're sharing to ourselves, we can close the handle, but
- // if the target process is a different process, we do nothing.
- if (process == our_process)
- ::CloseHandle(*handle_1);
- *handle_1 = NULL;
- return false;
- }
-
- return true;
-}
-
-// static
-bool CrossProcessNotification::InitializePair(CrossProcessNotification* a,
- CrossProcessNotification* b) {
- DCHECK(!a->IsValid());
- DCHECK(!b->IsValid());
-
- bool success = false;
-
- // Create two manually resettable events and give each party a handle
- // to both events.
- HANDLE event_a = ::CreateEvent(NULL, TRUE, FALSE, NULL);
- HANDLE event_b = ::CreateEvent(NULL, TRUE, FALSE, NULL);
- if (event_a && event_b) {
- a->mine_.Set(event_a);
- a->other_.Set(event_b);
- success = a->ShareToProcess(GetCurrentProcess(), &event_a, &event_b);
- if (success) {
- b->mine_.Set(event_b);
- b->other_.Set(event_a);
- } else {
- a->mine_.Close();
- a->other_.Close();
- }
- } else {
- if (event_a)
- ::CloseHandle(event_a);
- if (event_b)
- ::CloseHandle(event_b);
- }
-
- DCHECK(!success || a->IsValid());
- DCHECK(!success || b->IsValid());
-
- return success;
-}
-
-namespace {
-class ExtraWaitThread : public base::PlatformThread::Delegate {
- public:
- ExtraWaitThread(HANDLE stop, HANDLE* events, size_t count,
- int* signaled_event)
- : stop_(stop), events_(events), count_(count),
- signaled_event_(signaled_event) {
- *signaled_event_ = -1;
- }
- virtual ~ExtraWaitThread() {}
-
- virtual void ThreadMain() OVERRIDE {
- // Store the |stop_| event as the first event.
- HANDLE events[MAXIMUM_WAIT_OBJECTS] = { stop_ };
- HANDLE next_thread = NULL;
- DWORD event_count = MAXIMUM_WAIT_OBJECTS;
- int thread_signaled_event = -1;
- scoped_ptr<ExtraWaitThread> extra_wait_thread;
- if (count_ > (MAXIMUM_WAIT_OBJECTS - 1)) {
- std::copy(&events_[0], &events_[MAXIMUM_WAIT_OBJECTS - 2], &events[1]);
-
- extra_wait_thread.reset(new ExtraWaitThread(stop_,
- &events_[MAXIMUM_WAIT_OBJECTS - 2],
- count_ - (MAXIMUM_WAIT_OBJECTS - 2),
- &thread_signaled_event));
- base::PlatformThread::Create(0, extra_wait_thread.get(), &next_thread);
-
- event_count = MAXIMUM_WAIT_OBJECTS;
- events[MAXIMUM_WAIT_OBJECTS - 1] = next_thread;
- } else {
- std::copy(&events_[0], &events_[count_], &events[1]);
- event_count = count_ + 1;
- }
-
- DWORD wait = ::WaitForMultipleObjects(event_count, &events[0], FALSE,
- INFINITE);
- if (wait >= WAIT_OBJECT_0 && wait < (WAIT_OBJECT_0 + event_count)) {
- wait -= WAIT_OBJECT_0;
- if (wait == 0) {
- // The stop event was signaled. Check if it was signaled by a
- // sub thread. In case our sub thread had to spin another thread (and
- // so on), we must wait for ours to exit before we can check the
- // propagated event offset.
- if (next_thread) {
- base::PlatformThread::Join(next_thread);
- next_thread = NULL;
- }
- if (thread_signaled_event != -1)
- *signaled_event_ = thread_signaled_event + (MAXIMUM_WAIT_OBJECTS - 2);
- } else if (events[wait] == next_thread) {
- NOTREACHED();
- } else {
- *signaled_event_ = static_cast<int>(wait);
- SetEvent(stop_);
- }
- } else {
- NOTREACHED();
- }
-
- if (next_thread)
- base::PlatformThread::Join(next_thread);
- }
-
- private:
- HANDLE stop_;
- HANDLE* events_;
- size_t count_;
- int* signaled_event_;
- DISALLOW_COPY_AND_ASSIGN(ExtraWaitThread);
-};
-} // end namespace
-
-// static
-int CrossProcessNotification::WaitMultiple(const Notifications& notifications,
- size_t wait_offset) {
- DCHECK_LT(wait_offset, notifications.size());
-
- for (size_t i = 0; i < notifications.size(); ++i) {
- DCHECK(notifications[i]->IsValid());
- }
-
- // TODO(tommi): Should we wait in an alertable state so that we can be
- // canceled via an APC?
- scoped_array<HANDLE> handles(new HANDLE[notifications.size()]);
-
- // Because of the way WaitForMultipleObjects works, we do a little trick here.
- // When multiple events are signaled, WaitForMultipleObjects will return the
- // index of the first signaled item (lowest). This means that if we always
- // pass the array the same way to WaitForMultipleObjects, the objects that
- // come first, have higher priority. In times of heavy load, this will cause
- // elements at the back to become DOS-ed.
- // So, we store the location of the item that was last signaled. Then we split
- // up the array and move everything higher than the last signaled index to the
- // front and the rest to the back (meaning that the last signaled item will
- // become the last element in the list).
- // Assuming equally busy events, this approach distributes the priority
- // evenly.
-
- size_t index = 0;
- for (size_t i = wait_offset; i < notifications.size(); ++i)
- handles[index++] = notifications[i]->other_;
-
- for (size_t i = 0; i < wait_offset; ++i)
- handles[index++] = notifications[i]->other_;
- DCHECK_EQ(index, notifications.size());
-
- DWORD wait = WAIT_FAILED;
- bool wait_failed = false;
- if (notifications.size() <= MAXIMUM_WAIT_OBJECTS) {
- wait = ::WaitForMultipleObjects(notifications.size(), &handles[0], FALSE,
- INFINITE);
- wait_failed = wait < WAIT_OBJECT_0 ||
- wait >= (WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS);
- } else {
- // Used to stop the other wait threads when an event has been signaled.
- base::win::ScopedHandle stop(::CreateEvent(NULL, TRUE, FALSE, NULL));
-
- // Create the first thread and pass a pointer to all handles >63
- // to the thread + 'stop'. Then implement the thread so that it checks
- // if the number of handles is > 63. If so, spawns a new thread and
- // passes >62 handles to that thread and waits for the 62 handles + stop +
- // next thread. etc etc.
-
- // Create a list of threads so that each thread waits on at most 62 events
- // including one event for when a child thread signals completion and one
- // event for when all of the threads must be stopped (due to some event
- // being signaled).
-
- int thread_signaled_event = -1;
- ExtraWaitThread wait_thread(stop, &handles[MAXIMUM_WAIT_OBJECTS - 1],
- notifications.size() - (MAXIMUM_WAIT_OBJECTS - 1),
- &thread_signaled_event);
- base::PlatformThreadHandle thread;
- base::PlatformThread::Create(0, &wait_thread, &thread);
- HANDLE events[MAXIMUM_WAIT_OBJECTS];
- std::copy(&handles[0], &handles[MAXIMUM_WAIT_OBJECTS - 1], &events[0]);
- events[MAXIMUM_WAIT_OBJECTS - 1] = thread;
- wait = ::WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, &events[0], FALSE,
- INFINITE);
- wait_failed = wait < WAIT_OBJECT_0 ||
- wait >= (WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS);
- if (wait == WAIT_OBJECT_0 + (MAXIMUM_WAIT_OBJECTS - 1)) {
- if (thread_signaled_event < 0) {
- wait_failed = true;
- NOTREACHED();
- } else {
- wait = WAIT_OBJECT_0 + (MAXIMUM_WAIT_OBJECTS - 2) +
- thread_signaled_event;
- }
- } else {
- ::SetEvent(stop);
- }
- base::PlatformThread::Join(thread);
- }
-
- int ret = -1;
- if (!wait_failed) {
- // Subtract to be politically correct (WAIT_OBJECT_0 is actually 0).
- wait -= WAIT_OBJECT_0;
- BOOL ok = ::ResetEvent(handles[wait]);
- CHECK(ok);
- ret = (wait + wait_offset) % notifications.size();
- DCHECK_EQ(handles[wait], notifications[ret]->other_.Get());
- } else {
- NOTREACHED();
- }
-
- CHECK_NE(ret, -1);
- return ret;
-}
diff --git a/src/media/audio/fake_audio_input_stream.cc b/src/media/audio/fake_audio_input_stream.cc
deleted file mode 100644
index d2b1ce9..0000000
--- a/src/media/audio/fake_audio_input_stream.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/fake_audio_input_stream.h"
-
-#include "base/bind.h"
-#include "base/lazy_instance.h"
-#include "media/audio/audio_manager_base.h"
-
-using base::Time;
-using base::TimeDelta;
-
-namespace media {
-
-namespace {
-
-// These values are based on experiments for local-to-local
-// PeerConnection to demonstrate audio/video synchronization.
-const int kBeepDurationMilliseconds = 20;
-const int kBeepFrequency = 400;
-
-struct BeepContext {
- BeepContext() : beep_once(false) {}
- base::Lock beep_lock;
- bool beep_once;
-};
-
-static base::LazyInstance<BeepContext> g_beep_context =
- LAZY_INSTANCE_INITIALIZER;
-
-} // namespace
-
-AudioInputStream* FakeAudioInputStream::MakeFakeStream(
- AudioManagerBase* manager,
- const AudioParameters& params) {
- return new FakeAudioInputStream(manager, params);
-}
-
-FakeAudioInputStream::FakeAudioInputStream(AudioManagerBase* manager,
- const AudioParameters& params)
- : audio_manager_(manager),
- callback_(NULL),
- buffer_size_((params.channels() * params.bits_per_sample() *
- params.frames_per_buffer()) / 8),
- params_(params),
- thread_("FakeAudioRecordingThread"),
- callback_interval_(base::TimeDelta::FromMilliseconds(
- (params.frames_per_buffer() * 1000) / params.sample_rate())),
- beep_duration_in_buffers_(
- kBeepDurationMilliseconds * params.sample_rate() /
- params.frames_per_buffer() / 1000),
- beep_generated_in_buffers_(0),
- beep_period_in_frames_(params.sample_rate() / kBeepFrequency),
- frames_elapsed_(0) {
-}
-
-FakeAudioInputStream::~FakeAudioInputStream() {}
-
-bool FakeAudioInputStream::Open() {
- buffer_.reset(new uint8[buffer_size_]);
- memset(buffer_.get(), 0, buffer_size_);
- return true;
-}
-
-void FakeAudioInputStream::Start(AudioInputCallback* callback) {
- DCHECK(!thread_.IsRunning());
- callback_ = callback;
- last_callback_time_ = Time::Now();
- thread_.Start();
- thread_.message_loop()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&FakeAudioInputStream::DoCallback, base::Unretained(this)),
- callback_interval_);
-}
-
-void FakeAudioInputStream::DoCallback() {
- DCHECK(callback_);
-
- memset(buffer_.get(), 0, buffer_size_);
-
- bool should_beep = false;
- {
- BeepContext* beep_context = g_beep_context.Pointer();
- base::AutoLock auto_lock(beep_context->beep_lock);
- should_beep = beep_context->beep_once;
- beep_context->beep_once = false;
- }
-
- // If this object was instructed to generate a beep or has started to
- // generate a beep sound.
- if (should_beep || beep_generated_in_buffers_) {
- // Compute the number of frames to output high value. Then compute the
- // number of bytes based on channels and bits per channel.
- int high_frames = beep_period_in_frames_ / 2;
- int high_bytes = high_frames * params_.bits_per_sample() *
- params_.channels() / 8;
-
- // Separate high and low with the same number of bytes to generate a
- // square wave.
- int position = 0;
- while (position + high_bytes <= buffer_size_) {
- // Write high values first.
- memset(buffer_.get() + position, 128, high_bytes);
-
- // Then leave low values in the buffer with |high_bytes|.
- position += high_bytes * 2;
- }
-
- ++beep_generated_in_buffers_;
- if (beep_generated_in_buffers_ >= beep_duration_in_buffers_)
- beep_generated_in_buffers_ = 0;
- }
-
- callback_->OnData(this, buffer_.get(), buffer_size_, buffer_size_, 1.0);
- frames_elapsed_ += params_.frames_per_buffer();
-
- Time now = Time::Now();
- base::TimeDelta next_callback_time =
- last_callback_time_ + callback_interval_ * 2 - now;
-
- // If we are falling behind, try to catch up as much as we can in the next
- // callback.
- if (next_callback_time < base::TimeDelta())
- next_callback_time = base::TimeDelta();
-
- last_callback_time_ = now;
- thread_.message_loop()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&FakeAudioInputStream::DoCallback, base::Unretained(this)),
- next_callback_time);
-}
-
-void FakeAudioInputStream::Stop() {
- thread_.Stop();
-}
-
-void FakeAudioInputStream::Close() {
- if (callback_) {
- callback_->OnClose(this);
- callback_ = NULL;
- }
- audio_manager_->ReleaseInputStream(this);
-}
-
-double FakeAudioInputStream::GetMaxVolume() {
- return 1.0;
-}
-
-void FakeAudioInputStream::SetVolume(double volume) {
-}
-
-double FakeAudioInputStream::GetVolume() {
- return 1.0;
-}
-
-void FakeAudioInputStream::SetAutomaticGainControl(bool enabled) {}
-
-bool FakeAudioInputStream::GetAutomaticGainControl() {
- return true;
-}
-
-// static
-void FakeAudioInputStream::BeepOnce() {
- BeepContext* beep_context = g_beep_context.Pointer();
- base::AutoLock auto_lock(beep_context->beep_lock);
- beep_context->beep_once = true;
-}
-
-} // namespace media
diff --git a/src/media/audio/fake_audio_input_stream.h b/src/media/audio/fake_audio_input_stream.h
deleted file mode 100644
index c1e1ba5..0000000
--- a/src/media/audio/fake_audio_input_stream.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// A fake implementation of AudioInputStream, useful for testing purpose.
-
-#ifndef MEDIA_AUDIO_FAKE_AUDIO_INPUT_STREAM_H_
-#define MEDIA_AUDIO_FAKE_AUDIO_INOUT_STREAM_H_
-
-#include <vector>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerBase;
-
-class MEDIA_EXPORT FakeAudioInputStream
- : public AudioInputStream {
- public:
- static AudioInputStream* MakeFakeStream(AudioManagerBase* manager,
- const AudioParameters& params);
-
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- // Generate one beep sound. This method is called by
- // FakeVideoCaptureDevice to test audio/video synchronization.
- // This is a static method because FakeVideoCaptureDevice is
- // disconnected from an audio device. This means only one instance of
- // this class gets to respond, which is okay because we assume there's
- // only one stream for this testing purpose.
- // TODO(hclam): Make this non-static. To do this we'll need to fix
- // crbug.com/159053 such that video capture device is aware of audio
- // input stream.
- static void BeepOnce();
-
- private:
- FakeAudioInputStream(AudioManagerBase* manager,
- const AudioParameters& params);
-
- virtual ~FakeAudioInputStream();
-
- void DoCallback();
-
- AudioManagerBase* audio_manager_;
- AudioInputCallback* callback_;
- scoped_array<uint8> buffer_;
- int buffer_size_;
- AudioParameters params_;
- base::Thread thread_;
- base::Time last_callback_time_;
- base::TimeDelta callback_interval_;
- int beep_duration_in_buffers_;
- int beep_generated_in_buffers_;
- int beep_period_in_frames_;
- int frames_elapsed_;
-
- DISALLOW_COPY_AND_ASSIGN(FakeAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_FAKE_AUDIO_INPUT_STREAM_H_
diff --git a/src/media/audio/fake_audio_output_stream.cc b/src/media/audio/fake_audio_output_stream.cc
deleted file mode 100644
index c21026d..0000000
--- a/src/media/audio/fake_audio_output_stream.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/fake_audio_output_stream.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-// static
-AudioOutputStream* FakeAudioOutputStream::MakeFakeStream(
- AudioManagerBase* manager, const AudioParameters& params) {
- return new FakeAudioOutputStream(manager, params);
-}
-
-FakeAudioOutputStream::FakeAudioOutputStream(AudioManagerBase* manager,
- const AudioParameters& params)
- : audio_manager_(manager),
- callback_(NULL),
- audio_bus_(AudioBus::Create(params)),
- frames_per_millisecond_(
- params.sample_rate() / static_cast<float>(
- base::Time::kMillisecondsPerSecond)) {
-}
-
-FakeAudioOutputStream::~FakeAudioOutputStream() {
- DCHECK(!callback_);
-}
-
-bool FakeAudioOutputStream::Open() {
- DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- return true;
-}
-
-void FakeAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- callback_ = callback;
- on_more_data_cb_.Reset(base::Bind(
- &FakeAudioOutputStream::OnMoreDataTask, base::Unretained(this)));
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, on_more_data_cb_.callback());
-}
-
-void FakeAudioOutputStream::Stop() {
- DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- callback_ = NULL;
- on_more_data_cb_.Cancel();
-}
-
-void FakeAudioOutputStream::Close() {
- DCHECK(!callback_);
- DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- audio_manager_->ReleaseOutputStream(this);
-}
-
-void FakeAudioOutputStream::SetVolume(double volume) {};
-
-void FakeAudioOutputStream::GetVolume(double* volume) {
- *volume = 0;
-};
-
-void FakeAudioOutputStream::OnMoreDataTask() {
- DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- DCHECK(callback_);
-
- audio_bus_->Zero();
- int frames_received = callback_->OnMoreData(
- audio_bus_.get(), AudioBuffersState());
-
- // Calculate our sleep duration for simulated playback. Sleep for at least
- // one millisecond so we don't spin the CPU.
- audio_manager_->GetMessageLoop()->PostDelayedTask(
- FROM_HERE, on_more_data_cb_.callback(), base::TimeDelta::FromMilliseconds(
- std::max(1.0f, frames_received / frames_per_millisecond_)));
-}
-
-} // namespace media
diff --git a/src/media/audio/fake_audio_output_stream.h b/src/media/audio/fake_audio_output_stream.h
deleted file mode 100644
index d188b9f..0000000
--- a/src/media/audio/fake_audio_output_stream.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_FAKE_AUDIO_OUTPUT_STREAM_H_
-#define MEDIA_AUDIO_FAKE_AUDIO_OUTOUT_STREAM_H_
-
-#include "base/cancelable_callback.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerBase;
-
-// A fake implementation of AudioOutputStream. Used for testing and when a real
-// audio output device is unavailable or refusing output (e.g. remote desktop).
-class MEDIA_EXPORT FakeAudioOutputStream : public AudioOutputStream {
- public:
- static AudioOutputStream* MakeFakeStream(AudioManagerBase* manager,
- const AudioParameters& params);
-
- // AudioOutputStream implementation.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
- virtual void Close() OVERRIDE;
-
- private:
- FakeAudioOutputStream(AudioManagerBase* manager,
- const AudioParameters& params);
- virtual ~FakeAudioOutputStream();
-
- // Task that regularly calls |callback_->OnMoreData()| according to the
- // playback rate as determined by the audio parameters given during
- // construction. Runs on AudioManager's message loop.
- void OnMoreDataTask();
-
- AudioManagerBase* audio_manager_;
- AudioSourceCallback* callback_;
- scoped_ptr<AudioBus> audio_bus_;
- float frames_per_millisecond_;
-
- // Used to post delayed tasks to the AudioThread that we can cancel.
- base::CancelableClosure on_more_data_cb_;
-
- DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_FAKE_AUDIO_OUTPUT_STREAM_H_
diff --git a/src/media/audio/fake_audio_output_stream_unittest.cc b/src/media/audio/fake_audio_output_stream_unittest.cc
deleted file mode 100644
index 6838e3f..0000000
--- a/src/media/audio/fake_audio_output_stream_unittest.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/time.h"
-#include "media/audio/fake_audio_output_stream.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class FakeAudioOutputStreamTest : public testing::Test {
- public:
- FakeAudioOutputStreamTest()
- : audio_manager_(AudioManager::Create()),
- params_(
- AudioParameters::AUDIO_FAKE, CHANNEL_LAYOUT_STEREO, 8000, 8, 128),
- source_(params_.channels(), 200.0, params_.sample_rate()),
- done_(false, false) {
- stream_ = audio_manager_->MakeAudioOutputStream(AudioParameters(params_));
- CHECK(stream_);
-
- time_between_callbacks_ = base::TimeDelta::FromMilliseconds(
- params_.frames_per_buffer() * base::Time::kMillisecondsPerSecond /
- static_cast<float>(params_.sample_rate()));
- }
-
- virtual ~FakeAudioOutputStreamTest() {}
-
- void RunOnAudioThread() {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- ASSERT_TRUE(stream_->Open());
- stream_->Start(&source_);
- }
-
- void RunOnceOnAudioThread() {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- RunOnAudioThread();
- // Start() should immediately post a task to run the source callback, so we
- // should end up with only a single callback being run.
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::EndTest, base::Unretained(this), 1));
- }
-
- void StopStartOnAudioThread() {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_->Stop();
- stream_->Start(&source_);
- }
-
- void TimeCallbacksOnAudioThread(int callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- if (source_.callbacks() == 0) {
- RunOnAudioThread();
- start_time_ = base::Time::Now();
- }
-
- // Keep going until we've seen the requested number of callbacks.
- if (source_.callbacks() < callbacks) {
- audio_manager_->GetMessageLoop()->PostDelayedTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::TimeCallbacksOnAudioThread,
- base::Unretained(this), callbacks), time_between_callbacks_);
- } else {
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::EndTest, base::Unretained(this),
- callbacks));
- }
- }
-
- void EndTest(int callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_->Stop();
- stream_->Close();
- EXPECT_EQ(callbacks, source_.callbacks());
- EXPECT_EQ(0, source_.errors());
- done_.Signal();
- }
-
- protected:
- scoped_ptr<AudioManager> audio_manager_;
- AudioParameters params_;
- AudioOutputStream* stream_;
- SineWaveAudioSource source_;
- base::WaitableEvent done_;
- base::Time start_time_;
- base::TimeDelta time_between_callbacks_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputStreamTest);
-};
-
-// Ensure the fake audio stream runs on the audio thread and handles fires
-// callbacks to the AudioSourceCallback.
-TEST_F(FakeAudioOutputStreamTest, FakeStreamBasicCallback) {
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::RunOnceOnAudioThread,
- base::Unretained(this)));
- done_.Wait();
-}
-
-// Ensure the time between callbacks is sane.
-TEST_F(FakeAudioOutputStreamTest, TimeBetweenCallbacks) {
- static const int kTestCallbacks = 5;
-
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::TimeCallbacksOnAudioThread,
- base::Unretained(this), kTestCallbacks));
-
- // Let the loop run for a second or two then issue Stop() / Start().
- audio_manager_->GetMessageLoop()->PostDelayedTask(FROM_HERE, base::Bind(
- &FakeAudioOutputStreamTest::StopStartOnAudioThread,
- base::Unretained(this)), time_between_callbacks_);
-
- done_.Wait();
-
- base::TimeDelta elapsed = base::Time::Now() - start_time_;
-
- // There are only (kTestCallbacks - 1) intervals between kTestCallbacks.
- float actual_time_between_callbacks_ms =
- elapsed.InMillisecondsF() / (kTestCallbacks - 1);
- float expected_time_between_callbacks_ms =
- time_between_callbacks_.InMillisecondsF();
-
- // Ensure callback time is no faster than the expected time between callbacks.
- EXPECT_GE(actual_time_between_callbacks_ms,
- expected_time_between_callbacks_ms);
-
- // Softly check if the callback time is no slower than twice the expected time
- // between callbacks. Since this test runs on the bots we can't be too strict
- // with the bounds.
- if (actual_time_between_callbacks_ms > 2 * expected_time_between_callbacks_ms)
- LOG(ERROR) << "Time between fake audio callbacks is too large!";
-}
-
-} // namespace media
diff --git a/src/media/audio/ios/audio_manager_ios.h b/src/media/audio/ios/audio_manager_ios.h
deleted file mode 100644
index 55cbe6e..0000000
--- a/src/media/audio/ios/audio_manager_ios.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_IOS_AUDIO_MANAGER_IOS_H_
-#define MEDIA_AUDIO_IOS_AUDIO_MANAGER_IOS_H_
-
-#include "base/basictypes.h"
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-class PCMQueueInAudioInputStream;
-class PCMQueueOutAudioOutputStream;
-
-// iOS implementation of the AudioManager singleton. Supports only audio input.
-class MEDIA_EXPORT AudioManagerIOS : public AudioManagerBase {
- public:
- AudioManagerIOS();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
- virtual AudioOutputStream* MakeAudioOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeAudioInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE;
- virtual void ReleaseInputStream(AudioInputStream* stream) OVERRIDE;
-
- protected:
- virtual ~AudioManagerIOS();
-
- private:
- // Initializes the audio session if necessary. Safe to call multiple times.
- // Returns a bool indicating whether the audio session has been successfully
- // initialized (either in the current call or in a previous call).
- bool InitAudioSession();
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerIOS);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_IOS_AUDIO_MANAGER_IOS_H_
diff --git a/src/media/audio/ios/audio_manager_ios.mm b/src/media/audio/ios/audio_manager_ios.mm
deleted file mode 100644
index a4ffff5..0000000
--- a/src/media/audio/ios/audio_manager_ios.mm
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2012 The Chromium 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 "media/audio/ios/audio_manager_ios.h"
-
-#import <AudioToolbox/AudioToolbox.h>
-#import <AVFoundation/AVFoundation.h>
-
-#include "base/sys_info.h"
-#include "media/audio/fake_audio_input_stream.h"
-#include "media/audio/mac/audio_input_mac.h"
-#include "media/base/limits.h"
-
-namespace media {
-
-enum { kMaxInputChannels = 2 };
-
-// Initializes the audio session, returning a bool indicating whether
-// initialization was successful. Should only be called once.
-static bool InitAudioSessionInternal() {
- OSStatus error = AudioSessionInitialize(NULL, NULL, NULL, NULL);
- DCHECK(error != kAudioSessionAlreadyInitialized);
- AVAudioSession* audioSession = [AVAudioSession sharedInstance];
- BOOL result = [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
- error:nil];
- DCHECK(result);
- UInt32 allowMixing = true;
- AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,
- sizeof(allowMixing), &allowMixing);
- return error == kAudioSessionNoError;
-}
-
-AudioManagerIOS::AudioManagerIOS() {
-}
-
-AudioManagerIOS::~AudioManagerIOS() {
- Shutdown();
-}
-
-bool AudioManagerIOS::HasAudioOutputDevices() {
- return false;
-}
-
-bool AudioManagerIOS::HasAudioInputDevices() {
- if (!InitAudioSession())
- return false;
- // Note that the |kAudioSessionProperty_AudioInputAvailable| property is a
- // 32-bit integer, not a boolean.
- UInt32 property_size;
- OSStatus error =
- AudioSessionGetPropertySize(kAudioSessionProperty_AudioInputAvailable,
- &property_size);
- if (error != kAudioSessionNoError)
- return false;
- UInt32 audio_input_is_available = false;
- DCHECK(property_size == sizeof(audio_input_is_available));
- error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable,
- &property_size,
- &audio_input_is_available);
- return error == kAudioSessionNoError ? audio_input_is_available : false;
-}
-
-AudioOutputStream* AudioManagerIOS::MakeAudioOutputStream(
- const AudioParameters& params) {
- NOTIMPLEMENTED(); // Only input is supported on iOS.
- return NULL;
-}
-
-AudioInputStream* AudioManagerIOS::MakeAudioInputStream(
- const AudioParameters& params, const std::string& device_id) {
- // Current line of iOS devices has only one audio input.
- // Ignore the device_id (unittest uses a test value in it).
- if (!params.IsValid() || (params.channels() > kMaxInputChannels))
- return NULL;
-
- if (params.format() == AudioParameters::AUDIO_FAKE)
- return FakeAudioInputStream::MakeFakeStream(this, params);
- else if (params.format() == AudioParameters::AUDIO_PCM_LINEAR)
- return new PCMQueueInAudioInputStream(this, params);
- return NULL;
-}
-
-AudioOutputStream* AudioManagerIOS::MakeLinearOutputStream(
- const AudioParameters& params) {
- NOTIMPLEMENTED(); // Only input is supported on iOS.
- return NULL;
-}
-
-AudioOutputStream* AudioManagerIOS::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- NOTIMPLEMENTED(); // Only input is supported on iOS.
- return NULL;
-}
-
-AudioInputStream* AudioManagerIOS::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- return MakeAudioInputStream(params, device_id);
-}
-
-AudioInputStream* AudioManagerIOS::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- NOTIMPLEMENTED(); // Only linear audio input is supported on iOS.
- return MakeAudioInputStream(params, device_id);
-}
-
-// Called by the stream when it has been released by calling Close().
-void AudioManagerIOS::ReleaseOutputStream(AudioOutputStream* stream) {
- NOTIMPLEMENTED(); // Only input is supported on iOS.
-}
-
-// Called by the stream when it has been released by calling Close().
-void AudioManagerIOS::ReleaseInputStream(AudioInputStream* stream) {
- delete stream;
-}
-
-bool AudioManagerIOS::InitAudioSession() {
- static const bool kSessionInitialized = InitAudioSessionInternal();
- return kSessionInitialized;
-}
-
-// static
-AudioManager* CreateAudioManager() {
- return new AudioManagerIOS();
-}
-
-} // namespace media
diff --git a/src/media/audio/ios/audio_manager_ios_unittest.cc b/src/media/audio/ios/audio_manager_ios_unittest.cc
deleted file mode 100644
index e8013cc..0000000
--- a/src/media/audio/ios/audio_manager_ios_unittest.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2012 The Chromium 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 "base/basictypes.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using namespace media;
-
-// Test that input is supported and output is not.
-TEST(IOSAudioTest, AudioSupport) {
- AudioManager* audio_manager = AudioManager::Create();
- ASSERT_TRUE(NULL != audio_manager);
- ASSERT_FALSE(audio_manager->HasAudioOutputDevices());
- ASSERT_TRUE(audio_manager->HasAudioInputDevices());
-}
-
-// Test that input stream can be opened and closed.
-TEST(IOSAudioTest, InputStreamOpenAndClose) {
- AudioManager* audio_manager = AudioManager::Create();
- ASSERT_TRUE(NULL != audio_manager);
- if (!audio_manager->HasAudioInputDevices())
- return;
- AudioInputStream* ias = audio_manager->MakeAudioInputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 1024),
- std::string("test_device"));
- ASSERT_TRUE(NULL != ias);
- EXPECT_TRUE(ias->Open());
- ias->Close();
-}
diff --git a/src/media/audio/linux/alsa_input.cc b/src/media/audio/linux/alsa_input.cc
deleted file mode 100644
index ea199cb..0000000
--- a/src/media/audio/linux/alsa_input.cc
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/linux/alsa_input.h"
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "base/time.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/linux/alsa_output.h"
-#include "media/audio/linux/alsa_util.h"
-#include "media/audio/linux/alsa_wrapper.h"
-#include "media/audio/linux/audio_manager_linux.h"
-
-namespace media {
-
-static const int kNumPacketsInRingBuffer = 3;
-
-static const char kDefaultDevice1[] = "default";
-static const char kDefaultDevice2[] = "plug:default";
-
-const char* AlsaPcmInputStream::kAutoSelectDevice = "";
-
-AlsaPcmInputStream::AlsaPcmInputStream(AudioManagerLinux* audio_manager,
- const std::string& device_name,
- const AudioParameters& params,
- AlsaWrapper* wrapper)
- : audio_manager_(audio_manager),
- device_name_(device_name),
- params_(params),
- bytes_per_buffer_(params.frames_per_buffer() *
- (params.channels() * params.bits_per_sample()) / 8),
- wrapper_(wrapper),
- buffer_duration_ms_(
- (params.frames_per_buffer() * base::Time::kMillisecondsPerSecond) /
- params.sample_rate()),
- callback_(NULL),
- device_handle_(NULL),
- mixer_handle_(NULL),
- mixer_element_handle_(NULL),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
- read_callback_behind_schedule_(false) {
-}
-
-AlsaPcmInputStream::~AlsaPcmInputStream() {}
-
-bool AlsaPcmInputStream::Open() {
- if (device_handle_)
- return false; // Already open.
-
- snd_pcm_format_t pcm_format = alsa_util::BitsToFormat(
- params_.bits_per_sample());
- if (pcm_format == SND_PCM_FORMAT_UNKNOWN) {
- LOG(WARNING) << "Unsupported bits per sample: "
- << params_.bits_per_sample();
- return false;
- }
-
- uint32 latency_us = buffer_duration_ms_ * kNumPacketsInRingBuffer *
- base::Time::kMicrosecondsPerMillisecond;
-
- // Use the same minimum required latency as output.
- latency_us = std::max(latency_us, AlsaPcmOutputStream::kMinLatencyMicros);
-
- if (device_name_ == kAutoSelectDevice) {
- const char* device_names[] = { kDefaultDevice1, kDefaultDevice2 };
- for (size_t i = 0; i < arraysize(device_names); ++i) {
- device_handle_ = alsa_util::OpenCaptureDevice(
- wrapper_, device_names[i], params_.channels(),
- params_.sample_rate(), pcm_format, latency_us);
-
- if (device_handle_) {
- device_name_ = device_names[i];
- break;
- }
- }
- } else {
- device_handle_ = alsa_util::OpenCaptureDevice(wrapper_,
- device_name_.c_str(),
- params_.channels(),
- params_.sample_rate(),
- pcm_format, latency_us);
- }
-
- if (device_handle_) {
- audio_buffer_.reset(new uint8[bytes_per_buffer_]);
-
- // Open the microphone mixer.
- mixer_handle_ = alsa_util::OpenMixer(wrapper_, device_name_);
- if (mixer_handle_) {
- mixer_element_handle_ = alsa_util::LoadCaptureMixerElement(
- wrapper_, mixer_handle_);
- }
- }
-
- return device_handle_ != NULL;
-}
-
-void AlsaPcmInputStream::Start(AudioInputCallback* callback) {
- DCHECK(!callback_ && callback);
- callback_ = callback;
- int error = wrapper_->PcmPrepare(device_handle_);
- if (error < 0) {
- HandleError("PcmPrepare", error);
- } else {
- error = wrapper_->PcmStart(device_handle_);
- if (error < 0)
- HandleError("PcmStart", error);
- }
-
- if (error < 0) {
- callback_ = NULL;
- } else {
- // We start reading data half |buffer_duration_ms_| later than when the
- // buffer might have got filled, to accommodate some delays in the audio
- // driver. This could also give us a smooth read sequence going forward.
- base::TimeDelta delay = base::TimeDelta::FromMilliseconds(
- buffer_duration_ms_ + buffer_duration_ms_ / 2);
- next_read_time_ = base::Time::Now() + delay;
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()),
- delay);
-
- audio_manager_->IncreaseActiveInputStreamCount();
- }
-}
-
-bool AlsaPcmInputStream::Recover(int original_error) {
- int error = wrapper_->PcmRecover(device_handle_, original_error, 1);
- if (error < 0) {
- // Docs say snd_pcm_recover returns the original error if it is not one
- // of the recoverable ones, so this log message will probably contain the
- // same error twice.
- LOG(WARNING) << "Unable to recover from \""
- << wrapper_->StrError(original_error) << "\": "
- << wrapper_->StrError(error);
- return false;
- }
-
- if (original_error == -EPIPE) { // Buffer underrun/overrun.
- // For capture streams we have to repeat the explicit start() to get
- // data flowing again.
- error = wrapper_->PcmStart(device_handle_);
- if (error < 0) {
- HandleError("PcmStart", error);
- return false;
- }
- }
-
- return true;
-}
-
-snd_pcm_sframes_t AlsaPcmInputStream::GetCurrentDelay() {
- snd_pcm_sframes_t delay = -1;
-
- int error = wrapper_->PcmDelay(device_handle_, &delay);
- if (error < 0)
- Recover(error);
-
- // snd_pcm_delay() may not work in the beginning of the stream. In this case
- // return delay of data we know currently is in the ALSA's buffer.
- if (delay < 0)
- delay = wrapper_->PcmAvailUpdate(device_handle_);
-
- return delay;
-}
-
-void AlsaPcmInputStream::ReadAudio() {
- DCHECK(callback_);
-
- snd_pcm_sframes_t frames = wrapper_->PcmAvailUpdate(device_handle_);
- if (frames < 0) { // Potentially recoverable error?
- LOG(WARNING) << "PcmAvailUpdate(): " << wrapper_->StrError(frames);
- Recover(frames);
- }
-
- if (frames < params_.frames_per_buffer()) {
- // Not enough data yet or error happened. In both cases wait for a very
- // small duration before checking again.
- // Even Though read callback was behind schedule, there is no data, so
- // reset the next_read_time_.
- if (read_callback_behind_schedule_) {
- next_read_time_ = base::Time::Now();
- read_callback_behind_schedule_ = false;
- }
-
- base::TimeDelta next_check_time = base::TimeDelta::FromMilliseconds(
- buffer_duration_ms_ / 2);
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()),
- next_check_time);
- return;
- }
-
- int num_buffers = frames / params_.frames_per_buffer();
- uint32 hardware_delay_bytes =
- static_cast<uint32>(GetCurrentDelay() * params_.GetBytesPerFrame());
- double normalized_volume = 0.0;
-
- // Update the AGC volume level once every second. Note that, |volume| is
- // also updated each time SetVolume() is called through IPC by the
- // render-side AGC.
- QueryAgcVolume(&normalized_volume);
-
- while (num_buffers--) {
- int frames_read = wrapper_->PcmReadi(device_handle_, audio_buffer_.get(),
- params_.frames_per_buffer());
- if (frames_read == params_.frames_per_buffer()) {
- callback_->OnData(this, audio_buffer_.get(), bytes_per_buffer_,
- hardware_delay_bytes, normalized_volume);
- } else {
- LOG(WARNING) << "PcmReadi returning less than expected frames: "
- << frames_read << " vs. " << params_.frames_per_buffer()
- << ". Dropping this buffer.";
- }
- }
-
- next_read_time_ += base::TimeDelta::FromMilliseconds(buffer_duration_ms_);
- base::TimeDelta delay = next_read_time_ - base::Time::Now();
- if (delay < base::TimeDelta()) {
- LOG(WARNING) << "Audio read callback behind schedule by "
- << (buffer_duration_ms_ - delay.InMilliseconds())
- << " (ms).";
- // Read callback is behind schedule. Assuming there is data pending in
- // the soundcard, invoke the read callback immediate in order to catch up.
- read_callback_behind_schedule_ = true;
- delay = base::TimeDelta();
- }
-
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AlsaPcmInputStream::ReadAudio, weak_factory_.GetWeakPtr()),
- delay);
-}
-
-void AlsaPcmInputStream::Stop() {
- if (!device_handle_ || !callback_)
- return;
-
- // Stop is always called before Close. In case of error, this will be
- // also called when closing the input controller.
- audio_manager_->DecreaseActiveInputStreamCount();
-
- weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read.
- int error = wrapper_->PcmDrop(device_handle_);
- if (error < 0)
- HandleError("PcmDrop", error);
-}
-
-void AlsaPcmInputStream::Close() {
- if (device_handle_) {
- weak_factory_.InvalidateWeakPtrs(); // Cancel the next scheduled read.
- int error = alsa_util::CloseDevice(wrapper_, device_handle_);
- if (error < 0)
- HandleError("PcmClose", error);
-
- if (mixer_handle_)
- alsa_util::CloseMixer(wrapper_, mixer_handle_, device_name_);
-
- audio_buffer_.reset();
- device_handle_ = NULL;
- mixer_handle_ = NULL;
- mixer_element_handle_ = NULL;
-
- if (callback_)
- callback_->OnClose(this);
- }
-
- audio_manager_->ReleaseInputStream(this);
-}
-
-double AlsaPcmInputStream::GetMaxVolume() {
- if (!mixer_handle_ || !mixer_element_handle_) {
- DLOG(WARNING) << "GetMaxVolume is not supported for " << device_name_;
- return 0.0;
- }
-
- if (!wrapper_->MixerSelemHasCaptureVolume(mixer_element_handle_)) {
- DLOG(WARNING) << "Unsupported microphone volume for " << device_name_;
- return 0.0;
- }
-
- long min = 0;
- long max = 0;
- if (wrapper_->MixerSelemGetCaptureVolumeRange(mixer_element_handle_,
- &min,
- &max)) {
- DLOG(WARNING) << "Unsupported max microphone volume for " << device_name_;
- return 0.0;
- }
- DCHECK(min == 0);
- DCHECK(max > 0);
-
- return static_cast<double>(max);
-}
-
-void AlsaPcmInputStream::SetVolume(double volume) {
- if (!mixer_handle_ || !mixer_element_handle_) {
- DLOG(WARNING) << "SetVolume is not supported for " << device_name_;
- return;
- }
-
- int error = wrapper_->MixerSelemSetCaptureVolumeAll(
- mixer_element_handle_, static_cast<long>(volume));
- if (error < 0) {
- DLOG(WARNING) << "Unable to set volume for " << device_name_;
- }
-
- // Update the AGC volume level based on the last setting above. Note that,
- // the volume-level resolution is not infinite and it is therefore not
- // possible to assume that the volume provided as input parameter can be
- // used directly. Instead, a new query to the audio hardware is required.
- // This method does nothing if AGC is disabled.
- UpdateAgcVolume();
-}
-
-double AlsaPcmInputStream::GetVolume() {
- if (!mixer_handle_ || !mixer_element_handle_) {
- DLOG(WARNING) << "GetVolume is not supported for " << device_name_;
- return 0.0;
- }
-
- long current_volume = 0;
- int error = wrapper_->MixerSelemGetCaptureVolume(
- mixer_element_handle_, static_cast<snd_mixer_selem_channel_id_t>(0),
- ¤t_volume);
- if (error < 0) {
- DLOG(WARNING) << "Unable to get volume for " << device_name_;
- return 0.0;
- }
-
- return static_cast<double>(current_volume);
-}
-
-void AlsaPcmInputStream::HandleError(const char* method, int error) {
- LOG(WARNING) << method << ": " << wrapper_->StrError(error);
- callback_->OnError(this, error);
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/alsa_input.h b/src/media/audio/linux/alsa_input.h
deleted file mode 100644
index ae027d4..0000000
--- a/src/media/audio/linux/alsa_input.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_LINUX_ALSA_INPUT_H_
-#define MEDIA_AUDIO_LINUX_ALSA_INPUT_H_
-
-#include <alsa/asoundlib.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/time.h"
-#include "media/audio/audio_input_stream_impl.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AlsaWrapper;
-class AudioManagerLinux;
-
-// Provides an input stream for audio capture based on the ALSA PCM interface.
-// This object is not thread safe and all methods should be invoked in the
-// thread that created the object.
-class AlsaPcmInputStream : public AudioInputStreamImpl {
- public:
- // Pass this to the constructor if you want to attempt auto-selection
- // of the audio recording device.
- static const char* kAutoSelectDevice;
-
- // Create a PCM Output stream for the ALSA device identified by
- // |device_name|. If unsure of what to use for |device_name|, use
- // |kAutoSelectDevice|.
- AlsaPcmInputStream(AudioManagerLinux* audio_manager,
- const std::string& device_name,
- const AudioParameters& params,
- AlsaWrapper* wrapper);
-
- virtual ~AlsaPcmInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
-
- private:
- // Logs the error and invokes any registered callbacks.
- void HandleError(const char* method, int error);
-
- // Reads one or more buffers of audio from the device, passes on to the
- // registered callback and schedules the next read.
- void ReadAudio();
-
- // Recovers from any device errors if possible.
- bool Recover(int error);
-
- // Utility function for talking with the ALSA API.
- snd_pcm_sframes_t GetCurrentDelay();
-
- // Non-refcounted pointer back to the audio manager.
- // The AudioManager indirectly holds on to stream objects, so we don't
- // want circular references. Additionally, stream objects live on the audio
- // thread, which is owned by the audio manager and we don't want to addref
- // the manager from that thread.
- AudioManagerLinux* audio_manager_;
- std::string device_name_;
- AudioParameters params_;
- int bytes_per_buffer_;
- AlsaWrapper* wrapper_;
- int buffer_duration_ms_; // Length of each recorded buffer in milliseconds.
- AudioInputCallback* callback_; // Valid during a recording session.
- base::Time next_read_time_; // Scheduled time for the next read callback.
- snd_pcm_t* device_handle_; // Handle to the ALSA PCM recording device.
- snd_mixer_t* mixer_handle_; // Handle to the ALSA microphone mixer.
- snd_mixer_elem_t* mixer_element_handle_; // Handle to the capture element.
- base::WeakPtrFactory<AlsaPcmInputStream> weak_factory_;
- scoped_array<uint8> audio_buffer_; // Buffer used for reading audio data.
- bool read_callback_behind_schedule_;
-
- DISALLOW_COPY_AND_ASSIGN(AlsaPcmInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_LINUX_ALSA_INPUT_H_
diff --git a/src/media/audio/linux/alsa_output.cc b/src/media/audio/linux/alsa_output.cc
deleted file mode 100644
index 1c822b6..0000000
--- a/src/media/audio/linux/alsa_output.cc
+++ /dev/null
@@ -1,796 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// THREAD SAFETY
-//
-// AlsaPcmOutputStream object is *not* thread-safe and should only be used
-// from the audio thread. We DCHECK on this assumption whenever we can.
-//
-// SEMANTICS OF Close()
-//
-// Close() is responsible for cleaning up any resources that were acquired after
-// a successful Open(). Close() will nullify any scheduled outstanding runnable
-// methods.
-//
-//
-// SEMANTICS OF ERROR STATES
-//
-// The object has two distinct error states: |state_| == kInError
-// and |stop_stream_|. The |stop_stream_| variable is used to indicate
-// that the playback_handle should no longer be used either because of a
-// hardware/low-level event.
-//
-// When |state_| == kInError, all public API functions will fail with an error
-// (Start() will call the OnError() function on the callback immediately), or
-// no-op themselves with the exception of Close(). Even if an error state has
-// been entered, if Open() has previously returned successfully, Close() must be
-// called to cleanup the ALSA devices and release resources.
-//
-// When |stop_stream_| is set, no more commands will be made against the
-// ALSA device, and playback will effectively stop. From the client's point of
-// view, it will seem that the device has just clogged and stopped requesting
-// data.
-
-#include "media/audio/linux/alsa_output.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "base/stl_util.h"
-#include "base/time.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/linux/alsa_util.h"
-#include "media/audio/linux/alsa_wrapper.h"
-#include "media/audio/linux/audio_manager_linux.h"
-#include "media/base/channel_mixer.h"
-#include "media/base/data_buffer.h"
-#include "media/base/seekable_buffer.h"
-
-namespace media {
-
-// Amount of time to wait if we've exhausted the data source. This is to avoid
-// busy looping.
-static const uint32 kNoDataSleepMilliseconds = 10;
-
-// Mininum interval between OnMoreData() calls. This is to avoid glitches for
-// WebAudio which needs time to generate new data.
-static const uint32 kMinIntervalBetweenOnMoreDataCallsInMs = 5;
-
-// According to the linux nanosleep manpage, nanosleep on linux can miss the
-// deadline by up to 10ms because the kernel timeslice is 10ms. This should be
-// enough to compensate for the timeslice, and any additional slowdowns.
-static const uint32 kSleepErrorMilliseconds = 10;
-
-// Set to 0 during debugging if you want error messages due to underrun
-// events or other recoverable errors.
-#if defined(NDEBUG)
-static const int kPcmRecoverIsSilent = 1;
-#else
-static const int kPcmRecoverIsSilent = 0;
-#endif
-
-// While the "default" device may support multi-channel audio, in Alsa, only
-// the device names surround40, surround41, surround50, etc, have a defined
-// channel mapping according to Lennart:
-//
-// http://0pointer.de/blog/projects/guide-to-sound-apis.html
-//
-// This function makes a best guess at the specific > 2 channel device name
-// based on the number of channels requested. NULL is returned if no device
-// can be found to match the channel numbers. In this case, using
-// kDefaultDevice is probably the best bet.
-//
-// A five channel source is assumed to be surround50 instead of surround41
-// (which is also 5 channels).
-//
-// TODO(ajwong): The source data should have enough info to tell us if we want
-// surround41 versus surround51, etc., instead of needing us to guess based on
-// channel number. Fix API to pass that data down.
-static const char* GuessSpecificDeviceName(uint32 channels) {
- switch (channels) {
- case 8:
- return "surround71";
-
- case 7:
- return "surround70";
-
- case 6:
- return "surround51";
-
- case 5:
- return "surround50";
-
- case 4:
- return "surround40";
-
- default:
- return NULL;
- }
-}
-
-std::ostream& operator<<(std::ostream& os,
- AlsaPcmOutputStream::InternalState state) {
- switch (state) {
- case AlsaPcmOutputStream::kInError:
- os << "kInError";
- break;
- case AlsaPcmOutputStream::kCreated:
- os << "kCreated";
- break;
- case AlsaPcmOutputStream::kIsOpened:
- os << "kIsOpened";
- break;
- case AlsaPcmOutputStream::kIsPlaying:
- os << "kIsPlaying";
- break;
- case AlsaPcmOutputStream::kIsStopped:
- os << "kIsStopped";
- break;
- case AlsaPcmOutputStream::kIsClosed:
- os << "kIsClosed";
- break;
- };
- return os;
-}
-
-const char AlsaPcmOutputStream::kDefaultDevice[] = "default";
-const char AlsaPcmOutputStream::kAutoSelectDevice[] = "";
-const char AlsaPcmOutputStream::kPlugPrefix[] = "plug:";
-
-// We use 40ms as our minimum required latency. If it is needed, we may be able
-// to get it down to 20ms.
-const uint32 AlsaPcmOutputStream::kMinLatencyMicros = 40 * 1000;
-
-AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
- const AudioParameters& params,
- AlsaWrapper* wrapper,
- AudioManagerLinux* manager)
- : requested_device_name_(device_name),
- pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())),
- channels_(params.channels()),
- channel_layout_(params.channel_layout()),
- sample_rate_(params.sample_rate()),
- bytes_per_sample_(params.bits_per_sample() / 8),
- bytes_per_frame_(channels_ * params.bits_per_sample() / 8),
- packet_size_(params.GetBytesPerBuffer()),
- micros_per_packet_(FramesToMicros(
- params.frames_per_buffer(), sample_rate_)),
- latency_micros_(std::max(AlsaPcmOutputStream::kMinLatencyMicros,
- micros_per_packet_ * 2)),
- bytes_per_output_frame_(bytes_per_frame_),
- alsa_buffer_frames_(0),
- stop_stream_(false),
- wrapper_(wrapper),
- manager_(manager),
- message_loop_(MessageLoop::current()),
- playback_handle_(NULL),
- frames_per_packet_(packet_size_ / bytes_per_frame_),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
- state_(kCreated),
- volume_(1.0f),
- source_callback_(NULL),
- audio_bus_(AudioBus::Create(params)) {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
- DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_);
-
- // Sanity check input values.
- if (!params.IsValid()) {
- LOG(WARNING) << "Unsupported audio parameters.";
- TransitionTo(kInError);
- }
-
- if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) {
- LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample();
- TransitionTo(kInError);
- }
-}
-
-AlsaPcmOutputStream::~AlsaPcmOutputStream() {
- InternalState current_state = state();
- DCHECK(current_state == kCreated ||
- current_state == kIsClosed ||
- current_state == kInError);
- DCHECK(!playback_handle_);
-}
-
-bool AlsaPcmOutputStream::Open() {
- DCHECK(IsOnAudioThread());
-
- if (state() == kInError)
- return false;
-
- if (!CanTransitionTo(kIsOpened)) {
- NOTREACHED() << "Invalid state: " << state();
- return false;
- }
-
- // We do not need to check if the transition was successful because
- // CanTransitionTo() was checked above, and it is assumed that this
- // object's public API is only called on one thread so the state cannot
- // transition out from under us.
- TransitionTo(kIsOpened);
-
- // Try to open the device.
- if (requested_device_name_ == kAutoSelectDevice) {
- playback_handle_ = AutoSelectDevice(latency_micros_);
- if (playback_handle_)
- DVLOG(1) << "Auto-selected device: " << device_name_;
- } else {
- device_name_ = requested_device_name_;
- playback_handle_ = alsa_util::OpenPlaybackDevice(
- wrapper_, device_name_.c_str(), channels_, sample_rate_,
- pcm_format_, latency_micros_);
- }
-
- // Finish initializing the stream if the device was opened successfully.
- if (playback_handle_ == NULL) {
- stop_stream_ = true;
- TransitionTo(kInError);
- return false;
- } else {
- bytes_per_output_frame_ = channel_mixer_ ?
- mixed_audio_bus_->channels() * bytes_per_sample_ : bytes_per_frame_;
- uint32 output_packet_size = frames_per_packet_ * bytes_per_output_frame_;
- buffer_.reset(new media::SeekableBuffer(0, output_packet_size));
-
- // Get alsa buffer size.
- snd_pcm_uframes_t buffer_size;
- snd_pcm_uframes_t period_size;
- int error = wrapper_->PcmGetParams(playback_handle_, &buffer_size,
- &period_size);
- if (error < 0) {
- LOG(ERROR) << "Failed to get playback buffer size from ALSA: "
- << wrapper_->StrError(error);
- // Buffer size is at least twice of packet size.
- alsa_buffer_frames_ = frames_per_packet_ * 2;
- } else {
- alsa_buffer_frames_ = buffer_size;
- }
- }
-
- return true;
-}
-
-void AlsaPcmOutputStream::Close() {
- DCHECK(IsOnAudioThread());
-
- if (state() != kIsClosed)
- TransitionTo(kIsClosed);
-
- // Shutdown the audio device.
- if (playback_handle_) {
- if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) {
- LOG(WARNING) << "Unable to close audio device. Leaking handle.";
- }
- playback_handle_ = NULL;
-
- // Release the buffer.
- buffer_.reset();
-
- // Signal anything that might already be scheduled to stop.
- stop_stream_ = true; // Not necessary in production, but unit tests
- // uses the flag to verify that stream was closed.
- }
-
- weak_factory_.InvalidateWeakPtrs();
-
- // Signal to the manager that we're closed and can be removed.
- // Should be last call in the method as it deletes "this".
- manager_->ReleaseOutputStream(this);
-}
-
-void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(IsOnAudioThread());
-
- CHECK(callback);
-
- if (stop_stream_)
- return;
-
- set_source_callback(callback);
-
- // Only post the task if we can enter the playing state.
- if (TransitionTo(kIsPlaying) == kIsPlaying) {
- // Before starting, the buffer might have audio from previous user of this
- // device.
- buffer_->Clear();
-
- // When starting again, drop all packets in the device and prepare it again
- // in case we are restarting from a pause state and need to flush old data.
- int error = wrapper_->PcmDrop(playback_handle_);
- if (error < 0 && error != -EAGAIN) {
- LOG(ERROR) << "Failure clearing playback device ("
- << wrapper_->PcmName(playback_handle_) << "): "
- << wrapper_->StrError(error);
- stop_stream_ = true;
- } else {
- error = wrapper_->PcmPrepare(playback_handle_);
- if (error < 0 && error != -EAGAIN) {
- LOG(ERROR) << "Failure preparing stream ("
- << wrapper_->PcmName(playback_handle_) << "): "
- << wrapper_->StrError(error);
- stop_stream_ = true;
- }
- }
-
- if (!stop_stream_)
- WriteTask();
- }
-}
-
-void AlsaPcmOutputStream::Stop() {
- DCHECK(IsOnAudioThread());
-
- // Reset the callback, so that it is not called anymore.
- set_source_callback(NULL);
-
- TransitionTo(kIsStopped);
-}
-
-void AlsaPcmOutputStream::SetVolume(double volume) {
- DCHECK(IsOnAudioThread());
-
- volume_ = static_cast<float>(volume);
-}
-
-void AlsaPcmOutputStream::GetVolume(double* volume) {
- DCHECK(IsOnAudioThread());
-
- *volume = volume_;
-}
-
-void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
- DCHECK(IsOnAudioThread());
-
- // If stopped, simulate a 0-length packet.
- if (stop_stream_) {
- buffer_->Clear();
- *source_exhausted = true;
- return;
- }
-
- *source_exhausted = false;
-
- // Request more data only when we run out of data in the buffer, because
- // WritePacket() comsumes only the current chunk of data.
- if (!buffer_->forward_bytes()) {
- // Before making a request to source for data we need to determine the
- // delay (in bytes) for the requested data to be played.
-
- uint32 buffer_delay = buffer_->forward_bytes() * bytes_per_frame_ /
- bytes_per_output_frame_;
-
- uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_;
-
- scoped_refptr<media::DataBuffer> packet =
- new media::DataBuffer(packet_size_);
- int frames_filled = RunDataCallback(
- audio_bus_.get(), AudioBuffersState(buffer_delay, hardware_delay));
- size_t packet_size = frames_filled * bytes_per_frame_;
- DCHECK_LE(packet_size, packet_size_);
-
- // Reset the |last_fill_time| to avoid back to back RunDataCallback().
- last_fill_time_ = base::Time::Now();
-
- // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
- // volume adjust should use SSE optimized vector_fmul() prior to interleave.
- AudioBus* output_bus = audio_bus_.get();
- if (channel_mixer_) {
- output_bus = mixed_audio_bus_.get();
- channel_mixer_->Transform(audio_bus_.get(), output_bus);
- // Adjust packet size for downmix.
- packet_size = packet_size / bytes_per_frame_ * bytes_per_output_frame_;
- }
-
- // Note: If this ever changes to output raw float the data must be clipped
- // and sanitized since it may come from an untrusted source such as NaCl.
- output_bus->ToInterleaved(
- frames_filled, bytes_per_sample_, packet->GetWritableData());
-
- media::AdjustVolume(packet->GetWritableData(),
- packet_size,
- output_bus->channels(),
- bytes_per_sample_,
- volume_);
-
- if (packet_size > 0) {
- packet->SetDataSize(packet_size);
- // Add the packet to the buffer.
- buffer_->Append(packet);
- } else {
- *source_exhausted = true;
- }
- }
-}
-
-void AlsaPcmOutputStream::WritePacket() {
- DCHECK(IsOnAudioThread());
-
- // If the device is in error, just eat the bytes.
- if (stop_stream_) {
- buffer_->Clear();
- return;
- }
-
- if (state() != kIsPlaying)
- return;
-
- CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u);
-
- const uint8* buffer_data;
- int buffer_size;
- if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) {
- buffer_size = buffer_size - (buffer_size % bytes_per_output_frame_);
- snd_pcm_sframes_t frames = std::min(
- static_cast<snd_pcm_sframes_t>(buffer_size / bytes_per_output_frame_),
- GetAvailableFrames());
-
- snd_pcm_sframes_t frames_written =
- wrapper_->PcmWritei(playback_handle_, buffer_data, frames);
- if (frames_written < 0) {
- // Attempt once to immediately recover from EINTR,
- // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket
- // will eventually be called again, so eventual recovery will happen if
- // muliple retries are required.
- frames_written = wrapper_->PcmRecover(playback_handle_,
- frames_written,
- kPcmRecoverIsSilent);
- if (frames_written < 0) {
- if (frames_written != -EAGAIN) {
- LOG(ERROR) << "Failed to write to pcm device: "
- << wrapper_->StrError(frames_written);
- RunErrorCallback(frames_written);
- stop_stream_ = true;
- }
- }
- } else {
- DCHECK_EQ(frames_written, frames);
-
- // Seek forward in the buffer after we've written some data to ALSA.
- buffer_->Seek(frames_written * bytes_per_output_frame_);
- }
- } else {
- // If nothing left to write and playback hasn't started yet, start it now.
- // This ensures that shorter sounds will still play.
- if (playback_handle_ &&
- (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) &&
- GetCurrentDelay() > 0) {
- wrapper_->PcmStart(playback_handle_);
- }
- }
-}
-
-void AlsaPcmOutputStream::WriteTask() {
- DCHECK(IsOnAudioThread());
-
- if (stop_stream_)
- return;
-
- if (state() == kIsStopped)
- return;
-
- bool source_exhausted;
- BufferPacket(&source_exhausted);
- WritePacket();
-
- ScheduleNextWrite(source_exhausted);
-}
-
-void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) {
- DCHECK(IsOnAudioThread());
-
- if (stop_stream_)
- return;
-
- const uint32 kTargetFramesAvailable = alsa_buffer_frames_ / 2;
- uint32 available_frames = GetAvailableFrames();
- uint32 frames_in_buffer = buffer_->forward_bytes() / bytes_per_output_frame_;
-
- // Next write is initially scheduled for the moment when half of a packet
- // has been played out.
- uint32 next_fill_time_ms =
- FramesToMillis(frames_per_packet_ / 2, sample_rate_);
-
- if (frames_in_buffer && available_frames) {
- // There is data in the current buffer, consume them immediately once we
- // have enough space in the soundcard.
- if (frames_in_buffer <= available_frames)
- next_fill_time_ms = 0;
- } else {
- // Otherwise schedule the next write for the moment when the available
- // buffer of the soundcards hits the |kTargetFramesAvailable|.
- if (available_frames < kTargetFramesAvailable) {
- uint32 frames_until_empty_enough =
- kTargetFramesAvailable - available_frames;
- next_fill_time_ms =
- FramesToMillis(frames_until_empty_enough, sample_rate_);
-
- // Adjust for the kernel timeslice and any additional slowdown.
- // TODO(xians): Remove this adjustment if it is not required by
- // low performance machines any more.
- if (next_fill_time_ms > kSleepErrorMilliseconds)
- next_fill_time_ms -= kSleepErrorMilliseconds;
- else
- next_fill_time_ms = 0;
- } else {
- // The sound card has |kTargetFramesAvailable| or more frames available.
- // Invoke the next write immediately to avoid underrun.
- next_fill_time_ms = 0;
- }
-
- // Avoid back-to-back writing.
- base::TimeDelta delay = base::Time::Now() - last_fill_time_;
- if (delay.InMilliseconds() < kMinIntervalBetweenOnMoreDataCallsInMs &&
- next_fill_time_ms < kMinIntervalBetweenOnMoreDataCallsInMs)
- next_fill_time_ms = kMinIntervalBetweenOnMoreDataCallsInMs;
- }
-
- // Avoid busy looping if the data source is exhausted.
- if (source_exhausted)
- next_fill_time_ms = std::max(next_fill_time_ms, kNoDataSleepMilliseconds);
-
- // Only schedule more reads/writes if we are still in the playing state.
- if (state() == kIsPlaying) {
- message_loop_->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AlsaPcmOutputStream::WriteTask,
- weak_factory_.GetWeakPtr()),
- base::TimeDelta::FromMilliseconds(next_fill_time_ms));
- }
-}
-
-uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames,
- uint32 sample_rate) {
- return frames * base::Time::kMicrosecondsPerSecond / sample_rate;
-}
-
-uint32 AlsaPcmOutputStream::FramesToMillis(uint32 frames,
- uint32 sample_rate) {
- return frames * base::Time::kMillisecondsPerSecond / sample_rate;
-}
-
-std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels) {
- // Constants specified by the ALSA API for device hints.
- static const int kGetAllDevices = -1;
- static const char kPcmInterfaceName[] = "pcm";
- static const char kIoHintName[] = "IOID";
- static const char kNameHintName[] = "NAME";
-
- const char* wanted_device = GuessSpecificDeviceName(channels);
- if (!wanted_device)
- return "";
-
- std::string guessed_device;
- void** hints = NULL;
- int error = wrapper_->DeviceNameHint(kGetAllDevices,
- kPcmInterfaceName,
- &hints);
- if (error == 0) {
- // NOTE: Do not early return from inside this if statement. The
- // hints above need to be freed.
- for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
- // Only examine devices that are output capable.. Valid values are
- // "Input", "Output", and NULL which means both input and output.
- scoped_ptr_malloc<char> io(
- wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
- if (io != NULL && strcmp(io.get(), "Input") == 0)
- continue;
-
- // Attempt to select the closest device for number of channels.
- scoped_ptr_malloc<char> name(
- wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
- if (strncmp(wanted_device, name.get(), strlen(wanted_device)) == 0) {
- guessed_device = name.get();
- break;
- }
- }
-
- // Destroy the hint now that we're done with it.
- wrapper_->DeviceNameFreeHint(hints);
- hints = NULL;
- } else {
- LOG(ERROR) << "Unable to get hints for devices: "
- << wrapper_->StrError(error);
- }
-
- return guessed_device;
-}
-
-snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() {
- snd_pcm_sframes_t delay = -1;
- // Don't query ALSA's delay if we have underrun since it'll be jammed at some
- // non-zero value and potentially even negative!
- //
- // Also, if we're in the prepared state, don't query because that seems to
- // cause an I/O error when we do query the delay.
- snd_pcm_state_t pcm_state = wrapper_->PcmState(playback_handle_);
- if (pcm_state != SND_PCM_STATE_XRUN &&
- pcm_state != SND_PCM_STATE_PREPARED) {
- int error = wrapper_->PcmDelay(playback_handle_, &delay);
- if (error < 0) {
- // Assume a delay of zero and attempt to recover the device.
- delay = -1;
- error = wrapper_->PcmRecover(playback_handle_,
- error,
- kPcmRecoverIsSilent);
- if (error < 0) {
- LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error);
- }
- }
- }
-
- // snd_pcm_delay() sometimes returns crazy values. In this case return delay
- // of data we know currently is in ALSA's buffer.
- if (delay < 0 || static_cast<snd_pcm_uframes_t>(delay) > alsa_buffer_frames_)
- delay = alsa_buffer_frames_ - GetAvailableFrames();
-
- return delay;
-}
-
-snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
- DCHECK(IsOnAudioThread());
-
- if (stop_stream_)
- return 0;
-
- // Find the number of frames queued in the sound device.
- snd_pcm_sframes_t available_frames =
- wrapper_->PcmAvailUpdate(playback_handle_);
- if (available_frames < 0) {
- available_frames = wrapper_->PcmRecover(playback_handle_,
- available_frames,
- kPcmRecoverIsSilent);
- }
- if (available_frames < 0) {
- LOG(ERROR) << "Failed querying available frames. Assuming 0: "
- << wrapper_->StrError(available_frames);
- return 0;
- }
- if (static_cast<uint32>(available_frames) > alsa_buffer_frames_) {
- LOG(ERROR) << "ALSA returned " << available_frames << " of "
- << alsa_buffer_frames_ << " frames available.";
- return alsa_buffer_frames_;
- }
-
- return available_frames;
-}
-
-snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) {
- // For auto-selection:
- // 1) Attempt to open a device that best matches the number of channels
- // requested.
- // 2) If that fails, attempt the "plug:" version of it in case ALSA can
- // remap do some software conversion to make it work.
- // 3) Fallback to kDefaultDevice.
- // 4) If that fails too, try the "plug:" version of kDefaultDevice.
- // 5) Give up.
- snd_pcm_t* handle = NULL;
- device_name_ = FindDeviceForChannels(channels_);
-
- // Step 1.
- if (!device_name_.empty()) {
- if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(),
- channels_, sample_rate_,
- pcm_format_,
- latency)) != NULL) {
- return handle;
- }
-
- // Step 2.
- device_name_ = kPlugPrefix + device_name_;
- if ((handle = alsa_util::OpenPlaybackDevice(wrapper_, device_name_.c_str(),
- channels_, sample_rate_,
- pcm_format_,
- latency)) != NULL) {
- return handle;
- }
- }
-
- // For the kDefaultDevice device, we can only reliably depend on 2-channel
- // output to have the correct ordering according to Lennart. For the channel
- // formats that we know how to downmix from (3 channel to 8 channel), setup
- // downmixing.
- uint32 default_channels = channels_;
- if (default_channels > 2) {
- channel_mixer_.reset(new ChannelMixer(
- channel_layout_, CHANNEL_LAYOUT_STEREO));
- default_channels = 2;
- mixed_audio_bus_ = AudioBus::Create(
- default_channels, audio_bus_->frames());
- }
-
- // Step 3.
- device_name_ = kDefaultDevice;
- if ((handle = alsa_util::OpenPlaybackDevice(
- wrapper_, device_name_.c_str(), default_channels, sample_rate_,
- pcm_format_, latency)) != NULL) {
- return handle;
- }
-
- // Step 4.
- device_name_ = kPlugPrefix + device_name_;
- if ((handle = alsa_util::OpenPlaybackDevice(
- wrapper_, device_name_.c_str(), default_channels, sample_rate_,
- pcm_format_, latency)) != NULL) {
- return handle;
- }
-
- // Unable to open any device.
- device_name_.clear();
- return NULL;
-}
-
-bool AlsaPcmOutputStream::CanTransitionTo(InternalState to) {
- switch (state_) {
- case kCreated:
- return to == kIsOpened || to == kIsClosed || to == kInError;
-
- case kIsOpened:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kIsPlaying:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kIsStopped:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kInError:
- return to == kIsClosed || to == kInError;
-
- case kIsClosed:
- default:
- return false;
- }
-}
-
-AlsaPcmOutputStream::InternalState
-AlsaPcmOutputStream::TransitionTo(InternalState to) {
- DCHECK(IsOnAudioThread());
-
- if (!CanTransitionTo(to)) {
- NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to;
- state_ = kInError;
- } else {
- state_ = to;
- }
- return state_;
-}
-
-AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() {
- return state_;
-}
-
-bool AlsaPcmOutputStream::IsOnAudioThread() const {
- return message_loop_ && message_loop_ == MessageLoop::current();
-}
-
-int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback");
-
- if (source_callback_)
- return source_callback_->OnMoreData(audio_bus, buffers_state);
-
- return 0;
-}
-
-void AlsaPcmOutputStream::RunErrorCallback(int code) {
- if (source_callback_)
- source_callback_->OnError(this, code);
-}
-
-// Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
-// release ownership of the currently registered callback.
-void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) {
- DCHECK(IsOnAudioThread());
- source_callback_ = callback;
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/alsa_output.h b/src/media/audio/linux/alsa_output.h
deleted file mode 100644
index ffb29f4..0000000
--- a/src/media/audio/linux/alsa_output.h
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Creates an output stream based on the ALSA PCM interface.
-//
-// On device write failure, the stream will move itself to an invalid state.
-// No more data will be pulled from the data source, or written to the device.
-// All calls to public API functions will either no-op themselves, or return an
-// error if possible. Specifically, If the stream is in an error state, Open()
-// will return false, and Start() will call OnError() immediately on the
-// provided callback.
-//
-// If the stream is successfully opened, Close() must be called. After Close
-// has been called, the object should be regarded as deleted and not touched.
-//
-// AlsaPcmOutputStream is a single threaded class that should only be used from
-// the audio thread. When modifying the code in this class, please read the
-// threading assumptions at the top of the implementation.
-
-#ifndef MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
-#define MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
-
-#include <alsa/asoundlib.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-class MessageLoop;
-
-namespace media {
-
-class AlsaWrapper;
-class AudioManagerLinux;
-class ChannelMixer;
-class SeekableBuffer;
-
-class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
- public:
- // String for the generic "default" ALSA device that has the highest
- // compatibility and chance of working.
- static const char kDefaultDevice[];
-
- // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection
- // of the audio device.
- static const char kAutoSelectDevice[];
-
- // Prefix for device names to enable ALSA library resampling.
- static const char kPlugPrefix[];
-
- // The minimum latency that is accepted by the device.
- static const uint32 kMinLatencyMicros;
-
- // Create a PCM Output stream for the ALSA device identified by
- // |device_name|. The AlsaPcmOutputStream uses |wrapper| to communicate with
- // the alsa libraries, allowing for dependency injection during testing. All
- // requesting of data, and writing to the alsa device will be done on
- // |message_loop|.
- //
- // If unsure of what to use for |device_name|, use |kAutoSelectDevice|.
- AlsaPcmOutputStream(const std::string& device_name,
- const AudioParameters& params,
- AlsaWrapper* wrapper,
- AudioManagerLinux* manager);
-
- virtual ~AlsaPcmOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- private:
- friend class AlsaPcmOutputStreamTest;
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
- AutoSelectDevice_DeviceSelect);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
- AutoSelectDevice_FallbackDevices);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Negative);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_StopStream);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Underrun);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ConstructedState);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, LatencyFloor);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, OpenClose);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmOpenFailed);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmSetParamsFailed);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ScheduleNextWrite);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
- ScheduleNextWrite_StopStream);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, StartStop);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_NormalPacket);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream);
- FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails);
-
- // Flags indicating the state of the stream.
- enum InternalState {
- kInError = 0,
- kCreated,
- kIsOpened,
- kIsPlaying,
- kIsStopped,
- kIsClosed
- };
- friend std::ostream& operator<<(std::ostream& os, InternalState);
-
- // Functions to get another packet from the data source and write it into the
- // ALSA device.
- void BufferPacket(bool* source_exhausted);
- void WritePacket();
- void WriteTask();
- void ScheduleNextWrite(bool source_exhausted);
-
- // Utility functions for talking with the ALSA API.
- static uint32 FramesToMicros(uint32 frames, uint32 sample_rate);
- static uint32 FramesToMillis(uint32 frames, uint32 sample_rate);
- std::string FindDeviceForChannels(uint32 channels);
- snd_pcm_sframes_t GetAvailableFrames();
- snd_pcm_sframes_t GetCurrentDelay();
-
- // Attempts to find the best matching linux audio device for the given number
- // of channels. This function will set |device_name_| and |channel_mixer_|.
- snd_pcm_t* AutoSelectDevice(uint32 latency);
-
- // Functions to safeguard state transitions. All changes to the object state
- // should go through these functions.
- bool CanTransitionTo(InternalState to);
- InternalState TransitionTo(InternalState to);
- InternalState state();
-
- // Returns true when we're on the audio thread or if the audio thread's
- // message loop is NULL (which will happen during shutdown).
- bool IsOnAudioThread() const;
-
- // API for Proxying calls to the AudioSourceCallback provided during
- // Start().
- //
- // TODO(ajwong): This is necessary because the ownership semantics for the
- // |source_callback_| object are incorrect in AudioRenderHost. The callback
- // is passed into the output stream, but ownership is not transfered which
- // requires a synchronization on access of the |source_callback_| to avoid
- // using a deleted callback.
- int RunDataCallback(AudioBus* audio_bus, AudioBuffersState buffers_state);
- void RunErrorCallback(int code);
-
- // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
- // release ownership of the currently registered callback.
- void set_source_callback(AudioSourceCallback* callback);
-
- // Configuration constants from the constructor. Referenceable by all threads
- // since they are constants.
- const std::string requested_device_name_;
- const snd_pcm_format_t pcm_format_;
- const uint32 channels_;
- const ChannelLayout channel_layout_;
- const uint32 sample_rate_;
- const uint32 bytes_per_sample_;
- const uint32 bytes_per_frame_;
-
- // Device configuration data. Populated after OpenTask() completes.
- std::string device_name_;
- uint32 packet_size_;
- uint32 micros_per_packet_;
- uint32 latency_micros_;
- uint32 bytes_per_output_frame_;
- uint32 alsa_buffer_frames_;
-
- // Flag indicating the code should stop reading from the data source or
- // writing to the ALSA device. This is set because the device has entered
- // an unrecoverable error state, or the ClosedTask() has executed.
- bool stop_stream_;
-
- // Wrapper class to invoke all the ALSA functions.
- AlsaWrapper* wrapper_;
-
- // Audio manager that created us. Used to report that we've been closed.
- AudioManagerLinux* manager_;
-
- // Message loop to use for polling. The object is owned by the AudioManager.
- // We hold a reference to the audio thread message loop since
- // AudioManagerBase::ShutDown() can invalidate the message loop pointer
- // before the stream gets deleted.
- MessageLoop* message_loop_;
-
- // Handle to the actual PCM playback device.
- snd_pcm_t* playback_handle_;
-
- scoped_ptr<media::SeekableBuffer> buffer_;
- uint32 frames_per_packet_;
-
- // Allows us to run tasks on the AlsaPcmOutputStream instance which are
- // bound by its lifetime.
- base::WeakPtrFactory<AlsaPcmOutputStream> weak_factory_;
-
- InternalState state_;
- float volume_; // Volume level from 0.0 to 1.0.
-
- AudioSourceCallback* source_callback_;
-
- base::Time last_fill_time_; // Time for the last OnMoreData() callback.
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- // Channel mixer and temporary bus for the final mixed channel data.
- scoped_ptr<ChannelMixer> channel_mixer_;
- scoped_ptr<AudioBus> mixed_audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStream);
-};
-
-MEDIA_EXPORT std::ostream& operator<<(std::ostream& os,
- AlsaPcmOutputStream::InternalState);
-
-}; // namespace media
-
-#endif // MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
diff --git a/src/media/audio/linux/alsa_output_unittest.cc b/src/media/audio/linux/alsa_output_unittest.cc
deleted file mode 100644
index 1db97af..0000000
--- a/src/media/audio/linux/alsa_output_unittest.cc
+++ /dev/null
@@ -1,869 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/message_loop.h"
-#include "base/stringprintf.h"
-#include "media/audio/linux/alsa_output.h"
-#include "media/audio/linux/alsa_wrapper.h"
-#include "media/audio/linux/audio_manager_linux.h"
-#include "media/base/data_buffer.h"
-#include "media/base/seekable_buffer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-using testing::AllOf;
-using testing::AtLeast;
-using testing::DoAll;
-using testing::Field;
-using testing::InSequence;
-using testing::Invoke;
-using testing::InvokeWithoutArgs;
-using testing::Mock;
-using testing::MockFunction;
-using testing::Return;
-using testing::SetArgumentPointee;
-using testing::StrictMock;
-using testing::StrEq;
-using testing::Unused;
-
-namespace media {
-
-class MockAlsaWrapper : public AlsaWrapper {
- public:
- MOCK_METHOD3(DeviceNameHint, int(int card,
- const char* iface,
- void*** hints));
- MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id));
- MOCK_METHOD1(DeviceNameFreeHint, int(void** hints));
-
- MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name,
- snd_pcm_stream_t stream, int mode));
- MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle));
- MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle));
- MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle));
- MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay));
- MOCK_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle,
- const void* buffer,
- snd_pcm_uframes_t size));
- MOCK_METHOD3(PcmReadi, snd_pcm_sframes_t(snd_pcm_t* handle,
- void* buffer,
- snd_pcm_uframes_t size));
- MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent));
- MOCK_METHOD7(PcmSetParams, int(snd_pcm_t* handle, snd_pcm_format_t format,
- snd_pcm_access_t access, unsigned int channels,
- unsigned int rate, int soft_resample,
- unsigned int latency));
- MOCK_METHOD3(PcmGetParams, int(snd_pcm_t* handle,
- snd_pcm_uframes_t* buffer_size,
- snd_pcm_uframes_t* period_size));
- MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle));
- MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle));
- MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle));
- MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle));
-
- MOCK_METHOD1(StrError, const char*(int errnum));
-};
-
-class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-class MockAudioManagerLinux : public AudioManagerLinux {
- public:
- MOCK_METHOD0(Init, void());
- MOCK_METHOD0(HasAudioOutputDevices, bool());
- MOCK_METHOD0(HasAudioInputDevices, bool());
- MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD1(MakeLowLatencyOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
-
- // We need to override this function in order to skip the checking the number
- // of active output streams. It is because the number of active streams
- // is managed inside MakeAudioOutputStream, and we don't use
- // MakeAudioOutputStream to create the stream in the tests.
- virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE {
- DCHECK(stream);
- delete stream;
- }
-
- // We don't mock this method since all tests will do the same thing
- // and use the current message loop.
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE {
- return MessageLoop::current()->message_loop_proxy();
- }
-};
-
-class AlsaPcmOutputStreamTest : public testing::Test {
- protected:
- AlsaPcmOutputStreamTest() {
- mock_manager_.reset(new StrictMock<MockAudioManagerLinux>());
- }
-
- virtual ~AlsaPcmOutputStreamTest() {
- }
-
- AlsaPcmOutputStream* CreateStream(ChannelLayout layout) {
- return CreateStream(layout, kTestFramesPerPacket);
- }
-
- AlsaPcmOutputStream* CreateStream(ChannelLayout layout,
- int32 samples_per_packet) {
- AudioParameters params(kTestFormat, layout, kTestSampleRate,
- kTestBitsPerSample, samples_per_packet);
- return new AlsaPcmOutputStream(kTestDeviceName,
- params,
- &mock_alsa_wrapper_,
- mock_manager_.get());
- }
-
- // Helper function to malloc the string returned by DeviceNameHint for NAME.
- static char* EchoHint(const void* name, Unused) {
- return strdup(static_cast<const char*>(name));
- }
-
- // Helper function to malloc the string returned by DeviceNameHint for IOID.
- static char* OutputHint(Unused, Unused) {
- return strdup("Output");
- }
-
- // Helper function to initialize |test_stream->buffer_|. Must be called
- // in all tests that use buffer_ without opening the stream.
- void InitBuffer(AlsaPcmOutputStream* test_stream) {
- DCHECK(test_stream);
- packet_ = new media::DataBuffer(kTestPacketSize);
- packet_->SetDataSize(kTestPacketSize);
- test_stream->buffer_.reset(new media::SeekableBuffer(0, kTestPacketSize));
- test_stream->buffer_->Append(packet_.get());
- }
-
- static const ChannelLayout kTestChannelLayout;
- static const int kTestSampleRate;
- static const int kTestBitsPerSample;
- static const int kTestBytesPerFrame;
- static const AudioParameters::Format kTestFormat;
- static const char kTestDeviceName[];
- static const char kDummyMessage[];
- static const uint32 kTestFramesPerPacket;
- static const int kTestPacketSize;
- static const int kTestFailedErrno;
- static snd_pcm_t* const kFakeHandle;
-
- // Used to simulate DeviceNameHint.
- static char kSurround40[];
- static char kSurround41[];
- static char kSurround50[];
- static char kSurround51[];
- static char kSurround70[];
- static char kSurround71[];
- static void* kFakeHints[];
-
- StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
- scoped_ptr<StrictMock<MockAudioManagerLinux> > mock_manager_;
- MessageLoop message_loop_;
- scoped_refptr<media::DataBuffer> packet_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest);
-};
-
-const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout =
- CHANNEL_LAYOUT_STEREO;
-const int AlsaPcmOutputStreamTest::kTestSampleRate =
- AudioParameters::kAudioCDSampleRate;
-const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8;
-const int AlsaPcmOutputStreamTest::kTestBytesPerFrame =
- AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 *
- ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout);
-const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat =
- AudioParameters::AUDIO_PCM_LINEAR;
-const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
-const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
-const uint32 AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
-const int AlsaPcmOutputStreamTest::kTestPacketSize =
- AlsaPcmOutputStreamTest::kTestFramesPerPacket *
- AlsaPcmOutputStreamTest::kTestBytesPerFrame;
-const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES;
-snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle =
- reinterpret_cast<snd_pcm_t*>(1);
-
-char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0";
-char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0";
-char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0";
-char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0";
-char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0";
-char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0";
-void* AlsaPcmOutputStreamTest::kFakeHints[] = {
- kSurround40, kSurround41, kSurround50, kSurround51,
- kSurround70, kSurround71, NULL };
-
-// Custom action to clear a memory buffer.
-ACTION(ClearBuffer) {
- arg0->Zero();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
- test_stream->Close();
-
- // Should support mono.
- test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
- test_stream->Close();
-
- // Should support multi-channel.
- test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
- EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
- test_stream->Close();
-
- // Bad bits per sample.
- AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout,
- kTestSampleRate, kTestBitsPerSample - 1,
- kTestFramesPerPacket);
- test_stream = new AlsaPcmOutputStream(kTestDeviceName,
- bad_bps_params,
- &mock_alsa_wrapper_,
- mock_manager_.get());
- EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
- test_stream->Close();
-
- // Bad format.
- AudioParameters bad_format_params(
- AudioParameters::AUDIO_LAST_FORMAT, kTestChannelLayout, kTestSampleRate,
- kTestBitsPerSample, kTestFramesPerPacket);
- test_stream = new AlsaPcmOutputStream(kTestDeviceName,
- bad_format_params,
- &mock_alsa_wrapper_,
- mock_manager_.get());
- EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
- const double kMicrosPerFrame =
- static_cast<double>(1000000) / kTestSampleRate;
- const double kPacketFramesInMinLatency =
- AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;
-
- // Test that packets which would cause a latency under less than
- // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to
- // AlsaPcmOutputStream::kMinLatencyMicros,
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmSetParams(_, _, _, _, _, _,
- AlsaPcmOutputStream::kMinLatencyMicros))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
-
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout,
- kPacketFramesInMinLatency);
- ASSERT_TRUE(test_stream->Open());
-
- // Now close it and test that everything was released.
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-
- Mock::VerifyAndClear(&mock_alsa_wrapper_);
- Mock::VerifyAndClear(mock_manager_.get());
-
- // Test that having more packets ends up with a latency based on packet size.
- const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1;
- int64 expected_micros = 2 * AlsaPcmOutputStream::FramesToMicros(
- kOverMinLatencyPacketSize, kTestSampleRate);
-
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmSetParams(_, _, _, _, _, _, expected_micros))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
-
- test_stream = CreateStream(kTestChannelLayout,
- kOverMinLatencyPacketSize);
- ASSERT_TRUE(test_stream->Open());
-
- // Now close it and test that everything was released.
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-
- Mock::VerifyAndClear(&mock_alsa_wrapper_);
- Mock::VerifyAndClear(mock_manager_.get());
-}
-
-TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
- int64 expected_micros = 2 *
- AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
- kTestSampleRate);
-
- // Open() call opens the playback device, sets the parameters, posts a task
- // with the resulting configuration data, and transitions the object state to
- // kIsOpened.
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmOpen(_, StrEq(kTestDeviceName),
- SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmSetParams(kFakeHandle,
- SND_PCM_FORMAT_U8,
- SND_PCM_ACCESS_RW_INTERLEAVED,
- ChannelLayoutToChannelCount(kTestChannelLayout),
- kTestSampleRate,
- 1,
- expected_micros))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
-
- // Open the stream.
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_TRUE(test_stream->Open());
-
- EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream->state());
- EXPECT_EQ(kFakeHandle, test_stream->playback_handle_);
- EXPECT_EQ(kTestFramesPerPacket, test_stream->frames_per_packet_);
- EXPECT_TRUE(test_stream->buffer_.get());
- EXPECT_FALSE(test_stream->stop_stream_);
-
- // Now close it and test that everything was released.
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
- .WillOnce(Return(kDummyMessage));
-
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_FALSE(test_stream->Open());
- ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
-
- // Ensure internal state is set for a no-op stream if PcmOpen() failes.
- EXPECT_TRUE(test_stream->stop_stream_);
- EXPECT_TRUE(test_stream->playback_handle_ == NULL);
- EXPECT_FALSE(test_stream->buffer_.get());
-
- // Close the stream since we opened it to make destruction happy.
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
- .WillOnce(Return(kDummyMessage));
-
- // If open fails, the stream stays in kCreated because it has effectively had
- // no changes.
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_FALSE(test_stream->Open());
- EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
-
- // Ensure internal state is set for a no-op stream if PcmSetParams() failes.
- EXPECT_TRUE(test_stream->stop_stream_);
- EXPECT_TRUE(test_stream->playback_handle_ == NULL);
- EXPECT_FALSE(test_stream->buffer_.get());
-
- // Close the stream since we opened it to make destruction happy.
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, StartStop) {
- // Open() call opens the playback device, sets the parameters, posts a task
- // with the resulting configuration data, and transitions the object state to
- // kIsOpened.
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
-
- // Open the stream.
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_TRUE(test_stream->Open());
-
- // Expect Device setup.
- EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle))
- .WillOnce(Return(0));
-
- // Expect the pre-roll.
- MockAudioSourceCallback mock_callback;
- EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle))
- .WillRepeatedly(Return(SND_PCM_STATE_RUNNING));
- EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _))
- .WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0)));
- EXPECT_CALL(mock_callback, OnMoreData(_, _))
- .WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
- .WillRepeatedly(Return(kTestFramesPerPacket));
-
- // Expect scheduling.
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
- .Times(AtLeast(2))
- .WillRepeatedly(Return(kTestFramesPerPacket));
-
- test_stream->Start(&mock_callback);
- // Start() will issue a WriteTask() directly and then schedule the next one,
- // call Stop() immediately after to ensure we don't run the message loop
- // forever.
- test_stream->Stop();
- message_loop_.RunUntilIdle();
-
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
-
- // Nothing should happen. Don't set any expectations and Our strict mocks
- // should verify most of this.
-
- // Test empty buffer.
- test_stream->buffer_->Clear();
- test_stream->WritePacket();
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) {
- // We need to open the stream before writing data to ALSA.
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_TRUE(test_stream->Open());
- InitBuffer(test_stream);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
-
- // Write a little less than half the data.
- int written = packet_->GetDataSize() / kTestBytesPerFrame / 2 - 1;
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
- .WillOnce(Return(written));
- EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->GetData(), _))
- .WillOnce(Return(written));
-
- test_stream->WritePacket();
-
- ASSERT_EQ(test_stream->buffer_->forward_bytes(),
- packet_->GetDataSize() - written * kTestBytesPerFrame);
-
- // Write the rest.
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
- .WillOnce(Return(kTestFramesPerPacket - written));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmWritei(kFakeHandle,
- packet_->GetData() + written * kTestBytesPerFrame,
- _))
- .WillOnce(Return(packet_->GetDataSize() / kTestBytesPerFrame - written));
- test_stream->WritePacket();
- EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
-
- // Now close it and test that everything was released.
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) {
- // We need to open the stream before writing data to ALSA.
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
- Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
- SetArgumentPointee<2>(kTestFramesPerPacket / 2),
- Return(0)));
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- ASSERT_TRUE(test_stream->Open());
- InitBuffer(test_stream);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
-
- // Fail due to a recoverable error and see that PcmRecover code path
- // continues normally.
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
- .WillOnce(Return(kTestFramesPerPacket));
- EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
- .WillOnce(Return(-EINTR));
- EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
- .WillOnce(Return(0));
-
- test_stream->WritePacket();
-
- ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->GetDataSize());
-
- // Fail the next write, and see that stop_stream_ is set.
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
- .WillOnce(Return(kTestFramesPerPacket));
- EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
- .WillOnce(Return(kDummyMessage));
- test_stream->WritePacket();
- EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->GetDataSize());
- EXPECT_TRUE(test_stream->stop_stream_);
-
- // Now close it and test that everything was released.
- EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
- .WillOnce(Return(kTestDeviceName));
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
-
- // No expectations set on the strict mock because nothing should be called.
- test_stream->stop_stream_ = true;
- test_stream->WritePacket();
- EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, BufferPacket) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->buffer_->Clear();
-
- MockAudioSourceCallback mock_callback;
- EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
- .WillOnce(Return(SND_PCM_STATE_RUNNING));
- EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
- .WillRepeatedly(Return(0)); // Buffer is full.
-
- // Return a partially filled packet.
- EXPECT_CALL(mock_callback, OnMoreData(_, _))
- .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
-
- bool source_exhausted;
- test_stream->set_source_callback(&mock_callback);
- test_stream->packet_size_ = kTestPacketSize;
- test_stream->BufferPacket(&source_exhausted);
-
- EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
- EXPECT_FALSE(source_exhausted);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->buffer_->Clear();
-
- // Simulate where the underrun has occurred right after checking the delay.
- MockAudioSourceCallback mock_callback;
- EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
- .WillOnce(Return(SND_PCM_STATE_RUNNING));
- EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
- .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
- .WillRepeatedly(Return(0)); // Buffer is full.
- EXPECT_CALL(mock_callback, OnMoreData(_, _))
- .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
-
- bool source_exhausted;
- test_stream->set_source_callback(&mock_callback);
- test_stream->packet_size_ = kTestPacketSize;
- test_stream->BufferPacket(&source_exhausted);
-
- EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
- EXPECT_FALSE(source_exhausted);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->buffer_->Clear();
-
- // If ALSA has underrun then we should assume a delay of zero.
- MockAudioSourceCallback mock_callback;
- EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
- .WillOnce(Return(SND_PCM_STATE_XRUN));
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
- .WillRepeatedly(Return(0)); // Buffer is full.
- EXPECT_CALL(mock_callback,
- OnMoreData(_, AllOf(
- Field(&AudioBuffersState::pending_bytes, 0),
- Field(&AudioBuffersState::hardware_delay_bytes, 0))))
- .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
-
- bool source_exhausted;
- test_stream->set_source_callback(&mock_callback);
- test_stream->packet_size_ = kTestPacketSize;
- test_stream->BufferPacket(&source_exhausted);
-
- EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
- EXPECT_FALSE(source_exhausted);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- // No expectations set on the strict mock because nothing should be called.
- bool source_exhausted;
- test_stream->packet_size_ = kTestPacketSize;
- test_stream->BufferPacket(&source_exhausted);
- EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes());
- EXPECT_FALSE(source_exhausted);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
- // Try channels from 1 -> 9. and see that we get the more specific surroundXX
- // device opened for channels 4-8. For all other channels, the device should
- // default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not
- // downmix any channel in this case because downmixing is only defined for
- // channels 4-8, which we are guaranteeing to work.
- //
- // Note that the loop starts at "1", so the first parameter is ignored in
- // these arrays.
- const char* kExpectedDeviceName[] = { NULL,
- AlsaPcmOutputStream::kDefaultDevice,
- AlsaPcmOutputStream::kDefaultDevice,
- AlsaPcmOutputStream::kDefaultDevice,
- kSurround40, kSurround50, kSurround51,
- kSurround70, kSurround71,
- AlsaPcmOutputStream::kDefaultDevice };
- bool kExpectedDownmix[] = { false, false, false, false, false, true,
- false, false, false, false };
- ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE,
- CHANNEL_LAYOUT_MONO,
- CHANNEL_LAYOUT_STEREO,
- CHANNEL_LAYOUT_SURROUND,
- CHANNEL_LAYOUT_4_0,
- CHANNEL_LAYOUT_5_0,
- CHANNEL_LAYOUT_5_1,
- CHANNEL_LAYOUT_7_0,
- CHANNEL_LAYOUT_7_1 };
-
-
- for (int i = 1; i < 9; ++i) {
- if (i == 3 || i == 4 || i == 5) // invalid number of channels
- continue;
- SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i));
-
- // Hints will only be grabbed for channel numbers that have non-default
- // devices associated with them.
- if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) {
- // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a
- // memory leak.
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
- .Times(1);
- }
-
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmSetParams(kFakeHandle, _, _, i, _, _, _))
- .WillOnce(Return(0));
-
- // The parameters are specified by ALSA documentation, and are in constants
- // in the implementation files.
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
- .WillRepeatedly(Invoke(OutputHint));
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
- .WillRepeatedly(Invoke(EchoHint));
-
- AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]);
- EXPECT_TRUE(test_stream->AutoSelectDevice(i));
- EXPECT_EQ(kExpectedDownmix[i],
- static_cast<bool>(test_stream->channel_mixer_));
-
- Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
- Mock::VerifyAndClearExpectations(mock_manager_.get());
- test_stream->Close();
- }
-}
-
-TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
- using std::string;
-
- // If there are problems opening a multi-channel device, it the fallbacks
- // operations should be as follows. Assume the multi-channel device name is
- // surround50:
- //
- // 1) Try open "surround50"
- // 2) Try open "plug:surround50".
- // 3) Try open "default".
- // 4) Try open "plug:default".
- // 5) Give up trying to open.
- //
- const string first_try = kSurround50;
- const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) +
- kSurround50;
- const string third_try = AlsaPcmOutputStream::kDefaultDevice;
- const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) +
- AlsaPcmOutputStream::kDefaultDevice;
-
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
- .Times(1);
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
- .WillRepeatedly(Invoke(OutputHint));
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
- .WillRepeatedly(Invoke(EchoHint));
- EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
- .WillRepeatedly(Return(kDummyMessage));
-
- InSequence s;
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _))
- .WillOnce(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
- .WillOnce(Return(kTestFailedErrno));
-
- AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
- EXPECT_FALSE(test_stream->AutoSelectDevice(5));
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
- // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to
- // enumerate devices.
- EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
- .WillRepeatedly(Return(kTestFailedErrno));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _))
- .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
- EXPECT_CALL(mock_alsa_wrapper_,
- PcmSetParams(kFakeHandle, _, _, 2, _, _, _))
- .WillOnce(Return(0));
- EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
- .WillOnce(Return(kDummyMessage));
-
- AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
- EXPECT_TRUE(test_stream->AutoSelectDevice(5));
- EXPECT_TRUE(test_stream->channel_mixer_);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- InitBuffer(test_stream);
- test_stream->stop_stream_ = true;
- bool source_exhausted;
- test_stream->BufferPacket(&source_exhausted);
- EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
- EXPECT_TRUE(source_exhausted);
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
- InitBuffer(test_stream);
- DVLOG(1) << test_stream->state();
- EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
- .WillOnce(Return(10));
- test_stream->ScheduleNextWrite(false);
- DVLOG(1) << test_stream->state();
- // TODO(sergeyu): Figure out how to check that the task has been added to the
- // message loop.
-
- // Cleanup the message queue. Currently ~MessageQueue() doesn't free pending
- // tasks unless running on valgrind. The code below is needed to keep
- // heapcheck happy.
-
- test_stream->stop_stream_ = true;
- DVLOG(1) << test_stream->state();
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
- DVLOG(1) << test_stream->state();
- test_stream->Close();
-}
-
-TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
- AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
-
- InitBuffer(test_stream);
-
- test_stream->stop_stream_ = true;
- test_stream->ScheduleNextWrite(true);
-
- // TODO(ajwong): Find a way to test whether or not another task has been
- // posted so we can verify that the Alsa code will indeed break the task
- // posting loop.
-
- test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
- test_stream->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/alsa_util.cc b/src/media/audio/linux/alsa_util.cc
deleted file mode 100644
index 176ef69..0000000
--- a/src/media/audio/linux/alsa_util.cc
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/linux/alsa_util.h"
-
-#include <string>
-
-#include "base/logging.h"
-#include "media/audio/linux/alsa_wrapper.h"
-
-namespace alsa_util {
-
-static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper,
- const char* device_name,
- snd_pcm_stream_t type,
- int channels,
- int sample_rate,
- snd_pcm_format_t pcm_format,
- int latency_us) {
- snd_pcm_t* handle = NULL;
- int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK);
- if (error < 0) {
- LOG(WARNING) << "PcmOpen: " << device_name << ","
- << wrapper->StrError(error);
- return NULL;
- }
-
- error = wrapper->PcmSetParams(handle, pcm_format,
- SND_PCM_ACCESS_RW_INTERLEAVED, channels,
- sample_rate, 1, latency_us);
- if (error < 0) {
- LOG(WARNING) << "PcmSetParams: " << device_name << ", "
- << wrapper->StrError(error) << " - Format: " << pcm_format
- << " Channels: " << channels << " Latency: " << latency_us;
- if (alsa_util::CloseDevice(wrapper, handle) < 0) {
- // TODO(ajwong): Retry on certain errors?
- LOG(WARNING) << "Unable to close audio device. Leaking handle.";
- }
- return NULL;
- }
-
- return handle;
-}
-
-static std::string DeviceNameToControlName(const std::string& device_name) {
- const char kMixerPrefix[] = "hw";
- std::string control_name;
- size_t pos1 = device_name.find(':');
- if (pos1 == std::string::npos) {
- control_name = device_name;
- } else {
- // Examples:
- // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel".
- // deviceName: "default:CARD=Intel", controlName: "CARD=Intel".
- size_t pos2 = device_name.find(',');
- control_name = (pos2 == std::string::npos) ?
- device_name.substr(pos1) :
- kMixerPrefix + device_name.substr(pos1, pos2 - pos1);
- }
-
- return control_name;
-}
-
-snd_pcm_format_t BitsToFormat(int bits_per_sample) {
- switch (bits_per_sample) {
- case 8:
- return SND_PCM_FORMAT_U8;
-
- case 16:
- return SND_PCM_FORMAT_S16;
-
- case 24:
- return SND_PCM_FORMAT_S24;
-
- case 32:
- return SND_PCM_FORMAT_S32;
-
- default:
- return SND_PCM_FORMAT_UNKNOWN;
- }
-}
-
-int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) {
- std::string device_name = wrapper->PcmName(handle);
- int error = wrapper->PcmClose(handle);
- if (error < 0) {
- LOG(ERROR) << "PcmClose: " << device_name << ", "
- << wrapper->StrError(error);
- }
-
- return error;
-}
-
-snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
- const char* device_name,
- int channels,
- int sample_rate,
- snd_pcm_format_t pcm_format,
- int latency_us) {
- return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels,
- sample_rate, pcm_format, latency_us);
-}
-
-snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
- const char* device_name,
- int channels,
- int sample_rate,
- snd_pcm_format_t pcm_format,
- int latency_us) {
- return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels,
- sample_rate, pcm_format, latency_us);
-}
-
-snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
- const std::string& device_name) {
- snd_mixer_t* mixer = NULL;
-
- int error = wrapper->MixerOpen(&mixer, 0);
- if (error < 0) {
- LOG(ERROR) << "MixerOpen: " << device_name << ", "
- << wrapper->StrError(error);
- return NULL;
- }
-
- std::string control_name = DeviceNameToControlName(device_name);
- error = wrapper->MixerAttach(mixer, control_name.c_str());
- if (error < 0) {
- LOG(ERROR) << "MixerAttach, " << control_name << ", "
- << wrapper->StrError(error);
- alsa_util::CloseMixer(wrapper, mixer, device_name);
- return NULL;
- }
-
- error = wrapper->MixerElementRegister(mixer, NULL, NULL);
- if (error < 0) {
- LOG(ERROR) << "MixerElementRegister: " << control_name << ", "
- << wrapper->StrError(error);
- alsa_util::CloseMixer(wrapper, mixer, device_name);
- return NULL;
- }
-
- return mixer;
-}
-
-void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer,
- const std::string& device_name) {
- if (!mixer)
- return;
-
- wrapper->MixerFree(mixer);
-
- int error = 0;
- if (!device_name.empty()) {
- std::string control_name = DeviceNameToControlName(device_name);
- error = wrapper->MixerDetach(mixer, control_name.c_str());
- if (error < 0) {
- LOG(WARNING) << "MixerDetach: " << control_name << ", "
- << wrapper->StrError(error);
- }
- }
-
- error = wrapper->MixerClose(mixer);
- if (error < 0) {
- LOG(WARNING) << "MixerClose: " << wrapper->StrError(error);
- }
-}
-
-snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
- snd_mixer_t* mixer) {
- if (!mixer)
- return NULL;
-
- int error = wrapper->MixerLoad(mixer);
- if (error < 0) {
- LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error);
- return NULL;
- }
-
- snd_mixer_elem_t* elem = NULL;
- snd_mixer_elem_t* mic_elem = NULL;
- const char kCaptureElemName[] = "Capture";
- const char kMicElemName[] = "Mic";
- for (elem = wrapper->MixerFirstElem(mixer);
- elem;
- elem = wrapper->MixerNextElem(elem)) {
- if (wrapper->MixerSelemIsActive(elem)) {
- const char* elem_name = wrapper->MixerSelemName(elem);
- if (strcmp(elem_name, kCaptureElemName) == 0)
- return elem;
- else if (strcmp(elem_name, kMicElemName) == 0)
- mic_elem = elem;
- }
- }
-
- // Did not find any Capture handle, use the Mic handle.
- return mic_elem;
-}
-
-} // namespace alsa_util
diff --git a/src/media/audio/linux/alsa_util.h b/src/media/audio/linux/alsa_util.h
deleted file mode 100644
index 53cf80a..0000000
--- a/src/media/audio/linux/alsa_util.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_LINUX_ALSA_UTIL_H_
-#define MEDIA_AUDIO_LINUX_ALSA_UTIL_H_
-
-#include <alsa/asoundlib.h>
-#include <string>
-
-namespace media {
-class AlsaWrapper;
-}
-
-namespace alsa_util {
-
-snd_pcm_format_t BitsToFormat(int bits_per_sample);
-
-snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
- const char* device_name,
- int channels,
- int sample_rate,
- snd_pcm_format_t pcm_format,
- int latency_us);
-
-snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
- const char* device_name,
- int channels,
- int sample_rate,
- snd_pcm_format_t pcm_format,
- int latency_us);
-
-int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle);
-
-snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
- const std::string& device_name);
-
-void CloseMixer(media::AlsaWrapper* wrapper,
- snd_mixer_t* mixer,
- const std::string& device_name);
-
-snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
- snd_mixer_t* mixer);
-
-} // namespace alsa_util
-
-#endif // MEDIA_AUDIO_LINUX_ALSA_UTIL_H_
diff --git a/src/media/audio/linux/alsa_wrapper.cc b/src/media/audio/linux/alsa_wrapper.cc
deleted file mode 100644
index c1ce359..0000000
--- a/src/media/audio/linux/alsa_wrapper.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/linux/alsa_wrapper.h"
-
-#include <alsa/asoundlib.h>
-
-namespace media {
-
-AlsaWrapper::AlsaWrapper() {
-}
-
-AlsaWrapper::~AlsaWrapper() {
-}
-
-int AlsaWrapper::PcmOpen(snd_pcm_t** handle, const char* name,
- snd_pcm_stream_t stream, int mode) {
- return snd_pcm_open(handle, name, stream, mode);
-}
-
-int AlsaWrapper::DeviceNameHint(int card, const char* iface, void*** hints) {
- return snd_device_name_hint(card, iface, hints);
-}
-
-char* AlsaWrapper::DeviceNameGetHint(const void* hint, const char* id) {
- return snd_device_name_get_hint(hint, id);
-}
-
-int AlsaWrapper::DeviceNameFreeHint(void** hints) {
- return snd_device_name_free_hint(hints);
-}
-
-int AlsaWrapper::CardNext(int* rcard) {
- return snd_card_next(rcard);
-}
-
-int AlsaWrapper::PcmClose(snd_pcm_t* handle) {
- return snd_pcm_close(handle);
-}
-
-int AlsaWrapper::PcmPrepare(snd_pcm_t* handle) {
- return snd_pcm_prepare(handle);
-}
-
-int AlsaWrapper::PcmDrop(snd_pcm_t* handle) {
- return snd_pcm_drop(handle);
-}
-
-int AlsaWrapper::PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay) {
- return snd_pcm_delay(handle, delay);
-}
-
-snd_pcm_sframes_t AlsaWrapper::PcmWritei(snd_pcm_t* handle,
- const void* buffer,
- snd_pcm_uframes_t size) {
- return snd_pcm_writei(handle, buffer, size);
-}
-
-snd_pcm_sframes_t AlsaWrapper::PcmReadi(snd_pcm_t* handle,
- void* buffer,
- snd_pcm_uframes_t size) {
- return snd_pcm_readi(handle, buffer, size);
-}
-
-int AlsaWrapper::PcmRecover(snd_pcm_t* handle, int err, int silent) {
- return snd_pcm_recover(handle, err, silent);
-}
-
-const char* AlsaWrapper::PcmName(snd_pcm_t* handle) {
- return snd_pcm_name(handle);
-}
-
-int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format,
- snd_pcm_access_t access, unsigned int channels,
- unsigned int rate, int soft_resample,
- unsigned int latency) {
- return snd_pcm_set_params(handle,
- format,
- access,
- channels,
- rate,
- soft_resample,
- latency);
-}
-
-int AlsaWrapper::PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size,
- snd_pcm_uframes_t* period_size) {
- return snd_pcm_get_params(handle, buffer_size, period_size);
-}
-
-snd_pcm_sframes_t AlsaWrapper::PcmAvailUpdate(snd_pcm_t* handle) {
- return snd_pcm_avail_update(handle);
-}
-
-snd_pcm_state_t AlsaWrapper::PcmState(snd_pcm_t* handle) {
- return snd_pcm_state(handle);
-}
-
-const char* AlsaWrapper::StrError(int errnum) {
- return snd_strerror(errnum);
-}
-
-int AlsaWrapper::PcmStart(snd_pcm_t* handle) {
- return snd_pcm_start(handle);
-}
-
-int AlsaWrapper::MixerOpen(snd_mixer_t** mixer, int mode) {
- return snd_mixer_open(mixer, mode);
-}
-
-int AlsaWrapper::MixerAttach(snd_mixer_t* mixer, const char* name) {
- return snd_mixer_attach(mixer, name);
-}
-
-int AlsaWrapper::MixerElementRegister(snd_mixer_t* mixer,
- struct snd_mixer_selem_regopt* options,
- snd_mixer_class_t** classp) {
- return snd_mixer_selem_register(mixer, options, classp);
-}
-
-void AlsaWrapper::MixerFree(snd_mixer_t* mixer) {
- snd_mixer_free(mixer);
-}
-
-int AlsaWrapper::MixerDetach(snd_mixer_t* mixer, const char* name) {
- return snd_mixer_detach(mixer, name);
-}
-
-int AlsaWrapper::MixerClose(snd_mixer_t* mixer) {
- return snd_mixer_close(mixer);
-}
-
-int AlsaWrapper::MixerLoad(snd_mixer_t* mixer) {
- return snd_mixer_load(mixer);
-}
-
-snd_mixer_elem_t* AlsaWrapper::MixerFirstElem(snd_mixer_t* mixer) {
- return snd_mixer_first_elem(mixer);
-}
-
-snd_mixer_elem_t* AlsaWrapper::MixerNextElem(snd_mixer_elem_t* elem) {
- return snd_mixer_elem_next(elem);
-}
-
-int AlsaWrapper::MixerSelemIsActive(snd_mixer_elem_t* elem) {
- return snd_mixer_selem_is_active(elem);
-}
-
-const char* AlsaWrapper::MixerSelemName(snd_mixer_elem_t* elem) {
- return snd_mixer_selem_get_name(elem);
-}
-
-int AlsaWrapper::MixerSelemSetCaptureVolumeAll(
- snd_mixer_elem_t* elem, long value) {
- return snd_mixer_selem_set_capture_volume_all(elem, value);
-}
-
-int AlsaWrapper::MixerSelemGetCaptureVolume(
- snd_mixer_elem_t* elem, snd_mixer_selem_channel_id_t channel, long* value) {
- return snd_mixer_selem_get_capture_volume(elem, channel, value);
-}
-
-int AlsaWrapper::MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem) {
- return snd_mixer_selem_has_capture_volume(elem);
-}
-
-int AlsaWrapper::MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem,
- long* min, long* max) {
- return snd_mixer_selem_get_capture_volume_range(elem, min, max);
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/alsa_wrapper.h b/src/media/audio/linux/alsa_wrapper.h
deleted file mode 100644
index 30d9463..0000000
--- a/src/media/audio/linux/alsa_wrapper.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// AlsaWrapper is a simple stateless class that wraps the alsa library commands
-// we want to use. It's purpose is to allow injection of a mock so that the
-// higher level code is testable.
-
-#include <alsa/asoundlib.h>
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class MEDIA_EXPORT AlsaWrapper {
- public:
- AlsaWrapper();
- virtual ~AlsaWrapper();
-
- virtual int DeviceNameHint(int card, const char* iface, void*** hints);
- virtual char* DeviceNameGetHint(const void* hint, const char* id);
- virtual int DeviceNameFreeHint(void** hints);
- virtual int CardNext(int* rcard);
-
- virtual int PcmOpen(snd_pcm_t** handle, const char* name,
- snd_pcm_stream_t stream, int mode);
- virtual int PcmClose(snd_pcm_t* handle);
- virtual int PcmPrepare(snd_pcm_t* handle);
- virtual int PcmDrop(snd_pcm_t* handle);
- virtual int PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay);
- virtual snd_pcm_sframes_t PcmWritei(snd_pcm_t* handle,
- const void* buffer,
- snd_pcm_uframes_t size);
- virtual snd_pcm_sframes_t PcmReadi(snd_pcm_t* handle,
- void* buffer,
- snd_pcm_uframes_t size);
- virtual int PcmRecover(snd_pcm_t* handle, int err, int silent);
- virtual int PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format,
- snd_pcm_access_t access, unsigned int channels,
- unsigned int rate, int soft_resample,
- unsigned int latency);
- virtual int PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size,
- snd_pcm_uframes_t* period_size);
- virtual const char* PcmName(snd_pcm_t* handle);
- virtual snd_pcm_sframes_t PcmAvailUpdate(snd_pcm_t* handle);
- virtual snd_pcm_state_t PcmState(snd_pcm_t* handle);
- virtual int PcmStart(snd_pcm_t* handle);
-
- virtual int MixerOpen(snd_mixer_t** mixer, int mode);
- virtual int MixerAttach(snd_mixer_t* mixer, const char* name);
- virtual int MixerElementRegister(snd_mixer_t* mixer,
- struct snd_mixer_selem_regopt* options,
- snd_mixer_class_t** classp);
- virtual void MixerFree(snd_mixer_t* mixer);
- virtual int MixerDetach(snd_mixer_t* mixer, const char* name);
- virtual int MixerClose(snd_mixer_t* mixer);
- virtual int MixerLoad(snd_mixer_t* mixer);
- virtual snd_mixer_elem_t* MixerFirstElem(snd_mixer_t* mixer);
- virtual snd_mixer_elem_t* MixerNextElem(snd_mixer_elem_t* elem);
- virtual int MixerSelemIsActive(snd_mixer_elem_t* elem);
- virtual const char* MixerSelemName(snd_mixer_elem_t* elem);
- virtual int MixerSelemSetCaptureVolumeAll(snd_mixer_elem_t* elem, long value);
- virtual int MixerSelemGetCaptureVolume(snd_mixer_elem_t* elem,
- snd_mixer_selem_channel_id_t channel,
- long* value);
- virtual int MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem);
- virtual int MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem,
- long* min, long* max);
-
- virtual const char* StrError(int errnum);
-
- private:
- int ConfigureHwParams(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params,
- snd_pcm_format_t format, snd_pcm_access_t access,
- unsigned int channels, unsigned int rate,
- int soft_resample, unsigned int latency);
- DISALLOW_COPY_AND_ASSIGN(AlsaWrapper);
-};
-
-} // namespace media
diff --git a/src/media/audio/linux/audio_manager_linux.cc b/src/media/audio/linux/audio_manager_linux.cc
deleted file mode 100644
index 48be0b7..0000000
--- a/src/media/audio/linux/audio_manager_linux.cc
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/linux/audio_manager_linux.h"
-
-#include "base/command_line.h"
-#include "base/environment.h"
-#include "base/logging.h"
-#include "base/nix/xdg_util.h"
-#include "base/process_util.h"
-#include "base/stl_util.h"
-#include "media/audio/audio_output_dispatcher.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/linux/alsa_input.h"
-#include "media/audio/linux/alsa_output.h"
-#include "media/audio/linux/alsa_wrapper.h"
-#if defined(USE_PULSEAUDIO)
-#include "media/audio/pulse/pulse_output.h"
-#endif
-#if defined(USE_CRAS)
-#include "media/audio/linux/cras_input.h"
-#include "media/audio/linux/cras_output.h"
-#endif
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-namespace media {
-
-// Maximum number of output streams that can be open simultaneously.
-static const int kMaxOutputStreams = 50;
-
-// Since "default", "pulse" and "dmix" devices are virtual devices mapped to
-// real devices, we remove them from the list to avoiding duplicate counting.
-// In addition, note that we support no more than 2 channels for recording,
-// hence surround devices are not stored in the list.
-static const char* kInvalidAudioInputDevices[] = {
- "default",
- "null",
- "pulse",
- "dmix",
- "surround",
-};
-
-static const char kCrasAutomaticDeviceName[] = "Automatic";
-static const char kCrasAutomaticDeviceId[] = "automatic";
-
-// Implementation of AudioManager.
-bool AudioManagerLinux::HasAudioOutputDevices() {
- if (UseCras())
- return true;
-
- return HasAnyAlsaAudioDevice(kStreamPlayback);
-}
-
-bool AudioManagerLinux::HasAudioInputDevices() {
- if (UseCras())
- return true;
-
- return HasAnyAlsaAudioDevice(kStreamCapture);
-}
-
-AudioManagerLinux::AudioManagerLinux()
- : wrapper_(new AlsaWrapper()) {
- SetMaxOutputStreamsAllowed(kMaxOutputStreams);
-}
-
-AudioManagerLinux::~AudioManagerLinux() {
- Shutdown();
-}
-
-bool AudioManagerLinux::CanShowAudioInputSettings() {
- scoped_ptr<base::Environment> env(base::Environment::Create());
-
- switch (base::nix::GetDesktopEnvironment(env.get())) {
- case base::nix::DESKTOP_ENVIRONMENT_GNOME:
- case base::nix::DESKTOP_ENVIRONMENT_KDE3:
- case base::nix::DESKTOP_ENVIRONMENT_KDE4:
- return true;
- case base::nix::DESKTOP_ENVIRONMENT_OTHER:
- case base::nix::DESKTOP_ENVIRONMENT_UNITY:
- case base::nix::DESKTOP_ENVIRONMENT_XFCE:
- return false;
- }
- // Unless GetDesktopEnvironment() badly misbehaves, this should never happen.
- NOTREACHED();
- return false;
-}
-
-void AudioManagerLinux::ShowAudioInputSettings() {
- scoped_ptr<base::Environment> env(base::Environment::Create());
- base::nix::DesktopEnvironment desktop = base::nix::GetDesktopEnvironment(
- env.get());
- std::string command((desktop == base::nix::DESKTOP_ENVIRONMENT_GNOME) ?
- "gnome-volume-control" : "kmix");
- base::LaunchProcess(CommandLine(FilePath(command)), base::LaunchOptions(),
- NULL);
-}
-
-void AudioManagerLinux::GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) {
- DCHECK(device_names->empty());
- if (UseCras()) {
- GetCrasAudioInputDevices(device_names);
- return;
- }
-
- GetAlsaAudioInputDevices(device_names);
-}
-
-bool AudioManagerLinux::UseCras() {
-#if defined(USE_CRAS)
- if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseCras)) {
- return true;
- }
-#endif
- return false;
-}
-
-void AudioManagerLinux::GetCrasAudioInputDevices(
- media::AudioDeviceNames* device_names) {
- // Cras will route audio from a proper physical device automatically.
- device_names->push_back(media::AudioDeviceName(
- kCrasAutomaticDeviceName, kCrasAutomaticDeviceId));
-}
-
-void AudioManagerLinux::GetAlsaAudioInputDevices(
- media::AudioDeviceNames* device_names) {
- // Constants specified by the ALSA API for device hints.
- static const char kPcmInterfaceName[] = "pcm";
- int card = -1;
-
- // Loop through the sound cards to get ALSA device hints.
- while (!wrapper_->CardNext(&card) && card >= 0) {
- void** hints = NULL;
- int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
- if (!error) {
- GetAlsaDevicesInfo(hints, device_names);
-
- // Destroy the hints now that we're done with it.
- wrapper_->DeviceNameFreeHint(hints);
- } else {
- DLOG(WARNING) << "GetAudioInputDevices: unable to get device hints: "
- << wrapper_->StrError(error);
- }
- }
-}
-
-void AudioManagerLinux::GetAlsaDevicesInfo(
- void** hints, media::AudioDeviceNames* device_names) {
- static const char kIoHintName[] = "IOID";
- static const char kNameHintName[] = "NAME";
- static const char kDescriptionHintName[] = "DESC";
- static const char kOutputDevice[] = "Output";
-
- for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
- // Only examine devices that are input capable. Valid values are
- // "Input", "Output", and NULL which means both input and output.
- scoped_ptr_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
- kIoHintName));
- if (io != NULL && strcmp(kOutputDevice, io.get()) == 0)
- continue;
-
- // Found an input device, prepend the default device since we always want
- // it to be on the top of the list for all platforms. And there is no
- // duplicate counting here since it is only done if the list is still empty.
- // Note, pulse has exclusively opened the default device, so we must open
- // the device via the "default" moniker.
- if (device_names->empty()) {
- device_names->push_front(media::AudioDeviceName(
- AudioManagerBase::kDefaultDeviceName,
- AudioManagerBase::kDefaultDeviceId));
- }
-
- // Get the unique device name for the device.
- scoped_ptr_malloc<char> unique_device_name(
- wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
-
- // Find out if the device is available.
- if (IsAlsaDeviceAvailable(unique_device_name.get())) {
- // Get the description for the device.
- scoped_ptr_malloc<char> desc(wrapper_->DeviceNameGetHint(
- *hint_iter, kDescriptionHintName));
-
- media::AudioDeviceName name;
- name.unique_id = unique_device_name.get();
- if (desc.get()) {
- // Use the more user friendly description as name.
- // Replace '\n' with '-'.
- char* pret = strchr(desc.get(), '\n');
- if (pret)
- *pret = '-';
- name.device_name = desc.get();
- } else {
- // Virtual devices don't necessarily have descriptions.
- // Use their names instead.
- name.device_name = unique_device_name.get();
- }
-
- // Store the device information.
- device_names->push_back(name);
- }
- }
-}
-
-bool AudioManagerLinux::IsAlsaDeviceAvailable(const char* device_name) {
- if (!device_name)
- return false;
-
- // Check if the device is in the list of invalid devices.
- for (size_t i = 0; i < arraysize(kInvalidAudioInputDevices); ++i) {
- if (strncmp(kInvalidAudioInputDevices[i], device_name,
- strlen(kInvalidAudioInputDevices[i])) == 0)
- return false;
- }
-
- return true;
-}
-
-bool AudioManagerLinux::HasAnyAlsaAudioDevice(StreamType stream) {
- static const char kPcmInterfaceName[] = "pcm";
- static const char kIoHintName[] = "IOID";
- const char* kNotWantedDevice =
- (stream == kStreamPlayback ? "Input" : "Output");
- void** hints = NULL;
- bool has_device = false;
- int card = -1;
-
- // Loop through the sound cards.
- // Don't use snd_device_name_hint(-1,..) since there is a access violation
- // inside this ALSA API with libasound.so.2.0.0.
- while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) {
- int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
- if (!error) {
- for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
- // Only examine devices that are |stream| capable. Valid values are
- // "Input", "Output", and NULL which means both input and output.
- scoped_ptr_malloc<char> io(wrapper_->DeviceNameGetHint(*hint_iter,
- kIoHintName));
- if (io != NULL && strcmp(kNotWantedDevice, io.get()) == 0)
- continue; // Wrong type, skip the device.
-
- // Found an input device.
- has_device = true;
- break;
- }
-
- // Destroy the hints now that we're done with it.
- wrapper_->DeviceNameFreeHint(hints);
- hints = NULL;
- } else {
- DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: "
- << wrapper_->StrError(error);
- }
- }
-
- return has_device;
-}
-
-AudioOutputStream* AudioManagerLinux::MakeLinearOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return MakeOutputStream(params);
-}
-
-AudioOutputStream* AudioManagerLinux::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- return MakeOutputStream(params);
-}
-
-AudioInputStream* AudioManagerLinux::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return MakeInputStream(params, device_id);
-}
-
-AudioInputStream* AudioManagerLinux::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- return MakeInputStream(params, device_id);
-}
-
-AudioOutputStream* AudioManagerLinux::MakeOutputStream(
- const AudioParameters& params) {
-#if defined(USE_CRAS)
- if (UseCras()) {
- return new CrasOutputStream(params, this);
- }
-#endif
-
-#if defined(USE_PULSEAUDIO)
- if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUsePulseAudio)) {
- return new PulseAudioOutputStream(params, this);
- }
-#endif
-
- std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kAlsaOutputDevice)) {
- device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kAlsaOutputDevice);
- }
- return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
-}
-
-AudioInputStream* AudioManagerLinux::MakeInputStream(
- const AudioParameters& params, const std::string& device_id) {
-#if defined(USE_CRAS)
- if (UseCras()) {
- return new CrasInputStream(params, this);
- }
-#endif
-
- std::string device_name = (device_id == AudioManagerBase::kDefaultDeviceId) ?
- AlsaPcmInputStream::kAutoSelectDevice : device_id;
- if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaInputDevice)) {
- device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kAlsaInputDevice);
- }
-
- return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
-}
-
-AudioManager* CreateAudioManager() {
- return new AudioManagerLinux();
-}
-
-AudioParameters AudioManagerLinux::GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) {
- // Since Linux doesn't actually have a low latency path the hardware buffer
- // size is quite large in order to prevent glitches with general usage. Some
- // clients, such as WebRTC, have a more limited use case and work acceptably
- // with a smaller buffer size. The check below allows clients which want to
- // try a smaller buffer size on Linux to do so.
- int buffer_size = GetAudioHardwareBufferSize();
- if (input_params.frames_per_buffer() < buffer_size)
- buffer_size = input_params.frames_per_buffer();
-
- int sample_rate = GetAudioHardwareSampleRate();
- // CRAS will sample rate convert if needed, so pass through input sample rate.
- if (UseCras())
- sample_rate = input_params.sample_rate();
-
- // TODO(dalecurtis): This should include bits per channel and channel layout
- // eventually.
- return AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, input_params.channel_layout(),
- sample_rate, 16, buffer_size);
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/audio_manager_linux.h b/src/media/audio/linux/audio_manager_linux.h
deleted file mode 100644
index 7aab32a..0000000
--- a/src/media/audio/linux/audio_manager_linux.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
-#define MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
-
-#include <string>
-#include "base/compiler_specific.h"
-#include "base/memory/ref_counted.h"
-#include "base/threading/thread.h"
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-class AlsaWrapper;
-
-class MEDIA_EXPORT AudioManagerLinux : public AudioManagerBase {
- public:
- AudioManagerLinux();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
- virtual bool CanShowAudioInputSettings() OVERRIDE;
- virtual void ShowAudioInputSettings() OVERRIDE;
- virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names)
- OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) OVERRIDE;
-
- protected:
- virtual ~AudioManagerLinux();
-
- private:
- enum StreamType {
- kStreamPlayback = 0,
- kStreamCapture,
- };
-
- // Returns true if cras should be used for input/output.
- bool UseCras();
-
- // Gets a list of available cras input devices.
- void GetCrasAudioInputDevices(media::AudioDeviceNames* device_names);
-
- // Gets a list of available ALSA input devices.
- void GetAlsaAudioInputDevices(media::AudioDeviceNames* device_names);
-
- // Gets the ALSA devices' names and ids.
- void GetAlsaDevicesInfo(void** hint, media::AudioDeviceNames* device_names);
-
- // Checks if the specific ALSA device is available.
- bool IsAlsaDeviceAvailable(const char* device_name);
-
- // Returns true if a device is present for the given stream type.
- bool HasAnyAlsaAudioDevice(StreamType stream);
-
- // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream.
- AudioOutputStream* MakeOutputStream(const AudioParameters& params);
-
- // Called by MakeLinearInputStream and MakeLowLatencyInputStream.
- AudioInputStream* MakeInputStream(const AudioParameters& params,
- const std::string& device_id);
-
- scoped_ptr<AlsaWrapper> wrapper_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerLinux);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_LINUX_AUDIO_MANAGER_LINUX_H_
diff --git a/src/media/audio/linux/cras_input.cc b/src/media/audio/linux/cras_input.cc
deleted file mode 100644
index 6de405e..0000000
--- a/src/media/audio/linux/cras_input.cc
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/linux/cras_input.h"
-
-#include <math.h>
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/time.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/linux/alsa_util.h"
-#include "media/audio/linux/audio_manager_linux.h"
-
-namespace media {
-
-CrasInputStream::CrasInputStream(const AudioParameters& params,
- AudioManagerLinux* manager)
- : audio_manager_(manager),
- bytes_per_frame_(0),
- callback_(NULL),
- client_(NULL),
- params_(params),
- started_(false),
- stream_id_(0) {
- DCHECK(audio_manager_);
-}
-
-CrasInputStream::~CrasInputStream() {
- DCHECK(!client_);
-}
-
-bool CrasInputStream::Open() {
- if (client_) {
- NOTREACHED() << "CrasInputStream already open";
- return false; // Already open.
- }
-
- // Sanity check input values.
- if (params_.sample_rate() <= 0) {
- DLOG(WARNING) << "Unsupported audio frequency.";
- return false;
- }
-
- if (AudioParameters::AUDIO_PCM_LINEAR != params_.format() &&
- AudioParameters::AUDIO_PCM_LOW_LATENCY != params_.format()) {
- DLOG(WARNING) << "Unsupported audio format.";
- return false;
- }
-
- snd_pcm_format_t pcm_format =
- alsa_util::BitsToFormat(params_.bits_per_sample());
- if (pcm_format == SND_PCM_FORMAT_UNKNOWN) {
- DLOG(WARNING) << "Unsupported bits/sample: " << params_.bits_per_sample();
- return false;
- }
-
- // Create the client and connect to the CRAS server.
- if (cras_client_create(&client_) < 0) {
- DLOG(WARNING) << "Couldn't create CRAS client.\n";
- client_ = NULL;
- return false;
- }
-
- if (cras_client_connect(client_)) {
- DLOG(WARNING) << "Couldn't connect CRAS client.\n";
- cras_client_destroy(client_);
- client_ = NULL;
- return false;
- }
-
- // Then start running the client.
- if (cras_client_run_thread(client_)) {
- DLOG(WARNING) << "Couldn't run CRAS client.\n";
- cras_client_destroy(client_);
- client_ = NULL;
- return false;
- }
-
- return true;
-}
-
-void CrasInputStream::Close() {
- if (client_) {
- cras_client_stop(client_);
- cras_client_destroy(client_);
- client_ = NULL;
- }
-
- if (callback_) {
- callback_->OnClose(this);
- callback_ = NULL;
- }
-
- // Signal to the manager that we're closed and can be removed.
- // Should be last call in the method as it deletes "this".
- audio_manager_->ReleaseInputStream(this);
-}
-
-void CrasInputStream::Start(AudioInputCallback* callback) {
- DCHECK(client_);
- DCHECK(callback);
-
- // If already playing, stop before re-starting.
- if (started_)
- return;
-
- callback_ = callback;
-
- // Prepare |audio_format| and |stream_params| for the stream we
- // will create.
- cras_audio_format* audio_format = cras_audio_format_create(
- alsa_util::BitsToFormat(params_.bits_per_sample()),
- params_.sample_rate(),
- params_.channels());
- if (!audio_format) {
- DLOG(WARNING) << "Error setting up audio parameters.";
- callback_->OnError(this, -ENOMEM);
- callback_ = NULL;
- return;
- }
-
- unsigned int frames_per_packet = params_.frames_per_buffer();
- cras_stream_params* stream_params = cras_client_stream_params_create(
- CRAS_STREAM_INPUT,
- frames_per_packet, // Total latency.
- frames_per_packet, // Call back when this many ready.
- frames_per_packet, // Minimum Callback level ignored for capture streams.
- CRAS_STREAM_TYPE_DEFAULT,
- 0, // Unused flags.
- this,
- CrasInputStream::SamplesReady,
- CrasInputStream::StreamError,
- audio_format);
- if (!stream_params) {
- DLOG(WARNING) << "Error setting up stream parameters.";
- callback_->OnError(this, -ENOMEM);
- callback_ = NULL;
- cras_audio_format_destroy(audio_format);
- return;
- }
-
- // Before starting the stream, save the number of bytes in a frame for use in
- // the callback.
- bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format);
-
- // Adding the stream will start the audio callbacks.
- if (cras_client_add_stream(client_, &stream_id_, stream_params) == 0) {
- audio_manager_->IncreaseActiveInputStreamCount();
- } else {
- DLOG(WARNING) << "Failed to add the stream.";
- callback_->OnError(this, -EIO);
- callback_ = NULL;
- }
-
- // Done with config params.
- cras_audio_format_destroy(audio_format);
- cras_client_stream_params_destroy(stream_params);
-
- started_ = true;
-}
-
-void CrasInputStream::Stop() {
- DCHECK(client_);
-
- if (!callback_ || !started_)
- return;
-
- // Removing the stream from the client stops audio.
- cras_client_rm_stream(client_, stream_id_);
-
- audio_manager_->DecreaseActiveInputStreamCount();
-
- started_ = false;
-}
-
-// Static callback asking for samples. Run on high priority thread.
-int CrasInputStream::SamplesReady(cras_client* client,
- cras_stream_id_t stream_id,
- uint8* samples,
- size_t frames,
- const timespec* sample_ts,
- void* arg) {
- CrasInputStream* me = static_cast<CrasInputStream*>(arg);
- me->ReadAudio(frames, samples, sample_ts);
- return frames;
-}
-
-// Static callback for stream errors.
-int CrasInputStream::StreamError(cras_client* client,
- cras_stream_id_t stream_id,
- int err,
- void* arg) {
- CrasInputStream* me = static_cast<CrasInputStream*>(arg);
- me->NotifyStreamError(err);
- return 0;
-}
-
-void CrasInputStream::ReadAudio(size_t frames,
- uint8* buffer,
- const timespec* sample_ts) {
- DCHECK(callback_);
-
- timespec latency_ts = {0, 0};
-
- // Determine latency and pass that on to the sink. sample_ts is the wall time
- // indicating when the first sample in the buffer was captured. Convert that
- // to latency in bytes.
- cras_client_calc_capture_latency(sample_ts, &latency_ts);
- double latency_usec =
- latency_ts.tv_sec * base::Time::kMicrosecondsPerSecond +
- latency_ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond;
- double frames_latency =
- latency_usec * params_.sample_rate() / base::Time::kMicrosecondsPerSecond;
- unsigned int bytes_latency =
- static_cast<unsigned int>(frames_latency * bytes_per_frame_);
-
- // Update the AGC volume level once every second. Note that, |volume| is
- // also updated each time SetVolume() is called through IPC by the
- // render-side AGC.
- double normalized_volume = 0.0;
- QueryAgcVolume(&normalized_volume);
-
- callback_->OnData(this,
- buffer,
- frames * bytes_per_frame_,
- bytes_latency,
- normalized_volume);
-}
-
-void CrasInputStream::NotifyStreamError(int err) {
- if (callback_)
- callback_->OnError(this, err);
-}
-
-double CrasInputStream::GetMaxVolume() {
- DCHECK(client_);
-
- // Capture gain is returned as dB * 100 (150 => 1.5dBFS). Convert the dB
- // value to a ratio before returning.
- double dB = cras_client_get_system_max_capture_gain(client_) / 100.0;
- return GetVolumeRatioFromDecibels(dB);
-}
-
-void CrasInputStream::SetVolume(double volume) {
- DCHECK(client_);
-
- // Convert from the passed volume ratio, to dB * 100.
- double dB = GetDecibelsFromVolumeRatio(volume);
- cras_client_set_system_capture_gain(client_, static_cast<long>(dB * 100.0));
-
- // Update the AGC volume level based on the last setting above. Note that,
- // the volume-level resolution is not infinite and it is therefore not
- // possible to assume that the volume provided as input parameter can be
- // used directly. Instead, a new query to the audio hardware is required.
- // This method does nothing if AGC is disabled.
- UpdateAgcVolume();
-}
-
-double CrasInputStream::GetVolume() {
- if (!client_)
- return 0.0;
-
- long dB = cras_client_get_system_capture_gain(client_) / 100.0;
- return GetVolumeRatioFromDecibels(dB);
-}
-
-double CrasInputStream::GetVolumeRatioFromDecibels(double dB) const {
- return pow(10, dB / 20.0);
-}
-
-double CrasInputStream::GetDecibelsFromVolumeRatio(double volume_ratio) const {
- return 20 * log10(volume_ratio);
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/cras_input.h b/src/media/audio/linux/cras_input.h
deleted file mode 100644
index 87891da..0000000
--- a/src/media/audio/linux/cras_input.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_LINUX_CRAS_INPUT_H_
-#define MEDIA_AUDIO_LINUX_CRAS_INPUT_H_
-
-#include <cras_client.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "media/audio/audio_input_stream_impl.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerLinux;
-
-// Provides an input stream for audio capture based on CRAS, the ChromeOS Audio
-// Server. This object is not thread safe and all methods should be invoked in
-// the thread that created the object.
-class CrasInputStream : public AudioInputStreamImpl {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // audio manager who is creating this object.
- CrasInputStream(const AudioParameters& params, AudioManagerLinux* manager);
-
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~CrasInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
-
- private:
- // Handles requests to get samples from the provided buffer. This will be
- // called by the audio server when it has samples ready.
- static int SamplesReady(cras_client* client,
- cras_stream_id_t stream_id,
- uint8* samples,
- size_t frames,
- const timespec* sample_ts,
- void* arg);
-
- // Handles notificaiton that there was an error with the playback stream.
- static int StreamError(cras_client* client,
- cras_stream_id_t stream_id,
- int err,
- void* arg);
-
- // Reads one or more buffers of audio from the device, passes on to the
- // registered callback. Called from SamplesReady().
- void ReadAudio(size_t frames, uint8* buffer, const timespec* sample_ts);
-
- // Deals with an error that occured in the stream. Called from StreamError().
- void NotifyStreamError(int err);
-
- // Convert from dB * 100 to a volume ratio.
- double GetVolumeRatioFromDecibels(double dB) const;
-
- // Convert from a volume ratio to dB.
- double GetDecibelsFromVolumeRatio(double volume_ratio) const;
-
- // Non-refcounted pointer back to the audio manager.
- // The AudioManager indirectly holds on to stream objects, so we don't
- // want circular references. Additionally, stream objects live on the audio
- // thread, which is owned by the audio manager and we don't want to addref
- // the manager from that thread.
- AudioManagerLinux* audio_manager_;
-
- // Size of frame in bytes.
- uint32 bytes_per_frame_;
-
- // Callback to pass audio samples too, valid while recording.
- AudioInputCallback* callback_;
-
- // The client used to communicate with the audio server.
- cras_client* client_;
-
- // PCM parameters for the stream.
- AudioParameters params_;
-
- // True if the stream has been started.
- bool started_;
-
- // ID of the playing stream.
- cras_stream_id_t stream_id_;
-
- DISALLOW_COPY_AND_ASSIGN(CrasInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_LINUX_ALSA_INPUT_H_
diff --git a/src/media/audio/linux/cras_input_unittest.cc b/src/media/audio/linux/cras_input_unittest.cc
deleted file mode 100644
index 5d9a1c8..0000000
--- a/src/media/audio/linux/cras_input_unittest.cc
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <unistd.h>
-
-#include <string>
-
-#include "base/synchronization/waitable_event.h"
-#include "base/test/test_timeouts.h"
-#include "base/time.h"
-#include "media/audio/linux/audio_manager_linux.h"
-#include "media/audio/linux/cras_input.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-using testing::AtLeast;
-using testing::Ge;
-using testing::InvokeWithoutArgs;
-using testing::StrictMock;
-
-namespace media {
-
-class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
- public:
- MOCK_METHOD5(OnData, void(
- AudioInputStream*, const uint8*, uint32, uint32, double));
- MOCK_METHOD2(OnError, void(AudioInputStream*, int));
- MOCK_METHOD1(OnClose, void(AudioInputStream*));
-};
-
-class MockAudioManagerLinuxInput : public AudioManagerLinux {
- public:
- // We need to override this function in order to skip checking the number
- // of active output streams. It is because the number of active streams
- // is managed inside MakeAudioInputStream, and we don't use
- // MakeAudioInputStream to create the stream in the tests.
- virtual void ReleaseInputStream(AudioInputStream* stream) OVERRIDE {
- DCHECK(stream);
- delete stream;
- }
-};
-
-class CrasInputStreamTest : public testing::Test {
- protected:
- CrasInputStreamTest() {
- mock_manager_.reset(new StrictMock<MockAudioManagerLinuxInput>());
- }
-
- virtual ~CrasInputStreamTest() {
- }
-
- CrasInputStream* CreateStream(ChannelLayout layout) {
- return CreateStream(layout, kTestFramesPerPacket);
- }
-
- CrasInputStream* CreateStream(ChannelLayout layout,
- int32 samples_per_packet) {
- AudioParameters params(kTestFormat,
- layout,
- kTestSampleRate,
- kTestBitsPerSample,
- samples_per_packet);
- return new CrasInputStream(params, mock_manager_.get());
- }
-
- void CaptureSomeFrames(const AudioParameters ¶ms,
- unsigned int duration_ms) {
- CrasInputStream* test_stream = new CrasInputStream(params,
- mock_manager_.get());
-
- ASSERT_TRUE(test_stream->Open());
-
- // Allow 8 frames variance for SRC in the callback. Different numbers of
- // samples can be provided when doing non-integer SRC. For example
- // converting from 192k to 44.1k is a ratio of 4.35 to 1.
- MockAudioInputCallback mock_callback;
- unsigned int expected_size = (kTestFramesPerPacket - 8) *
- params.channels() *
- params.bits_per_sample() / 8;
-
- base::WaitableEvent event(false, false);
-
- EXPECT_CALL(mock_callback,
- OnData(test_stream, _, Ge(expected_size), _, _))
- .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
-
- test_stream->Start(&mock_callback);
-
- // Wait for samples to be captured.
- EXPECT_TRUE(event.TimedWait(TestTimeouts::action_timeout()));
-
- test_stream->Stop();
-
- EXPECT_CALL(mock_callback, OnClose(test_stream)).Times(1);
- test_stream->Close();
- }
-
- static const unsigned int kTestBitsPerSample;
- static const unsigned int kTestCaptureDurationMs;
- static const ChannelLayout kTestChannelLayout;
- static const AudioParameters::Format kTestFormat;
- static const uint32 kTestFramesPerPacket;
- static const int kTestSampleRate;
-
- scoped_ptr<StrictMock<MockAudioManagerLinuxInput> > mock_manager_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(CrasInputStreamTest);
-};
-
-const unsigned int CrasInputStreamTest::kTestBitsPerSample = 16;
-const unsigned int CrasInputStreamTest::kTestCaptureDurationMs = 250;
-const ChannelLayout CrasInputStreamTest::kTestChannelLayout =
- CHANNEL_LAYOUT_STEREO;
-const AudioParameters::Format CrasInputStreamTest::kTestFormat =
- AudioParameters::AUDIO_PCM_LINEAR;
-const uint32 CrasInputStreamTest::kTestFramesPerPacket = 1000;
-const int CrasInputStreamTest::kTestSampleRate = 44100;
-
-TEST_F(CrasInputStreamTest, OpenMono) {
- CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- EXPECT_TRUE(test_stream->Open());
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, OpenStereo) {
- CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_STEREO);
- EXPECT_TRUE(test_stream->Open());
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, BadBitsPerSample) {
- AudioParameters bad_bps_params(kTestFormat,
- kTestChannelLayout,
- kTestSampleRate,
- kTestBitsPerSample - 1,
- kTestFramesPerPacket);
- CrasInputStream* test_stream =
- new CrasInputStream(bad_bps_params, mock_manager_.get());
- EXPECT_FALSE(test_stream->Open());
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, BadFormat) {
- AudioParameters bad_format_params(AudioParameters::AUDIO_LAST_FORMAT,
- kTestChannelLayout,
- kTestSampleRate,
- kTestBitsPerSample,
- kTestFramesPerPacket);
- CrasInputStream* test_stream =
- new CrasInputStream(bad_format_params, mock_manager_.get());
- EXPECT_FALSE(test_stream->Open());
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, BadSampleRate) {
- AudioParameters bad_rate_params(kTestFormat,
- kTestChannelLayout,
- 0,
- kTestBitsPerSample,
- kTestFramesPerPacket);
- CrasInputStream* test_stream =
- new CrasInputStream(bad_rate_params, mock_manager_.get());
- EXPECT_FALSE(test_stream->Open());
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, SetGetVolume) {
- CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- EXPECT_TRUE(test_stream->Open());
-
- double max_volume = test_stream->GetMaxVolume();
- EXPECT_GE(max_volume, 1.0);
-
- test_stream->SetVolume(max_volume / 2);
-
- double new_volume = test_stream->GetVolume();
-
- EXPECT_GE(new_volume, 0.0);
- EXPECT_LE(new_volume, max_volume);
-
- test_stream->Close();
-}
-
-TEST_F(CrasInputStreamTest, CaptureFrames) {
- const unsigned int rates[] =
- {8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000};
-
- for (unsigned int i = 0; i < ARRAY_SIZE(rates); i++) {
- SCOPED_TRACE(testing::Message() << "Mono " << rates[i] << "Hz");
- AudioParameters params_mono(kTestFormat,
- CHANNEL_LAYOUT_MONO,
- rates[i],
- kTestBitsPerSample,
- kTestFramesPerPacket);
- CaptureSomeFrames(params_mono, kTestCaptureDurationMs);
- }
-
- for (unsigned int i = 0; i < ARRAY_SIZE(rates); i++) {
- SCOPED_TRACE(testing::Message() << "Stereo " << rates[i] << "Hz");
- AudioParameters params_stereo(kTestFormat,
- CHANNEL_LAYOUT_STEREO,
- rates[i],
- kTestBitsPerSample,
- kTestFramesPerPacket);
- CaptureSomeFrames(params_stereo, kTestCaptureDurationMs);
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/cras_output.cc b/src/media/audio/linux/cras_output.cc
deleted file mode 100644
index 429ffc2..0000000
--- a/src/media/audio/linux/cras_output.cc
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// The object has one error state: |state_| == kInError. When |state_| ==
-// kInError, all public API functions will fail with an error (Start() will call
-// the OnError() function on the callback immediately), or no-op themselves with
-// the exception of Close(). Even if an error state has been entered, if Open()
-// has previously returned successfully, Close() must be called.
-
-#include "media/audio/linux/cras_output.h"
-
-#include <cras_client.h>
-
-#include "base/logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/linux/alsa_util.h"
-#include "media/audio/linux/audio_manager_linux.h"
-
-namespace media {
-
-// Helps make log messages readable.
-std::ostream& operator<<(std::ostream& os,
- CrasOutputStream::InternalState state) {
- switch (state) {
- case CrasOutputStream::kInError:
- os << "kInError";
- break;
- case CrasOutputStream::kCreated:
- os << "kCreated";
- break;
- case CrasOutputStream::kIsOpened:
- os << "kIsOpened";
- break;
- case CrasOutputStream::kIsPlaying:
- os << "kIsPlaying";
- break;
- case CrasOutputStream::kIsStopped:
- os << "kIsStopped";
- break;
- case CrasOutputStream::kIsClosed:
- os << "kIsClosed";
- break;
- default:
- os << "UnknownState";
- break;
- };
- return os;
-}
-
-// Overview of operation:
-// 1) An object of CrasOutputStream is created by the AudioManager
-// factory: audio_man->MakeAudioStream().
-// 2) Next some thread will call Open(), at that point a client is created and
-// configured for the correct format and sample rate.
-// 3) Then Start(source) is called and a stream is added to the CRAS client
-// which will create its own thread that periodically calls the source for more
-// data as buffers are being consumed.
-// 4) When finished Stop() is called, which is handled by stopping the stream.
-// 5) Finally Close() is called. It cleans up and notifies the audio manager,
-// which likely will destroy this object.
-
-CrasOutputStream::CrasOutputStream(const AudioParameters& params,
- AudioManagerLinux* manager)
- : client_(NULL),
- stream_id_(0),
- samples_per_packet_(params.frames_per_buffer()),
- bytes_per_frame_(0),
- frame_rate_(params.sample_rate()),
- num_channels_(params.channels()),
- pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())),
- state_(kCreated),
- volume_(1.0),
- manager_(manager),
- source_callback_(NULL),
- audio_bus_(AudioBus::Create(params)) {
- // We must have a manager.
- DCHECK(manager_);
-
- // Sanity check input values.
- if (params.sample_rate() <= 0) {
- LOG(WARNING) << "Unsupported audio frequency.";
- TransitionTo(kInError);
- return;
- }
-
- if (AudioParameters::AUDIO_PCM_LINEAR != params.format() &&
- AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format()) {
- LOG(WARNING) << "Unsupported audio format.";
- TransitionTo(kInError);
- return;
- }
-
- if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) {
- LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample();
- TransitionTo(kInError);
- return;
- }
-}
-
-CrasOutputStream::~CrasOutputStream() {
- InternalState current_state = state();
- DCHECK(current_state == kCreated ||
- current_state == kIsClosed ||
- current_state == kInError);
-}
-
-bool CrasOutputStream::Open() {
- if (!CanTransitionTo(kIsOpened)) {
- NOTREACHED() << "Invalid state: " << state();
- return false;
- }
-
- // We do not need to check if the transition was successful because
- // CanTransitionTo() was checked above, and it is assumed that this
- // object's public API is only called on one thread so the state cannot
- // transition out from under us.
- TransitionTo(kIsOpened);
-
- // Create the client and connect to the CRAS server.
- int err = cras_client_create(&client_);
- if (err < 0) {
- LOG(WARNING) << "Couldn't create CRAS client.\n";
- client_ = NULL;
- TransitionTo(kInError);
- return false;
- }
- err = cras_client_connect(client_);
- if (err) {
- LOG(WARNING) << "Couldn't connect CRAS client.\n";
- cras_client_destroy(client_);
- client_ = NULL;
- TransitionTo(kInError);
- return false;
- }
- // Then start running the client.
- err = cras_client_run_thread(client_);
- if (err) {
- LOG(WARNING) << "Couldn't run CRAS client.\n";
- cras_client_destroy(client_);
- client_ = NULL;
- TransitionTo(kInError);
- return false;
- }
-
- return true;
-}
-
-void CrasOutputStream::Close() {
- // Sanity Check that we can transition to closed.
- if (TransitionTo(kIsClosed) != kIsClosed) {
- NOTREACHED() << "Unable to transition Closed.";
- return;
- }
-
- if (client_) {
- cras_client_stop(client_);
- cras_client_destroy(client_);
- client_ = NULL;
- }
-
- // Signal to the manager that we're closed and can be removed.
- // Should be last call in the method as it deletes "this".
- manager_->ReleaseOutputStream(this);
-}
-
-void CrasOutputStream::Start(AudioSourceCallback* callback) {
- CHECK(callback);
- source_callback_ = callback;
-
- // Only start if we can enter the playing state.
- if (TransitionTo(kIsPlaying) != kIsPlaying)
- return;
-
- // Prepare |audio_format| and |stream_params| for the stream we
- // will create.
- cras_audio_format* audio_format = cras_audio_format_create(
- pcm_format_,
- frame_rate_,
- num_channels_);
- if (audio_format == NULL) {
- LOG(WARNING) << "Error setting up audio parameters.";
- TransitionTo(kInError);
- callback->OnError(this, -ENOMEM);
- return;
- }
- cras_stream_params* stream_params = cras_client_stream_params_create(
- CRAS_STREAM_OUTPUT,
- samples_per_packet_ * 2, // Total latency.
- samples_per_packet_ / 2, // Call back when this many left.
- samples_per_packet_, // Call back with at least this much space.
- CRAS_STREAM_TYPE_DEFAULT,
- 0,
- this,
- CrasOutputStream::PutSamples,
- CrasOutputStream::StreamError,
- audio_format);
- if (stream_params == NULL) {
- LOG(WARNING) << "Error setting up stream parameters.";
- TransitionTo(kInError);
- callback->OnError(this, -ENOMEM);
- cras_audio_format_destroy(audio_format);
- return;
- }
-
- // Before starting the stream, save the number of bytes in a frame for use in
- // the callback.
- bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format);
-
- // Adding the stream will start the audio callbacks requesting data.
- int err = cras_client_add_stream(client_, &stream_id_, stream_params);
- if (err < 0) {
- LOG(WARNING) << "Failed to add the stream";
- TransitionTo(kInError);
- callback->OnError(this, err);
- cras_audio_format_destroy(audio_format);
- cras_client_stream_params_destroy(stream_params);
- return;
- }
-
- // Set initial volume.
- cras_client_set_stream_volume(client_, stream_id_, volume_);
-
- // Done with config params.
- cras_audio_format_destroy(audio_format);
- cras_client_stream_params_destroy(stream_params);
-}
-
-void CrasOutputStream::Stop() {
- if (!client_)
- return;
- // Removing the stream from the client stops audio.
- cras_client_rm_stream(client_, stream_id_);
- TransitionTo(kIsStopped);
-}
-
-void CrasOutputStream::SetVolume(double volume) {
- if (!client_)
- return;
- volume_ = static_cast<float>(volume);
- cras_client_set_stream_volume(client_, stream_id_, volume_);
-}
-
-void CrasOutputStream::GetVolume(double* volume) {
- *volume = volume_;
-}
-
-// Static callback asking for samples.
-int CrasOutputStream::PutSamples(cras_client* client,
- cras_stream_id_t stream_id,
- uint8* samples,
- size_t frames,
- const timespec* sample_ts,
- void* arg) {
- CrasOutputStream* me = static_cast<CrasOutputStream*>(arg);
- return me->Render(frames, samples, sample_ts);
-}
-
-// Static callback for stream errors.
-int CrasOutputStream::StreamError(cras_client* client,
- cras_stream_id_t stream_id,
- int err,
- void* arg) {
- CrasOutputStream* me = static_cast<CrasOutputStream*>(arg);
- me->NotifyStreamError(err);
- return 0;
-}
-
-// Note this is run from a real time thread, so don't waste cycles here.
-uint32 CrasOutputStream::Render(size_t frames,
- uint8* buffer,
- const timespec* sample_ts) {
- timespec latency_ts = {0, 0};
-
- // Determine latency and pass that on to the source.
- cras_client_calc_playback_latency(sample_ts, &latency_ts);
-
- // Treat negative latency (if we are too slow to render) as 0.
- uint32 latency_usec;
- if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) {
- latency_usec = 0;
- } else {
- latency_usec = (latency_ts.tv_sec * 1000000) +
- latency_ts.tv_nsec / 1000;
- }
-
- uint32 frames_latency = latency_usec * frame_rate_ / 1000000;
- uint32 bytes_latency = frames_latency * bytes_per_frame_;
- DCHECK_EQ(frames, static_cast<size_t>(audio_bus_->frames()));
- int frames_filled = source_callback_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(0, bytes_latency));
- // Note: If this ever changes to output raw float the data must be clipped and
- // sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, bytes_per_frame_ / num_channels_, buffer);
- return frames_filled;
-}
-
-void CrasOutputStream::NotifyStreamError(int err) {
- // This will remove the stream from the client.
- if (state_ == kIsClosed || state_ == kInError)
- return; // Don't care about error if we aren't using it.
- TransitionTo(kInError);
- if (source_callback_)
- source_callback_->OnError(this, err);
-}
-
-bool CrasOutputStream::CanTransitionTo(InternalState to) {
- switch (state_) {
- case kCreated:
- return to == kIsOpened || to == kIsClosed || to == kInError;
-
- case kIsOpened:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kIsPlaying:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kIsStopped:
- return to == kIsPlaying || to == kIsStopped ||
- to == kIsClosed || to == kInError;
-
- case kInError:
- return to == kIsClosed || to == kInError;
-
- case kIsClosed:
- return false;
- }
- return false;
-}
-
-CrasOutputStream::InternalState
-CrasOutputStream::TransitionTo(InternalState to) {
- if (!CanTransitionTo(to)) {
- state_ = kInError;
- } else {
- state_ = to;
- }
- return state_;
-}
-
-CrasOutputStream::InternalState CrasOutputStream::state() {
- return state_;
-}
-
-} // namespace media
diff --git a/src/media/audio/linux/cras_output.h b/src/media/audio/linux/cras_output.h
deleted file mode 100644
index 8dffbce..0000000
--- a/src/media/audio/linux/cras_output.h
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Creates an output stream based on the cras (ChromeOS audio server) interface.
-//
-// CrasOutputStream object is *not* thread-safe and should only be used
-// from the audio thread.
-
-#ifndef MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_
-#define MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_
-
-#include <alsa/asoundlib.h>
-#include <cras_client.h>
-#include <ostream>
-
-#include "base/compiler_specific.h"
-#include "base/gtest_prod_util.h"
-#include "media/audio/audio_io.h"
-
-namespace media {
-
-class AudioManagerLinux;
-class AudioParameters;
-
-// Implementation of AudioOuputStream for Chrome OS using the Chrome OS audio
-// server.
-class MEDIA_EXPORT CrasOutputStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // audio manager who is creating this object.
- CrasOutputStream(const AudioParameters& params, AudioManagerLinux* manager);
-
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~CrasOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- // Flags indicating the state of the stream.
- enum InternalState {
- kInError = 0,
- kCreated,
- kIsOpened,
- kIsPlaying,
- kIsStopped,
- kIsClosed
- };
- friend std::ostream& operator<<(std::ostream& os, InternalState);
- // Reports the current state for unit testing.
- InternalState state();
-
- private:
- // Handles requests to put samples in the provided buffer. This will be
- // called by the audio server when it needs more data.
- static int PutSamples(cras_client* client,
- cras_stream_id_t stream_id,
- uint8* samples,
- size_t frames,
- const timespec* sample_ts,
- void* arg);
-
- // Handles notificaiton that there was an error with the playback stream.
- static int StreamError(cras_client* client,
- cras_stream_id_t stream_id,
- int err,
- void* arg);
-
- // Actually fills buffer with audio data. Called from PutSamples().
- uint32 Render(size_t frames, uint8* buffer, const timespec* sample_ts);
-
- // Deals with an error that occured in the stream. Called from StreamError().
- void NotifyStreamError(int err);
-
- // Functions to safeguard state transitions. All changes to the object state
- // should go through these functions.
- bool CanTransitionTo(InternalState to);
- InternalState TransitionTo(InternalState to);
-
- // The client used to communicate with the audio server.
- cras_client* client_;
-
- // ID of the playing stream.
- cras_stream_id_t stream_id_;
-
- // Packet size in samples.
- uint32 samples_per_packet_;
-
- // Size of frame in bytes.
- uint32 bytes_per_frame_;
-
- // Rate in Hz.
- size_t frame_rate_;
-
- // Number of channels.
- size_t num_channels_;
-
- // PCM format for Alsa.
- const snd_pcm_format_t pcm_format_;
-
- // Current state.
- InternalState state_;
-
- // Volume level from 0.0 to 1.0.
- float volume_;
-
- // Audio manager that created us. Used to report that we've been closed.
- AudioManagerLinux* manager_;
-
- // Callback to get audio samples.
- AudioSourceCallback* source_callback_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(CrasOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_
diff --git a/src/media/audio/linux/cras_output_unittest.cc b/src/media/audio/linux/cras_output_unittest.cc
deleted file mode 100644
index f9e2b24..0000000
--- a/src/media/audio/linux/cras_output_unittest.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-
-#include "media/audio/linux/audio_manager_linux.h"
-#include "media/audio/linux/cras_output.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-using testing::DoAll;
-using testing::Return;
-using testing::SetArgumentPointee;
-using testing::StrictMock;
-
-namespace media {
-
-class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-class MockAudioManagerLinux : public AudioManagerLinux {
- public:
- MOCK_METHOD0(Init, void());
- MOCK_METHOD0(HasAudioOutputDevices, bool());
- MOCK_METHOD0(HasAudioInputDevices, bool());
- MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD1(MakeLowLatencyOutputStream, AudioOutputStream*(
- const AudioParameters& params));
- MOCK_METHOD2(MakeLinearOutputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
- MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*(
- const AudioParameters& params, const std::string& device_id));
-
- // We need to override this function in order to skip the checking the number
- // of active output streams. It is because the number of active streams
- // is managed inside MakeAudioOutputStream, and we don't use
- // MakeAudioOutputStream to create the stream in the tests.
- virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE {
- DCHECK(stream);
- delete stream;
- }
-
- // We don't mock this method since all tests will do the same thing
- // and use the current message loop.
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE {
- return MessageLoop::current()->message_loop_proxy();
- }
-};
-
-class CrasOutputStreamTest : public testing::Test {
- protected:
- CrasOutputStreamTest() {
- mock_manager_.reset(new StrictMock<MockAudioManagerLinux>());
- }
-
- virtual ~CrasOutputStreamTest() {
- }
-
- CrasOutputStream* CreateStream(ChannelLayout layout) {
- return CreateStream(layout, kTestFramesPerPacket);
- }
-
- CrasOutputStream* CreateStream(ChannelLayout layout,
- int32 samples_per_packet) {
- AudioParameters params(kTestFormat, layout, kTestSampleRate,
- kTestBitsPerSample, samples_per_packet);
- return new CrasOutputStream(params,
- mock_manager_.get());
- }
-
- MockAudioManagerLinux& mock_manager() {
- return *(mock_manager_.get());
- }
-
- static const ChannelLayout kTestChannelLayout;
- static const int kTestSampleRate;
- static const int kTestBitsPerSample;
- static const int kTestBytesPerFrame;
- static const AudioParameters::Format kTestFormat;
- static const uint32 kTestFramesPerPacket;
- static const uint32 kTestPacketSize;
- static struct cras_audio_format* const kFakeAudioFormat;
- static struct cras_stream_params* const kFakeStreamParams;
- static struct cras_client* const kFakeClient;
-
- scoped_ptr<StrictMock<MockAudioManagerLinux> > mock_manager_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(CrasOutputStreamTest);
-};
-
-const ChannelLayout CrasOutputStreamTest::kTestChannelLayout =
- CHANNEL_LAYOUT_STEREO;
-const int CrasOutputStreamTest::kTestSampleRate =
- AudioParameters::kAudioCDSampleRate;
-const int CrasOutputStreamTest::kTestBitsPerSample = 16;
-const int CrasOutputStreamTest::kTestBytesPerFrame =
- CrasOutputStreamTest::kTestBitsPerSample / 8 *
- ChannelLayoutToChannelCount(CrasOutputStreamTest::kTestChannelLayout);
-const AudioParameters::Format CrasOutputStreamTest::kTestFormat =
- AudioParameters::AUDIO_PCM_LINEAR;
-const uint32 CrasOutputStreamTest::kTestFramesPerPacket = 1000;
-const uint32 CrasOutputStreamTest::kTestPacketSize =
- CrasOutputStreamTest::kTestFramesPerPacket *
- CrasOutputStreamTest::kTestBytesPerFrame;
-struct cras_audio_format* const CrasOutputStreamTest::kFakeAudioFormat =
- reinterpret_cast<struct cras_audio_format*>(1);
-struct cras_stream_params* const CrasOutputStreamTest::kFakeStreamParams =
- reinterpret_cast<struct cras_stream_params*>(1);
-struct cras_client* const CrasOutputStreamTest::kFakeClient =
- reinterpret_cast<struct cras_client*>(1);
-
-TEST_F(CrasOutputStreamTest, ConstructedState) {
- // Should support mono.
- CrasOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- EXPECT_EQ(CrasOutputStream::kCreated, test_stream->state());
- test_stream->Close();
-
- // Should support stereo.
- test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
- EXPECT_EQ(CrasOutputStream::kCreated, test_stream->state());
- test_stream->Close();
-
- // Bad bits per sample.
- AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout,
- kTestSampleRate, kTestBitsPerSample - 1,
- kTestFramesPerPacket);
- test_stream = new CrasOutputStream(bad_bps_params, mock_manager_.get());
- EXPECT_EQ(CrasOutputStream::kInError, test_stream->state());
- test_stream->Close();
-
- // Bad format.
- AudioParameters bad_format_params(AudioParameters::AUDIO_LAST_FORMAT,
- kTestChannelLayout, kTestSampleRate,
- kTestBitsPerSample, kTestFramesPerPacket);
- test_stream = new CrasOutputStream(bad_format_params, mock_manager_.get());
- EXPECT_EQ(CrasOutputStream::kInError, test_stream->state());
- test_stream->Close();
-
- // Bad sample rate.
- AudioParameters bad_rate_params(kTestFormat, kTestChannelLayout,
- 0, kTestBitsPerSample, kTestFramesPerPacket);
- test_stream = new CrasOutputStream(bad_rate_params, mock_manager_.get());
- EXPECT_EQ(CrasOutputStream::kInError, test_stream->state());
- test_stream->Close();
-}
-
-TEST_F(CrasOutputStreamTest, OpenClose) {
- CrasOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- // Open the stream.
- ASSERT_TRUE(test_stream->Open());
- EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state());
-
- // Close the stream.
- test_stream->Close();
-}
-
-TEST_F(CrasOutputStreamTest, StartFailBeforeOpen) {
- CrasOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- MockAudioSourceCallback mock_callback;
-
- test_stream->Start(&mock_callback);
- EXPECT_EQ(CrasOutputStream::kInError, test_stream->state());
-}
-
-TEST_F(CrasOutputStreamTest, StartStop) {
- CrasOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- MockAudioSourceCallback mock_callback;
-
- // Open the stream.
- ASSERT_TRUE(test_stream->Open());
- EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state());
-
- // Start.
- test_stream->Start(&mock_callback);
- EXPECT_EQ(CrasOutputStream::kIsPlaying, test_stream->state());
-
- // Stop.
- test_stream->Stop();
- EXPECT_EQ(CrasOutputStream::kIsStopped, test_stream->state());
-
- // Close the stream.
- test_stream->Close();
-}
-
-TEST_F(CrasOutputStreamTest, RenderFrames) {
- CrasOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
- MockAudioSourceCallback mock_callback;
-
- // Open the stream.
- ASSERT_TRUE(test_stream->Open());
- EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state());
-
- // Render Callback.
- EXPECT_CALL(mock_callback, OnMoreData(_, _))
- .WillRepeatedly(Return(kTestFramesPerPacket));
-
- // Start.
- test_stream->Start(&mock_callback);
- EXPECT_EQ(CrasOutputStream::kIsPlaying, test_stream->state());
-
- // Stop.
- test_stream->Stop();
- EXPECT_EQ(CrasOutputStream::kIsStopped, test_stream->state());
-
- // Close the stream.
- test_stream->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_input_mac.cc b/src/media/audio/mac/audio_input_mac.cc
deleted file mode 100644
index e741b29..0000000
--- a/src/media/audio/mac/audio_input_mac.cc
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_input_mac.h"
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/mac/mac_logging.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/audio_util.h"
-
-#if !defined(OS_IOS)
-#include <CoreServices/CoreServices.h>
-#endif
-
-namespace media {
-
-PCMQueueInAudioInputStream::PCMQueueInAudioInputStream(
- AudioManagerBase* manager, const AudioParameters& params)
- : manager_(manager),
- callback_(NULL),
- audio_queue_(NULL),
- buffer_size_bytes_(0),
- started_(false) {
- // We must have a manager.
- DCHECK(manager_);
- // A frame is one sample across all channels. In interleaved audio the per
- // frame fields identify the set of n |channels|. In uncompressed audio, a
- // packet is always one frame.
- format_.mSampleRate = params.sample_rate();
- format_.mFormatID = kAudioFormatLinearPCM;
- format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
- kLinearPCMFormatFlagIsSignedInteger;
- format_.mBitsPerChannel = params.bits_per_sample();
- format_.mChannelsPerFrame = params.channels();
- format_.mFramesPerPacket = 1;
- format_.mBytesPerPacket = (params.bits_per_sample() * params.channels()) / 8;
- format_.mBytesPerFrame = format_.mBytesPerPacket;
- format_.mReserved = 0;
-
- buffer_size_bytes_ = params.GetBytesPerBuffer();
-}
-
-PCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() {
- DCHECK(!callback_);
- DCHECK(!audio_queue_);
-}
-
-bool PCMQueueInAudioInputStream::Open() {
- OSStatus err = AudioQueueNewInput(&format_,
- &HandleInputBufferStatic,
- this,
- NULL, // Use OS CFRunLoop for |callback|
- kCFRunLoopCommonModes,
- 0, // Reserved
- &audio_queue_);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- return SetupBuffers();
-}
-
-void PCMQueueInAudioInputStream::Start(AudioInputCallback* callback) {
- DCHECK(callback);
- DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully";
- if (callback_ || !audio_queue_)
- return;
- callback_ = callback;
- OSStatus err = AudioQueueStart(audio_queue_, NULL);
- if (err != noErr) {
- HandleError(err);
- } else {
- started_ = true;
- manager_->IncreaseActiveInputStreamCount();
- }
-}
-
-void PCMQueueInAudioInputStream::Stop() {
- if (!audio_queue_ || !started_)
- return;
-
- // Stop is always called before Close. In case of error, this will be
- // also called when closing the input controller.
- manager_->DecreaseActiveInputStreamCount();
-
- // We request a synchronous stop, so the next call can take some time. In
- // the windows implementation we block here as well.
- OSStatus err = AudioQueueStop(audio_queue_, true);
- if (err != noErr)
- HandleError(err);
-
- started_ = false;
-}
-
-void PCMQueueInAudioInputStream::Close() {
- // It is valid to call Close() before calling Open() or Start(), thus
- // |audio_queue_| and |callback_| might be NULL.
- if (audio_queue_) {
- OSStatus err = AudioQueueDispose(audio_queue_, true);
- audio_queue_ = NULL;
- if (err != noErr)
- HandleError(err);
- }
- if (callback_) {
- callback_->OnClose(this);
- callback_ = NULL;
- }
- manager_->ReleaseInputStream(this);
- // CARE: This object may now be destroyed.
-}
-
-double PCMQueueInAudioInputStream::GetMaxVolume() {
- NOTREACHED() << "Only supported for low-latency mode.";
- return 0.0;
-}
-
-void PCMQueueInAudioInputStream::SetVolume(double volume) {
- NOTREACHED() << "Only supported for low-latency mode.";
-}
-
-double PCMQueueInAudioInputStream::GetVolume() {
- NOTREACHED() << "Only supported for low-latency mode.";
- return 0.0;
-}
-
-void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) {
- NOTREACHED() << "Only supported for low-latency mode.";
-}
-
-bool PCMQueueInAudioInputStream::GetAutomaticGainControl() {
- NOTREACHED() << "Only supported for low-latency mode.";
- return false;
-}
-
-void PCMQueueInAudioInputStream::HandleError(OSStatus err) {
- if (callback_)
- callback_->OnError(this, static_cast<int>(err));
- // This point should never be reached.
- OSSTATUS_DCHECK(0, err);
-}
-
-bool PCMQueueInAudioInputStream::SetupBuffers() {
- DCHECK(buffer_size_bytes_);
- for (int i = 0; i < kNumberBuffers; ++i) {
- AudioQueueBufferRef buffer;
- OSStatus err = AudioQueueAllocateBuffer(audio_queue_,
- buffer_size_bytes_,
- &buffer);
- if (err == noErr)
- err = QueueNextBuffer(buffer);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- // |buffer| will automatically be freed when |audio_queue_| is released.
- }
- return true;
-}
-
-OSStatus PCMQueueInAudioInputStream::QueueNextBuffer(
- AudioQueueBufferRef audio_buffer) {
- // Only the first 2 params are needed for recording.
- return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL);
-}
-
-// static
-void PCMQueueInAudioInputStream::HandleInputBufferStatic(
- void* data,
- AudioQueueRef audio_queue,
- AudioQueueBufferRef audio_buffer,
- const AudioTimeStamp* start_time,
- UInt32 num_packets,
- const AudioStreamPacketDescription* desc) {
- reinterpret_cast<PCMQueueInAudioInputStream*>(data)->
- HandleInputBuffer(audio_queue, audio_buffer, start_time,
- num_packets, desc);
-}
-
-void PCMQueueInAudioInputStream::HandleInputBuffer(
- AudioQueueRef audio_queue,
- AudioQueueBufferRef audio_buffer,
- const AudioTimeStamp* start_time,
- UInt32 num_packets,
- const AudioStreamPacketDescription* packet_desc) {
- DCHECK_EQ(audio_queue_, audio_queue);
- DCHECK(audio_buffer->mAudioData);
- if (!callback_) {
- // This can happen if Stop() was called without start.
- DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize);
- return;
- }
-
- if (audio_buffer->mAudioDataByteSize) {
- // The AudioQueue API may use a large internal buffer and repeatedly call us
- // back to back once that internal buffer is filled. When this happens the
- // renderer client does not have enough time to read data back from the
- // shared memory before the next write comes along. If HandleInputBuffer()
- // is called too frequently, Sleep() at least 5ms to ensure the shared
- // memory doesn't get trampled.
- // TODO(dalecurtis): This is a HACK. Long term the AudioQueue path is going
- // away in favor of the AudioUnit based AUAudioInputStream(). Tracked by
- // http://crbug.com/161383.
- base::TimeDelta elapsed = base::Time::Now() - last_fill_;
- const base::TimeDelta kMinDelay = base::TimeDelta::FromMilliseconds(5);
- if (elapsed < kMinDelay)
- base::PlatformThread::Sleep(kMinDelay - elapsed);
-
- callback_->OnData(this,
- reinterpret_cast<const uint8*>(audio_buffer->mAudioData),
- audio_buffer->mAudioDataByteSize,
- audio_buffer->mAudioDataByteSize,
- 0.0);
-
- last_fill_ = base::Time::Now();
- }
- // Recycle the buffer.
- OSStatus err = QueueNextBuffer(audio_buffer);
- if (err != noErr) {
- if (err == kAudioQueueErr_EnqueueDuringReset) {
- // This is the error you get if you try to enqueue a buffer and the
- // queue has been closed. Not really a problem if indeed the queue
- // has been closed.
- // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an
- // extra guard for this situation, but it seems to introduce more
- // complications than it solves (memory barrier issues accessing it from
- // multiple threads, looses the means to indicate OnClosed to client).
- // Should determine if we need to do something equivalent here.
- return;
- }
- HandleError(err);
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_input_mac.h b/src/media/audio/mac/audio_input_mac.h
deleted file mode 100644
index 1f9856f..0000000
--- a/src/media/audio/mac/audio_input_mac.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
-
-#include <AudioToolbox/AudioQueue.h>
-#include <AudioToolbox/AudioFormat.h>
-
-#include "base/compiler_specific.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerBase;
-
-// Implementation of AudioInputStream for Mac OS X using the audio queue service
-// present in OS 10.5 and later. Design reflects PCMQueueOutAudioOutputStream.
-class PCMQueueInAudioInputStream : public AudioInputStream {
- public:
- // Parameters as per AudioManager::MakeAudioInputStream.
- PCMQueueInAudioInputStream(AudioManagerBase* manager,
- const AudioParameters& params);
- virtual ~PCMQueueInAudioInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- private:
- // Issue the OnError to |callback_|;
- void HandleError(OSStatus err);
-
- // Allocates and prepares the memory that will be used for recording.
- bool SetupBuffers();
-
- // Sends a buffer to the audio driver for recording.
- OSStatus QueueNextBuffer(AudioQueueBufferRef audio_buffer);
-
- // Callback from OS, delegates to non-static version below.
- static void HandleInputBufferStatic(
- void* data,
- AudioQueueRef audio_queue,
- AudioQueueBufferRef audio_buffer,
- const AudioTimeStamp* start_time,
- UInt32 num_packets,
- const AudioStreamPacketDescription* desc);
-
- // Handles callback from OS. Will be called on OS internal thread.
- void HandleInputBuffer(AudioQueueRef audio_queue,
- AudioQueueBufferRef audio_buffer,
- const AudioTimeStamp* start_time,
- UInt32 num_packets,
- const AudioStreamPacketDescription* packet_desc);
-
- static const int kNumberBuffers = 3;
-
- // Manager that owns this stream, used for closing down.
- AudioManagerBase* manager_;
- // We use the callback mostly to periodically supply the recorded audio data.
- AudioInputCallback* callback_;
- // Structure that holds the stream format details such as bitrate.
- AudioStreamBasicDescription format_;
- // Handle to the OS audio queue object.
- AudioQueueRef audio_queue_;
- // Size of each of the buffers in |audio_buffers_|
- uint32 buffer_size_bytes_;
- // True iff Start() has been called successfully.
- bool started_;
- // Used to determine if we need to slow down |callback_| calls.
- base::Time last_fill_;
-
- DISALLOW_COPY_AND_ASSIGN(PCMQueueInAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
diff --git a/src/media/audio/mac/audio_low_latency_input_mac.cc b/src/media/audio/mac/audio_low_latency_input_mac.cc
deleted file mode 100644
index 85eef1f..0000000
--- a/src/media/audio/mac/audio_low_latency_input_mac.cc
+++ /dev/null
@@ -1,656 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_low_latency_input_mac.h"
-
-#include <CoreServices/CoreServices.h>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/mac/mac_logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_manager_mac.h"
-#include "media/base/data_buffer.h"
-
-namespace media {
-
-static const int kMinIntervalBetweenVolumeUpdatesMs = 1000;
-
-static std::ostream& operator<<(std::ostream& os,
- const AudioStreamBasicDescription& format) {
- os << "sample rate : " << format.mSampleRate << std::endl
- << "format ID : " << format.mFormatID << std::endl
- << "format flags : " << format.mFormatFlags << std::endl
- << "bytes per packet : " << format.mBytesPerPacket << std::endl
- << "frames per packet : " << format.mFramesPerPacket << std::endl
- << "bytes per frame : " << format.mBytesPerFrame << std::endl
- << "channels per frame: " << format.mChannelsPerFrame << std::endl
- << "bits per channel : " << format.mBitsPerChannel;
- return os;
-}
-
-// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
-// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
-// for more details and background regarding this implementation.
-
-AUAudioInputStream::AUAudioInputStream(
- AudioManagerMac* manager, const AudioParameters& params,
- AudioDeviceID audio_device_id)
- : manager_(manager),
- sink_(NULL),
- audio_unit_(0),
- input_device_id_(audio_device_id),
- started_(false),
- hardware_latency_frames_(0),
- number_of_channels_in_frame_(0) {
- DCHECK(manager_);
-
- // Set up the desired (output) format specified by the client.
- format_.mSampleRate = params.sample_rate();
- format_.mFormatID = kAudioFormatLinearPCM;
- format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
- kLinearPCMFormatFlagIsSignedInteger;
- format_.mBitsPerChannel = params.bits_per_sample();
- format_.mChannelsPerFrame = params.channels();
- format_.mFramesPerPacket = 1; // uncompressed audio
- format_.mBytesPerPacket = (format_.mBitsPerChannel *
- params.channels()) / 8;
- format_.mBytesPerFrame = format_.mBytesPerPacket;
- format_.mReserved = 0;
-
- DVLOG(1) << "Desired ouput format: " << format_;
-
- // Set number of sample frames per callback used by the internal audio layer.
- // An internal FIFO is then utilized to adapt the internal size to the size
- // requested by the client.
- // Note that we use the same native buffer size as for the output side here
- // since the AUHAL implementation requires that both capture and render side
- // use the same buffer size. See http://crbug.com/154352 for more details.
- number_of_frames_ = GetAudioHardwareBufferSize();
- DVLOG(1) << "Size of data buffer in frames : " << number_of_frames_;
-
- // Derive size (in bytes) of the buffers that we will render to.
- UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame;
- DVLOG(1) << "Size of data buffer in bytes : " << data_byte_size;
-
- // Allocate AudioBuffers to be used as storage for the received audio.
- // The AudioBufferList structure works as a placeholder for the
- // AudioBuffer structure, which holds a pointer to the actual data buffer.
- audio_data_buffer_.reset(new uint8[data_byte_size]);
- audio_buffer_list_.mNumberBuffers = 1;
-
- AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
- audio_buffer->mNumberChannels = params.channels();
- audio_buffer->mDataByteSize = data_byte_size;
- audio_buffer->mData = audio_data_buffer_.get();
-
- // Set up an internal FIFO buffer that will accumulate recorded audio frames
- // until a requested size is ready to be sent to the client.
- // It is not possible to ask for less than |kAudioFramesPerCallback| number of
- // audio frames.
- const size_t requested_size_frames =
- params.GetBytesPerBuffer() / format_.mBytesPerPacket;
- DCHECK_GE(requested_size_frames, number_of_frames_);
- requested_size_bytes_ = requested_size_frames * format_.mBytesPerFrame;
- DVLOG(1) << "Requested buffer size in bytes : " << requested_size_bytes_;
- DLOG_IF(INFO, requested_size_frames > number_of_frames_) << "FIFO is used";
-
- // Allocate some extra memory to avoid memory reallocations.
- // Ensure that the size is an even multiple of |number_of_frames_ and
- // larger than |requested_size_frames|.
- // Example: number_of_frames_=128, requested_size_frames=480 =>
- // allocated space equals 4*128=512 audio frames
- const int max_forward_capacity = format_.mBytesPerFrame * number_of_frames_ *
- ((requested_size_frames / number_of_frames_) + 1);
- fifo_.reset(new media::SeekableBuffer(0, max_forward_capacity));
-
- data_ = new media::DataBuffer(requested_size_bytes_);
-}
-
-AUAudioInputStream::~AUAudioInputStream() {}
-
-// Obtain and open the AUHAL AudioOutputUnit for recording.
-bool AUAudioInputStream::Open() {
- // Verify that we are not already opened.
- if (audio_unit_)
- return false;
-
- // Verify that we have a valid device.
- if (input_device_id_ == kAudioObjectUnknown) {
- NOTREACHED() << "Device ID is unknown";
- return false;
- }
-
- // Start by obtaining an AudioOuputUnit using an AUHAL component description.
-
- Component comp;
- ComponentDescription desc;
-
- // Description for the Audio Unit we want to use (AUHAL in this case).
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = kAudioUnitSubType_HALOutput;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
- comp = FindNextComponent(0, &desc);
- DCHECK(comp);
-
- // Get access to the service provided by the specified Audio Unit.
- OSStatus result = OpenAComponent(comp, &audio_unit_);
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Enable IO on the input scope of the Audio Unit.
-
- // After creating the AUHAL object, we must enable IO on the input scope
- // of the Audio Unit to obtain the device input. Input must be explicitly
- // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
- // of the AUHAL. Beacause the AUHAL can be used for both input and output,
- // we must also disable IO on the output scope.
-
- UInt32 enableIO = 1;
-
- // Enable input on the AUHAL.
- result = AudioUnitSetProperty(audio_unit_,
- kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input,
- 1, // input element 1
- &enableIO, // enable
- sizeof(enableIO));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Disable output on the AUHAL.
- enableIO = 0;
- result = AudioUnitSetProperty(audio_unit_,
- kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output,
- 0, // output element 0
- &enableIO, // disable
- sizeof(enableIO));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Next, set the audio device to be the Audio Unit's current device.
- // Note that, devices can only be set to the AUHAL after enabling IO.
- result = AudioUnitSetProperty(audio_unit_,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &input_device_id_,
- sizeof(input_device_id_));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Register the input procedure for the AUHAL.
- // This procedure will be called when the AUHAL has received new data
- // from the input device.
- AURenderCallbackStruct callback;
- callback.inputProc = InputProc;
- callback.inputProcRefCon = this;
- result = AudioUnitSetProperty(audio_unit_,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global,
- 0,
- &callback,
- sizeof(callback));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Set up the the desired (output) format.
- // For obtaining input from a device, the device format is always expressed
- // on the output scope of the AUHAL's Element 1.
- result = AudioUnitSetProperty(audio_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 1,
- &format_,
- sizeof(format_));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Set the desired number of frames in the IO buffer (output scope).
- // WARNING: Setting this value changes the frame size for all audio units in
- // the current process. It's imperative that the input and output frame sizes
- // be the same as audio_util::GetAudioHardwareBufferSize().
- // TODO(henrika): Due to http://crrev.com/159666 this is currently not true
- // and should be fixed, a CHECK() should be added at that time.
- result = AudioUnitSetProperty(audio_unit_,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output,
- 1,
- &number_of_frames_, // size is set in the ctor
- sizeof(number_of_frames_));
- if (result) {
- HandleError(result);
- return false;
- }
-
- // Finally, initialize the audio unit and ensure that it is ready to render.
- // Allocates memory according to the maximum number of audio frames
- // it can produce in response to a single render call.
- result = AudioUnitInitialize(audio_unit_);
- if (result) {
- HandleError(result);
- return false;
- }
-
- // The hardware latency is fixed and will not change during the call.
- hardware_latency_frames_ = GetHardwareLatency();
-
- // The master channel is 0, Left and right are channels 1 and 2.
- // And the master channel is not counted in |number_of_channels_in_frame_|.
- number_of_channels_in_frame_ = GetNumberOfChannelsFromStream();
-
- return true;
-}
-
-void AUAudioInputStream::Start(AudioInputCallback* callback) {
- DCHECK(callback);
- DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully";
- if (started_ || !audio_unit_)
- return;
- sink_ = callback;
- OSStatus result = AudioOutputUnitStart(audio_unit_);
- if (result == noErr) {
- started_ = true;
- }
- OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
- << "Failed to start acquiring data";
-}
-
-void AUAudioInputStream::Stop() {
- if (!started_)
- return;
- OSStatus result = AudioOutputUnitStop(audio_unit_);
- if (result == noErr) {
- started_ = false;
- }
- OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
- << "Failed to stop acquiring data";
-}
-
-void AUAudioInputStream::Close() {
- // It is valid to call Close() before calling open or Start().
- // It is also valid to call Close() after Start() has been called.
- if (started_) {
- Stop();
- }
- if (audio_unit_) {
- // Deallocate the audio unit’s resources.
- AudioUnitUninitialize(audio_unit_);
-
- // Terminates our connection to the AUHAL component.
- CloseComponent(audio_unit_);
- audio_unit_ = 0;
- }
- if (sink_) {
- sink_->OnClose(this);
- sink_ = NULL;
- }
-
- // Inform the audio manager that we have been closed. This can cause our
- // destruction.
- manager_->ReleaseInputStream(this);
-}
-
-double AUAudioInputStream::GetMaxVolume() {
- // Verify that we have a valid device.
- if (input_device_id_ == kAudioObjectUnknown) {
- NOTREACHED() << "Device ID is unknown";
- return 0.0;
- }
-
- // Query if any of the master, left or right channels has volume control.
- for (int i = 0; i <= number_of_channels_in_frame_; ++i) {
- // If the volume is settable, the valid volume range is [0.0, 1.0].
- if (IsVolumeSettableOnChannel(i))
- return 1.0;
- }
-
- // Volume control is not available for the audio stream.
- return 0.0;
-}
-
-void AUAudioInputStream::SetVolume(double volume) {
- DVLOG(1) << "SetVolume(volume=" << volume << ")";
- DCHECK_GE(volume, 0.0);
- DCHECK_LE(volume, 1.0);
-
- // Verify that we have a valid device.
- if (input_device_id_ == kAudioObjectUnknown) {
- NOTREACHED() << "Device ID is unknown";
- return;
- }
-
- Float32 volume_float32 = static_cast<Float32>(volume);
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyVolumeScalar,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
-
- // Try to set the volume for master volume channel.
- if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) {
- OSStatus result = AudioObjectSetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- sizeof(volume_float32),
- &volume_float32);
- if (result != noErr) {
- DLOG(WARNING) << "Failed to set volume to " << volume_float32;
- }
- return;
- }
-
- // There is no master volume control, try to set volume for each channel.
- int successful_channels = 0;
- for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
- property_address.mElement = static_cast<UInt32>(i);
- if (IsVolumeSettableOnChannel(i)) {
- OSStatus result = AudioObjectSetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- sizeof(volume_float32),
- &volume_float32);
- if (result == noErr)
- ++successful_channels;
- }
- }
-
- DLOG_IF(WARNING, successful_channels == 0)
- << "Failed to set volume to " << volume_float32;
-
- // Update the AGC volume level based on the last setting above. Note that,
- // the volume-level resolution is not infinite and it is therefore not
- // possible to assume that the volume provided as input parameter can be
- // used directly. Instead, a new query to the audio hardware is required.
- // This method does nothing if AGC is disabled.
- UpdateAgcVolume();
-}
-
-double AUAudioInputStream::GetVolume() {
- // Verify that we have a valid device.
- if (input_device_id_ == kAudioObjectUnknown){
- NOTREACHED() << "Device ID is unknown";
- return 0.0;
- }
-
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyVolumeScalar,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
-
- if (AudioObjectHasProperty(input_device_id_, &property_address)) {
- // The device supports master volume control, get the volume from the
- // master channel.
- Float32 volume_float32 = 0.0;
- UInt32 size = sizeof(volume_float32);
- OSStatus result = AudioObjectGetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- &size,
- &volume_float32);
- if (result == noErr)
- return static_cast<double>(volume_float32);
- } else {
- // There is no master volume control, try to get the average volume of
- // all the channels.
- Float32 volume_float32 = 0.0;
- int successful_channels = 0;
- for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
- property_address.mElement = static_cast<UInt32>(i);
- if (AudioObjectHasProperty(input_device_id_, &property_address)) {
- Float32 channel_volume = 0;
- UInt32 size = sizeof(channel_volume);
- OSStatus result = AudioObjectGetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- &size,
- &channel_volume);
- if (result == noErr) {
- volume_float32 += channel_volume;
- ++successful_channels;
- }
- }
- }
-
- // Get the average volume of the channels.
- if (successful_channels != 0)
- return static_cast<double>(volume_float32 / successful_channels);
- }
-
- DLOG(WARNING) << "Failed to get volume";
- return 0.0;
-}
-
-// AUHAL AudioDeviceOutput unit callback
-OSStatus AUAudioInputStream::InputProc(void* user_data,
- AudioUnitRenderActionFlags* flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- // Verify that the correct bus is used (Input bus/Element 1)
- DCHECK_EQ(bus_number, static_cast<UInt32>(1));
- AUAudioInputStream* audio_input =
- reinterpret_cast<AUAudioInputStream*>(user_data);
- DCHECK(audio_input);
- if (!audio_input)
- return kAudioUnitErr_InvalidElement;
-
- // Receive audio from the AUHAL from the output scope of the Audio Unit.
- OSStatus result = AudioUnitRender(audio_input->audio_unit(),
- flags,
- time_stamp,
- bus_number,
- number_of_frames,
- audio_input->audio_buffer_list());
- if (result)
- return result;
-
- // Deliver recorded data to the consumer as a callback.
- return audio_input->Provide(number_of_frames,
- audio_input->audio_buffer_list(),
- time_stamp);
-}
-
-OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames,
- AudioBufferList* io_data,
- const AudioTimeStamp* time_stamp) {
- // Update the capture latency.
- double capture_latency_frames = GetCaptureLatency(time_stamp);
-
- // Update the AGC volume level once every second. Note that, |volume| is
- // also updated each time SetVolume() is called through IPC by the
- // render-side AGC.
- double normalized_volume = 0.0;
- QueryAgcVolume(&normalized_volume);
-
- AudioBuffer& buffer = io_data->mBuffers[0];
- uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData);
- uint32 capture_delay_bytes = static_cast<uint32>
- ((capture_latency_frames + 0.5) * format_.mBytesPerFrame);
- DCHECK(audio_data);
- if (!audio_data)
- return kAudioUnitErr_InvalidElement;
-
- // See http://crbug.com/154352 for details.
- CHECK_EQ(number_of_frames, static_cast<UInt32>(number_of_frames_));
-
- // Accumulate captured audio in FIFO until we can match the output size
- // requested by the client.
- DCHECK_LE(fifo_->forward_bytes(), requested_size_bytes_);
- fifo_->Append(audio_data, buffer.mDataByteSize);
-
- // Deliver recorded data to the client as soon as the FIFO contains a
- // sufficient amount.
- if (fifo_->forward_bytes() >= requested_size_bytes_) {
- // Read from FIFO into temporary data buffer.
- fifo_->Read(data_->GetWritableData(), requested_size_bytes_);
-
- // Deliver data packet, delay estimation and volume level to the user.
- sink_->OnData(this,
- data_->GetData(),
- requested_size_bytes_,
- capture_delay_bytes,
- normalized_volume);
- }
-
- return noErr;
-}
-
-int AUAudioInputStream::HardwareSampleRate() {
- // Determine the default input device's sample-rate.
- AudioDeviceID device_id = kAudioObjectUnknown;
- UInt32 info_size = sizeof(device_id);
-
- AudioObjectPropertyAddress default_input_device_address = {
- kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &default_input_device_address,
- 0,
- 0,
- &info_size,
- &device_id);
- if (result != noErr)
- return 0.0;
-
- Float64 nominal_sample_rate;
- info_size = sizeof(nominal_sample_rate);
-
- AudioObjectPropertyAddress nominal_sample_rate_address = {
- kAudioDevicePropertyNominalSampleRate,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- result = AudioObjectGetPropertyData(device_id,
- &nominal_sample_rate_address,
- 0,
- 0,
- &info_size,
- &nominal_sample_rate);
- if (result != noErr)
- return 0.0;
-
- return static_cast<int>(nominal_sample_rate);
-}
-
-double AUAudioInputStream::GetHardwareLatency() {
- if (!audio_unit_ || input_device_id_ == kAudioObjectUnknown) {
- DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
- return 0.0;
- }
-
- // Get audio unit latency.
- Float64 audio_unit_latency_sec = 0.0;
- UInt32 size = sizeof(audio_unit_latency_sec);
- OSStatus result = AudioUnitGetProperty(audio_unit_,
- kAudioUnitProperty_Latency,
- kAudioUnitScope_Global,
- 0,
- &audio_unit_latency_sec,
- &size);
- OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
- << "Could not get audio unit latency";
-
- // Get input audio device latency.
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyLatency,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
- UInt32 device_latency_frames = 0;
- size = sizeof(device_latency_frames);
- result = AudioObjectGetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- &size,
- &device_latency_frames);
- DLOG_IF(WARNING, result != noErr) << "Could not get audio device latency.";
-
- return static_cast<double>((audio_unit_latency_sec *
- format_.mSampleRate) + device_latency_frames);
-}
-
-double AUAudioInputStream::GetCaptureLatency(
- const AudioTimeStamp* input_time_stamp) {
- // Get the delay between between the actual recording instant and the time
- // when the data packet is provided as a callback.
- UInt64 capture_time_ns = AudioConvertHostTimeToNanos(
- input_time_stamp->mHostTime);
- UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
- double delay_frames = static_cast<double>
- (1e-9 * (now_ns - capture_time_ns) * format_.mSampleRate);
-
- // Total latency is composed by the dynamic latency and the fixed
- // hardware latency.
- return (delay_frames + hardware_latency_frames_);
-}
-
-int AUAudioInputStream::GetNumberOfChannelsFromStream() {
- // Get the stream format, to be able to read the number of channels.
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyStreamFormat,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
- AudioStreamBasicDescription stream_format;
- UInt32 size = sizeof(stream_format);
- OSStatus result = AudioObjectGetPropertyData(input_device_id_,
- &property_address,
- 0,
- NULL,
- &size,
- &stream_format);
- if (result != noErr) {
- DLOG(WARNING) << "Could not get stream format";
- return 0;
- }
-
- return static_cast<int>(stream_format.mChannelsPerFrame);
-}
-
-void AUAudioInputStream::HandleError(OSStatus err) {
- NOTREACHED() << "error " << GetMacOSStatusErrorString(err)
- << " (" << err << ")";
- if (sink_)
- sink_->OnError(this, static_cast<int>(err));
-}
-
-bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) {
- Boolean is_settable = false;
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyVolumeScalar,
- kAudioDevicePropertyScopeInput,
- static_cast<UInt32>(channel)
- };
- OSStatus result = AudioObjectIsPropertySettable(input_device_id_,
- &property_address,
- &is_settable);
- return (result == noErr) ? is_settable : false;
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_low_latency_input_mac.h b/src/media/audio/mac/audio_low_latency_input_mac.h
deleted file mode 100644
index 0c6edc0..0000000
--- a/src/media/audio/mac/audio_low_latency_input_mac.h
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Implementation of AudioInputStream for Mac OS X using the special AUHAL
-// input Audio Unit present in OS 10.4 and later.
-// The AUHAL input Audio Unit is for low-latency audio I/O.
-//
-// Overview of operation:
-//
-// - An object of AUAudioInputStream is created by the AudioManager
-// factory: audio_man->MakeAudioInputStream().
-// - Next some thread will call Open(), at that point the underlying
-// AUHAL output Audio Unit is created and configured.
-// - Then some thread will call Start(sink).
-// Then the Audio Unit is started which creates its own thread which
-// periodically will provide the sink with more data as buffers are being
-// produced/recorded.
-// - At some point some thread will call Stop(), which we handle by directly
-// stopping the AUHAL output Audio Unit.
-// - The same thread that called stop will call Close() where we cleanup
-// and notify the audio manager, which likely will destroy this object.
-//
-// Implementation notes:
-//
-// - It is recommended to first acquire the native sample rate of the default
-// input device and then use the same rate when creating this object.
-// Use AUAudioInputStream::HardwareSampleRate() to retrieve the sample rate.
-// - Calling Close() also leads to self destruction.
-// - The latency consists of two parts:
-// 1) Hardware latency, which includes Audio Unit latency, audio device
-// latency;
-// 2) The delay between the actual recording instant and the time when the
-// data packet is provided as a callback.
-//
-#ifndef MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
-
-#include <AudioUnit/AudioUnit.h>
-#include <CoreAudio/CoreAudio.h>
-
-#include "base/atomicops.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_input_stream_impl.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/seekable_buffer.h"
-
-namespace media {
-
-class AudioManagerMac;
-class DataBuffer;
-
-class AUAudioInputStream : public AudioInputStreamImpl {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- AUAudioInputStream(AudioManagerMac* manager,
- const AudioParameters& params,
- AudioDeviceID audio_device_id);
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioInputStream::Close().
- virtual ~AUAudioInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
-
- // Returns the current hardware sample rate for the default input device.
- MEDIA_EXPORT static int HardwareSampleRate();
-
- bool started() const { return started_; }
- AudioUnit audio_unit() { return audio_unit_; }
- AudioBufferList* audio_buffer_list() { return &audio_buffer_list_; }
-
- private:
- // AudioOutputUnit callback.
- static OSStatus InputProc(void* user_data,
- AudioUnitRenderActionFlags* flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- // Pushes recorded data to consumer of the input audio stream.
- OSStatus Provide(UInt32 number_of_frames, AudioBufferList* io_data,
- const AudioTimeStamp* time_stamp);
-
- // Gets the fixed capture hardware latency and store it during initialization.
- // Returns 0 if not available.
- double GetHardwareLatency();
-
- // Gets the current capture delay value.
- double GetCaptureLatency(const AudioTimeStamp* input_time_stamp);
-
- // Gets the number of channels for a stream of audio data.
- int GetNumberOfChannelsFromStream();
-
- // Issues the OnError() callback to the |sink_|.
- void HandleError(OSStatus err);
-
- // Helper function to check if the volume control is avialable on specific
- // channel.
- bool IsVolumeSettableOnChannel(int channel);
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerMac* manager_;
-
- // Contains the desired number of audio frames in each callback.
- size_t number_of_frames_;
-
- // Pointer to the object that will receive the recorded audio samples.
- AudioInputCallback* sink_;
-
- // Structure that holds the desired output format of the stream.
- // Note that, this format can differ from the device(=input) format.
- AudioStreamBasicDescription format_;
-
- // The special Audio Unit called AUHAL, which allows us to pass audio data
- // directly from a microphone, through the HAL, and to our application.
- // The AUHAL also enables selection of non default devices.
- AudioUnit audio_unit_;
-
- // The UID refers to the current input audio device.
- AudioDeviceID input_device_id_;
-
- // Provides a mechanism for encapsulating one or more buffers of audio data.
- AudioBufferList audio_buffer_list_;
-
- // Temporary storage for recorded data. The InputProc() renders into this
- // array as soon as a frame of the desired buffer size has been recorded.
- scoped_array<uint8> audio_data_buffer_;
-
- // True after successfull Start(), false after successful Stop().
- bool started_;
-
- // Fixed capture hardware latency in frames.
- double hardware_latency_frames_;
-
- // The number of channels in each frame of audio data, which is used
- // when querying the volume of each channel.
- int number_of_channels_in_frame_;
-
- // Accumulates recorded data packets until the requested size has been stored.
- scoped_ptr<media::SeekableBuffer> fifo_;
-
- // Intermediate storage of data from the FIFO before sending it to the
- // client using the OnData() callback.
- scoped_refptr<media::DataBuffer> data_;
-
- // The client requests that the recorded data shall be delivered using
- // OnData() callbacks where each callback contains this amount of bytes.
- int requested_size_bytes_;
-
- DISALLOW_COPY_AND_ASSIGN(AUAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
diff --git a/src/media/audio/mac/audio_low_latency_input_mac_unittest.cc b/src/media/audio/mac/audio_low_latency_input_mac_unittest.cc
deleted file mode 100644
index e8ef33d..0000000
--- a/src/media/audio/mac/audio_low_latency_input_mac_unittest.cc
+++ /dev/null
@@ -1,317 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/environment.h"
-#include "base/message_loop.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/platform_thread.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/mac/audio_low_latency_input_mac.h"
-#include "media/base/seekable_buffer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::AtLeast;
-using ::testing::Ge;
-using ::testing::NotNull;
-
-namespace media {
-
-ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) {
- if (++*count >= limit) {
- loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
- }
-}
-
-class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
- public:
- MOCK_METHOD5(OnData, void(AudioInputStream* stream,
- const uint8* src, uint32 size,
- uint32 hardware_delay_bytes, double volume));
- MOCK_METHOD1(OnClose, void(AudioInputStream* stream));
- MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code));
-};
-
-// This audio sink implementation should be used for manual tests only since
-// the recorded data is stored on a raw binary data file.
-// The last test (WriteToFileAudioSink) - which is disabled by default -
-// can use this audio sink to store the captured data on a file for offline
-// analysis.
-class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
- public:
- // Allocate space for ~10 seconds of data @ 48kHz in stereo:
- // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
- static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
-
- explicit WriteToFileAudioSink(const char* file_name)
- : buffer_(0, kMaxBufferSize),
- file_(fopen(file_name, "wb")),
- bytes_to_write_(0) {
- }
-
- virtual ~WriteToFileAudioSink() {
- int bytes_written = 0;
- while (bytes_written < bytes_to_write_) {
- const uint8* chunk;
- int chunk_size;
-
- // Stop writing if no more data is available.
- if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
- break;
-
- // Write recorded data chunk to the file and prepare for next chunk.
- fwrite(chunk, 1, chunk_size, file_);
- buffer_.Seek(chunk_size);
- bytes_written += chunk_size;
- }
- fclose(file_);
- }
-
- // AudioInputStream::AudioInputCallback implementation.
- virtual void OnData(AudioInputStream* stream,
- const uint8* src, uint32 size,
- uint32 hardware_delay_bytes, double volume) {
- // Store data data in a temporary buffer to avoid making blocking
- // fwrite() calls in the audio callback. The complete buffer will be
- // written to file in the destructor.
- if (buffer_.Append(src, size)) {
- bytes_to_write_ += size;
- }
- }
-
- virtual void OnClose(AudioInputStream* stream) {}
- virtual void OnError(AudioInputStream* stream, int code) {}
-
- private:
- media::SeekableBuffer buffer_;
- FILE* file_;
- int bytes_to_write_;
-};
-
-class MacAudioInputTest : public testing::Test {
- protected:
- MacAudioInputTest() : audio_manager_(AudioManager::Create()) {}
- virtual ~MacAudioInputTest() {}
-
- // Convenience method which ensures that we are not running on the build
- // bots and that at least one valid input device can be found.
- bool CanRunAudioTests() {
- bool has_input = audio_manager_->HasAudioInputDevices();
- if (!has_input)
- LOG(WARNING) << "No input devices detected";
- return has_input;
- }
-
- // Convenience method which creates a default AudioInputStream object using
- // a 10ms frame size and a sample rate which is set to the hardware sample
- // rate.
- AudioInputStream* CreateDefaultAudioInputStream() {
- int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
- int samples_per_packet = fs / 100;
- AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_STEREO, fs, 16, samples_per_packet),
- AudioManagerBase::kDefaultDeviceId);
- EXPECT_TRUE(ais);
- return ais;
- }
-
- // Convenience method which creates an AudioInputStream object with a
- // specified channel layout.
- AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) {
- int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
- int samples_per_packet = fs / 100;
- AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- channel_layout, fs, 16, samples_per_packet),
- AudioManagerBase::kDefaultDeviceId);
- EXPECT_TRUE(ais);
- return ais;
- }
-
- scoped_ptr<AudioManager> audio_manager_;
-};
-
-// Test Create(), Close().
-TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
- if (!CanRunAudioTests())
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- ais->Close();
-}
-
-// Test Open(), Close().
-TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
- if (!CanRunAudioTests())
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- EXPECT_TRUE(ais->Open());
- ais->Close();
-}
-
-// Test Open(), Start(), Close().
-TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
- if (!CanRunAudioTests())
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- EXPECT_TRUE(ais->Open());
- MockAudioInputCallback sink;
- ais->Start(&sink);
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Test Open(), Start(), Stop(), Close().
-TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
- if (!CanRunAudioTests())
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- EXPECT_TRUE(ais->Open());
- MockAudioInputCallback sink;
- ais->Start(&sink);
- ais->Stop();
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Test some additional calling sequences.
-TEST_F(MacAudioInputTest, AUAudioInputStreamMiscCallingSequences) {
- if (!CanRunAudioTests())
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- AUAudioInputStream* auais = static_cast<AUAudioInputStream*>(ais);
-
- // Open(), Open() should fail the second time.
- EXPECT_TRUE(ais->Open());
- EXPECT_FALSE(ais->Open());
-
- MockAudioInputCallback sink;
-
- // Start(), Start() is a valid calling sequence (second call does nothing).
- ais->Start(&sink);
- EXPECT_TRUE(auais->started());
- ais->Start(&sink);
- EXPECT_TRUE(auais->started());
-
- // Stop(), Stop() is a valid calling sequence (second call does nothing).
- ais->Stop();
- EXPECT_FALSE(auais->started());
- ais->Stop();
- EXPECT_FALSE(auais->started());
-
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Verify that recording starts and stops correctly in mono using mocked sink.
-TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
- if (!CanRunAudioTests())
- return;
-
- int count = 0;
- MessageLoopForUI loop;
-
- // Create an audio input stream which records in mono.
- AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO);
- EXPECT_TRUE(ais->Open());
-
- int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
- int samples_per_packet = fs / 100;
- int bits_per_sample = 16;
- uint32 bytes_per_packet = samples_per_packet * (bits_per_sample / 8);
-
- MockAudioInputCallback sink;
-
- // We use 10ms packets and will run the test until ten packets are received.
- // All should contain valid packets of the same size and a valid delay
- // estimate.
- EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
- ais->Start(&sink);
- loop.Run();
- ais->Stop();
-
- // Verify that the sink receieves OnClose() call when calling Close().
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Verify that recording starts and stops correctly in mono using mocked sink.
-TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
- if (!CanRunAudioTests())
- return;
-
- int count = 0;
- MessageLoopForUI loop;
-
- // Create an audio input stream which records in stereo.
- AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO);
- EXPECT_TRUE(ais->Open());
-
- int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
- int samples_per_packet = fs / 100;
- int bits_per_sample = 16;
- uint32 bytes_per_packet = 2 * samples_per_packet * (bits_per_sample / 8);
-
- MockAudioInputCallback sink;
-
- // We use 10ms packets and will run the test until ten packets are received.
- // All should contain valid packets of the same size and a valid delay
- // estimate.
- // TODO(henrika): http://crbug.com/154352 forced us to run the capture side
- // using a native buffer size of 128 audio frames and combine it with a FIFO
- // to match the requested size by the client. This change might also have
- // modified the delay estimates since the existing Ge(bytes_per_packet) for
- // parameter #4 does no longer pass. I am removing this restriction here to
- // ensure that we can land the patch but will revisit this test again when
- // more analysis of the delay estimates are done.
- EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
- ais->Start(&sink);
- loop.Run();
- ais->Stop();
-
- // Verify that the sink receieves OnClose() call when calling Close().
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// This test is intended for manual tests and should only be enabled
-// when it is required to store the captured data on a local file.
-// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
-// To include disabled tests in test execution, just invoke the test program
-// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
-// environment variable to a value greater than 0.
-TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
- if (!CanRunAudioTests())
- return;
- const char* file_name = "out_stereo_10sec.pcm";
-
- int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
- AudioInputStream* ais = CreateDefaultAudioInputStream();
- EXPECT_TRUE(ais->Open());
-
- fprintf(stderr, " File name : %s\n", file_name);
- fprintf(stderr, " Sample rate: %d\n", fs);
- WriteToFileAudioSink file_sink(file_name);
- fprintf(stderr, " >> Speak into the mic while recording...\n");
- ais->Start(&file_sink);
- base::PlatformThread::Sleep(TestTimeouts::action_timeout());
- ais->Stop();
- fprintf(stderr, " >> Recording has stopped.\n");
- ais->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_low_latency_output_mac.cc b/src/media/audio/mac/audio_low_latency_output_mac.cc
deleted file mode 100644
index 98182b0..0000000
--- a/src/media/audio/mac/audio_low_latency_output_mac.cc
+++ /dev/null
@@ -1,402 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_low_latency_output_mac.h"
-
-#include <CoreServices/CoreServices.h>
-
-#include "base/basictypes.h"
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/mac/mac_logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_manager_mac.h"
-#include "media/base/media_switches.h"
-
-namespace media {
-
-static std::ostream& operator<<(std::ostream& os,
- const AudioStreamBasicDescription& format) {
- os << "sample rate : " << format.mSampleRate << std::endl
- << "format ID : " << format.mFormatID << std::endl
- << "format flags : " << format.mFormatFlags << std::endl
- << "bytes per packet : " << format.mBytesPerPacket << std::endl
- << "frames per packet : " << format.mFramesPerPacket << std::endl
- << "bytes per frame : " << format.mBytesPerFrame << std::endl
- << "channels per frame: " << format.mChannelsPerFrame << std::endl
- << "bits per channel : " << format.mBitsPerChannel;
- return os;
-}
-
-static AudioObjectPropertyAddress kDefaultOutputDeviceAddress = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
-};
-
-// Overview of operation:
-// 1) An object of AUAudioOutputStream is created by the AudioManager
-// factory: audio_man->MakeAudioStream().
-// 2) Next some thread will call Open(), at that point the underlying
-// default output Audio Unit is created and configured.
-// 3) Then some thread will call Start(source).
-// Then the Audio Unit is started which creates its own thread which
-// periodically will call the source for more data as buffers are being
-// consumed.
-// 4) At some point some thread will call Stop(), which we handle by directly
-// stopping the default output Audio Unit.
-// 6) The same thread that called stop will call Close() where we cleanup
-// and notify the audio manager, which likely will destroy this object.
-
-AUAudioOutputStream::AUAudioOutputStream(
- AudioManagerMac* manager, const AudioParameters& params)
- : manager_(manager),
- source_(NULL),
- output_unit_(0),
- output_device_id_(kAudioObjectUnknown),
- volume_(1),
- hardware_latency_frames_(0),
- stopped_(false),
- audio_bus_(AudioBus::Create(params)) {
- // We must have a manager.
- DCHECK(manager_);
-
- // A frame is one sample across all channels. In interleaved audio the per
- // frame fields identify the set of n |channels|. In uncompressed audio, a
- // packet is always one frame.
- format_.mSampleRate = params.sample_rate();
- format_.mFormatID = kAudioFormatLinearPCM;
- format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
- kLinearPCMFormatFlagIsSignedInteger;
- format_.mBitsPerChannel = params.bits_per_sample();
- format_.mChannelsPerFrame = params.channels();
- format_.mFramesPerPacket = 1;
- format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8;
- format_.mBytesPerFrame = format_.mBytesPerPacket;
- format_.mReserved = 0;
-
- DVLOG(1) << "Desired ouput format: " << format_;
-
- // Calculate the number of sample frames per callback.
- number_of_frames_ = params.GetBytesPerBuffer() / format_.mBytesPerPacket;
- DVLOG(1) << "Number of frames per callback: " << number_of_frames_;
- CHECK_EQ(number_of_frames_, GetAudioHardwareBufferSize());
-}
-
-AUAudioOutputStream::~AUAudioOutputStream() {
-}
-
-bool AUAudioOutputStream::Open() {
- // Obtain the current input device selected by the user.
- UInt32 size = sizeof(output_device_id_);
- OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &kDefaultOutputDeviceAddress,
- 0,
- 0,
- &size,
- &output_device_id_);
- if (result != noErr || output_device_id_ == kAudioObjectUnknown) {
- OSSTATUS_DLOG(WARNING, result)
- << "Could not get default audio output device.";
- return false;
- }
-
- // Open and initialize the DefaultOutputUnit.
- AudioComponent comp;
- AudioComponentDescription desc;
-
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
- comp = AudioComponentFindNext(0, &desc);
- if (!comp)
- return false;
-
- result = AudioComponentInstanceNew(comp, &output_unit_);
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result) << "AudioComponentInstanceNew() failed.";
- return false;
- }
-
- result = AudioUnitInitialize(output_unit_);
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result) << "AudioUnitInitialize() failed.";
- return false;
- }
-
- hardware_latency_frames_ = GetHardwareLatency();
-
- return Configure();
-}
-
-bool AUAudioOutputStream::Configure() {
- // Set the render callback.
- AURenderCallbackStruct input;
- input.inputProc = InputProc;
- input.inputProcRefCon = this;
- OSStatus result = AudioUnitSetProperty(
- output_unit_,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Global,
- 0,
- &input,
- sizeof(input));
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result)
- << "AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed.";
- return false;
- }
-
- // Set the stream format.
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 0,
- &format_,
- sizeof(format_));
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result)
- << "AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed.";
- return false;
- }
-
- // Set the buffer frame size.
- // WARNING: Setting this value changes the frame size for all audio units in
- // the current process. It's imperative that the input and output frame sizes
- // be the same as audio_util::GetAudioHardwareBufferSize().
- // See http://crbug.com/154352 for details.
- UInt32 buffer_size = number_of_frames_;
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output,
- 0,
- &buffer_size,
- sizeof(buffer_size));
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result)
- << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
- return false;
- }
-
- return true;
-}
-
-void AUAudioOutputStream::Close() {
- if (output_unit_)
- AudioComponentInstanceDispose(output_unit_);
-
- // Inform the audio manager that we have been closed. This can cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void AUAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(callback);
- if (!output_unit_) {
- DLOG(ERROR) << "Open() has not been called successfully";
- return;
- }
-
- stopped_ = false;
- source_ = callback;
-
- AudioOutputUnitStart(output_unit_);
-}
-
-void AUAudioOutputStream::Stop() {
- // We request a synchronous stop, so the next call can take some time. In
- // the windows implementation we block here as well.
- if (stopped_)
- return;
-
- AudioOutputUnitStop(output_unit_);
-
- source_ = NULL;
- stopped_ = true;
-}
-
-void AUAudioOutputStream::SetVolume(double volume) {
- if (!output_unit_)
- return;
- volume_ = static_cast<float>(volume);
-
- // TODO(crogers): set volume property
-}
-
-void AUAudioOutputStream::GetVolume(double* volume) {
- if (!output_unit_)
- return;
- *volume = volume_;
-}
-
-// Pulls on our provider to get rendered audio stream.
-// Note to future hackers of this function: Do not add locks here because this
-// is running on a real-time thread (for low-latency).
-OSStatus AUAudioOutputStream::Render(UInt32 number_of_frames,
- AudioBufferList* io_data,
- const AudioTimeStamp* output_time_stamp) {
- // Update the playout latency.
- double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
-
- AudioBuffer& buffer = io_data->mBuffers[0];
- uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData);
- uint32 hardware_pending_bytes = static_cast<uint32>
- ((playout_latency_frames + 0.5) * format_.mBytesPerFrame);
-
- // Unfortunately AUAudioInputStream and AUAudioOutputStream share the frame
- // size set by kAudioDevicePropertyBufferFrameSize above on a per process
- // basis. What this means is that the |number_of_frames| value may be larger
- // or smaller than the value set during Configure(). In this case either
- // audio input or audio output will be broken, so just output silence.
- // TODO(crogers): Figure out what can trigger a change in |number_of_frames|.
- // See http://crbug.com/1543 for details.
- if (number_of_frames != static_cast<UInt32>(audio_bus_->frames())) {
- memset(audio_data, 0, number_of_frames * format_.mBytesPerFrame);
- return noErr;
- }
-
- int frames_filled = source_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(0, hardware_pending_bytes));
-
- // Note: If this ever changes to output raw float the data must be clipped and
- // sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, format_.mBitsPerChannel / 8, audio_data);
- uint32 filled = frames_filled * format_.mBytesPerFrame;
-
- // Perform in-place, software-volume adjustments.
- media::AdjustVolume(audio_data,
- filled,
- audio_bus_->channels(),
- format_.mBitsPerChannel / 8,
- volume_);
-
- return noErr;
-}
-
-// DefaultOutputUnit callback
-OSStatus AUAudioOutputStream::InputProc(void* user_data,
- AudioUnitRenderActionFlags*,
- const AudioTimeStamp* output_time_stamp,
- UInt32,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- AUAudioOutputStream* audio_output =
- static_cast<AUAudioOutputStream*>(user_data);
- if (!audio_output)
- return -1;
-
- return audio_output->Render(number_of_frames, io_data, output_time_stamp);
-}
-
-int AUAudioOutputStream::HardwareSampleRate() {
- // Determine the default output device's sample-rate.
- AudioDeviceID device_id = kAudioObjectUnknown;
- UInt32 info_size = sizeof(device_id);
- OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &kDefaultOutputDeviceAddress,
- 0,
- 0,
- &info_size,
- &device_id);
- if (result != noErr || device_id == kAudioObjectUnknown) {
- OSSTATUS_DLOG(WARNING, result)
- << "Could not get default audio output device.";
- return 0;
- }
-
- Float64 nominal_sample_rate;
- info_size = sizeof(nominal_sample_rate);
-
- AudioObjectPropertyAddress nominal_sample_rate_address = {
- kAudioDevicePropertyNominalSampleRate,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- result = AudioObjectGetPropertyData(device_id,
- &nominal_sample_rate_address,
- 0,
- 0,
- &info_size,
- &nominal_sample_rate);
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result)
- << "Could not get default sample rate for device: " << device_id;
- return 0;
- }
-
- return static_cast<int>(nominal_sample_rate);
-}
-
-double AUAudioOutputStream::GetHardwareLatency() {
- if (!output_unit_ || output_device_id_ == kAudioObjectUnknown) {
- DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
- return 0.0;
- }
-
- // Get audio unit latency.
- Float64 audio_unit_latency_sec = 0.0;
- UInt32 size = sizeof(audio_unit_latency_sec);
- OSStatus result = AudioUnitGetProperty(output_unit_,
- kAudioUnitProperty_Latency,
- kAudioUnitScope_Global,
- 0,
- &audio_unit_latency_sec,
- &size);
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency";
- return 0.0;
- }
-
- // Get output audio device latency.
- AudioObjectPropertyAddress property_address = {
- kAudioDevicePropertyLatency,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
- UInt32 device_latency_frames = 0;
- size = sizeof(device_latency_frames);
- result = AudioObjectGetPropertyData(output_device_id_,
- &property_address,
- 0,
- NULL,
- &size,
- &device_latency_frames);
- if (result != noErr) {
- OSSTATUS_DLOG(WARNING, result) << "Could not get audio unit latency";
- return 0.0;
- }
-
- return static_cast<double>((audio_unit_latency_sec *
- format_.mSampleRate) + device_latency_frames);
-}
-
-double AUAudioOutputStream::GetPlayoutLatency(
- const AudioTimeStamp* output_time_stamp) {
- // Ensure mHostTime is valid.
- if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
- return 0;
-
- // Get the delay between the moment getting the callback and the scheduled
- // time stamp that tells when the data is going to be played out.
- UInt64 output_time_ns = AudioConvertHostTimeToNanos(
- output_time_stamp->mHostTime);
- UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-
- // Prevent overflow leading to huge delay information; occurs regularly on
- // the bots, probably less so in the wild.
- if (now_ns > output_time_ns)
- return 0;
-
- double delay_frames = static_cast<double>
- (1e-9 * (output_time_ns - now_ns) * format_.mSampleRate);
-
- return (delay_frames + hardware_latency_frames_);
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_low_latency_output_mac.h b/src/media/audio/mac/audio_low_latency_output_mac.h
deleted file mode 100644
index 4ceb4af..0000000
--- a/src/media/audio/mac/audio_low_latency_output_mac.h
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Implementation notes:
-//
-// - It is recommended to first acquire the native sample rate of the default
-// output device and then use the same rate when creating this object.
-// Use AUAudioOutputStream::HardwareSampleRate() to retrieve the sample rate.
-// - Calling Close() also leads to self destruction.
-// - The latency consists of two parts:
-// 1) Hardware latency, which includes Audio Unit latency, audio device
-// latency;
-// 2) The delay between the moment getting the callback and the scheduled time
-// stamp that tells when the data is going to be played out.
-//
-#ifndef MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_
-
-#include <AudioUnit/AudioUnit.h>
-#include <CoreAudio/CoreAudio.h>
-
-#include "base/compiler_specific.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerMac;
-
-// Implementation of AudioOuputStream for Mac OS X using the
-// default output Audio Unit present in OS 10.4 and later.
-// The default output Audio Unit is for low-latency audio I/O.
-class AUAudioOutputStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- AUAudioOutputStream(AudioManagerMac* manager,
- const AudioParameters& params);
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~AUAudioOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- static int HardwareSampleRate();
-
- private:
- // DefaultOutputUnit callback.
- static OSStatus InputProc(void* user_data,
- AudioUnitRenderActionFlags* flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- OSStatus Render(UInt32 number_of_frames, AudioBufferList* io_data,
- const AudioTimeStamp* output_time_stamp);
-
- // Sets up the stream format for the default output Audio Unit.
- bool Configure();
-
- // Gets the fixed playout device hardware latency and stores it. Returns 0
- // if not available.
- double GetHardwareLatency();
-
- // Gets the current playout latency value.
- double GetPlayoutLatency(const AudioTimeStamp* output_time_stamp);
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerMac* manager_;
-
- size_t number_of_frames_;
-
- // Pointer to the object that will provide the audio samples.
- AudioSourceCallback* source_;
-
- // Structure that holds the stream format details such as bitrate.
- AudioStreamBasicDescription format_;
-
- // The default output Audio Unit which talks to the audio hardware.
- AudioUnit output_unit_;
-
- // The UID refers to the current output audio device.
- AudioDeviceID output_device_id_;
-
- // Volume level from 0 to 1.
- float volume_;
-
- // Fixed playout hardware latency in frames.
- double hardware_latency_frames_;
-
- // The flag used to stop the streaming.
- bool stopped_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(AUAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_OUTPUT_MAC_H_
diff --git a/src/media/audio/mac/audio_manager_mac.cc b/src/media/audio/mac/audio_manager_mac.cc
deleted file mode 100644
index e0300ed..0000000
--- a/src/media/audio/mac/audio_manager_mac.cc
+++ /dev/null
@@ -1,406 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_manager_mac.h"
-
-#include <CoreAudio/AudioHardware.h>
-#include <string>
-
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/mac/mac_logging.h"
-#include "base/mac/scoped_cftyperef.h"
-#include "base/sys_string_conversions.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_input_mac.h"
-#include "media/audio/mac/audio_low_latency_input_mac.h"
-#include "media/audio/mac/audio_low_latency_output_mac.h"
-#include "media/audio/mac/audio_output_mac.h"
-#include "media/audio/mac/audio_synchronized_mac.h"
-#include "media/audio/mac/audio_unified_mac.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-namespace media {
-
-// Maximum number of output streams that can be open simultaneously.
-static const int kMaxOutputStreams = 50;
-
-static bool HasAudioHardware(AudioObjectPropertySelector selector) {
- AudioDeviceID output_device_id = kAudioObjectUnknown;
- const AudioObjectPropertyAddress property_address = {
- selector,
- kAudioObjectPropertyScopeGlobal, // mScope
- kAudioObjectPropertyElementMaster // mElement
- };
- UInt32 output_device_id_size = static_cast<UInt32>(sizeof(output_device_id));
- OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &property_address,
- 0, // inQualifierDataSize
- NULL, // inQualifierData
- &output_device_id_size,
- &output_device_id);
- return err == kAudioHardwareNoError &&
- output_device_id != kAudioObjectUnknown;
-}
-
-// Returns true if the default input device is the same as
-// the default output device.
-static bool HasUnifiedDefaultIO() {
- AudioDeviceID input_id, output_id;
-
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- pa.mScope = kAudioObjectPropertyScopeGlobal;
- pa.mElement = kAudioObjectPropertyElementMaster;
- UInt32 size = sizeof(input_id);
-
- // Get the default input.
- OSStatus result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &input_id);
-
- if (result != noErr)
- return false;
-
- // Get the default output.
- pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
- result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &output_id);
-
- if (result != noErr)
- return false;
-
- return input_id == output_id;
-}
-
-static void GetAudioDeviceInfo(bool is_input,
- media::AudioDeviceNames* device_names) {
- DCHECK(device_names);
- device_names->clear();
-
- // Query the number of total devices.
- AudioObjectPropertyAddress property_address = {
- kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- UInt32 size = 0;
- OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
- &property_address,
- 0,
- NULL,
- &size);
- if (result || !size)
- return;
-
- int device_count = size / sizeof(AudioDeviceID);
-
- // Get the array of device ids for all the devices, which includes both
- // input devices and output devices.
- scoped_ptr_malloc<AudioDeviceID>
- devices(reinterpret_cast<AudioDeviceID*>(malloc(size)));
- AudioDeviceID* device_ids = devices.get();
- result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &property_address,
- 0,
- NULL,
- &size,
- device_ids);
- if (result)
- return;
-
- // Iterate over all available devices to gather information.
- for (int i = 0; i < device_count; ++i) {
- // Get the number of input or output channels of the device.
- property_address.mScope = is_input ?
- kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
- property_address.mSelector = kAudioDevicePropertyStreams;
- size = 0;
- result = AudioObjectGetPropertyDataSize(device_ids[i],
- &property_address,
- 0,
- NULL,
- &size);
- if (result || !size)
- continue;
-
- // Get device UID.
- CFStringRef uid = NULL;
- size = sizeof(uid);
- property_address.mSelector = kAudioDevicePropertyDeviceUID;
- property_address.mScope = kAudioObjectPropertyScopeGlobal;
- result = AudioObjectGetPropertyData(device_ids[i],
- &property_address,
- 0,
- NULL,
- &size,
- &uid);
- if (result)
- continue;
-
- // Get device name.
- CFStringRef name = NULL;
- property_address.mSelector = kAudioObjectPropertyName;
- property_address.mScope = kAudioObjectPropertyScopeGlobal;
- result = AudioObjectGetPropertyData(device_ids[i],
- &property_address,
- 0,
- NULL,
- &size,
- &name);
- if (result) {
- if (uid)
- CFRelease(uid);
- continue;
- }
-
- // Store the device name and UID.
- media::AudioDeviceName device_name;
- device_name.device_name = base::SysCFStringRefToUTF8(name);
- device_name.unique_id = base::SysCFStringRefToUTF8(uid);
- device_names->push_back(device_name);
-
- // We are responsible for releasing the returned CFObject. See the
- // comment in the AudioHardware.h for constant
- // kAudioDevicePropertyDeviceUID.
- if (uid)
- CFRelease(uid);
- if (name)
- CFRelease(name);
- }
-}
-
-static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
- const std::string& device_id) {
- AudioObjectPropertyAddress property_address = {
- kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- AudioDeviceID audio_device_id = kAudioObjectUnknown;
- UInt32 device_size = sizeof(audio_device_id);
- OSStatus result = -1;
-
- if (device_id == AudioManagerBase::kDefaultDeviceId) {
- // Default Device.
- property_address.mSelector = is_input ?
- kAudioHardwarePropertyDefaultInputDevice :
- kAudioHardwarePropertyDefaultOutputDevice;
-
- result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &property_address,
- 0,
- 0,
- &device_size,
- &audio_device_id);
- } else {
- // Non-default device.
- base::mac::ScopedCFTypeRef<CFStringRef>
- uid(base::SysUTF8ToCFStringRef(device_id));
- AudioValueTranslation value;
- value.mInputData = &uid;
- value.mInputDataSize = sizeof(CFStringRef);
- value.mOutputData = &audio_device_id;
- value.mOutputDataSize = device_size;
- UInt32 translation_size = sizeof(AudioValueTranslation);
-
- property_address.mSelector = kAudioHardwarePropertyDeviceForUID;
- result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &property_address,
- 0,
- 0,
- &translation_size,
- &value);
- }
-
- if (result) {
- OSSTATUS_DLOG(WARNING, result) << "Unable to query device " << device_id
- << " for AudioDeviceID";
- }
-
- return audio_device_id;
-}
-
-// Property address to monitor for device changes.
-static const AudioObjectPropertyAddress kDeviceChangePropertyAddress = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
-};
-
-// Callback from the system when the default device changes; this must be called
-// on the MessageLoop that created the AudioManager.
-static OSStatus OnDefaultDeviceChangedCallback(
- AudioObjectID object,
- UInt32 num_addresses,
- const AudioObjectPropertyAddress addresses[],
- void* context) {
- if (object != kAudioObjectSystemObject)
- return noErr;
-
- for (UInt32 i = 0; i < num_addresses; ++i) {
- if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector &&
- addresses[i].mScope == kDeviceChangePropertyAddress.mScope &&
- addresses[i].mElement == kDeviceChangePropertyAddress.mElement &&
- context) {
- static_cast<AudioManagerMac*>(context)->OnDeviceChange();
- break;
- }
- }
-
- return noErr;
-}
-
-AudioManagerMac::AudioManagerMac()
- : listener_registered_(false),
- creating_message_loop_(base::MessageLoopProxy::current()) {
- SetMaxOutputStreamsAllowed(kMaxOutputStreams);
-
- // AudioManagerMac is expected to be created by the root platform thread, this
- // is generally BrowserMainLoop, it's MessageLoop will drive the NSApplication
- // pump which in turn fires the property listener callbacks.
- if (!creating_message_loop_)
- return;
-
- OSStatus result = AudioObjectAddPropertyListener(
- kAudioObjectSystemObject,
- &kDeviceChangePropertyAddress,
- &OnDefaultDeviceChangedCallback,
- this);
-
- if (result != noErr) {
- OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!";
- return;
- }
-
- listener_registered_ = true;
-}
-
-AudioManagerMac::~AudioManagerMac() {
- if (listener_registered_) {
- // TODO(dalecurtis): CHECK destruction happens on |creating_message_loop_|,
- // should be true, but currently several unit tests perform destruction in
- // odd places so we can't CHECK here currently.
- OSStatus result = AudioObjectRemovePropertyListener(
- kAudioObjectSystemObject,
- &kDeviceChangePropertyAddress,
- &OnDefaultDeviceChangedCallback,
- this);
- OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
- << "AudioObjectRemovePropertyListener() failed!";
- }
-
- Shutdown();
-}
-
-bool AudioManagerMac::HasAudioOutputDevices() {
- return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice);
-}
-
-bool AudioManagerMac::HasAudioInputDevices() {
- return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice);
-}
-
-void AudioManagerMac::GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) {
- GetAudioDeviceInfo(true, device_names);
- if (!device_names->empty()) {
- // Prepend the default device to the list since we always want it to be
- // on the top of the list for all platforms. There is no duplicate
- // counting here since the default device has been abstracted out before.
- media::AudioDeviceName name;
- name.device_name = AudioManagerBase::kDefaultDeviceName;
- name.unique_id = AudioManagerBase::kDefaultDeviceId;
- device_names->push_front(name);
- }
-}
-
-AudioOutputStream* AudioManagerMac::MakeLinearOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return new PCMQueueOutAudioOutputStream(this, params);
-}
-
-AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
-
- // TODO(crogers): remove once we properly handle input device selection.
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kEnableWebAudioInput)) {
- if (HasUnifiedDefaultIO())
- return new AudioHardwareUnifiedStream(this, params);
-
- // kAudioDeviceUnknown translates to "use default" here.
- return new AudioSynchronizedStream(this,
- params,
- kAudioDeviceUnknown,
- kAudioDeviceUnknown);
- }
-
- return new AUAudioOutputStream(this, params);
-}
-
-AudioInputStream* AudioManagerMac::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return new PCMQueueInAudioInputStream(this, params);
-}
-
-AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- // Gets the AudioDeviceID that refers to the AudioOutputDevice with the device
- // unique id. This AudioDeviceID is used to set the device for Audio Unit.
- AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
- AudioInputStream* stream = NULL;
- if (audio_device_id != kAudioObjectUnknown)
- stream = new AUAudioInputStream(this, params, audio_device_id);
-
- return stream;
-}
-
-AudioParameters AudioManagerMac::GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) {
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kEnableWebAudioInput)) {
- // TODO(crogers): given the limitations of the AudioOutputStream
- // back-ends used with kEnableWebAudioInput, we hard-code to stereo.
- // Specifically, this is a limitation of AudioSynchronizedStream which
- // can be removed as part of the work to consolidate these back-ends.
- return AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
- GetAudioHardwareSampleRate(), 16, GetAudioHardwareBufferSize());
- }
-
- return AudioManagerBase::GetPreferredLowLatencyOutputStreamParameters(
- input_params);
-}
-
-void AudioManagerMac::OnDeviceChange() {
- // Post the task to the |creating_message_loop_| to execute our listener
- // callback. The callback is created using BindToLoop() so will hop over
- // to the audio thread upon execution.
- creating_message_loop_->PostTask(FROM_HERE, BindToLoop(
- GetMessageLoop(), base::Bind(
- &AudioManagerMac::NotifyAllOutputDeviceChangeListeners,
- base::Unretained(this))));
-}
-
-AudioManager* CreateAudioManager() {
- return new AudioManagerMac();
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_manager_mac.h b/src/media/audio/mac/audio_manager_mac.h
deleted file mode 100644
index d8b6b2d..0000000
--- a/src/media/audio/mac/audio_manager_mac.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/message_loop_proxy.h"
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-// Mac OS X implementation of the AudioManager singleton. This class is internal
-// to the audio output and only internal users can call methods not exposed by
-// the AudioManager class.
-class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
- public:
- AudioManagerMac();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
- virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names)
- OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) OVERRIDE;
-
- // Called by an internal device change listener. Must be called on
- // |creating_message_loop_|.
- void OnDeviceChange();
-
- protected:
- virtual ~AudioManagerMac();
-
- private:
- bool listener_registered_;
- scoped_refptr<base::MessageLoopProxy> creating_message_loop_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerMac);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
diff --git a/src/media/audio/mac/audio_output_mac.cc b/src/media/audio/mac/audio_output_mac.cc
deleted file mode 100644
index d0e0afa..0000000
--- a/src/media/audio/mac/audio_output_mac.cc
+++ /dev/null
@@ -1,553 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_output_mac.h"
-
-#include <CoreServices/CoreServices.h>
-
-#include "base/basictypes.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_manager_mac.h"
-#include "media/base/channel_mixer.h"
-
-namespace media {
-
-// A custom data structure to store information an AudioQueue buffer.
-struct AudioQueueUserData {
- AudioQueueUserData() : empty_buffer(false) {}
- bool empty_buffer;
-};
-
-// Overview of operation:
-// 1) An object of PCMQueueOutAudioOutputStream is created by the AudioManager
-// factory: audio_man->MakeAudioStream(). This just fills some structure.
-// 2) Next some thread will call Open(), at that point the underliying OS
-// queue is created and the audio buffers allocated.
-// 3) Then some thread will call Start(source) At this point the source will be
-// called to fill the initial buffers in the context of that same thread.
-// Then the OS queue is started which will create its own thread which
-// periodically will call the source for more data as buffers are being
-// consumed.
-// 4) At some point some thread will call Stop(), which we handle by directly
-// stoping the OS queue.
-// 5) One more callback to the source could be delivered in in the context of
-// the queue's own thread. Data, if any will be discared.
-// 6) The same thread that called stop will call Close() where we cleanup
-// and notifiy the audio manager, which likley will destroy this object.
-
-PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream(
- AudioManagerMac* manager, const AudioParameters& params)
- : audio_queue_(NULL),
- source_(NULL),
- manager_(manager),
- packet_size_(params.GetBytesPerBuffer()),
- silence_bytes_(0),
- volume_(1),
- pending_bytes_(0),
- num_source_channels_(params.channels()),
- source_layout_(params.channel_layout()),
- num_core_channels_(0),
- should_swizzle_(false),
- stopped_event_(true /* manual reset */, false /* initial state */),
- num_buffers_left_(kNumBuffers),
- audio_bus_(AudioBus::Create(params)) {
- // We must have a manager.
- DCHECK(manager_);
- // A frame is one sample across all channels. In interleaved audio the per
- // frame fields identify the set of n |channels|. In uncompressed audio, a
- // packet is always one frame.
- format_.mSampleRate = params.sample_rate();
- format_.mFormatID = kAudioFormatLinearPCM;
- format_.mFormatFlags = kLinearPCMFormatFlagIsPacked;
- format_.mBitsPerChannel = params.bits_per_sample();
- format_.mChannelsPerFrame = params.channels();
- format_.mFramesPerPacket = 1;
- format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8;
- format_.mBytesPerFrame = format_.mBytesPerPacket;
- format_.mReserved = 0;
-
- memset(buffer_, 0, sizeof(buffer_));
- memset(core_channel_orderings_, 0, sizeof(core_channel_orderings_));
- memset(channel_remap_, 0, sizeof(channel_remap_));
-
- if (params.bits_per_sample() > 8) {
- format_.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
- }
-
- // Silence buffer has a duration of 6ms to simulate the behavior of Windows.
- // This value is choosen by experiments and macs cannot keep up with
- // anything less than 6ms.
- silence_bytes_ = format_.mBytesPerFrame * params.sample_rate() * 6 / 1000;
-}
-
-PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() {
-}
-
-void PCMQueueOutAudioOutputStream::HandleError(OSStatus err) {
- // source_ can be set to NULL from another thread. We need to cache its
- // pointer while we operate here. Note that does not mean that the source
- // has been destroyed.
- AudioSourceCallback* source = GetSource();
- if (source)
- source->OnError(this, static_cast<int>(err));
- LOG(ERROR) << "error " << GetMacOSStatusErrorString(err)
- << " (" << err << ")";
-}
-
-bool PCMQueueOutAudioOutputStream::Open() {
- // Get the default device id.
- AudioObjectID device_id = 0;
- AudioObjectPropertyAddress property_address = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
- UInt32 device_id_size = sizeof(device_id);
- OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &property_address, 0, NULL,
- &device_id_size, &device_id);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- // Get the size of the channel layout.
- UInt32 core_layout_size;
- property_address.mSelector = kAudioDevicePropertyPreferredChannelLayout;
- property_address.mScope = kAudioDevicePropertyScopeOutput;
- err = AudioObjectGetPropertyDataSize(device_id, &property_address, 0, NULL,
- &core_layout_size);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- // Get the device's channel layout. This layout may vary in sized based on
- // the number of channels. Use |core_layout_size| to allocate memory.
- scoped_ptr_malloc<AudioChannelLayout> core_channel_layout;
- core_channel_layout.reset(
- reinterpret_cast<AudioChannelLayout*>(malloc(core_layout_size)));
- memset(core_channel_layout.get(), 0, core_layout_size);
- err = AudioObjectGetPropertyData(device_id, &property_address, 0, NULL,
- &core_layout_size,
- core_channel_layout.get());
- if (err != noErr) {
- HandleError(err);
- return false;
- }
-
- num_core_channels_ = std::min(
- static_cast<int>(CHANNELS_MAX),
- static_cast<int>(core_channel_layout->mNumberChannelDescriptions));
- if (num_core_channels_ == 2 &&
- ChannelLayoutToChannelCount(source_layout_) > 2) {
- channel_mixer_.reset(new ChannelMixer(
- source_layout_, CHANNEL_LAYOUT_STEREO));
- mixed_audio_bus_ = AudioBus::Create(
- num_core_channels_, audio_bus_->frames());
-
- format_.mChannelsPerFrame = num_core_channels_;
- format_.mBytesPerFrame = (format_.mBitsPerChannel >> 3) *
- format_.mChannelsPerFrame;
- format_.mBytesPerPacket = format_.mBytesPerFrame * format_.mFramesPerPacket;
- }
-
- // Create the actual queue object and let the OS use its own thread to
- // run its CFRunLoop.
- err = AudioQueueNewOutput(&format_, RenderCallback, this, NULL,
- kCFRunLoopCommonModes, 0, &audio_queue_);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- // Allocate the hardware-managed buffers.
- for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
- err = AudioQueueAllocateBuffer(audio_queue_, packet_size_, &buffer_[ix]);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
- // Allocate memory for user data.
- buffer_[ix]->mUserData = new AudioQueueUserData();
- }
- // Set initial volume here.
- err = AudioQueueSetParameter(audio_queue_, kAudioQueueParam_Volume, 1.0);
- if (err != noErr) {
- HandleError(err);
- return false;
- }
-
- // Capture channel layout in a format we can use.
- for (int i = 0; i < CHANNELS_MAX; ++i)
- core_channel_orderings_[i] = kEmptyChannel;
-
- bool all_channels_unknown = true;
- for (int i = 0; i < num_core_channels_; ++i) {
- AudioChannelLabel label =
- core_channel_layout->mChannelDescriptions[i].mChannelLabel;
- if (label == kAudioChannelLabel_Unknown) {
- continue;
- }
- all_channels_unknown = false;
- switch (label) {
- case kAudioChannelLabel_Left:
- core_channel_orderings_[LEFT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, LEFT);
- break;
- case kAudioChannelLabel_Right:
- core_channel_orderings_[RIGHT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, RIGHT);
- break;
- case kAudioChannelLabel_Center:
- core_channel_orderings_[CENTER] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, CENTER);
- break;
- case kAudioChannelLabel_LFEScreen:
- core_channel_orderings_[LFE] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, LFE);
- break;
- case kAudioChannelLabel_LeftSurround:
- core_channel_orderings_[SIDE_LEFT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, SIDE_LEFT);
- break;
- case kAudioChannelLabel_RightSurround:
- core_channel_orderings_[SIDE_RIGHT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, SIDE_RIGHT);
- break;
- case kAudioChannelLabel_LeftCenter:
- core_channel_orderings_[LEFT_OF_CENTER] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, LEFT_OF_CENTER);
- break;
- case kAudioChannelLabel_RightCenter:
- core_channel_orderings_[RIGHT_OF_CENTER] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, RIGHT_OF_CENTER);
- break;
- case kAudioChannelLabel_CenterSurround:
- core_channel_orderings_[BACK_CENTER] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, BACK_CENTER);
- break;
- case kAudioChannelLabel_RearSurroundLeft:
- core_channel_orderings_[BACK_LEFT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, BACK_LEFT);
- break;
- case kAudioChannelLabel_RearSurroundRight:
- core_channel_orderings_[BACK_RIGHT] = i;
- channel_remap_[i] = ChannelOrder(source_layout_, BACK_RIGHT);
- break;
- default:
- DLOG(WARNING) << "Channel label not supported";
- channel_remap_[i] = kEmptyChannel;
- break;
- }
- }
-
- if (all_channels_unknown) {
- return true;
- }
-
- // Check if we need to adjust the layout.
- // If the device has a BACK_LEFT and no SIDE_LEFT and the source has
- // a SIDE_LEFT but no BACK_LEFT, then move (and preserve the channel).
- // e.g. CHANNEL_LAYOUT_5POINT1 -> CHANNEL_LAYOUT_5POINT1_BACK
- CheckForAdjustedLayout(SIDE_LEFT, BACK_LEFT);
- // Same for SIDE_RIGHT -> BACK_RIGHT.
- CheckForAdjustedLayout(SIDE_RIGHT, BACK_RIGHT);
- // Move BACK_LEFT to SIDE_LEFT.
- // e.g. CHANNEL_LAYOUT_5POINT1_BACK -> CHANNEL_LAYOUT_5POINT1
- CheckForAdjustedLayout(BACK_LEFT, SIDE_LEFT);
- // Same for BACK_RIGHT -> SIDE_RIGHT.
- CheckForAdjustedLayout(BACK_RIGHT, SIDE_RIGHT);
- // Move SIDE_LEFT to LEFT_OF_CENTER.
- // e.g. CHANNEL_LAYOUT_7POINT1 -> CHANNEL_LAYOUT_7POINT1_WIDE
- CheckForAdjustedLayout(SIDE_LEFT, LEFT_OF_CENTER);
- // Same for SIDE_RIGHT -> RIGHT_OF_CENTER.
- CheckForAdjustedLayout(SIDE_RIGHT, RIGHT_OF_CENTER);
- // Move LEFT_OF_CENTER to SIDE_LEFT.
- // e.g. CHANNEL_LAYOUT_7POINT1_WIDE -> CHANNEL_LAYOUT_7POINT1
- CheckForAdjustedLayout(LEFT_OF_CENTER, SIDE_LEFT);
- // Same for RIGHT_OF_CENTER -> SIDE_RIGHT.
- CheckForAdjustedLayout(RIGHT_OF_CENTER, SIDE_RIGHT);
- // For MONO -> STEREO, move audio to LEFT and RIGHT if applicable.
- CheckForAdjustedLayout(CENTER, LEFT);
- CheckForAdjustedLayout(CENTER, RIGHT);
-
- // Check if we will need to swizzle from source to device layout (maybe not!).
- should_swizzle_ = false;
- for (int i = 0; i < num_core_channels_; ++i) {
- if (ChannelOrder(source_layout_, static_cast<Channels>(i)) !=
- core_channel_orderings_[i]) {
- should_swizzle_ = true;
- break;
- }
- }
-
- return true;
-}
-
-void PCMQueueOutAudioOutputStream::Close() {
- // It is valid to call Close() before calling Open(), thus audio_queue_
- // might be NULL.
- if (audio_queue_) {
- OSStatus err = 0;
- for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
- if (buffer_[ix]) {
- // Free user data.
- delete static_cast<AudioQueueUserData*>(buffer_[ix]->mUserData);
- // Free AudioQueue buffer.
- err = AudioQueueFreeBuffer(audio_queue_, buffer_[ix]);
- if (err != noErr) {
- HandleError(err);
- break;
- }
- }
- }
- err = AudioQueueDispose(audio_queue_, true);
- if (err != noErr)
- HandleError(err);
- }
- // Inform the audio manager that we have been closed. This can cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void PCMQueueOutAudioOutputStream::Stop() {
- if (source_) {
- // We request a synchronous stop, so the next call can take some time. In
- // the windows implementation we block here as well.
- SetSource(NULL);
- stopped_event_.Wait();
- }
-}
-
-void PCMQueueOutAudioOutputStream::SetVolume(double volume) {
- if (!audio_queue_)
- return;
- volume_ = static_cast<float>(volume);
- OSStatus err = AudioQueueSetParameter(audio_queue_,
- kAudioQueueParam_Volume,
- volume);
- if (err != noErr) {
- HandleError(err);
- }
-}
-
-void PCMQueueOutAudioOutputStream::GetVolume(double* volume) {
- if (!audio_queue_)
- return;
- *volume = volume_;
-}
-
-template<class Format>
-void PCMQueueOutAudioOutputStream::SwizzleLayout(Format* b, uint32 filled) {
- Format src_format[num_source_channels_];
- int filled_channels = (num_core_channels_ < num_source_channels_) ?
- num_core_channels_ : num_source_channels_;
- for (uint32 i = 0; i < filled; i += sizeof(src_format),
- b += num_source_channels_) {
- // TODO(fbarchard): This could be further optimized with pshufb.
- memcpy(src_format, b, sizeof(src_format));
- for (int ch = 0; ch < filled_channels; ++ch) {
- if (channel_remap_[ch] != kEmptyChannel &&
- channel_remap_[ch] <= CHANNELS_MAX) {
- b[ch] = src_format[channel_remap_[ch]];
- } else {
- b[ch] = 0;
- }
- }
- }
-}
-
-bool PCMQueueOutAudioOutputStream::CheckForAdjustedLayout(
- Channels input_channel,
- Channels output_channel) {
- if (core_channel_orderings_[output_channel] > kEmptyChannel &&
- core_channel_orderings_[input_channel] == kEmptyChannel &&
- ChannelOrder(source_layout_, input_channel) > kEmptyChannel &&
- ChannelOrder(source_layout_, output_channel) == kEmptyChannel) {
- channel_remap_[core_channel_orderings_[output_channel]] =
- ChannelOrder(source_layout_, input_channel);
- return true;
- }
- return false;
-}
-
-// Note to future hackers of this function: Do not add locks to this function
-// that are held through any calls made back into AudioQueue APIs, or other
-// OS audio functions. This is because the OS dispatch may grab external
-// locks, or possibly re-enter this function which can lead to a deadlock.
-void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this,
- AudioQueueRef queue,
- AudioQueueBufferRef buffer) {
- TRACE_EVENT0("audio", "PCMQueueOutAudioOutputStream::RenderCallback");
-
- PCMQueueOutAudioOutputStream* audio_stream =
- static_cast<PCMQueueOutAudioOutputStream*>(p_this);
-
- // Call the audio source to fill the free buffer with data. Not having a
- // source means that the queue has been stopped.
- AudioSourceCallback* source = audio_stream->GetSource();
- if (!source) {
- // PCMQueueOutAudioOutputStream::Stop() is waiting for callback to
- // stop the stream and signal when all callbacks are done.
- // (we probably can stop the stream there, but it is better to have
- // all the complex logic in one place; stopping latency is not very
- // important if you reuse audio stream in the mixer and not close it
- // immediately).
- --audio_stream->num_buffers_left_;
- if (audio_stream->num_buffers_left_ == kNumBuffers - 1) {
- // First buffer after stop requested, stop the queue.
- OSStatus err = AudioQueueStop(audio_stream->audio_queue_, true);
- if (err != noErr)
- audio_stream->HandleError(err);
- }
- if (audio_stream->num_buffers_left_ == 0) {
- // Now we finally saw all the buffers.
- // Signal that stopping is complete.
- // Should never touch audio_stream after signaling as it
- // can be deleted at any moment.
- audio_stream->stopped_event_.Signal();
- }
- return;
- }
-
- // Adjust the number of pending bytes by subtracting the amount played.
- if (!static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer)
- audio_stream->pending_bytes_ -= buffer->mAudioDataByteSize;
-
- uint32 capacity = buffer->mAudioDataBytesCapacity;
- AudioBus* audio_bus = audio_stream->audio_bus_.get();
- DCHECK_EQ(
- audio_bus->frames() * audio_stream->format_.mBytesPerFrame, capacity);
- // TODO(sergeyu): Specify correct hardware delay for AudioBuffersState.
- int frames_filled = source->OnMoreData(
- audio_bus, AudioBuffersState(audio_stream->pending_bytes_, 0));
- uint32 filled = frames_filled * audio_stream->format_.mBytesPerFrame;
-
- // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
- // volume adjust should use SSE optimized vector_fmul() prior to interleave.
- AudioBus* output_bus = audio_bus;
- if (audio_stream->channel_mixer_) {
- output_bus = audio_stream->mixed_audio_bus_.get();
- audio_stream->channel_mixer_->Transform(audio_bus, output_bus);
- }
-
- // Note: If this ever changes to output raw float the data must be clipped
- // and sanitized since it may come from an untrusted source such as NaCl.
- output_bus->ToInterleaved(
- frames_filled, audio_stream->format_.mBitsPerChannel / 8,
- buffer->mAudioData);
-
- // In order to keep the callback running, we need to provide a positive amount
- // of data to the audio queue. To simulate the behavior of Windows, we write
- // a buffer of silence.
- if (!filled) {
- CHECK(audio_stream->silence_bytes_ <= static_cast<int>(capacity));
- filled = audio_stream->silence_bytes_;
-
- // Assume unsigned audio.
- int silence_value = 128;
- if (audio_stream->format_.mBitsPerChannel > 8) {
- // When bits per channel is greater than 8, audio is signed.
- silence_value = 0;
- }
-
- memset(buffer->mAudioData, silence_value, filled);
- static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer = true;
- } else if (filled > capacity) {
- // User probably overran our buffer.
- audio_stream->HandleError(0);
- return;
- } else {
- static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer = false;
- }
-
- if (audio_stream->should_swizzle_) {
- // Handle channel order for surround sound audio.
- if (audio_stream->format_.mBitsPerChannel == 8) {
- audio_stream->SwizzleLayout(reinterpret_cast<uint8*>(buffer->mAudioData),
- filled);
- } else if (audio_stream->format_.mBitsPerChannel == 16) {
- audio_stream->SwizzleLayout(reinterpret_cast<int16*>(buffer->mAudioData),
- filled);
- } else if (audio_stream->format_.mBitsPerChannel == 32) {
- audio_stream->SwizzleLayout(reinterpret_cast<int32*>(buffer->mAudioData),
- filled);
- }
- }
-
- buffer->mAudioDataByteSize = filled;
-
- // Increment bytes by amount filled into audio buffer if this is not a
- // silence buffer.
- if (!static_cast<AudioQueueUserData*>(buffer->mUserData)->empty_buffer)
- audio_stream->pending_bytes_ += filled;
- if (NULL == queue)
- return;
- // Queue the audio data to the audio driver.
- OSStatus err = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
- if (err != noErr) {
- if (err == kAudioQueueErr_EnqueueDuringReset) {
- // This is the error you get if you try to enqueue a buffer and the
- // queue has been closed. Not really a problem if indeed the queue
- // has been closed. We recheck the value of source now to see if it has
- // indeed been closed.
- if (!audio_stream->GetSource())
- return;
- }
- audio_stream->HandleError(err);
- }
-}
-
-void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(callback);
- DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully";
- if (!audio_queue_)
- return;
-
- OSStatus err = noErr;
- SetSource(callback);
- pending_bytes_ = 0;
- stopped_event_.Reset();
- num_buffers_left_ = kNumBuffers;
- // Ask the source to pre-fill all our buffers before playing.
- for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
- buffer_[ix]->mAudioDataByteSize = 0;
- // Caller waits for 1st packet to become available, but not for others,
- // so we wait for them here.
- if (ix != 0) {
- AudioSourceCallback* source = GetSource();
- if (source)
- source->WaitTillDataReady();
- }
- RenderCallback(this, NULL, buffer_[ix]);
- }
-
- // Queue the buffers to the audio driver, sounds starts now.
- for (uint32 ix = 0; ix != kNumBuffers; ++ix) {
- err = AudioQueueEnqueueBuffer(audio_queue_, buffer_[ix], 0, NULL);
- if (err != noErr) {
- HandleError(err);
- return;
- }
- }
- err = AudioQueueStart(audio_queue_, NULL);
- if (err != noErr) {
- HandleError(err);
- return;
- }
-}
-
-void PCMQueueOutAudioOutputStream::SetSource(AudioSourceCallback* source) {
- base::AutoLock lock(source_lock_);
- source_ = source;
-}
-
-AudioOutputStream::AudioSourceCallback*
-PCMQueueOutAudioOutputStream::GetSource() {
- base::AutoLock lock(source_lock_);
- return source_;
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_output_mac.h b/src/media/audio/mac/audio_output_mac.h
deleted file mode 100644
index c4b12f2..0000000
--- a/src/media/audio/mac/audio_output_mac.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MAC_AUDIO_OUTPUT_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_OUTPUT_MAC_H_
-
-#include <AudioToolbox/AudioFormat.h>
-#include <AudioToolbox/AudioQueue.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreAudio/CoreAudio.h>
-
-#include "base/compiler_specific.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerMac;
-class ChannelMixer;
-
-// Implementation of AudioOuputStream for Mac OS X using the audio queue service
-// present in OS 10.5 and later. Audioqueue is the successor to the SoundManager
-// services but it is supported in 64 bits.
-class PCMQueueOutAudioOutputStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- PCMQueueOutAudioOutputStream(AudioManagerMac* manager,
- const AudioParameters& params);
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~PCMQueueOutAudioOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- private:
- // The audio is double buffered.
- static const uint32 kNumBuffers = 2;
- static const int kEmptyChannel = -1;
-
- // Reorder PCM from source layout to device layout found in Core Audio.
- template<class Format>
- void SwizzleLayout(Format* b, uint32 filled);
- // Check and move channels if surround sound layout needs adjusted.
- bool CheckForAdjustedLayout(Channels input_channel, Channels output_channel);
-
- // The OS calls back here when an audio buffer has been processed.
- static void RenderCallback(void* p_this, AudioQueueRef queue,
- AudioQueueBufferRef buffer);
- // Called when an error occurs.
- void HandleError(OSStatus err);
-
- // Atomic operations for setting/getting the source callback.
- void SetSource(AudioSourceCallback* source);
- AudioSourceCallback* GetSource();
-
- // Structure that holds the stream format details such as bitrate.
- AudioStreamBasicDescription format_;
- // Handle to the OS audio queue object.
- AudioQueueRef audio_queue_;
- // Array of pointers to the OS managed audio buffers.
- AudioQueueBufferRef buffer_[kNumBuffers];
- // Mutex for the |source_| to implment atomic set and get.
- // It is important to NOT wait on any other locks while this is held.
- base::Lock source_lock_;
- // Pointer to the object that will provide the audio samples.
- AudioSourceCallback* source_;
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerMac* manager_;
- // Packet size in bytes.
- uint32 packet_size_;
- // Number of bytes for making a silence buffer.
- int silence_bytes_;
- // Volume level from 0 to 1.
- float volume_;
- // Number of bytes yet to be played in audio buffer.
- uint32 pending_bytes_;
- // Number of channels in the source audio.
- int num_source_channels_;
- // Source's channel layout for surround sound channels.
- ChannelLayout source_layout_;
- // Device's channel layout.
- int core_channel_orderings_[CHANNELS_MAX];
- // An array for remapping source to device channel layouts during a swizzle.
- int channel_remap_[CHANNELS_MAX];
- // Number of channels in device layout.
- int num_core_channels_;
- // A flag to determine if swizzle is needed from source to device layouts.
- bool should_swizzle_;
- // A flag to determine if downmix is needed from source to device layouts.
- bool should_down_mix_;
-
- // Event used for synchronization when stopping the stream.
- // Callback sets it after stream is stopped.
- base::WaitableEvent stopped_event_;
- // When stopping we keep track of number of buffers in flight and
- // signal "stop completed" from the last buffer's callback.
- int num_buffers_left_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- // Channel mixer and temporary bus for the final mixed channel data.
- scoped_ptr<ChannelMixer> channel_mixer_;
- scoped_ptr<AudioBus> mixed_audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(PCMQueueOutAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_OUTPUT_MAC_H_
diff --git a/src/media/audio/mac/audio_output_mac_unittest.cc b/src/media/audio/mac/audio_output_mac_unittest.cc
deleted file mode 100644
index 919c17d..0000000
--- a/src/media/audio/mac/audio_output_mac_unittest.cc
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::DoAll;
-using ::testing::Field;
-using ::testing::InSequence;
-using ::testing::Invoke;
-using ::testing::NiceMock;
-using ::testing::NotNull;
-using ::testing::Return;
-
-namespace media {
-
-class MockAudioSource : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-// ===========================================================================
-// Validation of AudioParameters::AUDIO_PCM_LINEAR
-//
-
-// Test that can it be created and closed.
-TEST(MacAudioTest, PCMWaveStreamGetAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices())
- return;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 1024));
- ASSERT_TRUE(NULL != oas);
- oas->Close();
-}
-
-// Test that it can be opened and closed.
-TEST(MacAudioTest, PCMWaveStreamOpenAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices())
- return;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 1024));
- ASSERT_TRUE(NULL != oas);
- EXPECT_TRUE(oas->Open());
- oas->Close();
-}
-
-// This test produces actual audio for 1.5 seconds on the default wave device at
-// 44.1K s/sec. Parameters have been chosen carefully so you should not hear
-// pops or noises while the sound is playing. The sound must also be identical
-// to the sound of PCMWaveStreamPlay200HzTone22KssMono test.
-TEST(MacAudioTest, PCMWaveStreamPlay200HzTone44KssMono) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices())
- return;
- uint32 frames_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, frames_100_ms));
- ASSERT_TRUE(NULL != oas);
- EXPECT_TRUE(oas->Open());
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
- oas->SetVolume(0.5);
- oas->Start(&source);
- usleep(500000);
-
- // Test that the volume is within the set limits.
- double volume = 0.0;
- oas->GetVolume(&volume);
- EXPECT_LT(volume, 0.51);
- EXPECT_GT(volume, 0.49);
- oas->Stop();
- oas->Close();
-}
-
-// This test produces actual audio for 1.5 seconds on the default wave device at
-// 22K s/sec. Parameters have been chosen carefully so you should not hear pops
-// or noises while the sound is playing. The sound must also be identical to the
-// sound of PCMWaveStreamPlay200HzTone44KssMono test.
-TEST(MacAudioTest, PCMWaveStreamPlay200HzTone22KssMono) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices())
- return;
- uint32 frames_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate / 2, 16,
- frames_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate/2);
- EXPECT_TRUE(oas->Open());
- oas->Start(&source);
- usleep(1500000);
- oas->Stop();
- oas->Close();
-}
-
-// Custom action to clear a memory buffer.
-static int ClearBuffer(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- audio_bus->Zero();
- return audio_bus->frames();
-}
-
-TEST(MacAudioTest, PCMWaveStreamPendingBytes) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices())
- return;
-
- uint32 frames_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, frames_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- NiceMock<MockAudioSource> source;
- EXPECT_TRUE(oas->Open());
-
- uint32 bytes_100_ms = frames_100_ms * 2;
-
- // We expect the amount of pending bytes will reaching |bytes_100_ms|
- // because the audio output stream has a double buffer scheme.
- // And then we will try to provide zero data so the amount of pending bytes
- // will go down and eventually read zero.
- InSequence s;
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes, 0)))
- .WillOnce(Invoke(ClearBuffer));
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- bytes_100_ms)))
- .WillOnce(Invoke(ClearBuffer));
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- bytes_100_ms)))
- .WillOnce(Return(0));
- EXPECT_CALL(source, OnMoreData(NotNull(), _))
- .Times(AnyNumber())
- .WillRepeatedly(Return(0));
-
- oas->Start(&source);
- usleep(500000);
- oas->Stop();
- oas->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_synchronized_mac.cc b/src/media/audio/mac/audio_synchronized_mac.cc
deleted file mode 100644
index 3861bcb..0000000
--- a/src/media/audio/mac/audio_synchronized_mac.cc
+++ /dev/null
@@ -1,945 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_synchronized_mac.h"
-
-#include <CoreServices/CoreServices.h>
-#include <algorithm>
-
-#include "base/basictypes.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/mac/mac_logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_manager_mac.h"
-
-namespace media {
-
-static const int kHardwareBufferSize = 128;
-static const int kFifoSize = 16384;
-
-// TODO(crogers): handle the non-stereo case.
-static const int kChannels = 2;
-
-// This value was determined empirically for minimum latency while still
-// guarding against FIFO under-runs.
-static const int kBaseTargetFifoFrames = 256 + 64;
-
-// If the input and output sample-rate don't match, then we need to maintain
-// an additional safety margin due to the callback timing jitter and the
-// varispeed buffering. This value was empirically tuned.
-static const int kAdditionalTargetFifoFrames = 128;
-
-static void ZeroBufferList(AudioBufferList* buffer_list) {
- for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i)
- memset(buffer_list->mBuffers[i].mData,
- 0,
- buffer_list->mBuffers[i].mDataByteSize);
-}
-
-static void WrapBufferList(AudioBufferList* buffer_list,
- AudioBus* bus,
- int frames) {
- DCHECK(buffer_list);
- DCHECK(bus);
- int channels = bus->channels();
- int buffer_list_channels = buffer_list->mNumberBuffers;
-
- // Copy pointers from AudioBufferList.
- int source_idx = 0;
- for (int i = 0; i < channels; ++i) {
- bus->SetChannelData(
- i, static_cast<float*>(buffer_list->mBuffers[source_idx].mData));
-
- // It's ok to pass in a |buffer_list| with fewer channels, in which
- // case we just duplicate the last channel.
- if (source_idx < buffer_list_channels - 1)
- ++source_idx;
- }
-
- // Finally set the actual length.
- bus->set_frames(frames);
-}
-
-AudioSynchronizedStream::AudioSynchronizedStream(
- AudioManagerMac* manager,
- const AudioParameters& params,
- AudioDeviceID input_id,
- AudioDeviceID output_id)
- : manager_(manager),
- params_(params),
- input_sample_rate_(0),
- output_sample_rate_(0),
- input_id_(input_id),
- output_id_(output_id),
- input_buffer_list_(NULL),
- fifo_(kChannels, kFifoSize),
- target_fifo_frames_(kBaseTargetFifoFrames),
- average_delta_(0.0),
- fifo_rate_compensation_(1.0),
- input_unit_(0),
- varispeed_unit_(0),
- output_unit_(0),
- first_input_time_(-1),
- is_running_(false),
- hardware_buffer_size_(kHardwareBufferSize),
- channels_(kChannels) {
-}
-
-AudioSynchronizedStream::~AudioSynchronizedStream() {
- DCHECK(!input_unit_);
- DCHECK(!output_unit_);
- DCHECK(!varispeed_unit_);
-}
-
-bool AudioSynchronizedStream::Open() {
- if (params_.channels() != kChannels) {
- LOG(ERROR) << "Only stereo output is currently supported.";
- return false;
- }
-
- // Create the input, output, and varispeed AudioUnits.
- OSStatus result = CreateAudioUnits();
- if (result != noErr) {
- LOG(ERROR) << "Cannot create AudioUnits.";
- return false;
- }
-
- result = SetupInput(input_id_);
- if (result != noErr) {
- LOG(ERROR) << "Error configuring input AudioUnit.";
- return false;
- }
-
- result = SetupOutput(output_id_);
- if (result != noErr) {
- LOG(ERROR) << "Error configuring output AudioUnit.";
- return false;
- }
-
- result = SetupCallbacks();
- if (result != noErr) {
- LOG(ERROR) << "Error setting up callbacks on AudioUnits.";
- return false;
- }
-
- result = SetupStreamFormats();
- if (result != noErr) {
- LOG(ERROR) << "Error configuring stream formats on AudioUnits.";
- return false;
- }
-
- AllocateInputData();
-
- // Final initialization of the AudioUnits.
- result = AudioUnitInitialize(input_unit_);
- if (result != noErr) {
- LOG(ERROR) << "Error initializing input AudioUnit.";
- return false;
- }
-
- result = AudioUnitInitialize(output_unit_);
- if (result != noErr) {
- LOG(ERROR) << "Error initializing output AudioUnit.";
- return false;
- }
-
- result = AudioUnitInitialize(varispeed_unit_);
- if (result != noErr) {
- LOG(ERROR) << "Error initializing varispeed AudioUnit.";
- return false;
- }
-
- if (input_sample_rate_ != output_sample_rate_) {
- // Add extra safety margin.
- target_fifo_frames_ += kAdditionalTargetFifoFrames;
- }
-
- // Buffer initial silence corresponding to target I/O buffering.
- fifo_.Clear();
- scoped_ptr<AudioBus> silence =
- AudioBus::Create(channels_, target_fifo_frames_);
- silence->Zero();
- fifo_.Push(silence.get());
-
- return true;
-}
-
-void AudioSynchronizedStream::Close() {
- DCHECK(!is_running_);
-
- if (input_buffer_list_) {
- free(input_buffer_list_);
- input_buffer_list_ = 0;
- input_bus_.reset(NULL);
- wrapper_bus_.reset(NULL);
- }
-
- if (input_unit_) {
- AudioUnitUninitialize(input_unit_);
- CloseComponent(input_unit_);
- }
-
- if (output_unit_) {
- AudioUnitUninitialize(output_unit_);
- CloseComponent(output_unit_);
- }
-
- if (varispeed_unit_) {
- AudioUnitUninitialize(varispeed_unit_);
- CloseComponent(varispeed_unit_);
- }
-
- input_unit_ = NULL;
- output_unit_ = NULL;
- varispeed_unit_ = NULL;
-
- // Inform the audio manager that we have been closed. This can cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void AudioSynchronizedStream::Start(AudioSourceCallback* callback) {
- DCHECK(callback);
- DCHECK(input_unit_);
- DCHECK(output_unit_);
- DCHECK(varispeed_unit_);
-
- if (is_running_ || !input_unit_ || !output_unit_ || !varispeed_unit_)
- return;
-
- source_ = callback;
-
- // Reset state variables each time we Start().
- fifo_rate_compensation_ = 1.0;
- average_delta_ = 0.0;
-
- OSStatus result = noErr;
-
- if (!is_running_) {
- first_input_time_ = -1;
-
- result = AudioOutputUnitStart(input_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr) {
- result = AudioOutputUnitStart(output_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- }
- }
-
- is_running_ = true;
-}
-
-void AudioSynchronizedStream::Stop() {
- OSStatus result = noErr;
- if (is_running_) {
- result = AudioOutputUnitStop(input_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr) {
- result = AudioOutputUnitStop(output_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- }
- }
-
- if (result == noErr)
- is_running_ = false;
-}
-
-bool AudioSynchronizedStream::IsRunning() {
- return is_running_;
-}
-
-// TODO(crogers): implement - or remove from AudioOutputStream.
-void AudioSynchronizedStream::SetVolume(double volume) {}
-void AudioSynchronizedStream::GetVolume(double* volume) {}
-
-OSStatus AudioSynchronizedStream::SetOutputDeviceAsCurrent(
- AudioDeviceID output_id) {
- OSStatus result = noErr;
-
- // Get the default output device if device is unknown.
- if (output_id == kAudioDeviceUnknown) {
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
- pa.mScope = kAudioObjectPropertyScopeGlobal;
- pa.mElement = kAudioObjectPropertyElementMaster;
- UInt32 size = sizeof(output_id);
-
- result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &output_id);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
- }
-
- // Set the render frame size.
- UInt32 frame_size = hardware_buffer_size_;
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioDevicePropertyBufferFrameSize;
- pa.mScope = kAudioDevicePropertyScopeInput;
- pa.mElement = kAudioObjectPropertyElementMaster;
- result = AudioObjectSetPropertyData(
- output_id,
- &pa,
- 0,
- 0,
- sizeof(frame_size),
- &frame_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- output_info_.Initialize(output_id, false);
-
- // Set the Current Device to the Default Output Unit.
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &output_info_.id_,
- sizeof(output_info_.id_));
-
- OSSTATUS_DCHECK(result == noErr, result);
- return result;
-}
-
-OSStatus AudioSynchronizedStream::SetInputDeviceAsCurrent(
- AudioDeviceID input_id) {
- OSStatus result = noErr;
-
- // Get the default input device if device is unknown.
- if (input_id == kAudioDeviceUnknown) {
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- pa.mScope = kAudioObjectPropertyScopeGlobal;
- pa.mElement = kAudioObjectPropertyElementMaster;
- UInt32 size = sizeof(input_id);
-
- result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &input_id);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
- }
-
- // Set the render frame size.
- UInt32 frame_size = hardware_buffer_size_;
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioDevicePropertyBufferFrameSize;
- pa.mScope = kAudioDevicePropertyScopeInput;
- pa.mElement = kAudioObjectPropertyElementMaster;
- result = AudioObjectSetPropertyData(
- input_id,
- &pa,
- 0,
- 0,
- sizeof(frame_size),
- &frame_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- input_info_.Initialize(input_id, true);
-
- // Set the Current Device to the AUHAL.
- // This should be done only after I/O has been enabled on the AUHAL.
- result = AudioUnitSetProperty(
- input_unit_,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &input_info_.id_,
- sizeof(input_info_.id_));
-
- OSSTATUS_DCHECK(result == noErr, result);
- return result;
-}
-
-OSStatus AudioSynchronizedStream::CreateAudioUnits() {
- // Q: Why do we need a varispeed unit?
- // A: If the input device and the output device are running at
- // different sample rates and/or on different clocks, we will need
- // to compensate to avoid a pitch change and
- // to avoid buffer under and over runs.
- ComponentDescription varispeed_desc;
- varispeed_desc.componentType = kAudioUnitType_FormatConverter;
- varispeed_desc.componentSubType = kAudioUnitSubType_Varispeed;
- varispeed_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- varispeed_desc.componentFlags = 0;
- varispeed_desc.componentFlagsMask = 0;
-
- Component varispeed_comp = FindNextComponent(NULL, &varispeed_desc);
- if (varispeed_comp == NULL)
- return -1;
-
- OSStatus result = OpenAComponent(varispeed_comp, &varispeed_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Open input AudioUnit.
- ComponentDescription input_desc;
- input_desc.componentType = kAudioUnitType_Output;
- input_desc.componentSubType = kAudioUnitSubType_HALOutput;
- input_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- input_desc.componentFlags = 0;
- input_desc.componentFlagsMask = 0;
-
- Component input_comp = FindNextComponent(NULL, &input_desc);
- if (input_comp == NULL)
- return -1;
-
- result = OpenAComponent(input_comp, &input_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Open output AudioUnit.
- ComponentDescription output_desc;
- output_desc.componentType = kAudioUnitType_Output;
- output_desc.componentSubType = kAudioUnitSubType_DefaultOutput;
- output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- output_desc.componentFlags = 0;
- output_desc.componentFlagsMask = 0;
-
- Component output_comp = FindNextComponent(NULL, &output_desc);
- if (output_comp == NULL)
- return -1;
-
- result = OpenAComponent(output_comp, &output_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- return noErr;
-}
-
-OSStatus AudioSynchronizedStream::SetupInput(AudioDeviceID input_id) {
- // The AUHAL used for input needs to be initialized
- // before anything is done to it.
- OSStatus result = AudioUnitInitialize(input_unit_);
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // We must enable the Audio Unit (AUHAL) for input and disable output
- // BEFORE setting the AUHAL's current device.
- result = EnableIO();
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- result = SetInputDeviceAsCurrent(input_id);
- OSSTATUS_DCHECK(result == noErr, result);
-
- return result;
-}
-
-OSStatus AudioSynchronizedStream::EnableIO() {
- // Enable input on the AUHAL.
- UInt32 enable_io = 1;
- OSStatus result = AudioUnitSetProperty(
- input_unit_,
- kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input,
- 1, // input element
- &enable_io,
- sizeof(enable_io));
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Disable Output on the AUHAL.
- enable_io = 0;
- result = AudioUnitSetProperty(
- input_unit_,
- kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output,
- 0, // output element
- &enable_io,
- sizeof(enable_io));
-
- OSSTATUS_DCHECK(result == noErr, result);
- return result;
-}
-
-OSStatus AudioSynchronizedStream::SetupOutput(AudioDeviceID output_id) {
- OSStatus result = noErr;
-
- result = SetOutputDeviceAsCurrent(output_id);
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Tell the output unit not to reset timestamps.
- // Otherwise sample rate changes will cause sync loss.
- UInt32 start_at_zero = 0;
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioOutputUnitProperty_StartTimestampsAtZero,
- kAudioUnitScope_Global,
- 0,
- &start_at_zero,
- sizeof(start_at_zero));
-
- OSSTATUS_DCHECK(result == noErr, result);
-
- return result;
-}
-
-OSStatus AudioSynchronizedStream::SetupCallbacks() {
- // Set the input callback.
- AURenderCallbackStruct callback;
- callback.inputProc = InputProc;
- callback.inputProcRefCon = this;
- OSStatus result = AudioUnitSetProperty(
- input_unit_,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global,
- 0,
- &callback,
- sizeof(callback));
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Set the output callback.
- callback.inputProc = OutputProc;
- callback.inputProcRefCon = this;
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input,
- 0,
- &callback,
- sizeof(callback));
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Set the varispeed callback.
- callback.inputProc = VarispeedProc;
- callback.inputProcRefCon = this;
- result = AudioUnitSetProperty(
- varispeed_unit_,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input,
- 0,
- &callback,
- sizeof(callback));
-
- OSSTATUS_DCHECK(result == noErr, result);
-
- return result;
-}
-
-OSStatus AudioSynchronizedStream::SetupStreamFormats() {
- AudioStreamBasicDescription asbd, asbd_dev1_in, asbd_dev2_out;
-
- // Get the Stream Format (Output client side).
- UInt32 property_size = sizeof(asbd_dev1_in);
- OSStatus result = AudioUnitGetProperty(
- input_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 1,
- &asbd_dev1_in,
- &property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Get the Stream Format (client side).
- property_size = sizeof(asbd);
- result = AudioUnitGetProperty(
- input_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 1,
- &asbd,
- &property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Get the Stream Format (Output client side).
- property_size = sizeof(asbd_dev2_out);
- result = AudioUnitGetProperty(
- output_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 0,
- &asbd_dev2_out,
- &property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Set the format of all the AUs to the input/output devices channel count.
- // For a simple case, you want to set this to
- // the lower of count of the channels in the input device vs output device.
- asbd.mChannelsPerFrame = std::min(asbd_dev1_in.mChannelsPerFrame,
- asbd_dev2_out.mChannelsPerFrame);
-
- // We must get the sample rate of the input device and set it to the
- // stream format of AUHAL.
- Float64 rate = 0;
- property_size = sizeof(rate);
-
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioDevicePropertyNominalSampleRate;
- pa.mScope = kAudioObjectPropertyScopeWildcard;
- pa.mElement = kAudioObjectPropertyElementMaster;
- result = AudioObjectGetPropertyData(
- input_info_.id_,
- &pa,
- 0,
- 0,
- &property_size,
- &rate);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- input_sample_rate_ = rate;
-
- asbd.mSampleRate = rate;
- property_size = sizeof(asbd);
-
- // Set the new formats to the AUs...
- result = AudioUnitSetProperty(
- input_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 1,
- &asbd,
- property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- result = AudioUnitSetProperty(
- varispeed_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 0,
- &asbd,
- property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Set the correct sample rate for the output device,
- // but keep the channel count the same.
- property_size = sizeof(rate);
-
- pa.mSelector = kAudioDevicePropertyNominalSampleRate;
- pa.mScope = kAudioObjectPropertyScopeWildcard;
- pa.mElement = kAudioObjectPropertyElementMaster;
- result = AudioObjectGetPropertyData(
- output_info_.id_,
- &pa,
- 0,
- 0,
- &property_size,
- &rate);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- output_sample_rate_ = rate;
-
- // The requested sample-rate must match the hardware sample-rate.
- if (output_sample_rate_ != params_.sample_rate()) {
- LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
- << " must match the hardware sample-rate: " << output_sample_rate_;
- return kAudioDeviceUnsupportedFormatError;
- }
-
- asbd.mSampleRate = rate;
- property_size = sizeof(asbd);
-
- // Set the new audio stream formats for the rest of the AUs...
- result = AudioUnitSetProperty(
- varispeed_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- 0,
- &asbd,
- property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- result = AudioUnitSetProperty(
- output_unit_,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 0,
- &asbd,
- property_size);
-
- OSSTATUS_DCHECK(result == noErr, result);
- return result;
-}
-
-void AudioSynchronizedStream::AllocateInputData() {
- // Allocate storage for the AudioBufferList used for the
- // input data from the input AudioUnit.
- // We allocate enough space for with one AudioBuffer per channel.
- size_t malloc_size = offsetof(AudioBufferList, mBuffers[0]) +
- (sizeof(AudioBuffer) * channels_);
-
- input_buffer_list_ = static_cast<AudioBufferList*>(malloc(malloc_size));
- input_buffer_list_->mNumberBuffers = channels_;
-
- input_bus_ = AudioBus::Create(channels_, hardware_buffer_size_);
- wrapper_bus_ = AudioBus::CreateWrapper(channels_);
-
- // Allocate buffers for AudioBufferList.
- UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
- for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
- input_buffer_list_->mBuffers[i].mNumberChannels = 1;
- input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
- input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
- }
-}
-
-OSStatus AudioSynchronizedStream::HandleInputCallback(
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- TRACE_EVENT0("audio", "AudioSynchronizedStream::HandleInputCallback");
-
- if (first_input_time_ < 0.0)
- first_input_time_ = time_stamp->mSampleTime;
-
- // Get the new audio input data.
- OSStatus result = AudioUnitRender(
- input_unit_,
- io_action_flags,
- time_stamp,
- bus_number,
- number_of_frames,
- input_buffer_list_);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Buffer input into FIFO.
- int available_frames = fifo_.max_frames() - fifo_.frames();
- if (input_bus_->frames() <= available_frames)
- fifo_.Push(input_bus_.get());
-
- return result;
-}
-
-OSStatus AudioSynchronizedStream::HandleVarispeedCallback(
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- // Create a wrapper bus on the AudioBufferList.
- WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);
-
- if (fifo_.frames() < static_cast<int>(number_of_frames)) {
- // We don't DCHECK here, since this is a possible run-time condition
- // if the machine is bogged down.
- wrapper_bus_->Zero();
- return noErr;
- }
-
- // Read from the FIFO to feed the varispeed.
- fifo_.Consume(wrapper_bus_.get(), 0, number_of_frames);
-
- return noErr;
-}
-
-OSStatus AudioSynchronizedStream::HandleOutputCallback(
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- if (first_input_time_ < 0.0) {
- // Input callback hasn't run yet -> silence.
- ZeroBufferList(io_data);
- return noErr;
- }
-
- // Use the varispeed playback rate to offset small discrepancies
- // in hardware clocks, and also any differences in sample-rate
- // between input and output devices.
-
- // Calculate a varispeed rate scalar factor to compensate for drift between
- // input and output. We use the actual number of frames still in the FIFO
- // compared with the ideal value of |target_fifo_frames_|.
- int delta = fifo_.frames() - target_fifo_frames_;
-
- // Average |delta| because it can jitter back/forth quite frequently
- // by +/- the hardware buffer-size *if* the input and output callbacks are
- // happening at almost exactly the same time. Also, if the input and output
- // sample-rates are different then |delta| will jitter quite a bit due to
- // the rate conversion happening in the varispeed, plus the jittering of
- // the callbacks. The average value is what's important here.
- average_delta_ += (delta - average_delta_) * 0.1;
-
- // Compute a rate compensation which always attracts us back to the
- // |target_fifo_frames_| over a period of kCorrectionTimeSeconds.
- const double kCorrectionTimeSeconds = 0.1;
- double correction_time_frames = kCorrectionTimeSeconds * output_sample_rate_;
- fifo_rate_compensation_ =
- (correction_time_frames + average_delta_) / correction_time_frames;
-
- // Adjust for FIFO drift.
- OSStatus result = AudioUnitSetParameter(
- varispeed_unit_,
- kVarispeedParam_PlaybackRate,
- kAudioUnitScope_Global,
- 0,
- fifo_rate_compensation_,
- 0);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Render to the output using the varispeed.
- result = AudioUnitRender(
- varispeed_unit_,
- io_action_flags,
- time_stamp,
- 0,
- number_of_frames,
- io_data);
-
- OSSTATUS_DCHECK(result == noErr, result);
- if (result != noErr)
- return result;
-
- // Create a wrapper bus on the AudioBufferList.
- WrapBufferList(io_data, wrapper_bus_.get(), number_of_frames);
-
- // Process in-place!
- source_->OnMoreIOData(wrapper_bus_.get(),
- wrapper_bus_.get(),
- AudioBuffersState(0, 0));
-
- return noErr;
-}
-
-OSStatus AudioSynchronizedStream::InputProc(
- void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- AudioSynchronizedStream* stream =
- static_cast<AudioSynchronizedStream*>(user_data);
- DCHECK(stream);
-
- return stream->HandleInputCallback(
- io_action_flags,
- time_stamp,
- bus_number,
- number_of_frames,
- io_data);
-}
-
-OSStatus AudioSynchronizedStream::VarispeedProc(
- void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- AudioSynchronizedStream* stream =
- static_cast<AudioSynchronizedStream*>(user_data);
- DCHECK(stream);
-
- return stream->HandleVarispeedCallback(
- io_action_flags,
- time_stamp,
- bus_number,
- number_of_frames,
- io_data);
-}
-
-OSStatus AudioSynchronizedStream::OutputProc(
- void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data) {
- AudioSynchronizedStream* stream =
- static_cast<AudioSynchronizedStream*>(user_data);
- DCHECK(stream);
-
- return stream->HandleOutputCallback(
- io_action_flags,
- time_stamp,
- bus_number,
- number_of_frames,
- io_data);
-}
-
-void AudioSynchronizedStream::AudioDeviceInfo::Initialize(
- AudioDeviceID id, bool is_input) {
- id_ = id;
- is_input_ = is_input;
- if (id_ == kAudioDeviceUnknown)
- return;
-
- UInt32 property_size = sizeof(buffer_size_frames_);
-
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioDevicePropertyBufferFrameSize;
- pa.mScope = kAudioObjectPropertyScopeWildcard;
- pa.mElement = kAudioObjectPropertyElementMaster;
- OSStatus result = AudioObjectGetPropertyData(
- id_,
- &pa,
- 0,
- 0,
- &property_size,
- &buffer_size_frames_);
-
- OSSTATUS_DCHECK(result == noErr, result);
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_synchronized_mac.h b/src/media/audio/mac/audio_synchronized_mac.h
deleted file mode 100644
index e99d9c8..0000000
--- a/src/media/audio/mac/audio_synchronized_mac.h
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_
-
-#include <AudioToolbox/AudioToolbox.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreAudio/CoreAudio.h>
-
-#include "base/compiler_specific.h"
-#include "base/synchronization/lock.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_bus.h"
-#include "media/base/audio_fifo.h"
-
-namespace media {
-
-class AudioManagerMac;
-
-// AudioSynchronizedStream allows arbitrary combinations of input and output
-// devices running off different clocks and using different drivers, with
-// potentially differing sample-rates. It implements AudioOutputStream
-// and shuttles its synchronized I/O data using AudioSourceCallback.
-//
-// It is required to first acquire the native sample rate of the selected
-// output device and then use the same rate when creating this object.
-//
-// ............................................................................
-// Theory of Operation:
-// .
-// INPUT THREAD . OUTPUT THREAD
-// +-----------------+ +------+ .
-// | Input AudioUnit | --> | | .
-// +-----------------+ | | .
-// | FIFO | .
-// | | +-----------+
-// | | -----> | Varispeed |
-// | | +-----------+
-// +------+ . |
-// . | +-----------+
-// . OnMoreIOData() --> | Output AU |
-// . +-----------+
-//
-// The input AudioUnit's InputProc is called on one thread which feeds the
-// FIFO. The output AudioUnit's OutputProc is called on a second thread
-// which pulls on the varispeed to get the current input data. The varispeed
-// handles mismatches between input and output sample-rate and also clock drift
-// between the input and output drivers. The varispeed consumes its data from
-// the FIFO and adjusts its rate dynamically according to the amount
-// of data buffered in the FIFO. If the FIFO starts getting too much data
-// buffered then the varispeed will speed up slightly to compensate
-// and similarly if the FIFO doesn't have enough data buffered then the
-// varispeed will slow down slightly.
-//
-// Finally, once the input data is available then OnMoreIOData() is called
-// which is given this input, and renders the output which is finally sent
-// to the Output AudioUnit.
-class AudioSynchronizedStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- AudioSynchronizedStream(AudioManagerMac* manager,
- const AudioParameters& params,
- AudioDeviceID input_id,
- AudioDeviceID output_id);
-
- virtual ~AudioSynchronizedStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
-
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- OSStatus SetInputDeviceAsCurrent(AudioDeviceID input_id);
- OSStatus SetOutputDeviceAsCurrent(AudioDeviceID output_id);
- AudioDeviceID GetInputDeviceID() { return input_info_.id_; }
- AudioDeviceID GetOutputDeviceID() { return output_info_.id_; }
-
- bool IsRunning();
-
- private:
- // Initialization.
- OSStatus CreateAudioUnits();
- OSStatus SetupInput(AudioDeviceID input_id);
- OSStatus EnableIO();
- OSStatus SetupOutput(AudioDeviceID output_id);
- OSStatus SetupCallbacks();
- OSStatus SetupStreamFormats();
- void AllocateInputData();
-
- // Handlers for the AudioUnit callbacks.
- OSStatus HandleInputCallback(AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- OSStatus HandleVarispeedCallback(AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- OSStatus HandleOutputCallback(AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- // AudioUnit callbacks.
- static OSStatus InputProc(void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- static OSStatus VarispeedProc(void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- static OSStatus OutputProc(void* user_data,
- AudioUnitRenderActionFlags* io_action_flags,
- const AudioTimeStamp* time_stamp,
- UInt32 bus_number,
- UInt32 number_of_frames,
- AudioBufferList* io_data);
-
- // Our creator.
- AudioManagerMac* manager_;
-
- // Client parameters.
- AudioParameters params_;
-
- double input_sample_rate_;
- double output_sample_rate_;
-
- // Pointer to the object that will provide the audio samples.
- AudioSourceCallback* source_;
-
- // Values used in Open().
- AudioDeviceID input_id_;
- AudioDeviceID output_id_;
-
- // The input AudioUnit renders its data here.
- AudioBufferList* input_buffer_list_;
-
- // Holds the actual data for |input_buffer_list_|.
- scoped_ptr<AudioBus> input_bus_;
-
- // Used to overlay AudioBufferLists.
- scoped_ptr<AudioBus> wrapper_bus_;
-
- class AudioDeviceInfo {
- public:
- AudioDeviceInfo()
- : id_(kAudioDeviceUnknown),
- is_input_(false),
- buffer_size_frames_(0) {}
- void Initialize(AudioDeviceID inID, bool isInput);
- bool IsInitialized() const { return id_ != kAudioDeviceUnknown; }
-
- AudioDeviceID id_;
- bool is_input_;
- UInt32 buffer_size_frames_;
- };
-
- AudioDeviceInfo input_info_;
- AudioDeviceInfo output_info_;
-
- // Used for input to output buffering.
- AudioFifo fifo_;
-
- // The optimal number of frames we'd like to keep in the FIFO at all times.
- int target_fifo_frames_;
-
- // A running average of the measured delta between actual number of frames
- // in the FIFO versus |target_fifo_frames_|.
- double average_delta_;
-
- // A varispeed rate scalar which is calculated based on FIFO drift.
- double fifo_rate_compensation_;
-
- // AudioUnits.
- AudioUnit input_unit_;
- AudioUnit varispeed_unit_;
- AudioUnit output_unit_;
-
- double first_input_time_;
-
- bool is_running_;
- int hardware_buffer_size_;
- int channels_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioSynchronizedStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_SYNCHRONIZED_MAC_H_
diff --git a/src/media/audio/mac/audio_unified_mac.cc b/src/media/audio/mac/audio_unified_mac.cc
deleted file mode 100644
index f8622e4..0000000
--- a/src/media/audio/mac/audio_unified_mac.cc
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mac/audio_unified_mac.h"
-
-#include <CoreServices/CoreServices.h>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/mac/mac_logging.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/mac/audio_manager_mac.h"
-
-namespace media {
-
-// TODO(crogers): support more than hard-coded stereo input.
-// Ideally we would like to receive this value as a constructor argument.
-static const int kDefaultInputChannels = 2;
-
-AudioHardwareUnifiedStream::AudioHardwareUnifiedStream(
- AudioManagerMac* manager, const AudioParameters& params)
- : manager_(manager),
- source_(NULL),
- client_input_channels_(kDefaultInputChannels),
- volume_(1.0f),
- input_channels_(0),
- output_channels_(0),
- input_channels_per_frame_(0),
- output_channels_per_frame_(0),
- io_proc_id_(0),
- device_(kAudioObjectUnknown),
- is_playing_(false) {
- DCHECK(manager_);
-
- // A frame is one sample across all channels. In interleaved audio the per
- // frame fields identify the set of n |channels|. In uncompressed audio, a
- // packet is always one frame.
- format_.mSampleRate = params.sample_rate();
- format_.mFormatID = kAudioFormatLinearPCM;
- format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
- kLinearPCMFormatFlagIsSignedInteger;
- format_.mBitsPerChannel = params.bits_per_sample();
- format_.mChannelsPerFrame = params.channels();
- format_.mFramesPerPacket = 1;
- format_.mBytesPerPacket = (format_.mBitsPerChannel * params.channels()) / 8;
- format_.mBytesPerFrame = format_.mBytesPerPacket;
- format_.mReserved = 0;
-
- // Calculate the number of sample frames per callback.
- number_of_frames_ = params.GetBytesPerBuffer() / format_.mBytesPerPacket;
-
- input_bus_ = AudioBus::Create(client_input_channels_,
- params.frames_per_buffer());
- output_bus_ = AudioBus::Create(params);
-}
-
-AudioHardwareUnifiedStream::~AudioHardwareUnifiedStream() {
- DCHECK_EQ(device_, kAudioObjectUnknown);
-}
-
-bool AudioHardwareUnifiedStream::Open() {
- // Obtain the current output device selected by the user.
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
- pa.mScope = kAudioObjectPropertyScopeGlobal;
- pa.mElement = kAudioObjectPropertyElementMaster;
-
- UInt32 size = sizeof(device_);
-
- OSStatus result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &device_);
-
- if ((result != kAudioHardwareNoError) || (device_ == kAudioDeviceUnknown)) {
- LOG(ERROR) << "Cannot open unified AudioDevice.";
- return false;
- }
-
- // The requested sample-rate must match the hardware sample-rate.
- Float64 sample_rate = 0.0;
- size = sizeof(sample_rate);
-
- pa.mSelector = kAudioDevicePropertyNominalSampleRate;
- pa.mScope = kAudioObjectPropertyScopeWildcard;
- pa.mElement = kAudioObjectPropertyElementMaster;
-
- result = AudioObjectGetPropertyData(
- device_,
- &pa,
- 0,
- 0,
- &size,
- &sample_rate);
-
- if (result != noErr || sample_rate != format_.mSampleRate) {
- LOG(ERROR) << "Requested sample-rate: " << format_.mSampleRate
- << " must match the hardware sample-rate: " << sample_rate;
- return false;
- }
-
- // Configure buffer frame size.
- UInt32 frame_size = number_of_frames_;
-
- pa.mSelector = kAudioDevicePropertyBufferFrameSize;
- pa.mScope = kAudioDevicePropertyScopeInput;
- pa.mElement = kAudioObjectPropertyElementMaster;
- result = AudioObjectSetPropertyData(
- device_,
- &pa,
- 0,
- 0,
- sizeof(frame_size),
- &frame_size);
-
- if (result != noErr) {
- LOG(ERROR) << "Unable to set input buffer frame size: " << frame_size;
- return false;
- }
-
- pa.mScope = kAudioDevicePropertyScopeOutput;
- result = AudioObjectSetPropertyData(
- device_,
- &pa,
- 0,
- 0,
- sizeof(frame_size),
- &frame_size);
-
- if (result != noErr) {
- LOG(ERROR) << "Unable to set output buffer frame size: " << frame_size;
- return false;
- }
-
- DVLOG(1) << "Sample rate: " << sample_rate;
- DVLOG(1) << "Frame size: " << frame_size;
-
- // Determine the number of input and output channels.
- // We handle both the interleaved and non-interleaved cases.
-
- // Get input stream configuration.
- pa.mSelector = kAudioDevicePropertyStreamConfiguration;
- pa.mScope = kAudioDevicePropertyScopeInput;
- pa.mElement = kAudioObjectPropertyElementMaster;
-
- result = AudioObjectGetPropertyDataSize(device_, &pa, 0, 0, &size);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr && size > 0) {
- // Allocate storage.
- scoped_array<uint8> input_list_storage(new uint8[size]);
- AudioBufferList& input_list =
- *reinterpret_cast<AudioBufferList*>(input_list_storage.get());
-
- result = AudioObjectGetPropertyData(
- device_,
- &pa,
- 0,
- 0,
- &size,
- &input_list);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr) {
- // Determine number of input channels.
- input_channels_per_frame_ = input_list.mNumberBuffers > 0 ?
- input_list.mBuffers[0].mNumberChannels : 0;
- if (input_channels_per_frame_ == 1 && input_list.mNumberBuffers > 1) {
- // Non-interleaved.
- input_channels_ = input_list.mNumberBuffers;
- } else {
- // Interleaved.
- input_channels_ = input_channels_per_frame_;
- }
- }
- }
-
- DVLOG(1) << "Input channels: " << input_channels_;
- DVLOG(1) << "Input channels per frame: " << input_channels_per_frame_;
-
- // The hardware must have at least the requested input channels.
- if (result != noErr || client_input_channels_ > input_channels_) {
- LOG(ERROR) << "AudioDevice does not support requested input channels.";
- return false;
- }
-
- // Get output stream configuration.
- pa.mSelector = kAudioDevicePropertyStreamConfiguration;
- pa.mScope = kAudioDevicePropertyScopeOutput;
- pa.mElement = kAudioObjectPropertyElementMaster;
-
- result = AudioObjectGetPropertyDataSize(device_, &pa, 0, 0, &size);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr && size > 0) {
- // Allocate storage.
- scoped_array<uint8> output_list_storage(new uint8[size]);
- AudioBufferList& output_list =
- *reinterpret_cast<AudioBufferList*>(output_list_storage.get());
-
- result = AudioObjectGetPropertyData(
- device_,
- &pa,
- 0,
- 0,
- &size,
- &output_list);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr) {
- // Determine number of output channels.
- output_channels_per_frame_ = output_list.mBuffers[0].mNumberChannels;
- if (output_channels_per_frame_ == 1 && output_list.mNumberBuffers > 1) {
- // Non-interleaved.
- output_channels_ = output_list.mNumberBuffers;
- } else {
- // Interleaved.
- output_channels_ = output_channels_per_frame_;
- }
- }
- }
-
- DVLOG(1) << "Output channels: " << output_channels_;
- DVLOG(1) << "Output channels per frame: " << output_channels_per_frame_;
-
- // The hardware must have at least the requested output channels.
- if (result != noErr ||
- output_channels_ < static_cast<int>(format_.mChannelsPerFrame)) {
- LOG(ERROR) << "AudioDevice does not support requested output channels.";
- return false;
- }
-
- // Setup the I/O proc.
- result = AudioDeviceCreateIOProcID(device_, RenderProc, this, &io_proc_id_);
- if (result != noErr) {
- LOG(ERROR) << "Error creating IOProc.";
- return false;
- }
-
- return true;
-}
-
-void AudioHardwareUnifiedStream::Close() {
- DCHECK(!is_playing_);
-
- OSStatus result = AudioDeviceDestroyIOProcID(device_, io_proc_id_);
- OSSTATUS_DCHECK(result == noErr, result);
-
- io_proc_id_ = 0;
- device_ = kAudioObjectUnknown;
-
- // Inform the audio manager that we have been closed. This can cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void AudioHardwareUnifiedStream::Start(AudioSourceCallback* callback) {
- DCHECK(callback);
- DCHECK_NE(device_, kAudioObjectUnknown);
- DCHECK(!is_playing_);
- if (device_ == kAudioObjectUnknown || is_playing_)
- return;
-
- source_ = callback;
-
- OSStatus result = AudioDeviceStart(device_, io_proc_id_);
- OSSTATUS_DCHECK(result == noErr, result);
-
- if (result == noErr)
- is_playing_ = true;
-}
-
-void AudioHardwareUnifiedStream::Stop() {
- if (!is_playing_)
- return;
-
- if (device_ != kAudioObjectUnknown) {
- OSStatus result = AudioDeviceStop(device_, io_proc_id_);
- OSSTATUS_DCHECK(result == noErr, result);
- }
-
- is_playing_ = false;
- source_ = NULL;
-}
-
-void AudioHardwareUnifiedStream::SetVolume(double volume) {
- volume_ = static_cast<float>(volume);
- // TODO(crogers): set volume property
-}
-
-void AudioHardwareUnifiedStream::GetVolume(double* volume) {
- *volume = volume_;
-}
-
-// Pulls on our provider with optional input, asking it to render output.
-// Note to future hackers of this function: Do not add locks here because this
-// is running on a real-time thread (for low-latency).
-OSStatus AudioHardwareUnifiedStream::Render(
- AudioDeviceID device,
- const AudioTimeStamp* now,
- const AudioBufferList* input_data,
- const AudioTimeStamp* input_time,
- AudioBufferList* output_data,
- const AudioTimeStamp* output_time) {
- // Convert the input data accounting for possible interleaving.
- // TODO(crogers): it's better to simply memcpy() if source is already planar.
- if (input_channels_ >= client_input_channels_) {
- for (int channel_index = 0; channel_index < client_input_channels_;
- ++channel_index) {
- float* source;
-
- int source_channel_index = channel_index;
-
- if (input_channels_per_frame_ > 1) {
- // Interleaved.
- source = static_cast<float*>(input_data->mBuffers[0].mData) +
- source_channel_index;
- } else {
- // Non-interleaved.
- source = static_cast<float*>(
- input_data->mBuffers[source_channel_index].mData);
- }
-
- float* p = input_bus_->channel(channel_index);
- for (int i = 0; i < number_of_frames_; ++i) {
- p[i] = *source;
- source += input_channels_per_frame_;
- }
- }
- } else if (input_channels_) {
- input_bus_->Zero();
- }
-
- // Give the client optional input data and have it render the output data.
- source_->OnMoreIOData(input_bus_.get(),
- output_bus_.get(),
- AudioBuffersState(0, 0));
-
- // TODO(crogers): handle final Core Audio 5.1 layout for 5.1 audio.
-
- // Handle interleaving as necessary.
- // TODO(crogers): it's better to simply memcpy() if dest is already planar.
-
- for (int channel_index = 0;
- channel_index < static_cast<int>(format_.mChannelsPerFrame);
- ++channel_index) {
- float* dest;
-
- int dest_channel_index = channel_index;
-
- if (output_channels_per_frame_ > 1) {
- // Interleaved.
- dest = static_cast<float*>(output_data->mBuffers[0].mData) +
- dest_channel_index;
- } else {
- // Non-interleaved.
- dest = static_cast<float*>(
- output_data->mBuffers[dest_channel_index].mData);
- }
-
- float* p = output_bus_->channel(channel_index);
- for (int i = 0; i < number_of_frames_; ++i) {
- *dest = p[i];
- dest += output_channels_per_frame_;
- }
- }
-
- return noErr;
-}
-
-OSStatus AudioHardwareUnifiedStream::RenderProc(
- AudioDeviceID device,
- const AudioTimeStamp* now,
- const AudioBufferList* input_data,
- const AudioTimeStamp* input_time,
- AudioBufferList* output_data,
- const AudioTimeStamp* output_time,
- void* user_data) {
- AudioHardwareUnifiedStream* audio_output =
- static_cast<AudioHardwareUnifiedStream*>(user_data);
- DCHECK(audio_output);
- if (!audio_output)
- return -1;
-
- return audio_output->Render(
- device,
- now,
- input_data,
- input_time,
- output_data,
- output_time);
-}
-
-} // namespace media
diff --git a/src/media/audio/mac/audio_unified_mac.h b/src/media/audio/mac/audio_unified_mac.h
deleted file mode 100644
index ff090e3..0000000
--- a/src/media/audio/mac/audio_unified_mac.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MAC_AUDIO_UNIFIED_MAC_H_
-#define MEDIA_AUDIO_MAC_AUDIO_UNIFIED_MAC_H_
-
-#include <CoreAudio/CoreAudio.h>
-
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerMac;
-
-// Implementation of AudioOutputStream for Mac OS X using the
-// CoreAudio AudioHardware API suitable for low-latency unified audio I/O
-// when using devices which support *both* input and output
-// in the same driver. This is the case with professional
-// USB and Firewire devices.
-//
-// Please note that it's required to first get the native sample-rate of the
-// default output device and use that sample-rate when creating this object.
-class AudioHardwareUnifiedStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- AudioHardwareUnifiedStream(AudioManagerMac* manager,
- const AudioParameters& params);
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~AudioHardwareUnifiedStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- int input_channels() const { return input_channels_; }
- int output_channels() const { return output_channels_; }
-
- private:
- OSStatus Render(AudioDeviceID device,
- const AudioTimeStamp* now,
- const AudioBufferList* input_data,
- const AudioTimeStamp* input_time,
- AudioBufferList* output_data,
- const AudioTimeStamp* output_time);
-
- static OSStatus RenderProc(AudioDeviceID device,
- const AudioTimeStamp* now,
- const AudioBufferList* input_data,
- const AudioTimeStamp* input_time,
- AudioBufferList* output_data,
- const AudioTimeStamp* output_time,
- void* user_data);
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerMac* manager_;
-
- // Pointer to the object that will provide the audio samples.
- AudioSourceCallback* source_;
-
- // Structure that holds the stream format details such as bitrate.
- AudioStreamBasicDescription format_;
-
- // Hardware buffer size.
- int number_of_frames_;
-
- // Number of audio channels provided to the client via OnMoreIOData().
- int client_input_channels_;
-
- // Volume level from 0 to 1.
- float volume_;
-
- // Number of input and output channels queried from the hardware.
- int input_channels_;
- int output_channels_;
- int input_channels_per_frame_;
- int output_channels_per_frame_;
-
- AudioDeviceIOProcID io_proc_id_;
- AudioDeviceID device_;
- bool is_playing_;
-
- // Intermediate buffers used with call to OnMoreIOData().
- scoped_ptr<AudioBus> input_bus_;
- scoped_ptr<AudioBus> output_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioHardwareUnifiedStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MAC_AUDIO_UNIFIED_MAC_H_
diff --git a/src/media/audio/mock_audio_manager.cc b/src/media/audio/mock_audio_manager.cc
deleted file mode 100644
index 9a40b65..0000000
--- a/src/media/audio/mock_audio_manager.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/mock_audio_manager.h"
-
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-
-namespace media {
-
-MockAudioManager::MockAudioManager(base::MessageLoopProxy* message_loop_proxy)
- : message_loop_proxy_(message_loop_proxy) {
-}
-
-MockAudioManager::~MockAudioManager() {
-}
-
-bool MockAudioManager::HasAudioOutputDevices() {
- return true;
-}
-
-bool MockAudioManager::HasAudioInputDevices() {
- return true;
-}
-
-string16 MockAudioManager::GetAudioInputDeviceModel() {
- return string16();
-}
-
-bool MockAudioManager::CanShowAudioInputSettings() {
- return false;
-}
-
-void MockAudioManager::ShowAudioInputSettings() {
-}
-
-void MockAudioManager::GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) {
-}
-
-media::AudioOutputStream* MockAudioManager::MakeAudioOutputStream(
- const media::AudioParameters& params) {
- NOTREACHED();
- return NULL;
-}
-
-media::AudioOutputStream* MockAudioManager::MakeAudioOutputStreamProxy(
- const media::AudioParameters& params) {
- NOTREACHED();
- return NULL;
-}
-
-media::AudioInputStream* MockAudioManager::MakeAudioInputStream(
- const media::AudioParameters& params,
- const std::string& device_id) {
- NOTREACHED();
- return NULL;
-}
-
-bool MockAudioManager::IsRecordingInProcess() {
- return false;
-}
-
-scoped_refptr<base::MessageLoopProxy> MockAudioManager::GetMessageLoop() {
- return message_loop_proxy_;
-}
-
-void MockAudioManager::AddOutputDeviceChangeListener(
- AudioDeviceListener* listener) {
-}
-
-void MockAudioManager::RemoveOutputDeviceChangeListener(
- AudioDeviceListener* listener) {
-}
-
-} // namespace media.
diff --git a/src/media/audio/mock_audio_manager.h b/src/media/audio/mock_audio_manager.h
deleted file mode 100644
index 6a94055..0000000
--- a/src/media/audio/mock_audio_manager.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
-#define MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
-
-#include "media/audio/audio_manager.h"
-
-namespace media {
-
-// This class is a simple mock around AudioManager, used exclusively for tests,
-// which has the following purposes:
-// 1) Avoids to use the actual (system and platform dependent) AudioManager.
-// Some bots does not have input devices, thus using the actual AudioManager
-// would causing failures on classes which expect that.
-// 2) Allows the mock audio events to be dispatched on an arbitrary thread,
-// rather than forcing them on the audio thread, easing their handling in
-// browser tests (Note: sharing a thread can cause deadlocks on production
-// classes if WaitableEvents or any other form of lock is used for
-// synchronization purposes).
-class MockAudioManager : public media::AudioManager {
- public:
- explicit MockAudioManager(base::MessageLoopProxy* message_loop_proxy);
-
- virtual bool HasAudioOutputDevices() OVERRIDE;
-
- virtual bool HasAudioInputDevices() OVERRIDE;
-
- virtual string16 GetAudioInputDeviceModel() OVERRIDE;
-
- virtual bool CanShowAudioInputSettings() OVERRIDE;
-
- virtual void ShowAudioInputSettings() OVERRIDE;
-
- virtual void GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) OVERRIDE;
-
- virtual media::AudioOutputStream* MakeAudioOutputStream(
- const media::AudioParameters& params) OVERRIDE;
-
- virtual media::AudioOutputStream* MakeAudioOutputStreamProxy(
- const media::AudioParameters& params) OVERRIDE;
-
- virtual media::AudioInputStream* MakeAudioInputStream(
- const media::AudioParameters& params,
- const std::string& device_id) OVERRIDE;
-
- virtual bool IsRecordingInProcess() OVERRIDE;
-
- virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE;
-
- virtual void AddOutputDeviceChangeListener(
- AudioDeviceListener* listener) OVERRIDE;
- virtual void RemoveOutputDeviceChangeListener(
- AudioDeviceListener* listener) OVERRIDE;
-
- private:
- virtual ~MockAudioManager();
-
- scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
-
- DISALLOW_COPY_AND_ASSIGN(MockAudioManager);
-};
-
-} // namespace media.
-
-#endif // MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
diff --git a/src/media/audio/mock_shell_audio_streamer.h b/src/media/audio/mock_shell_audio_streamer.h
deleted file mode 100644
index b6f2325..0000000
--- a/src/media/audio/mock_shell_audio_streamer.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_MOCK_SHELL_AUDIO_STREAMER_H_
-#define MEDIA_AUDIO_MOCK_SHELL_AUDIO_STREAMER_H_
-
-#include "media/audio/shell_audio_streamer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class MockShellAudioStream : public ShellAudioStream {
- public:
- MockShellAudioStream() {}
- MOCK_CONST_METHOD0(PauseRequested, bool ());
- MOCK_METHOD2(PullFrames, bool (uint32_t*, uint32_t*));
- MOCK_METHOD1(ConsumeFrames, void (uint32_t));
- MOCK_CONST_METHOD0(GetAudioParameters, const AudioParameters& ());
- MOCK_METHOD0(GetAudioBus, AudioBus* ());
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockShellAudioStream);
-};
-
-class MockShellAudioStreamer : public ShellAudioStreamer {
- public:
- MockShellAudioStreamer() {}
- MOCK_CONST_METHOD0(GetConfig, Config ());
- MOCK_METHOD1(AddStream, bool (ShellAudioStream*));
- MOCK_METHOD1(RemoveStream, void (ShellAudioStream*));
- MOCK_CONST_METHOD1(HasStream, bool (ShellAudioStream*));
- MOCK_METHOD2(SetVolume, bool (ShellAudioStream*, double));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockShellAudioStreamer);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_MOCK_SHELL_AUDIO_STREAMER_H_
-
diff --git a/src/media/audio/null_audio_sink.cc b/src/media/audio/null_audio_sink.cc
deleted file mode 100644
index c93ceb1..0000000
--- a/src/media/audio/null_audio_sink.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/null_audio_sink.h"
-
-#include "base/bind.h"
-#include "base/stringprintf.h"
-#include "base/sys_byteorder.h"
-#include "base/threading/platform_thread.h"
-
-namespace media {
-
-NullAudioSink::NullAudioSink()
- : initialized_(false),
- playing_(false),
- callback_(NULL),
- thread_("NullAudioThread"),
- hash_audio_for_testing_(false) {
-}
-
-void NullAudioSink::Initialize(const AudioParameters& params,
- RenderCallback* callback) {
- DCHECK(!initialized_);
- params_ = params;
-
- audio_bus_ = AudioBus::Create(params_);
-
- if (hash_audio_for_testing_) {
- md5_channel_contexts_.reset(new base::MD5Context[params_.channels()]);
- for (int i = 0; i < params_.channels(); i++)
- base::MD5Init(&md5_channel_contexts_[i]);
- }
-
- callback_ = callback;
- initialized_ = true;
-}
-
-void NullAudioSink::Start() {
- if (!thread_.Start())
- return;
-
- thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
- &NullAudioSink::FillBufferTask, this));
-}
-
-void NullAudioSink::Stop() {
- SetPlaying(false);
- thread_.Stop();
-}
-
-void NullAudioSink::Play() {
- SetPlaying(true);
-}
-
-void NullAudioSink::Pause(bool /* flush */) {
- SetPlaying(false);
-}
-
-bool NullAudioSink::SetVolume(double volume) {
- // Audio is always muted.
- return volume == 0.0;
-}
-
-void NullAudioSink::SetPlaying(bool is_playing) {
- base::AutoLock auto_lock(lock_);
- playing_ = is_playing;
-}
-
-NullAudioSink::~NullAudioSink() {
- DCHECK(!thread_.IsRunning());
-}
-
-void NullAudioSink::FillBufferTask() {
- base::AutoLock auto_lock(lock_);
-
- base::TimeDelta delay;
- // Only consume buffers when actually playing.
- if (playing_) {
- int frames_received = callback_->Render(audio_bus_.get(), 0);
- int frames_per_millisecond =
- params_.sample_rate() / base::Time::kMillisecondsPerSecond;
-
- if (hash_audio_for_testing_ && frames_received > 0) {
- DCHECK_EQ(sizeof(float), sizeof(uint32));
- int channels = audio_bus_->channels();
- for (int channel_idx = 0; channel_idx < channels; ++channel_idx) {
- float* channel = audio_bus_->channel(channel_idx);
- for (int frame_idx = 0; frame_idx < frames_received; frame_idx++) {
- // Convert float to uint32 w/o conversion loss.
- uint32 frame = base::ByteSwapToLE32(
- bit_cast<uint32>(channel[frame_idx]));
- base::MD5Update(
- &md5_channel_contexts_[channel_idx], base::StringPiece(
- reinterpret_cast<char*>(&frame), sizeof(frame)));
- }
- }
- }
-
- // Calculate our sleep duration.
- delay = base::TimeDelta::FromMilliseconds(
- frames_received / frames_per_millisecond);
- } else {
- // If paused, sleep for 10 milliseconds before polling again.
- delay = base::TimeDelta::FromMilliseconds(10);
- }
-
- // Sleep for at least one millisecond so we don't spin the CPU.
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&NullAudioSink::FillBufferTask, this),
- std::max(delay, base::TimeDelta::FromMilliseconds(1)));
-}
-
-void NullAudioSink::StartAudioHashForTesting() {
- DCHECK(!initialized_);
- hash_audio_for_testing_ = true;
-}
-
-std::string NullAudioSink::GetAudioHashForTesting() {
- DCHECK(hash_audio_for_testing_);
-
- // If initialize failed or was never called, ensure we return an empty hash.
- int channels = 1;
- if (!initialized_) {
- md5_channel_contexts_.reset(new base::MD5Context[1]);
- base::MD5Init(&md5_channel_contexts_[0]);
- } else {
- channels = audio_bus_->channels();
- }
-
- // Hash all channels into the first channel.
- base::MD5Digest digest;
- for (int i = 1; i < channels; i++) {
- base::MD5Final(&digest, &md5_channel_contexts_[i]);
- base::MD5Update(&md5_channel_contexts_[0], base::StringPiece(
- reinterpret_cast<char*>(&digest), sizeof(base::MD5Digest)));
- }
-
- base::MD5Final(&digest, &md5_channel_contexts_[0]);
- return base::MD5DigestToBase16(digest);
-}
-
-} // namespace media
diff --git a/src/media/audio/null_audio_sink.h b/src/media/audio/null_audio_sink.h
deleted file mode 100644
index 7d73f93..0000000
--- a/src/media/audio/null_audio_sink.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_NULL_AUDIO_RENDERER_H_
-#define MEDIA_FILTERS_NULL_AUDIO_RENDERER_H_
-
-// NullAudioSink effectively uses an extra thread to "throw away" the
-// audio data at a rate resembling normal playback speed. It's just like
-// decoding to /dev/null!
-//
-// NullAudioSink can also be used in situations where the client has no
-// audio device or we haven't written an audio implementation for a particular
-// platform yet.
-
-#include "base/md5.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/threading/thread.h"
-#include "media/base/audio_renderer_sink.h"
-
-namespace media {
-class AudioBus;
-
-class MEDIA_EXPORT NullAudioSink
- : NON_EXPORTED_BASE(public AudioRendererSink) {
- public:
- NullAudioSink();
-
- // AudioRendererSink implementation.
- virtual void Initialize(const AudioParameters& params,
- RenderCallback* callback) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Pause(bool flush) OVERRIDE;
- virtual void Play() OVERRIDE;
- virtual bool SetVolume(double volume) OVERRIDE;
- virtual void ResumeAfterUnderflow(bool buffer_more_audio) OVERRIDE {}
-
- // Enables audio frame hashing and reinitializes the MD5 context. Must be
- // called prior to Initialize().
- void StartAudioHashForTesting();
-
- // Returns the MD5 hash of all audio frames seen since the last reset.
- std::string GetAudioHashForTesting();
-
- protected:
- virtual ~NullAudioSink();
-
- private:
- // Audio thread task that periodically calls FillBuffer() to consume
- // audio data.
- void FillBufferTask();
-
- void SetPlaying(bool is_playing);
-
- // A buffer passed to FillBuffer to advance playback.
- scoped_ptr<AudioBus> audio_bus_;
-
- AudioParameters params_;
- bool initialized_;
- bool playing_;
- RenderCallback* callback_;
-
- // Separate thread used to throw away data.
- base::Thread thread_;
- base::Lock lock_;
-
- // Controls whether or not a running MD5 hash is computed for audio frames.
- bool hash_audio_for_testing_;
- scoped_array<base::MD5Context> md5_channel_contexts_;
-
- DISALLOW_COPY_AND_ASSIGN(NullAudioSink);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_NULL_AUDIO_RENDERER_H_
diff --git a/src/media/audio/null_audio_streamer.cc b/src/media/audio/null_audio_streamer.cc
deleted file mode 100644
index 0937b17..0000000
--- a/src/media/audio/null_audio_streamer.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/audio/null_audio_streamer.h"
-
-#include <algorithm>
-
-#include "media/audio/audio_parameters.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-NullAudioStreamer::NullAudioStreamer()
- : null_streamer_thread_("Null Audio Streamer") {
- null_streamer_thread_.Start();
- null_streamer_thread_.message_loop()->PostTask(
- FROM_HERE, base::Bind(&NullAudioStreamer::StartNullStreamer,
- base::Unretained(this)));
-}
-
-NullAudioStreamer::~NullAudioStreamer() {
- null_streamer_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&NullAudioStreamer::StopNullStreamer, base::Unretained(this)));
- null_streamer_thread_.Stop();
-}
-
-ShellAudioStreamer::Config NullAudioStreamer::GetConfig() const {
- // Reasonable looking settings.
- const uint32 initial_rebuffering_frames_per_channel =
- mp4::AAC::kFramesPerAccessUnit * 32;
- const uint32 sink_buffer_size_in_frames_per_channel =
- initial_rebuffering_frames_per_channel * 8;
- const uint32 max_hardware_channels = 2;
-
- return Config(Config::INTERLEAVED, initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, max_hardware_channels,
- sizeof(float) /* bytes_per_sample */);
-}
-
-bool NullAudioStreamer::AddStream(ShellAudioStream* stream) {
- base::AutoLock auto_lock(streams_lock_);
- streams_.insert(std::make_pair(stream, NullAudioStream()));
- return true;
-}
-
-void NullAudioStreamer::RemoveStream(ShellAudioStream* stream) {
- base::AutoLock auto_lock(streams_lock_);
- DLOG(INFO) << "Remove";
- streams_.erase(stream);
-}
-
-bool NullAudioStreamer::HasStream(ShellAudioStream* stream) const {
- base::AutoLock auto_lock(streams_lock_);
- return streams_.find(stream) != streams_.end();
-}
-
-void NullAudioStreamer::StartNullStreamer() {
- last_run_time_ = base::Time::Now();
- advance_streams_timer_.emplace();
- advance_streams_timer_->Start(
- FROM_HERE, base::TimeDelta::FromMilliseconds(10),
- base::Bind(&NullAudioStreamer::AdvanceStreams, base::Unretained(this)));
-}
-
-void NullAudioStreamer::StopNullStreamer() {
- advance_streams_timer_->Stop();
- advance_streams_timer_ = base::nullopt;
-}
-
-void NullAudioStreamer::AdvanceStreams() {
- base::Time now = base::Time::Now();
- base::TimeDelta time_played = now - last_run_time_;
- last_run_time_ = now;
-
- base::AutoLock auto_lock(streams_lock_);
- for (NullAudioStreamMap::iterator it = streams_.begin(); it != streams_.end();
- ++it) {
- PullFrames(it->first, time_played, &it->second);
- }
-}
-
-void NullAudioStreamer::PullFrames(ShellAudioStream* stream,
- base::TimeDelta time_played,
- NullAudioStream* null_stream) {
- // Calculate how many frames were consumed.
- int sample_rate = stream->GetAudioParameters().sample_rate();
- uint32 frames_played = sample_rate * time_played.InSecondsF();
- frames_played = std::min(frames_played, null_stream->total_available_frames);
- if (!stream->PauseRequested()) {
- stream->ConsumeFrames(frames_played);
- }
- // Pull more frames.
- stream->PullFrames(NULL, &null_stream->total_available_frames);
-}
-
-} // namespace media
diff --git a/src/media/audio/null_audio_streamer.h b/src/media/audio/null_audio_streamer.h
deleted file mode 100644
index 5342f23..0000000
--- a/src/media/audio/null_audio_streamer.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_NULL_AUDIO_STREAMER_H_
-#define MEDIA_AUDIO_NULL_AUDIO_STREAMER_H_
-
-#include <map>
-
-#include "base/memory/singleton.h"
-#include "base/optional.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread.h"
-#include "base/time.h"
-#include "base/timer.h"
-#include "media/audio/shell_audio_streamer.h"
-
-namespace media {
-
-// This class implements a ShellAudioStreamer to be used when output to an
-// audio device is not possible or not desired. It starts a thread that will
-// regularly pull and consume frames from any added ShellAudioStreams at a rate
-// expected by the stream's sampling frequency.
-class NullAudioStreamer : public ShellAudioStreamer {
- public:
- static NullAudioStreamer* GetInstance() {
- return Singleton<NullAudioStreamer>::get();
- }
-
- Config GetConfig() const OVERRIDE;
- bool AddStream(ShellAudioStream* stream) OVERRIDE;
- void RemoveStream(ShellAudioStream* stream) OVERRIDE;
- bool HasStream(ShellAudioStream* stream) const OVERRIDE;
- bool SetVolume(ShellAudioStream* /* stream */, double /* volume*/) OVERRIDE {
- return true;
- }
-
- private:
- struct NullAudioStream {
- NullAudioStream() : total_available_frames(0) {}
- uint32 total_available_frames;
- };
-
- NullAudioStreamer();
- ~NullAudioStreamer() OVERRIDE;
-
- void StartNullStreamer();
- void StopNullStreamer();
- void AdvanceStreams();
- void PullFrames(ShellAudioStream* stream,
- base::TimeDelta time_played,
- NullAudioStream* null_audio_stream);
-
- base::Thread null_streamer_thread_;
- typedef base::RepeatingTimer<NullAudioStreamer> RepeatingTimer;
- base::optional<RepeatingTimer> advance_streams_timer_;
- base::Time last_run_time_;
-
- mutable base::Lock streams_lock_;
- typedef std::map<ShellAudioStream*, NullAudioStream> NullAudioStreamMap;
- NullAudioStreamMap streams_;
-
- DISALLOW_COPY_AND_ASSIGN(NullAudioStreamer);
- friend struct DefaultSingletonTraits<NullAudioStreamer>;
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_NULL_AUDIO_STREAMER_H_
diff --git a/src/media/audio/openbsd/audio_manager_openbsd.cc b/src/media/audio/openbsd/audio_manager_openbsd.cc
deleted file mode 100644
index 84304a5..0000000
--- a/src/media/audio/openbsd/audio_manager_openbsd.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/openbsd/audio_manager_openbsd.h"
-
-#include "base/command_line.h"
-#include "base/stl_util.h"
-#include "media/audio/audio_output_dispatcher.h"
-#if defined(USE_PULSEAUDIO)
-#include "media/audio/pulse/pulse_output.h"
-#endif
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-#include <fcntl.h>
-
-namespace media {
-
-// Maximum number of output streams that can be open simultaneously.
-static const int kMaxOutputStreams = 50;
-
-// Implementation of AudioManager.
-static bool HasAudioHardware() {
- int fd;
- const char *file;
-
- if ((file = getenv("AUDIOCTLDEVICE")) == 0 || *file == '\0')
- file = "/dev/audioctl";
-
- if ((fd = open(file, O_RDONLY)) < 0)
- return false;
-
- close(fd);
- return true;
-}
-
-bool AudioManagerOpenBSD::HasAudioOutputDevices() {
- return HasAudioHardware();
-}
-
-bool AudioManagerOpenBSD::HasAudioInputDevices() {
- return HasAudioHardware();
-}
-
-AudioManagerOpenBSD::AudioManagerOpenBSD() {
- SetMaxOutputStreamsAllowed(kMaxOutputStreams);
-}
-
-AudioManagerOpenBSD::~AudioManagerOpenBSD() {
- Shutdown();
-}
-
-AudioOutputStream* AudioManagerOpenBSD::MakeLinearOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format);
- return MakeOutputStream(params);
-}
-
-AudioOutputStream* AudioManagerOpenBSD::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format);
- return MakeOutputStream(params);
-}
-
-AudioInputStream* AudioManagerOpenBSD::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format);
- NOTIMPLEMENTED();
- return NULL;
-}
-
-AudioInputStream* AudioManagerOpenBSD::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format);
- NOTIMPLEMENTED();
- return NULL;
-}
-
-AudioOutputStream* AudioManagerOpenBSD::MakeOutputStream(
- const AudioParameters& params) {
-#if defined(USE_PULSEAUDIO)
- if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUsePulseAudio)) {
- return new PulseAudioOutputStream(params, this);
- }
-#endif
-
- NOTIMPLEMENTED();
- return NULL;
-}
-
-// static
-AudioManager* CreateAudioManager() {
- return new AudioManagerOpenBSD();
-}
-
-} // namespace media
diff --git a/src/media/audio/openbsd/audio_manager_openbsd.h b/src/media/audio/openbsd/audio_manager_openbsd.h
deleted file mode 100644
index aeba43e..0000000
--- a/src/media/audio/openbsd/audio_manager_openbsd.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_OPENBSD_AUDIO_MANAGER_OPENBSD_H_
-#define MEDIA_AUDIO_OPENBSD_AUDIO_MANAGER_OPENBSD_H_
-
-#include <set>
-
-#include "base/compiler_specific.h"
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-class MEDIA_EXPORT AudioManagerOpenBSD : public AudioManagerBase {
- public:
- AudioManagerOpenBSD();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
-
- protected:
- virtual ~AudioManagerOpenBSD();
-
- private:
- // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream.
- AudioOutputStream* MakeOutputStream(const AudioParameters& params);
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerOpenBSD);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_OPENBSD_AUDIO_MANAGER_OPENBSD_H_
diff --git a/src/media/audio/pulse/pulse_output.cc b/src/media/audio/pulse/pulse_output.cc
deleted file mode 100644
index 0687e6e..0000000
--- a/src/media/audio/pulse/pulse_output.cc
+++ /dev/null
@@ -1,452 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/pulse/pulse_output.h"
-
-#include <pulse/pulseaudio.h>
-
-#include "base/message_loop.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/audio_util.h"
-
-namespace media {
-
-// A helper class that acquires pa_threaded_mainloop_lock() while in scope.
-class AutoPulseLock {
- public:
- explicit AutoPulseLock(pa_threaded_mainloop* pa_mainloop)
- : pa_mainloop_(pa_mainloop) {
- pa_threaded_mainloop_lock(pa_mainloop_);
- }
-
- ~AutoPulseLock() {
- pa_threaded_mainloop_unlock(pa_mainloop_);
- }
-
- private:
- pa_threaded_mainloop* pa_mainloop_;
-
- DISALLOW_COPY_AND_ASSIGN(AutoPulseLock);
-};
-
-static pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) {
- switch (bits_per_sample) {
- case 8:
- return PA_SAMPLE_U8;
- case 16:
- return PA_SAMPLE_S16LE;
- case 24:
- return PA_SAMPLE_S24LE;
- case 32:
- return PA_SAMPLE_S32LE;
- default:
- NOTREACHED() << "Invalid bits per sample: " << bits_per_sample;
- return PA_SAMPLE_INVALID;
- }
-}
-
-static pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
- switch (channel) {
- // PulseAudio does not differentiate between left/right and
- // stereo-left/stereo-right, both translate to front-left/front-right.
- case LEFT:
- return PA_CHANNEL_POSITION_FRONT_LEFT;
- case RIGHT:
- return PA_CHANNEL_POSITION_FRONT_RIGHT;
- case CENTER:
- return PA_CHANNEL_POSITION_FRONT_CENTER;
- case LFE:
- return PA_CHANNEL_POSITION_LFE;
- case BACK_LEFT:
- return PA_CHANNEL_POSITION_REAR_LEFT;
- case BACK_RIGHT:
- return PA_CHANNEL_POSITION_REAR_RIGHT;
- case LEFT_OF_CENTER:
- return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
- case RIGHT_OF_CENTER:
- return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
- case BACK_CENTER:
- return PA_CHANNEL_POSITION_REAR_CENTER;
- case SIDE_LEFT:
- return PA_CHANNEL_POSITION_SIDE_LEFT;
- case SIDE_RIGHT:
- return PA_CHANNEL_POSITION_SIDE_RIGHT;
- case CHANNELS_MAX:
- return PA_CHANNEL_POSITION_INVALID;
- default:
- NOTREACHED() << "Invalid channel: " << channel;
- return PA_CHANNEL_POSITION_INVALID;
- }
-}
-
-static pa_channel_map ChannelLayoutToPAChannelMap(
- ChannelLayout channel_layout) {
- pa_channel_map channel_map;
- pa_channel_map_init(&channel_map);
-
- channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
- for (Channels ch = LEFT; ch < CHANNELS_MAX;
- ch = static_cast<Channels>(ch + 1)) {
- int channel_index = ChannelOrder(channel_layout, ch);
- if (channel_index < 0)
- continue;
-
- channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch);
- }
-
- return channel_map;
-}
-
-// static, pa_context_notify_cb
-void PulseAudioOutputStream::ContextNotifyCallback(pa_context* c,
- void* p_this) {
- PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
-
- // Forward unexpected failures to the AudioSourceCallback if available. All
- // these variables are only modified under pa_threaded_mainloop_lock() so this
- // should be thread safe.
- if (c && stream->source_callback_ &&
- pa_context_get_state(c) == PA_CONTEXT_FAILED) {
- stream->source_callback_->OnError(stream, pa_context_errno(c));
- }
-
- pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
-}
-
-// static, pa_stream_notify_cb
-void PulseAudioOutputStream::StreamNotifyCallback(pa_stream* s, void* p_this) {
- PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
-
- // Forward unexpected failures to the AudioSourceCallback if available. All
- // these variables are only modified under pa_threaded_mainloop_lock() so this
- // should be thread safe.
- if (s && stream->source_callback_ &&
- pa_stream_get_state(s) == PA_STREAM_FAILED) {
- stream->source_callback_->OnError(
- stream, pa_context_errno(stream->pa_context_));
- }
-
- pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
-}
-
-// static, pa_stream_success_cb_t
-void PulseAudioOutputStream::StreamSuccessCallback(pa_stream* s, int success,
- void* p_this) {
- PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
- pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
-}
-
-// static, pa_stream_request_cb_t
-void PulseAudioOutputStream::StreamRequestCallback(pa_stream* s, size_t len,
- void* p_this) {
- // Fulfill write request; must always result in a pa_stream_write() call.
- static_cast<PulseAudioOutputStream*>(p_this)->FulfillWriteRequest(len);
-}
-
-PulseAudioOutputStream::PulseAudioOutputStream(const AudioParameters& params,
- AudioManagerBase* manager)
- : params_(params),
- manager_(manager),
- pa_context_(NULL),
- pa_mainloop_(NULL),
- pa_stream_(NULL),
- volume_(1.0f),
- source_callback_(NULL) {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- CHECK(params_.IsValid());
- audio_bus_ = AudioBus::Create(params_);
-}
-
-PulseAudioOutputStream::~PulseAudioOutputStream() {
- // All internal structures should already have been freed in Close(), which
- // calls AudioManagerBase::ReleaseOutputStream() which deletes this object.
- DCHECK(!pa_stream_);
- DCHECK(!pa_context_);
- DCHECK(!pa_mainloop_);
-}
-
-// Helper macro for Open() to avoid code spam and string bloat.
-#define RETURN_ON_FAILURE(expression, message) do { \
- if (!(expression)) { \
- if (pa_context_) { \
- DLOG(ERROR) << message << " Error: " \
- << pa_strerror(pa_context_errno(pa_context_)); \
- } else { \
- DLOG(ERROR) << message; \
- } \
- return false; \
- } \
-} while(0)
-
-bool PulseAudioOutputStream::Open() {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- pa_mainloop_ = pa_threaded_mainloop_new();
- RETURN_ON_FAILURE(pa_mainloop_, "Failed to create PulseAudio main loop.");
-
- pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(pa_mainloop_);
- pa_context_ = pa_context_new(pa_mainloop_api, "Chromium");
- RETURN_ON_FAILURE(pa_context_, "Failed to create PulseAudio context.");
-
- // A state callback must be set before calling pa_threaded_mainloop_lock() or
- // pa_threaded_mainloop_wait() calls may lead to dead lock.
- pa_context_set_state_callback(pa_context_, &ContextNotifyCallback, this);
-
- // Lock the main loop while setting up the context. Failure to do so may lead
- // to crashes as the PulseAudio thread tries to run before things are ready.
- AutoPulseLock auto_lock(pa_mainloop_);
-
- RETURN_ON_FAILURE(
- pa_threaded_mainloop_start(pa_mainloop_) == 0,
- "Failed to start PulseAudio main loop.");
- RETURN_ON_FAILURE(
- pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0,
- "Failed to connect PulseAudio context.");
-
- // Wait until |pa_context_| is ready. pa_threaded_mainloop_wait() must be
- // called after pa_context_get_state() in case the context is already ready,
- // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
- while (true) {
- pa_context_state_t context_state = pa_context_get_state(pa_context_);
- RETURN_ON_FAILURE(
- PA_CONTEXT_IS_GOOD(context_state), "Invalid PulseAudio context state.");
- if (context_state == PA_CONTEXT_READY)
- break;
- pa_threaded_mainloop_wait(pa_mainloop_);
- }
-
- // Set sample specifications.
- pa_sample_spec pa_sample_specifications;
- pa_sample_specifications.format = BitsToPASampleFormat(
- params_.bits_per_sample());
- pa_sample_specifications.rate = params_.sample_rate();
- pa_sample_specifications.channels = params_.channels();
-
- // Get channel mapping and open playback stream.
- pa_channel_map* map = NULL;
- pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
- params_.channel_layout());
- if (source_channel_map.channels != 0) {
- // The source data uses a supported channel map so we will use it rather
- // than the default channel map (NULL).
- map = &source_channel_map;
- }
- pa_stream_ = pa_stream_new(
- pa_context_, "Playback", &pa_sample_specifications, map);
- RETURN_ON_FAILURE(pa_stream_, "Failed to create PulseAudio stream.");
- pa_stream_set_state_callback(pa_stream_, &StreamNotifyCallback, this);
-
- // Even though we start the stream corked below, PulseAudio will issue one
- // stream request after setup. FulfillWriteRequest() must fulfill the write.
- pa_stream_set_write_callback(pa_stream_, &StreamRequestCallback, this);
-
- // Tell pulse audio we only want callbacks of a certain size.
- pa_buffer_attr pa_buffer_attributes;
- pa_buffer_attributes.maxlength = params_.GetBytesPerBuffer();
- pa_buffer_attributes.minreq = params_.GetBytesPerBuffer();
- pa_buffer_attributes.prebuf = params_.GetBytesPerBuffer();
- pa_buffer_attributes.tlength = params_.GetBytesPerBuffer();
- pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
-
- // Connect playback stream.
- // TODO(dalecurtis): Pulse tends to want really large buffer sizes if we are
- // not using the native sample rate. We should always open the stream with
- // PA_STREAM_FIX_RATE and ensure this is true.
- RETURN_ON_FAILURE(
- pa_stream_connect_playback(
- pa_stream_, NULL, &pa_buffer_attributes,
- static_cast<pa_stream_flags_t>(
- PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE |
- PA_STREAM_NOT_MONOTONIC | PA_STREAM_START_CORKED),
- NULL, NULL) == 0,
- "Failed to connect PulseAudio stream.");
-
- // Wait for the stream to be ready.
- while (true) {
- pa_stream_state_t stream_state = pa_stream_get_state(pa_stream_);
- RETURN_ON_FAILURE(
- PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state.");
- if (stream_state == PA_STREAM_READY)
- break;
- pa_threaded_mainloop_wait(pa_mainloop_);
- }
-
- return true;
-}
-
-#undef RETURN_ON_FAILURE
-
-void PulseAudioOutputStream::Reset() {
- if (!pa_mainloop_) {
- DCHECK(!pa_stream_);
- DCHECK(!pa_context_);
- return;
- }
-
- {
- AutoPulseLock auto_lock(pa_mainloop_);
-
- // Close the stream.
- if (pa_stream_) {
- // Ensure all samples are played out before shutdown.
- WaitForPulseOperation(pa_stream_flush(
- pa_stream_, &StreamSuccessCallback, this));
-
- // Release PulseAudio structures.
- pa_stream_disconnect(pa_stream_);
- pa_stream_set_write_callback(pa_stream_, NULL, NULL);
- pa_stream_set_state_callback(pa_stream_, NULL, NULL);
- pa_stream_unref(pa_stream_);
- pa_stream_ = NULL;
- }
-
- if (pa_context_) {
- pa_context_disconnect(pa_context_);
- pa_context_set_state_callback(pa_context_, NULL, NULL);
- pa_context_unref(pa_context_);
- pa_context_ = NULL;
- }
- }
-
- pa_threaded_mainloop_stop(pa_mainloop_);
- pa_threaded_mainloop_free(pa_mainloop_);
- pa_mainloop_ = NULL;
-}
-
-void PulseAudioOutputStream::Close() {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- Reset();
-
- // Signal to the manager that we're closed and can be removed.
- // This should be the last call in the function as it deletes "this".
- manager_->ReleaseOutputStream(this);
-}
-
-int PulseAudioOutputStream::GetHardwareLatencyInBytes() {
- int negative = 0;
- pa_usec_t pa_latency_micros = 0;
- if (pa_stream_get_latency(pa_stream_, &pa_latency_micros, &negative) != 0)
- return 0;
-
- if (negative)
- return 0;
-
- return (pa_latency_micros * params_.sample_rate() *
- params_.GetBytesPerFrame()) / base::Time::kMicrosecondsPerSecond;
-}
-
-void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
- CHECK_EQ(requested_bytes, static_cast<size_t>(params_.GetBytesPerBuffer()));
-
- int frames_filled = 0;
- if (source_callback_) {
- frames_filled = source_callback_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(0, GetHardwareLatencyInBytes()));
- }
-
- // Zero any unfilled data so it plays back as silence.
- if (frames_filled < audio_bus_->frames()) {
- audio_bus_->ZeroFramesPartial(
- frames_filled, audio_bus_->frames() - frames_filled);
- }
-
- // PulseAudio won't always be able to provide a buffer large enough, so we may
- // need to request multiple buffers and fill them individually.
- int current_frame = 0;
- size_t bytes_remaining = requested_bytes;
- while (bytes_remaining > 0) {
- void* buffer = NULL;
- size_t bytes_to_fill = bytes_remaining;
- CHECK_GE(pa_stream_begin_write(pa_stream_, &buffer, &bytes_to_fill), 0);
-
- // In case PulseAudio gives us a bigger buffer than we want, cap our size.
- bytes_to_fill = std::min(
- std::min(bytes_remaining, bytes_to_fill),
- static_cast<size_t>(params_.GetBytesPerBuffer()));
-
- int frames_to_fill = bytes_to_fill / params_.GetBytesPerFrame();;
-
- // Note: If this ever changes to output raw float the data must be clipped
- // and sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleavedPartial(
- current_frame, frames_to_fill, params_.bits_per_sample() / 8, buffer);
- media::AdjustVolume(buffer, bytes_to_fill, params_.channels(),
- params_.bits_per_sample() / 8, volume_);
-
- if (pa_stream_write(pa_stream_, buffer, bytes_to_fill, NULL, 0LL,
- PA_SEEK_RELATIVE) < 0) {
- if (source_callback_) {
- source_callback_->OnError(this, pa_context_errno(pa_context_));
- }
- }
-
- bytes_remaining -= bytes_to_fill;
- current_frame = frames_to_fill;
- }
-}
-
-void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
- CHECK(callback);
- CHECK(pa_stream_);
-
- AutoPulseLock auto_lock(pa_mainloop_);
-
- // Ensure the context and stream are ready.
- if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY &&
- pa_stream_get_state(pa_stream_) != PA_STREAM_READY) {
- callback->OnError(this, pa_context_errno(pa_context_));
- return;
- }
-
- source_callback_ = callback;
-
- // Uncork (resume) the stream.
- WaitForPulseOperation(pa_stream_cork(
- pa_stream_, 0, &StreamSuccessCallback, this));
-}
-
-void PulseAudioOutputStream::Stop() {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- // Cork (pause) the stream. Waiting for the main loop lock will ensure
- // outstanding callbacks have completed.
- AutoPulseLock auto_lock(pa_mainloop_);
-
- // Flush the stream prior to cork, doing so after will cause hangs. Write
- // callbacks are suspended while inside pa_threaded_mainloop_lock() so this
- // is all thread safe.
- WaitForPulseOperation(pa_stream_flush(
- pa_stream_, &StreamSuccessCallback, this));
-
- WaitForPulseOperation(pa_stream_cork(
- pa_stream_, 1, &StreamSuccessCallback, this));
-
- source_callback_ = NULL;
-}
-
-void PulseAudioOutputStream::SetVolume(double volume) {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- volume_ = static_cast<float>(volume);
-}
-
-void PulseAudioOutputStream::GetVolume(double* volume) {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- *volume = volume_;
-}
-
-void PulseAudioOutputStream::WaitForPulseOperation(pa_operation* op) {
- CHECK(op);
- while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(pa_mainloop_);
- }
- pa_operation_unref(op);
-}
-
-} // namespace media
diff --git a/src/media/audio/pulse/pulse_output.h b/src/media/audio/pulse/pulse_output.h
deleted file mode 100644
index cdd7cfd..0000000
--- a/src/media/audio/pulse/pulse_output.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Creates an audio output stream based on the PulseAudio asynchronous API;
-// specifically using the pa_threaded_mainloop model.
-//
-// If the stream is successfully opened, Close() must be called before the
-// stream is deleted as Close() is responsible for ensuring resource cleanup
-// occurs.
-//
-// This object is designed so that all AudioOutputStream methods will be called
-// on the same thread that created the object.
-//
-// WARNING: This object blocks on internal PulseAudio calls in Open() while
-// waiting for PulseAudio's context structure to be ready. It also blocks in
-// inside PulseAudio in Start() and repeated during playback, waiting for
-// PulseAudio write callbacks to occur.
-
-#ifndef MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
-#define MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-struct pa_context;
-struct pa_operation;
-struct pa_stream;
-struct pa_threaded_mainloop;
-
-namespace media {
-class AudioManagerBase;
-
-class PulseAudioOutputStream : public AudioOutputStream {
- public:
- PulseAudioOutputStream(const AudioParameters& params,
- AudioManagerBase* manager);
-
- virtual ~PulseAudioOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- private:
- // Called by PulseAudio when |pa_context_| and |pa_stream_| change state. If
- // an unexpected failure state change happens and |source_callback_| is set
- // these methods will forward the error via OnError().
- static void ContextNotifyCallback(pa_context* c, void* p_this);
- static void StreamNotifyCallback(pa_stream* s, void* p_this);
-
- // Triggers pa_threaded_mainloop_signal() to avoid deadlocks.
- static void StreamSuccessCallback(pa_stream* s, int success, void* p_this);
-
- // Called by PulseAudio when it needs more audio data.
- static void StreamRequestCallback(pa_stream* s, size_t len, void* p_this);
-
- // Fulfill a write request from the write request callback. Outputs silence
- // if the request could not be fulfilled.
- void FulfillWriteRequest(size_t requested_bytes);
-
- // Close() helper function to free internal structs.
- void Reset();
-
- // Returns the current hardware latency value in bytes.
- int GetHardwareLatencyInBytes();
-
- // Helper method for waiting on Pulse Audio operations to complete.
- void WaitForPulseOperation(pa_operation* op);
-
- // AudioParameters from the constructor.
- const AudioParameters params_;
-
- // Audio manager that created us. Used to report that we've closed.
- AudioManagerBase* manager_;
-
- // PulseAudio API structs.
- pa_context* pa_context_;
- pa_threaded_mainloop* pa_mainloop_;
- pa_stream* pa_stream_;
-
- // Float representation of volume from 0.0 to 1.0.
- float volume_;
-
- // Callback to audio data source. Must only be modified while holding a lock
- // on |pa_mainloop_| via pa_threaded_mainloop_lock().
- AudioSourceCallback* source_callback_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(PulseAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
diff --git a/src/media/audio/sample_rates.cc b/src/media/audio/sample_rates.cc
deleted file mode 100644
index a082a93..0000000
--- a/src/media/audio/sample_rates.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/sample_rates.h"
-
-namespace media {
-
-AudioSampleRate AsAudioSampleRate(int sample_rate) {
- switch (sample_rate) {
- case 8000: return k8000Hz;
- case 16000: return k16000Hz;
- case 32000: return k32000Hz;
- case 48000: return k48000Hz;
- case 96000: return k96000Hz;
- case 11025: return k11025Hz;
- case 22050: return k22050Hz;
- case 44100: return k44100Hz;
- case 88200: return k88200Hz;
- case 176400: return k176400Hz;
- case 192000: return k192000Hz;
- }
- return kUnexpectedAudioSampleRate;
-}
-
-} // namespace media
diff --git a/src/media/audio/sample_rates.h b/src/media/audio/sample_rates.h
deleted file mode 100644
index 7c29e54..0000000
--- a/src/media/audio/sample_rates.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_SAMPLE_RATES_H_
-#define MEDIA_AUDIO_SAMPLE_RATES_H_
-
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Enumeration used for histogramming sample rates into distinct buckets.
-// Logged to UMA, so never reuse a value, always add new/greater ones!
-enum AudioSampleRate {
- k8000Hz = 0,
- k16000Hz = 1,
- k32000Hz = 2,
- k48000Hz = 3,
- k96000Hz = 4,
- k11025Hz = 5,
- k22050Hz = 6,
- k44100Hz = 7,
- k88200Hz = 8,
- k176400Hz = 9,
- k192000Hz = 10,
- kUnexpectedAudioSampleRate // Must always be last!
-};
-
-// Helper method to convert integral values to their respective enum values,
-// or kUnexpectedAudioSampleRate if no match exists.
-MEDIA_EXPORT AudioSampleRate AsAudioSampleRate(int sample_rate);
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SAMPLE_RATES_H_
diff --git a/src/media/audio/scoped_loop_observer.cc b/src/media/audio/scoped_loop_observer.cc
deleted file mode 100644
index 1332b07..0000000
--- a/src/media/audio/scoped_loop_observer.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/scoped_loop_observer.h"
-
-#include "base/bind.h"
-#include "base/synchronization/waitable_event.h"
-
-namespace media {
-
-ScopedLoopObserver::ScopedLoopObserver(
- const scoped_refptr<base::MessageLoopProxy>& loop)
- : loop_(loop) {
- ObserveLoopDestruction(true, NULL);
-}
-
-ScopedLoopObserver::~ScopedLoopObserver() {
- ObserveLoopDestruction(false, NULL);
-}
-
-void ScopedLoopObserver::ObserveLoopDestruction(bool enable,
- base::WaitableEvent* done) {
- // Note: |done| may be NULL.
- if (loop_->BelongsToCurrentThread()) {
- MessageLoop* loop = MessageLoop::current();
- if (enable) {
- loop->AddDestructionObserver(this);
- } else {
- loop->RemoveDestructionObserver(this);
- }
- } else {
- base::WaitableEvent event(false, false);
- if (loop_->PostTask(FROM_HERE,
- base::Bind(&ScopedLoopObserver::ObserveLoopDestruction,
- base::Unretained(this), enable, &event))) {
- event.Wait();
- } else {
- // The message loop's thread has already terminated, so no need to wait.
- }
- }
-
- if (done)
- done->Signal();
-}
-
-} // namespace media.
diff --git a/src/media/audio/scoped_loop_observer.h b/src/media/audio/scoped_loop_observer.h
deleted file mode 100644
index 659c68b..0000000
--- a/src/media/audio/scoped_loop_observer.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_SCOPED_LOOP_OBSERVER_H_
-#define MEDIA_AUDIO_SCOPED_LOOP_OBSERVER_H_
-
-#include "base/memory/ref_counted.h"
-#include "base/message_loop.h"
-#include "base/message_loop_proxy.h"
-
-namespace base {
-class WaitableEvent;
-}
-
-namespace media {
-
-// A common base class for AudioOutputDevice and AudioInputDevice that manages
-// a message loop pointer and monitors it for destruction. If the object goes
-// out of scope before the message loop, the object will automatically remove
-// itself from the message loop's list of destruction observers.
-// NOTE: The class that inherits from this class must implement the
-// WillDestroyCurrentMessageLoop virtual method from DestructionObserver.
-class ScopedLoopObserver
- : public MessageLoop::DestructionObserver {
- public:
- explicit ScopedLoopObserver(
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- protected:
- virtual ~ScopedLoopObserver();
-
- // Accessor to the loop that's used by the derived class.
- const scoped_refptr<base::MessageLoopProxy>& message_loop() { return loop_; }
-
- private:
- // Call to add or remove ourselves from the list of destruction observers for
- // the message loop.
- void ObserveLoopDestruction(bool enable, base::WaitableEvent* done);
-
- // A pointer to the message loop's proxy. In case the loop gets destroyed
- // before this object goes out of scope, PostTask etc will fail but not crash.
- scoped_refptr<base::MessageLoopProxy> loop_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedLoopObserver);
-};
-
-} // namespace media.
-
-#endif // MEDIA_AUDIO_SCOPED_LOOP_OBSERVER_H_
diff --git a/src/media/audio/shared_memory_util.cc b/src/media/audio/shared_memory_util.cc
deleted file mode 100644
index b65df03..0000000
--- a/src/media/audio/shared_memory_util.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/shared_memory_util.h"
-
-#include "base/atomicops.h"
-#include "base/logging.h"
-
-using base::subtle::Atomic32;
-
-static const uint32 kUnknownDataSize = static_cast<uint32>(-1);
-
-namespace media {
-
-uint32 TotalSharedMemorySizeInBytes(uint32 packet_size) {
- // Need to reserve extra 4 bytes for size of data.
- return packet_size + sizeof(Atomic32);
-}
-
-uint32 PacketSizeInBytes(uint32 shared_memory_created_size) {
- return shared_memory_created_size - sizeof(Atomic32);
-}
-
-uint32 GetActualDataSizeInBytes(base::SharedMemory* shared_memory,
- uint32 packet_size) {
- char* ptr = static_cast<char*>(shared_memory->memory()) + packet_size;
- DCHECK_EQ(0u, reinterpret_cast<size_t>(ptr) & 3);
-
- // Actual data size stored at the end of the buffer.
- uint32 actual_data_size =
- base::subtle::Acquire_Load(reinterpret_cast<volatile Atomic32*>(ptr));
- return std::min(actual_data_size, packet_size);
-}
-
-void SetActualDataSizeInBytes(void* shared_memory_ptr,
- uint32 packet_size,
- uint32 actual_data_size) {
- char* ptr = static_cast<char*>(shared_memory_ptr) + packet_size;
- DCHECK_EQ(0u, reinterpret_cast<size_t>(ptr) & 3);
-
- // Set actual data size at the end of the buffer.
- base::subtle::Release_Store(reinterpret_cast<volatile Atomic32*>(ptr),
- actual_data_size);
-}
-
-void SetActualDataSizeInBytes(base::SharedMemory* shared_memory,
- uint32 packet_size,
- uint32 actual_data_size) {
- SetActualDataSizeInBytes(shared_memory->memory(),
- packet_size, actual_data_size);
-}
-
-void SetUnknownDataSize(base::SharedMemory* shared_memory,
- uint32 packet_size) {
- SetActualDataSizeInBytes(shared_memory, packet_size, kUnknownDataSize);
-}
-
-bool IsUnknownDataSize(base::SharedMemory* shared_memory,
- uint32 packet_size) {
- char* ptr = static_cast<char*>(shared_memory->memory()) + packet_size;
- DCHECK_EQ(0u, reinterpret_cast<size_t>(ptr) & 3);
-
- // Actual data size stored at the end of the buffer.
- uint32 actual_data_size =
- base::subtle::Acquire_Load(reinterpret_cast<volatile Atomic32*>(ptr));
- return actual_data_size == kUnknownDataSize;
-}
-
-} // namespace media
diff --git a/src/media/audio/shared_memory_util.h b/src/media/audio/shared_memory_util.h
deleted file mode 100644
index 2ae6ffe..0000000
--- a/src/media/audio/shared_memory_util.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_SHARED_MEMORY_UTIL_H_
-#define MEDIA_AUDIO_SHARED_MEMORY_UTIL_H_
-
-#include "base/basictypes.h"
-#include "base/shared_memory.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Value sent by the controller to the renderer in low-latency mode
-// indicating that the stream is paused.
-enum { kPauseMark = -1 };
-
-// Functions that handle data buffer passed between processes in the shared
-// memory. Called on both IPC sides. These are necessary because the shared
-// memory has a layout: the last word in the block is the data size in bytes.
-
-MEDIA_EXPORT uint32 TotalSharedMemorySizeInBytes(uint32 packet_size);
-MEDIA_EXPORT uint32 PacketSizeInBytes(uint32 shared_memory_created_size);
-MEDIA_EXPORT uint32 GetActualDataSizeInBytes(base::SharedMemory* shared_memory,
- uint32 packet_size);
-MEDIA_EXPORT void SetActualDataSizeInBytes(base::SharedMemory* shared_memory,
- uint32 packet_size,
- uint32 actual_data_size);
-MEDIA_EXPORT void SetActualDataSizeInBytes(void* shared_memory_ptr,
- uint32 packet_size,
- uint32 actual_data_size);
-MEDIA_EXPORT void SetUnknownDataSize(base::SharedMemory* shared_memory,
- uint32 packet_size);
-MEDIA_EXPORT bool IsUnknownDataSize(base::SharedMemory* shared_memory,
- uint32 packet_size);
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SHARED_MEMORY_UTIL_H_
diff --git a/src/media/audio/shell_audio_sink.cc b/src/media/audio/shell_audio_sink.cc
deleted file mode 100644
index e46d3f7..0000000
--- a/src/media/audio/shell_audio_sink.cc
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/audio/shell_audio_sink.h"
-
-#include <limits>
-
-#include "base/bind.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/audio_bus.h"
-#include "media/base/shell_media_statistics.h"
-#include "media/filters/shell_audio_renderer.h"
-#include "media/mp4/aac.h"
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#endif // defined(OS_STARBOARD)
-
-namespace {
-
-scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> s_audio_sink_buffer;
-size_t s_audio_sink_buffer_size_in_float;
-
-} // namespace
-
-namespace media {
-
-void AudioSinkSettings::Reset(const ShellAudioStreamer::Config& config,
- const AudioParameters& audio_parameters) {
- config_ = config;
- audio_parameters_ = audio_parameters;
-}
-
-const ShellAudioStreamer::Config& AudioSinkSettings::config() const {
- return config_;
-}
-
-const AudioParameters& AudioSinkSettings::audio_parameters() const {
- return audio_parameters_;
-}
-
-int AudioSinkSettings::channels() const {
- return audio_parameters_.channels();
-}
-
-int AudioSinkSettings::per_channel_frames(AudioBus* audio_bus) const {
- return audio_bus->frames() * sizeof(float) /
- (config_.interleaved() ? channels() : 1) /
- (audio_parameters_.bits_per_sample() / 8);
-}
-
-// static
-ShellAudioSink* ShellAudioSink::Create(ShellAudioStreamer* audio_streamer) {
- return new ShellAudioSink(audio_streamer);
-}
-
-ShellAudioSink::ShellAudioSink(ShellAudioStreamer* audio_streamer)
- : render_callback_(NULL),
- pause_requested_(true),
- rebuffering_(true),
- rebuffer_num_frames_(0),
- render_frame_cursor_(0),
- output_frame_cursor_(0),
- audio_streamer_(audio_streamer) {
- buffer_factory_ = ShellBufferFactory::Instance();
-}
-
-ShellAudioSink::~ShellAudioSink() {
- if (render_callback_) {
- DCHECK(!audio_streamer_->HasStream(this));
- }
-}
-
-void ShellAudioSink::Initialize(const AudioParameters& params,
- RenderCallback* callback) {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Initialize()");
- DCHECK(!render_callback_);
- DCHECK(params.bits_per_sample() == 16 || params.bits_per_sample() == 32);
-
- render_callback_ = callback;
- audio_parameters_ = params;
-
- streamer_config_ = audio_streamer_->GetConfig();
- settings_.Reset(streamer_config_, params);
-
- // Creating the audio bus
- size_t per_channel_size_in_float =
- streamer_config_.sink_buffer_size_in_frames_per_channel() *
- audio_parameters_.bits_per_sample() / (8 * sizeof(float));
- size_t audio_bus_buffer_size_in_float =
- settings_.channels() * per_channel_size_in_float;
- if (audio_bus_buffer_size_in_float > s_audio_sink_buffer_size_in_float) {
- s_audio_sink_buffer_size_in_float = audio_bus_buffer_size_in_float;
- // free the existing memory first so we have more free memory for the
- // allocation following.
- s_audio_sink_buffer.reset(NULL);
- s_audio_sink_buffer.reset(static_cast<float*>(
- base::AlignedAlloc(s_audio_sink_buffer_size_in_float * sizeof(float),
- AudioBus::kChannelAlignment)));
- if (!s_audio_sink_buffer) {
- DLOG(ERROR) << "couldn't reallocate sink buffer";
- render_callback_->OnRenderError();
- return;
- }
- }
-
- if (streamer_config_.interleaved()) {
- audio_bus_ = AudioBus::WrapMemory(
- 1, settings_.channels() * per_channel_size_in_float,
- s_audio_sink_buffer.get());
- } else {
- audio_bus_ =
- AudioBus::WrapMemory(settings_.channels(), per_channel_size_in_float,
- s_audio_sink_buffer.get());
- }
-
- if (!audio_bus_) {
- NOTREACHED() << "couldn't create sink buffer";
- render_callback_->OnRenderError();
- return;
- }
-
- rebuffer_num_frames_ =
- streamer_config_.initial_rebuffering_frames_per_channel();
- renderer_audio_bus_ = AudioBus::CreateWrapper(audio_bus_->channels());
-}
-
-void ShellAudioSink::Start() {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Start()");
- DCHECK(render_callback_);
-
- if (!audio_streamer_->HasStream(this)) {
- pause_requested_ = true;
- rebuffering_ = true;
- audio_streamer_->StopBackgroundMusic();
- audio_streamer_->AddStream(this);
- DCHECK(audio_streamer_->HasStream(this));
- }
-}
-
-void ShellAudioSink::Stop() {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Stop()");
- // It is possible that Stop() is called before Initialize() is called. In
- // this case the audio_streamer_ will not be able to check if it has the
- // stream as audio_parameters_ hasn't been initialized.
- if (render_callback_ && audio_streamer_->HasStream(this)) {
- audio_streamer_->RemoveStream(this);
- pause_requested_ = true;
- rebuffering_ = true;
- render_frame_cursor_ = 0;
- output_frame_cursor_ = 0;
- }
-}
-
-void ShellAudioSink::Pause(bool flush) {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Pause()");
- // clear consumption of data on the mixer.
- pause_requested_ = true;
- if (flush) {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Pause() flushing.");
- // remove and re-add the stream to flush
- audio_streamer_->RemoveStream(this);
- rebuffering_ = true;
- render_frame_cursor_ = 0;
- output_frame_cursor_ = 0;
- audio_streamer_->AddStream(this);
- }
-}
-
-void ShellAudioSink::Play() {
- TRACE_EVENT0("media_stack", "ShellAudioSink::Play()");
- // clear flag on mixer callback, will start to consume more data
- pause_requested_ = false;
-}
-
-bool ShellAudioSink::SetVolume(double volume) {
- return audio_streamer_->SetVolume(this, volume);
-}
-
-void ShellAudioSink::ResumeAfterUnderflow(bool buffer_more_audio) {
- // only rebuffer when paused, we access state variables non atomically
- DCHECK(pause_requested_);
- DCHECK(rebuffering_);
-
- if (!buffer_more_audio)
- return;
-
- rebuffer_num_frames_ = std::min<int>(
- rebuffer_num_frames_ * 2, settings_.per_channel_frames(audio_bus_.get()));
-}
-
-bool ShellAudioSink::PauseRequested() const {
- return pause_requested_ || rebuffering_;
-}
-
-bool ShellAudioSink::PullFrames(uint32_t* offset_in_frame,
- uint32_t* total_frames) {
- TRACE_EVENT0("media_stack", "ShellAudioSink::PullFrames()");
- // with a valid render callback
- DCHECK(render_callback_);
-
- uint32_t dummy_offset_in_frame, dummy_total_frames;
- if (!offset_in_frame)
- offset_in_frame = &dummy_offset_in_frame;
- if (!total_frames)
- total_frames = &dummy_total_frames;
-
- *total_frames = render_frame_cursor_ - output_frame_cursor_;
- uint32 free_frames =
- settings_.per_channel_frames(audio_bus_.get()) - *total_frames;
- // Number of ms of buffered playback remaining
- uint32_t buffered_time =
- (*total_frames * 1000 / audio_parameters_.sample_rate());
- if (free_frames >= mp4::AAC::kFramesPerAccessUnit) {
- SetupRenderAudioBus();
-
- int frames_rendered =
- render_callback_->Render(renderer_audio_bus_.get(), buffered_time);
- // 0 indicates the read is still pending. Positive number is # of frames
- // rendered, negative number indicates an error.
- if (frames_rendered > 0) {
- // +ve value indicates number of samples in a successful read
- // TODO: We cannot guarantee this on platforms that use a resampler. Check
- // if it is possible to move the resample into the streamer.
- // DCHECK_EQ(frames_rendered, mp4::AAC::kFramesPerAccessUnit);
- render_frame_cursor_ += frames_rendered;
- *total_frames += frames_rendered;
- free_frames -= frames_rendered;
- }
- } else {
- render_callback_->Render(NULL, buffered_time);
- }
-
- bool buffer_full = free_frames < mp4::AAC::kFramesPerAccessUnit;
- DCHECK_LE(*total_frames,
- static_cast<uint32>(std::numeric_limits<int32>::max()));
- bool rebuffer_threshold_reached =
- static_cast<int>(*total_frames) >= rebuffer_num_frames_;
- if (rebuffering_ && (buffer_full || rebuffer_threshold_reached)) {
- render_callback_->SinkFull();
- rebuffering_ = false;
- }
-
-#if defined(OS_STARBOARD)
-#if SB_IS(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
-
-#define MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK 1
-
-#endif // SB_IS(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
-#endif // #if defined(OS_STARBOARD)
-
-#if defined(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
- const size_t kUnderflowThreshold = mp4::AAC::kFramesPerAccessUnit / 2;
- if (*total_frames < kUnderflowThreshold) {
- if (!rebuffering_) {
- rebuffering_ = true;
- render_callback_->SinkUnderflow();
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_AUDIO_UNDERFLOW, 0);
- }
- }
- *offset_in_frame =
- output_frame_cursor_ % settings_.per_channel_frames(audio_bus_.get());
- return !PauseRequested();
-#else // defined(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
- rebuffering_ = true;
- *offset_in_frame =
- output_frame_cursor_ % settings_.per_channel_frames(audio_bus_.get());
- if (pause_requested_) {
- return false;
- }
- return true;
-#endif // defined(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
-}
-
-void ShellAudioSink::ConsumeFrames(uint32_t frame_played) {
- TRACE_EVENT1("media_stack", "ShellAudioSink::ConsumeFrames()", "audio_clock",
- (output_frame_cursor_ * 1000) / audio_parameters_.sample_rate());
- // Called by the Streamer thread to indicate where the hardware renderer
- // is in playback
- if (frame_played > 0) {
- // advance our output cursor by the number of frames we're returning
- // update audio clock, used for jitter calculations
- output_frame_cursor_ += frame_played;
- DCHECK_LE(output_frame_cursor_, render_frame_cursor_);
- }
-}
-
-AudioBus* ShellAudioSink::GetAudioBus() {
- return audio_bus_.get();
-}
-
-const AudioParameters& ShellAudioSink::GetAudioParameters() const {
- return audio_parameters_;
-}
-
-void ShellAudioSink::SetupRenderAudioBus() {
- // check for buffer wraparound, hopefully rare
- int render_frame_position =
- render_frame_cursor_ % settings_.per_channel_frames(audio_bus_.get());
- int requested_frames = mp4::AAC::kFramesPerAccessUnit;
- if (render_frame_position + requested_frames >
- settings_.per_channel_frames(audio_bus_.get())) {
- requested_frames =
- settings_.per_channel_frames(audio_bus_.get()) - render_frame_position;
- }
- // calculate the offset into the buffer where we'd like to store these data
- if (streamer_config_.interleaved()) {
- uint8* channel_data = reinterpret_cast<uint8*>(audio_bus_->channel(0));
- uint8* channel_offset = channel_data +
- render_frame_position *
- audio_parameters_.bits_per_sample() / 8 *
- settings_.channels();
- // setup the AudioBus to pass to the renderer
- renderer_audio_bus_->SetChannelData(
- 0, reinterpret_cast<float*>(channel_offset));
- renderer_audio_bus_->set_frames(requested_frames *
- audio_parameters_.bits_per_sample() / 8 /
- sizeof(float) * settings_.channels());
- } else {
- for (int i = 0; i < audio_bus_->channels(); ++i) {
- uint8* channel_data = reinterpret_cast<uint8*>(audio_bus_->channel(i));
- uint8* channel_offset =
- channel_data +
- render_frame_position * audio_parameters_.bits_per_sample() / 8;
- renderer_audio_bus_->SetChannelData(
- i, reinterpret_cast<float*>(channel_offset));
- }
- renderer_audio_bus_->set_frames(requested_frames *
- audio_parameters_.bits_per_sample() / 8 /
- sizeof(float));
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/shell_audio_sink.h b/src/media/audio/shell_audio_sink.h
deleted file mode 100644
index c2eca08..0000000
--- a/src/media/audio/shell_audio_sink.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_SHELL_AUDIO_SINK_H_
-#define MEDIA_AUDIO_SHELL_AUDIO_SINK_H_
-
-#include "base/threading/thread.h"
-#include "media/base/audio_renderer_sink.h"
-#include "media/audio/shell_audio_streamer.h"
-#include "media/base/shell_buffer_factory.h"
-
-namespace media {
-
-// This class is used to manage the complexity of audio settings as the audio
-// settings are determined by the original audio data (stereo, 5.1, etc. )
-// and by the decoder (Some decoders decode mono into stereo) and hardware
-// (some hardware requires audio data to be interleaved but others might
-// require it to be non-interleaved.
-class AudioSinkSettings {
- public:
- AudioSinkSettings() {}
-
- void Reset(const ShellAudioStreamer::Config& config,
- const AudioParameters& audio_parameters);
- const ShellAudioStreamer::Config& config() const;
- const AudioParameters& audio_parameters() const;
-
- int channels() const;
- int per_channel_frames(AudioBus* audio_bus) const;
-
- private:
- ShellAudioStreamer::Config config_;
- AudioParameters audio_parameters_;
-};
-
-// platform-specific implementation of an audio endpoint.
-class MEDIA_EXPORT ShellAudioSink : NON_EXPORTED_BASE(public AudioRendererSink),
- NON_EXPORTED_BASE(public ShellAudioStream) {
- public:
- ShellAudioSink(ShellAudioStreamer* audio_streamer);
- virtual ~ShellAudioSink();
-
- // static factory method
- static ShellAudioSink* Create(ShellAudioStreamer* audio_streamer);
-
- // AudioRendererSink implementation
- void Initialize(const AudioParameters& params,
- RenderCallback* callback) OVERRIDE;
- void Start() OVERRIDE;
- void Stop() OVERRIDE;
- void Pause(bool flush) OVERRIDE;
- void Play() OVERRIDE;
- bool SetVolume(double volume) OVERRIDE;
- void ResumeAfterUnderflow(bool buffer_more_audio) OVERRIDE;
-
- // ShellAudioStream implementation
- bool PauseRequested() const OVERRIDE;
- bool PullFrames(uint32_t* offset_in_frame, uint32_t* total_frames) OVERRIDE;
- void ConsumeFrames(uint32_t frame_played) OVERRIDE;
- const AudioParameters& GetAudioParameters() const OVERRIDE;
- AudioBus* GetAudioBus() OVERRIDE;
-
- private:
- // Config the audio bus that will be sent to the AudioRenderer. It reueses
- // the memory occupied by the sink audio bus (audio_bus_).
- void SetupRenderAudioBus();
-
- AudioParameters audio_parameters_;
- RenderCallback* render_callback_;
-
- scoped_ptr<AudioBus> audio_bus_;
-
- // Used as a paremeter when calling render_callback_->Render().
- // We can only construct it through a static Create method that does a heap
- // allocate so it is a member variable to avoid a heap allocation each
- // frame.
- scoped_ptr<AudioBus> renderer_audio_bus_;
-
- bool pause_requested_;
- bool rebuffering_;
- // Number of frames to rebuffer before calling SinkFull
- int rebuffer_num_frames_;
-
- // number of samples have been loaded into audio_bus from the Renderer
- // (and may have been played and since been overwritten by newer samples)
- uint64_t render_frame_cursor_;
- // advanced by ConsumeSamples() as the Streamer reports playback advancing
- uint64_t output_frame_cursor_;
-
- scoped_refptr<ShellBufferFactory> buffer_factory_;
- ShellAudioStreamer* audio_streamer_;
- ShellAudioStreamer::Config streamer_config_;
-
- AudioSinkSettings settings_;
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SHELL_AUDIO_SINK_H_
diff --git a/src/media/audio/shell_audio_sink_unittest.cc b/src/media/audio/shell_audio_sink_unittest.cc
deleted file mode 100644
index 60995c1..0000000
--- a/src/media/audio/shell_audio_sink_unittest.cc
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/audio/shell_audio_sink.h"
-
-#include <algorithm>
-
-#include "media/audio/mock_shell_audio_streamer.h"
-#include "media/base/mock_filters.h"
-#include "media/mp4/aac.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-typedef media::ShellAudioStreamer::Config Config;
-
-// TODO: Add 32 bits per sample tests.
-
-namespace {
-
-using namespace testing;
-
-const uint32 kMaxHardwareChannelsStereo = 2;
-const size_t kFramesPerAccessUnit = media::mp4::AAC::kFramesPerAccessUnit;
-
-bool operator==(const media::AudioParameters& params1,
- const media::AudioParameters& params2) {
- return params1.channels() == params2.channels() &&
- params1.bits_per_sample() == params2.bits_per_sample() &&
- params1.sample_rate() == params2.sample_rate();
-}
-
-void InterleavedFill(size_t frames_per_channel,
- int channels,
- int bytes_per_sample,
- uint8* data,
- uint8* fill_byte) {
- while (frames_per_channel) {
- for (int i = 0; i < channels; ++i) {
- for (int j = 0; j < bytes_per_sample; ++j) {
- *data = *fill_byte + j;
- ++data;
- }
- }
- *fill_byte += bytes_per_sample;
- --frames_per_channel;
- }
-}
-
-void InterleavedVerify(size_t frames_per_channel,
- int channels,
- int bytes_per_sample,
- const uint8* data,
- uint8* fill_byte) {
- while (frames_per_channel) {
- for (int i = 0; i < channels; ++i) {
- for (int j = 0; j < bytes_per_sample; ++j) {
- ASSERT_EQ(*data, static_cast<uint8>(*fill_byte + j));
- ++data;
- }
- }
- *fill_byte += bytes_per_sample;
- --frames_per_channel;
- }
-}
-
-class MockRenderCallback : public media::AudioRendererSink::RenderCallback {
- public:
- MockRenderCallback() {}
-
- MOCK_METHOD2(Render, int(media::AudioBus*, int));
- MOCK_METHOD3(RenderIO, void(media::AudioBus*, media::AudioBus*, int));
- MOCK_METHOD0(OnRenderError, void());
- MOCK_METHOD0(SinkFull, void());
-
- MOCK_METHOD0(SinkUnderflow, void());
-
- DISALLOW_COPY_AND_ASSIGN(MockRenderCallback);
-};
-
-class ShellAudioSinkTest : public testing::Test {
- public:
- ShellAudioSinkTest() { media::ShellBufferFactory::Initialize(); }
-
- virtual ~ShellAudioSinkTest() { media::ShellBufferFactory::Terminate(); }
-
- void Configure(const Config& config) {
- render_byte_ = 0;
- consumption_byte_ = 0;
- render_bytes_num_ = 0;
- consumption_bytes_num_ = 0;
- render_frames_per_channel_ = 0;
- consumption_frames_per_channel_ = 0;
-
- sink_ = media::ShellAudioSink::Create(&streamer_);
- EXPECT_CALL(streamer_, GetConfig()).WillRepeatedly(Return(config));
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- EXPECT_CALL(render_callback_, Render(_, _)).WillRepeatedly(Return(0));
- EXPECT_CALL(render_callback_, SinkFull()).Times(AnyNumber());
- EXPECT_CALL(render_callback_, SinkUnderflow()).Times(AnyNumber());
- }
-
- void FillAudioBus(int frames_per_channel, media::AudioBus* audio_bus) {
- Config config = streamer_.GetConfig();
- media::AudioParameters params = sink_->GetAudioParameters();
- int bytes_per_channel = frames_per_channel * params.bits_per_sample() / 8;
- int channels = params.channels();
- if (config.interleaved()) {
- bytes_per_channel *= channels;
- }
- ASSERT_LE(bytes_per_channel, audio_bus->frames() * sizeof(float));
-
- if (config.interleaved()) {
- ASSERT_EQ(audio_bus->channels(), 1);
- InterleavedFill(
- frames_per_channel, channels, params.bits_per_sample() / 8,
- reinterpret_cast<uint8*>(audio_bus->channel(0)), &render_byte_);
- } else {
- ASSERT_EQ(audio_bus->channels(), channels);
- uint8 render_byte;
- for (int i = 0; i < channels; ++i) {
- render_byte = render_byte_;
- InterleavedFill(frames_per_channel, 1, params.bits_per_sample() / 8,
- reinterpret_cast<uint8*>(audio_bus->channel(i)),
- &render_byte);
- }
- render_byte_ = render_byte;
- }
-
- render_bytes_num_ += channels * bytes_per_channel;
- render_frames_per_channel_ += frames_per_channel;
- }
-
- void Consume(int frames_per_channel) {
- Config config = streamer_.GetConfig();
- media::AudioParameters params = sink_->GetAudioParameters();
- media::AudioSinkSettings settings;
- media::AudioBus* audio_bus = sink_->GetAudioBus();
- int bytes_per_channel = frames_per_channel * params.bits_per_sample() / 8;
-
- settings.Reset(config, params);
- if (config.interleaved()) {
- bytes_per_channel *= settings.channels();
- }
- ASSERT_LE(bytes_per_channel, audio_bus->frames() * sizeof(float));
-
- uint32_t offset_in_frame, total_frames;
- EXPECT_CALL(render_callback_, Render(_, _)).WillOnce(Return(0));
- sink_->PullFrames(&offset_in_frame, &total_frames);
-
- EXPECT_LE(frames_per_channel, total_frames);
-
- int frames_to_request =
- std::min<int>(frames_per_channel,
- settings.per_channel_frames(audio_bus) - offset_in_frame);
- if (config.interleaved()) {
- ASSERT_EQ(audio_bus->channels(), 1);
- uint8* data =
- reinterpret_cast<uint8*>(audio_bus->channel(0)) +
- offset_in_frame * params.bits_per_sample() / 8 * settings.channels();
- InterleavedVerify(frames_to_request, settings.channels(),
- params.bits_per_sample() / 8, data, &consumption_byte_);
- } else {
- ASSERT_EQ(audio_bus->channels(), settings.channels());
- uint8 consumption_byte;
- for (int i = 0; i < settings.channels(); ++i) {
- consumption_byte = consumption_byte_;
- uint8* data = reinterpret_cast<uint8*>(audio_bus->channel(i)) +
- offset_in_frame * params.bits_per_sample() / 8;
- InterleavedVerify(frames_to_request, 1, params.bits_per_sample() / 8,
- data, &consumption_byte);
- }
- consumption_byte_ = consumption_byte;
- }
-
- frames_to_request = frames_per_channel - frames_to_request;
- if (frames_to_request != 0) {
- if (config.interleaved()) {
- ASSERT_EQ(audio_bus->channels(), 1);
- uint8* data = reinterpret_cast<uint8*>(audio_bus->channel(0));
- InterleavedVerify(frames_to_request, settings.channels(),
- params.bits_per_sample() / 8, data,
- &consumption_byte_);
- } else {
- ASSERT_EQ(audio_bus->channels(), settings.channels());
- uint8 consumption_byte;
- for (int i = 0; i < settings.channels(); ++i) {
- consumption_byte = consumption_byte_;
- uint8* data = reinterpret_cast<uint8*>(audio_bus->channel(i));
- InterleavedVerify(frames_to_request, 1, params.bits_per_sample() / 8,
- data, &consumption_byte);
- }
- consumption_byte_ = consumption_byte;
- }
- }
-
- sink_->ConsumeFrames(frames_per_channel);
-
- consumption_bytes_num_ += settings.channels() * bytes_per_channel;
- consumption_frames_per_channel_ += frames_per_channel;
- }
-
- bool AllConsumed() {
- uint32_t offset_in_frame, total_frames;
- EXPECT_CALL(render_callback_, Render(_, _))
- .Times(AtLeast(0))
- .WillRepeatedly(Return(0));
- sink_->PullFrames(&offset_in_frame, &total_frames);
-
- return total_frames == 0 && render_byte_ == consumption_byte_ &&
- render_bytes_num_ == consumption_bytes_num_ &&
- render_frames_per_channel_ == consumption_frames_per_channel_;
- }
-
- // ==== Test Fixture Members
- media::MockShellAudioStreamer streamer_;
- MockRenderCallback render_callback_;
- scoped_refptr<media::ShellAudioSink> sink_;
-
- uint8 render_byte_;
- uint8 consumption_byte_;
- int render_bytes_num_;
- int consumption_bytes_num_;
- int render_frames_per_channel_;
- int consumption_frames_per_channel_;
-};
-
-// Verify the frame count of audio_bus
-ACTION_P3(VerifyAudioBusFrameCount, config, init_params, frames_per_channel) {
- media::AudioBus* audio_bus = arg0;
- int bytes_per_channel =
- frames_per_channel * init_params.bits_per_sample() / 8;
- if (config.interleaved()) {
- int channels = init_params.channels();
- bytes_per_channel *= channels;
- }
- EXPECT_EQ(bytes_per_channel, audio_bus->frames() * sizeof(float));
- return 0;
-}
-
-// Verify the frame count of audio_bus
-ACTION_P2(RenderAudioBus, frames_per_channel, sink_test) {
- media::AudioBus* audio_bus = arg0;
- sink_test->FillAudioBus(frames_per_channel, audio_bus);
- return frames_per_channel;
-}
-
-TEST_F(ShellAudioSinkTest, Prerequisites) {
- uint8 data[48000] = {0};
- uint8 render_byte = 0, consume_byte = 0;
- uint8 verify_data[] = {0x00, 0x01, 0x02, 0x00, 0x01, 0x02, 0x00, 0x01, 0x02,
- 0x03, 0x04, 0x05, 0x03, 0x04, 0x05, 0x03, 0x04, 0x05};
- InterleavedFill(2, 3, 3, data, &render_byte);
- EXPECT_EQ(memcmp(verify_data, data, 9), 0);
- EXPECT_EQ(memcmp(data + 18, data + 19, 1024), 0);
-
- for (size_t frames_per_channel = 1; frames_per_channel < 301;
- frames_per_channel += 3) {
- for (int channels = 1; channels < 5; ++channels) {
- for (int bytes_per_sample = 1; bytes_per_sample < 5; ++bytes_per_sample) {
- render_byte = consume_byte = 0;
- InterleavedFill(frames_per_channel, channels, bytes_per_sample, data,
- &render_byte);
- InterleavedVerify(frames_per_channel, channels, bytes_per_sample, data,
- &consume_byte);
- ASSERT_EQ(render_byte, consume_byte);
- }
- }
- }
-}
-
-TEST_F(ShellAudioSinkTest, Initialize) {
- const uint32 initial_rebuffering_frames_per_channel = 2048;
- const uint32 sink_buffer_size_in_frames_per_channel = 8192;
-
- // 2 configurations with different interleaved
- for (int i = 0; i < 2; ++i) {
- for (media::ChannelLayout layout = media::CHANNEL_LAYOUT_MONO;
- layout != media::CHANNEL_LAYOUT_MAX;
- layout = static_cast<media::ChannelLayout>(layout + 1)) {
- for (int bytes_per_sample = 2; bytes_per_sample < 5;
- bytes_per_sample *= 2) {
- Config config(i == 0 ? Config::INTERLEAVED : Config::PLANAR,
- initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel,
- kMaxHardwareChannelsStereo, bytes_per_sample,
- 48000 /* output_sample_rate */);
-
- Configure(config);
- EXPECT_TRUE(!sink_->GetAudioBus());
- media::AudioParameters init_params = media::AudioParameters(
- media::AudioParameters::AUDIO_PCM_LOW_LATENCY, layout, 48000,
- bytes_per_sample * 8, 1024);
- sink_->Initialize(init_params, &render_callback_);
-
- EXPECT_TRUE(sink_->PauseRequested());
- EXPECT_TRUE(sink_->GetAudioBus());
-
- media::AudioBus* audio_bus = sink_->GetAudioBus();
- media::AudioParameters params = sink_->GetAudioParameters();
- int expected_channels = init_params.channels();
- if (config.interleaved()) {
- EXPECT_EQ(audio_bus->channels(), 1);
- EXPECT_EQ(audio_bus->frames(),
- config.sink_buffer_size_in_frames_per_channel() *
- expected_channels * bytes_per_sample / sizeof(float));
- EXPECT_EQ(params.channels(), init_params.channels());
- } else {
- EXPECT_EQ(audio_bus->channels(), expected_channels);
- EXPECT_EQ(audio_bus->frames(),
- config.sink_buffer_size_in_frames_per_channel() *
- bytes_per_sample / sizeof(float));
- EXPECT_EQ(params.channels(), init_params.channels());
- }
-
- EXPECT_EQ(params.bits_per_sample(), init_params.bits_per_sample());
- EXPECT_EQ(params.sample_rate(), init_params.sample_rate());
- }
- }
- }
-}
-
-TEST_F(ShellAudioSinkTest, StartAndStop) {
- const uint32 initial_rebuffering_frames_per_channel = 2048;
- const uint32 sink_buffer_size_in_frames_per_channel = 8192;
-
- Config config(
- Config::INTERLEAVED, initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, kMaxHardwareChannelsStereo,
- sizeof(int16_t) /* bytes_per_sample */, 48000 /* output_sample_rate */);
-
- media::AudioParameters init_params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- media::CHANNEL_LAYOUT_MONO, 48000, 16, 1024);
-
- Configure(config);
-
- sink_->Initialize(init_params, &render_callback_);
-
- InSequence seq;
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(false));
- EXPECT_CALL(streamer_, AddStream(sink_.get())).WillOnce(Return(true));
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- sink_->Start();
- EXPECT_TRUE(sink_->PauseRequested());
- EXPECT_CALL(streamer_, RemoveStream(sink_.get())).WillOnce(Return());
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(false));
- sink_->Stop();
- EXPECT_TRUE(sink_->PauseRequested());
-}
-
-TEST_F(ShellAudioSinkTest, RenderNoFrames) {
- const uint32 initial_rebuffering_frames_per_channel = 2048;
- const uint32 sink_buffer_size_in_frames_per_channel = 8192;
-
- Config config(
- Config::INTERLEAVED, initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, kMaxHardwareChannelsStereo,
- sizeof(int16_t) /* bytes_per_sample */, 48000 /* output_sample_rate */);
-
- Configure(config);
-
- media::AudioParameters init_params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- media::CHANNEL_LAYOUT_MONO, 48000, 16, 1024);
-
- sink_->Initialize(init_params, &render_callback_);
-
- InSequence seq;
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(false));
- EXPECT_CALL(streamer_, AddStream(sink_.get())).WillOnce(Return(true));
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- sink_->Start();
-
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
- uint32_t offset_in_frame, total_frames;
- EXPECT_FALSE(sink_->PullFrames(&offset_in_frame, &total_frames));
-
- EXPECT_CALL(streamer_, RemoveStream(sink_.get())).WillOnce(Return());
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(false));
- sink_->Stop();
-}
-
-TEST_F(ShellAudioSinkTest, RenderFrames) {
- const uint32 initial_rebuffering_frames_per_channel = 2048;
- const uint32 sink_buffer_size_in_frames_per_channel = 8192;
-
- for (int i = 0; i < 2; ++i) {
- Config config(
- i == 0 ? Config::INTERLEAVED : Config::PLANAR,
- initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, kMaxHardwareChannelsStereo,
- sizeof(int16_t) /* bytes_per_sample */, 48000 /* output_sample_rate */);
-
- Configure(config);
-
- media::AudioParameters init_params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- media::CHANNEL_LAYOUT_MONO, 48000, 16, 1024);
-
- sink_->Initialize(init_params, &render_callback_);
-
- InSequence seq;
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- EXPECT_CALL(streamer_, AddStream(sink_.get())).WillOnce(Return(true));
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- sink_->Start();
-
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(16, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- uint32_t offset_in_frame, total_frames;
- EXPECT_CALL(render_callback_, Render(_, _)).WillOnce(Return(0));
- EXPECT_FALSE(sink_->PullFrames(&offset_in_frame, &total_frames));
- EXPECT_EQ(offset_in_frame, 0);
- EXPECT_EQ(total_frames, 16);
-
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(8, this));
- EXPECT_FALSE(sink_->PullFrames(&offset_in_frame, &total_frames));
- EXPECT_EQ(offset_in_frame, 0);
- EXPECT_EQ(total_frames, 24);
-
- Consume(8);
-
- EXPECT_CALL(render_callback_, Render(_, _)).WillOnce(Return(0));
- EXPECT_FALSE(sink_->PullFrames(&offset_in_frame, &total_frames));
- EXPECT_EQ(offset_in_frame, 8);
- EXPECT_EQ(total_frames, 16);
-
- Consume(16);
-
- EXPECT_TRUE(AllConsumed());
-
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- EXPECT_CALL(streamer_, RemoveStream(sink_.get())).WillOnce(Return());
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- sink_->Stop();
- }
-}
-
-TEST_F(ShellAudioSinkTest, RenderRequestSizeAkaAudioBusFrames) {
- const uint32 initial_rebuffering_frames_per_channel = 2048;
- const uint32 sink_buffer_size_in_frames_per_channel = 2048;
-
- for (int i = 0; i < 2; ++i) {
- Config config(
- i == 0 ? Config::INTERLEAVED : Config::PLANAR,
- initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, kMaxHardwareChannelsStereo,
- sizeof(int16_t) /* bytes_per_sample */, 48000 /* output_sample_rate */);
- Configure(config);
-
- media::AudioParameters init_params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- media::CHANNEL_LAYOUT_MONO, 48000, 16, 1024);
-
- sink_->Initialize(init_params, &render_callback_);
-
- InSequence seq;
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- EXPECT_CALL(streamer_, AddStream(sink_.get())).WillOnce(Return(true));
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- sink_->Start();
-
- for (int i = 0; i < 10; ++i) {
- // Try to get 1024 frames but don't give it any data
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(VerifyAudioBusFrameCount(config, init_params,
- kFramesPerAccessUnit));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Ok, now give it 1024 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(1024, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Try to get another 1024 frames but don't give it any data
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(VerifyAudioBusFrameCount(config, init_params,
- kFramesPerAccessUnit));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Ok, now give it 480 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(480, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Consume 1024 frames, leave 1568 frames space but only 544 are
- // continuous
- Consume(1024);
-
- // It still only has room for 544 continuous frames, don't give it any
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(VerifyAudioBusFrameCount(config, init_params, 544));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Ok, now give it 544 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(544, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Now it has room for another 1024 frames, don't give it
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(VerifyAudioBusFrameCount(config, init_params, 1024));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Ok, now give it 1024 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(1024, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Consume 2048 frames
- Consume(2048);
-
- // Give it another 64 and then consume 64 to ensure we can get into the
- // next iteration of the loop with empty buffer
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(1024, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
- Consume(1024);
-
- EXPECT_TRUE(AllConsumed());
- }
-
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- EXPECT_CALL(streamer_, RemoveStream(sink_.get())).WillOnce(Return());
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- sink_->Stop();
- }
-}
-
-TEST_F(ShellAudioSinkTest, ResumeAfterUnderflow) {
- const uint32 initial_rebuffering_frames_per_channel = 1024;
- const uint32 sink_buffer_size_in_frames_per_channel = 2048;
-
- for (int i = 0; i < 2; ++i) {
- Config config(
- i == 0 ? Config::INTERLEAVED : Config::PLANAR,
- initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, kMaxHardwareChannelsStereo,
- sizeof(int16_t) /* bytes_per_sample */, 48000 /* output_sample_rate */);
- Configure(config);
-
- media::AudioParameters init_params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- media::CHANNEL_LAYOUT_MONO, 48000, 16, 1024);
-
- sink_->Initialize(init_params, &render_callback_);
-
- InSequence seq;
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- EXPECT_CALL(streamer_, AddStream(sink_.get())).WillOnce(Return(true));
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- sink_->Start();
-
- // Render 64 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(64, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Render another 64 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(64, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- // Consume 112 frames, leave 16 frames left
- Consume(112);
-
- // Render another 16 frames
- EXPECT_CALL(render_callback_, Render(_, _))
- .WillOnce(RenderAudioBus(16, this));
- EXPECT_FALSE(sink_->PullFrames(NULL, NULL));
-
- Consume(32);
-
- EXPECT_TRUE(AllConsumed());
-
- EXPECT_CALL(streamer_, HasStream(sink_.get())).WillRepeatedly(Return(true));
- EXPECT_CALL(streamer_, RemoveStream(sink_.get())).WillOnce(Return());
- EXPECT_CALL(streamer_, HasStream(sink_.get()))
- .WillRepeatedly(Return(false));
- sink_->Stop();
- }
-}
-
-} // namespace
diff --git a/src/media/audio/shell_audio_streamer.h b/src/media/audio/shell_audio_streamer.h
deleted file mode 100644
index ca529b3..0000000
--- a/src/media/audio/shell_audio_streamer.h
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_SHELL_AUDIO_STREAMER_H_
-#define MEDIA_AUDIO_SHELL_AUDIO_STREAMER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-class AudioBus;
-class AudioParameters;
-
-// Abstract class for adding an audio stream to the audio streamer.
-// Your class can implement this interface and then call AddStream(this) to
-// attach itself to the hardware audio streamer.
-class ShellAudioStream {
- public:
- // Checks if "Pause" has been requested on this stream. The streamer will
- // halt playback the next time it updates.
- virtual bool PauseRequested() const = 0;
- // This function serves several purposes:
- // 1. Once the audio stream is added to the streamer. This function will be
- // called periodically so the stream (the AudioSink) can pull data from
- // upper level, even when it is paused.
- // 2. It will return true to indicate that it is playing, false to pause.
- // The frame referred in this function is not an AAC frame but a PCM frame. It
- // contains a group of samples start at the same timestamp, each of them are
- // from different channels of a multi-channel audio stream.
- // NOTE: This function can be called on a low level audio mixer thread and
- // is LATENCY-SENSITIVE. Avoid locks and other high-latency operations!
- virtual bool PullFrames(uint32_t* offset_in_frame,
- uint32_t* total_frames) = 0;
- // This function tells the stream that `frame_played` of audio frames have
- // been played and can be removed from the buffer. The stream can also use
- // this to calculate the time elapsed. The stream shouldn't pull any data
- // in this function, PullFrames is the only point to pull data.
- virtual void ConsumeFrames(uint32_t frame_played) = 0;
- // Get the AudioParameters for this stream
- virtual const AudioParameters& GetAudioParameters() const = 0;
- // Get the internal buffer of this audio stream as an AudioBus.
- virtual AudioBus* GetAudioBus() = 0;
-};
-
-// The class contains stub functions for platform specific audio playback.
-// Classes inherited from it have to implement all the pure virtual functions
-// and provide implementations for the static functions.
-class ShellAudioStreamer {
- public:
- class Config {
- public:
- static const uint32 kInvalidSampleRate = 0;
-
- enum StorageMode { INTERLEAVED, PLANAR };
-
- Config() : valid_(false) {}
-
- // Initialize the Config settings, see the comment on individual member
- // below for more details.
- Config(StorageMode storage_mode,
- uint32 initial_rebuffering_frames_per_channel,
- uint32 sink_buffer_size_in_frames_per_channel,
- uint32 max_hardware_channels,
- uint32 bytes_per_sample,
- uint32 native_output_sample_rate = kInvalidSampleRate)
- : valid_(true),
- interleaved_(storage_mode == INTERLEAVED),
- initial_rebuffering_frames_per_channel_(
- initial_rebuffering_frames_per_channel),
- sink_buffer_size_in_frames_per_channel_(
- sink_buffer_size_in_frames_per_channel),
- max_hardware_channels_(max_hardware_channels),
- bytes_per_sample_(bytes_per_sample),
- native_output_sample_rate_(native_output_sample_rate) {
- const size_t kFramesPerAccessUnit = mp4::AAC::kFramesPerAccessUnit;
- DCHECK_LE(initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel);
- DCHECK_EQ(initial_rebuffering_frames_per_channel % kFramesPerAccessUnit,
- 0);
- DCHECK_EQ(sink_buffer_size_in_frames_per_channel % kFramesPerAccessUnit,
- 0);
- }
-
- bool interleaved() const {
- AssertValid();
- return interleaved_;
- }
- uint32 initial_rebuffering_frames_per_channel() const {
- AssertValid();
- return initial_rebuffering_frames_per_channel_;
- }
- uint32 sink_buffer_size_in_frames_per_channel() const {
- AssertValid();
- return sink_buffer_size_in_frames_per_channel_;
- }
- uint32 max_hardware_channels() const {
- AssertValid();
- return max_hardware_channels_;
- }
- uint32 bytes_per_sample() const {
- AssertValid();
- return bytes_per_sample_;
- }
- uint32 native_output_sample_rate() const {
- AssertValid();
- return native_output_sample_rate_;
- }
-
- private:
- void AssertValid() const { DCHECK(valid_); }
-
- bool valid_;
-
- // Is the data in audio bus interleaved and stored as one channel.
- bool interleaved_;
- // The following parameter controls the sink rebuffering.
- // See ShellAudioSink::ResumeAfterUnderflow for more details.
- uint32 initial_rebuffering_frames_per_channel_;
- uint32 sink_buffer_size_in_frames_per_channel_;
- // Max channels the current audio hardware can render. This can be changed
- // during the running of the application as the user can plug/unplug
- // different devices. So it represent the current status on the time of
- // query.
- uint32 max_hardware_channels_;
- uint32 bytes_per_sample_;
- uint32 native_output_sample_rate_;
- };
-
- struct OutputDevice {
- std::string connector;
- uint32 latency_ms;
- std::string coding_type;
- uint32 number_of_channels;
- uint32 sampling_frequency;
- };
-
- ShellAudioStreamer() {}
- virtual ~ShellAudioStreamer(){};
-
- // The only instance of the platform specific audio streamer. It becomes
- // valid after calling Initialize and become NULL after calling Terminate.
- static ShellAudioStreamer* Instance();
- static void Initialize();
- static void Terminate();
-
- virtual Config GetConfig() const = 0;
- virtual bool AddStream(ShellAudioStream* stream) = 0;
- virtual void RemoveStream(ShellAudioStream* stream) = 0;
- virtual bool HasStream(ShellAudioStream* stream) const = 0;
- virtual bool SetVolume(ShellAudioStream* stream, double volume) = 0;
- // Some consoles have background music tracks playing even when other apps
- // are running. This function can be used to stop the background music.
- virtual void StopBackgroundMusic() {}
- // Returns the available audio output devices. This function is for
- // informational purpose and is currently only used to create
- // h5vcc::AudioConfig.
- virtual std::vector<OutputDevice> GetOutputDevices() {
- return std::vector<OutputDevice>();
- }
-
- DISALLOW_COPY_AND_ASSIGN(ShellAudioStreamer);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SHELL_AUDIO_STREAMER_H_
diff --git a/src/media/audio/shell_audio_streamer_linux.cc b/src/media/audio/shell_audio_streamer_linux.cc
deleted file mode 100644
index cd35b97..0000000
--- a/src/media/audio/shell_audio_streamer_linux.cc
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/audio/shell_audio_streamer_linux.h"
-
-#include "base/logging.h"
-#include "lb_platform.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/shell_pulse_audio.h"
-#include "media/base/audio_bus.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-namespace {
-
-ShellAudioStreamerLinux* instance = NULL;
-
-} // namespace
-
-class PulseAudioHost : public ShellPulseAudioStream::Host {
- public:
- PulseAudioHost(ShellPulseAudioContext* pulse_audio_context,
- ShellAudioStream* stream,
- int rate,
- int channels);
- ~PulseAudioHost();
- virtual void RequestFrame(size_t length, WriteFunc write) OVERRIDE;
-
- private:
- enum StreamState {
- STATE_INVALID,
- STATE_PAUSED, // Voice is paused, will play when unpaused
- STATE_RUNNING, // Voice is playing, reading new data when possible
- };
-
- ShellPulseAudioContext* pulse_audio_context_;
- int channels_;
- uint32 played_frames_; // frames played by the audio driver
- uint32 written_frames_; // frames written to the audio driver
- StreamState state_;
- ShellAudioStream* lb_audio_stream_;
- ShellPulseAudioStream* pulse_audio_stream_;
-};
-
-ShellAudioStreamer::Config ShellAudioStreamerLinux::GetConfig() const {
- const uint32 initial_rebuffering_frames_per_channel =
- mp4::AAC::kFramesPerAccessUnit * 32;
- const uint32 sink_buffer_size_in_frames_per_channel =
- initial_rebuffering_frames_per_channel * 8;
- const uint32 max_hardware_channels = 2;
-
- return Config(Config::INTERLEAVED, initial_rebuffering_frames_per_channel,
- sink_buffer_size_in_frames_per_channel, max_hardware_channels,
- sizeof(float) /* bytes_per_sample */);
-}
-
-bool ShellAudioStreamerLinux::AddStream(ShellAudioStream* stream) {
- base::AutoLock lock(streams_lock_);
-
- if (pulse_audio_context_ == NULL) {
- pulse_audio_context_.reset(new ShellPulseAudioContext());
- bool result = pulse_audio_context_->Initialize();
- if (!result) {
- pulse_audio_context_.reset();
- DLOG(WARNING) << "Failed to initialize pulse audio.";
- return false;
- }
- }
-
- // other basic checks, it is assumed that the decoder or renderer algorithm
- // will have rejected invalid configurations before creating a sink, so
- // here they are asserts instead of run-time errors
- const AudioParameters& params = stream->GetAudioParameters();
- DCHECK(params.channels() == 1 || params.channels() == 2);
- DCHECK_EQ(params.bits_per_sample(), 32);
-
- const AudioParameters& audio_parameters = stream->GetAudioParameters();
- const int sample_rate = audio_parameters.sample_rate();
-
- streams_[stream] = new PulseAudioHost(pulse_audio_context_.get(), stream,
- sample_rate, params.channels());
-
- return true;
-}
-
-void ShellAudioStreamerLinux::RemoveStream(ShellAudioStream* stream) {
- base::AutoLock lock(streams_lock_);
-
- StreamMap::iterator it = streams_.find(stream);
- if (it == streams_.end())
- return;
- delete it->second;
- streams_.erase(it);
-
- if (streams_.empty()) {
- pulse_audio_context_.reset();
- }
-}
-
-bool ShellAudioStreamerLinux::HasStream(ShellAudioStream* stream) const {
- base::AutoLock lock(streams_lock_);
- return streams_.find(stream) != streams_.end();
-}
-
-bool ShellAudioStreamerLinux::SetVolume(ShellAudioStream* stream,
- double volume) {
- if (volume != 1.0) {
- NOTIMPLEMENTED();
- }
- return volume != 1.0;
-}
-
-ShellAudioStreamerLinux::ShellAudioStreamerLinux()
- : streams_value_deleter_(&streams_) {
- instance = this;
-}
-
-ShellAudioStreamerLinux::~ShellAudioStreamerLinux() {
- DCHECK(streams_.empty());
- instance = NULL;
-}
-
-PulseAudioHost::PulseAudioHost(ShellPulseAudioContext* pulse_audio_context,
- ShellAudioStream* stream,
- int rate,
- int channels)
- : channels_(channels),
- pulse_audio_context_(pulse_audio_context),
- played_frames_(0),
- written_frames_(0),
- state_(STATE_PAUSED),
- lb_audio_stream_(stream) {
- pulse_audio_stream_ = pulse_audio_context->CreateStream(this, rate, channels);
-}
-
-PulseAudioHost::~PulseAudioHost() {
- if (pulse_audio_stream_) {
- pulse_audio_context_->DestroyStream(pulse_audio_stream_);
- }
-}
-
-void PulseAudioHost::RequestFrame(size_t length, WriteFunc write) {
- uint64 time_played = pulse_audio_stream_->GetPlaybackCursorInMicroSeconds();
- int sample_rate = lb_audio_stream_->GetAudioParameters().sample_rate();
- uint64 frames_played = time_played * sample_rate / 1000000;
- uint32 frame_consumed = 0;
- uint32 frame_pulled;
-
- if (frames_played > written_frames_)
- frames_played = written_frames_;
- if (frames_played > played_frames_)
- frame_consumed = frames_played - played_frames_;
- played_frames_ += frame_consumed;
-
- // Our samples are in floats.
- const int kBytesPerFrame = sizeof(float) * channels_;
- DCHECK_EQ(length % kBytesPerFrame, 0);
- length /= kBytesPerFrame;
- const AudioBus* audio_bus = lb_audio_stream_->GetAudioBus();
-
- lb_audio_stream_->ConsumeFrames(frame_consumed);
- lb_audio_stream_->PullFrames(NULL, &frame_pulled);
-
- if (played_frames_ + frame_pulled > written_frames_ && length) {
- frame_pulled = played_frames_ + frame_pulled - written_frames_;
- frame_pulled = std::min<size_t>(frame_pulled, length);
-
- uint32 frames = audio_bus->frames() / channels_;
- uint32 frame_offset = written_frames_ % frames;
-
- uint32 frame_to_write =
- std::min<size_t>(frame_pulled, frames - frame_offset);
- const float* buffer = audio_bus->channel(0) + frame_offset * channels_;
- write.Run(reinterpret_cast<const uint8*>(buffer),
- frame_to_write * kBytesPerFrame);
- written_frames_ += frame_to_write;
- }
-
- switch (state_) {
- case STATE_PAUSED:
- if (!lb_audio_stream_->PauseRequested()) {
- pulse_audio_stream_->Play();
- state_ = STATE_RUNNING;
- }
- break;
- case STATE_RUNNING:
- if (lb_audio_stream_->PauseRequested()) {
- pulse_audio_stream_->Pause();
- state_ = STATE_PAUSED;
- }
- break;
- case STATE_INVALID:
- break;
- }
-}
-
-void ShellAudioStreamer::Initialize() {
- CHECK(!instance);
- new ShellAudioStreamerLinux();
-}
-
-void ShellAudioStreamer::Terminate() {
- CHECK(instance);
- delete instance;
- instance = NULL;
-}
-
-ShellAudioStreamer* ShellAudioStreamer::Instance() {
- CHECK(instance);
- return instance;
-}
-
-} // namespace media
diff --git a/src/media/audio/shell_audio_streamer_linux.h b/src/media/audio/shell_audio_streamer_linux.h
deleted file mode 100644
index 06d9e38..0000000
--- a/src/media/audio/shell_audio_streamer_linux.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2013 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_SHELL_AUDIO_STREAMER_LINUX_H_
-#define MEDIA_AUDIO_SHELL_AUDIO_STREAMER_LINUX_H_
-
-#include <map>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/stl_util.h"
-#include "base/synchronization/lock.h"
-#include "media/audio/shell_audio_streamer.h"
-#include "media/audio/shell_pulse_audio.h"
-
-namespace media {
-
-class PulseAudioHost;
-
-class ShellAudioStreamerLinux : public ShellAudioStreamer {
- public:
- ShellAudioStreamerLinux();
- ~ShellAudioStreamerLinux();
-
- virtual Config GetConfig() const OVERRIDE;
- virtual bool AddStream(ShellAudioStream* stream) OVERRIDE;
- virtual void RemoveStream(ShellAudioStream* stream) OVERRIDE;
- virtual bool HasStream(ShellAudioStream* stream) const OVERRIDE;
- virtual bool SetVolume(ShellAudioStream* stream, double volume) OVERRIDE;
-
- private:
- typedef std::map<ShellAudioStream*, PulseAudioHost*> StreamMap;
- StreamMap streams_;
- scoped_ptr<ShellPulseAudioContext> pulse_audio_context_;
- STLValueDeleter<StreamMap> streams_value_deleter_;
- mutable base::Lock streams_lock_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellAudioStreamerLinux);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SHELL_AUDIO_STREAMER_LINUX_H_
diff --git a/src/media/audio/shell_pulse_audio.cc b/src/media/audio/shell_pulse_audio.cc
deleted file mode 100644
index 561a24f..0000000
--- a/src/media/audio/shell_pulse_audio.cc
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "media/audio/shell_pulse_audio.h"
-
-#include "base/bind.h"
-
-namespace media {
-
-ShellPulseAudioStream::ShellPulseAudioStream()
- : context_(NULL),
- latency_(kMinLatency),
- stream_(NULL),
- last_request_size_(0),
- host_(NULL) {
-}
-
-ShellPulseAudioStream::~ShellPulseAudioStream() {
- if (stream_) {
- pa_stream_set_write_callback(stream_, NULL, NULL);
- pa_stream_set_underflow_callback(stream_, NULL, NULL);
- pa_stream_disconnect(stream_);
- pa_stream_unref(stream_);
- }
-}
-
-bool ShellPulseAudioStream::Initialize(ShellPulseAudioContext* context,
- Host* host, int rate, int channel) {
- context_ = context;
- host_ = host;
- sample_spec_.rate = rate;
- sample_spec_.channels = channel;
- sample_spec_.format = PA_SAMPLE_FLOAT32LE;
-
- stream_ = pa_stream_new(context_->GetContext(), "Playback", &sample_spec_,
- NULL);
- if (!stream_)
- return false;
-
- pa_stream_set_write_callback(stream_, RequestCallback, this);
- pa_stream_set_underflow_callback(stream_, UnderflowCallback, this);
- buf_attr_.fragsize = ~0;
- buf_attr_.maxlength = pa_usec_to_bytes(latency_, &sample_spec_);
- buf_attr_.minreq = pa_usec_to_bytes(0, &sample_spec_);
- buf_attr_.prebuf = ~0;
- buf_attr_.tlength = buf_attr_.maxlength;
-
- const pa_stream_flags_t kNoLatency =
- static_cast<pa_stream_flags_t>(PA_STREAM_INTERPOLATE_TIMING |
- PA_STREAM_AUTO_TIMING_UPDATE |
- PA_STREAM_START_CORKED);
- const pa_stream_flags_t kWithLatency =
- static_cast<pa_stream_flags_t>(kNoLatency | PA_STREAM_ADJUST_LATENCY);
- if (pa_stream_connect_playback(
- stream_, NULL, &buf_attr_, kWithLatency, NULL, NULL) >= 0) {
- return true;
- }
-
- // Try again without latency flag.
- if (pa_stream_connect_playback(
- stream_, NULL, &buf_attr_, kNoLatency, NULL, NULL) >= 0) {
- return true;
- }
- pa_stream_unref(stream_);
- stream_ = NULL;
- return false;
-}
-
-bool ShellPulseAudioStream::Play() {
- return Cork(false);
-}
-
-bool ShellPulseAudioStream::Pause() {
- return Cork(true);
-}
-
-uint64 ShellPulseAudioStream::GetPlaybackCursorInMicroSeconds() {
- pa_usec_t usec = 0;
- if (pa_stream_get_time(stream_, &usec) == 0)
- return usec;
- return 0;
-}
-
-void ShellPulseAudioStream::RequestFrame() {
- host_->RequestFrame(
- last_request_size_, base::Bind(&ShellPulseAudioStream::WriteFrame,
- base::Unretained(this)));
-}
-
-void ShellPulseAudioStream::RequestCallback(pa_stream* s, size_t length,
- void* userdata) {
- ShellPulseAudioStream* stream = static_cast<ShellPulseAudioStream*>(userdata);
- stream->HandleRequest(length);
-}
-
-void ShellPulseAudioStream::HandleRequest(size_t length) {
- last_request_size_ = length;
-}
-
-void ShellPulseAudioStream::UnderflowCallback(pa_stream* s, void* userdata) {
- ShellPulseAudioStream* stream = static_cast<ShellPulseAudioStream*>(userdata);
- stream->HandleUnderflow();
-}
-
-void ShellPulseAudioStream::HandleUnderflow() {
- if (latency_ < kMaxLatency) {
- latency_ *= 2;
- if (latency_ > kMaxLatency)
- latency_ = kMaxLatency;
- buf_attr_.maxlength = pa_usec_to_bytes(latency_, &sample_spec_);
- buf_attr_.tlength = buf_attr_.maxlength;
- pa_stream_set_buffer_attr(stream_, &buf_attr_, NULL, NULL);
- }
-}
-
-bool ShellPulseAudioStream::Cork(bool pause) {
- pa_stream_cork(stream_, pause, SuccessCallback, NULL);
- return true;
-}
-
-void ShellPulseAudioStream::SuccessCallback(pa_stream* s, int success,
- void* userdata) {
-}
-
-void ShellPulseAudioStream::WriteFrame(const uint8* data, size_t size) {
- DCHECK_LE(size, last_request_size_);
- if (size != 0)
- pa_stream_write(stream_, data, size, NULL, 0LL, PA_SEEK_RELATIVE);
- last_request_size_ -= size;
-}
-
-ShellPulseAudioContext::ShellPulseAudioContext()
- : mainloop_(NULL),
- context_(NULL),
- pulse_thread_("PulseAudioThread") {
-}
-
-ShellPulseAudioContext::~ShellPulseAudioContext() {
- pulse_thread_.Stop();
- DCHECK(streams_.empty());
- if (context_) {
- pa_context_disconnect(context_);
- pa_context_unref(context_);
- }
- if (mainloop_) {
- pa_mainloop_free(mainloop_);
- }
- if (pulse_thread_.IsRunning()) {
- pulse_thread_.Stop();
- }
-}
-
-bool ShellPulseAudioContext::Initialize() {
- mainloop_ = pa_mainloop_new();
- context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "ShellPulseAudio");
-
- // Set the state callback. This will be called from with pa_mainloop_iterate.
- int pa_ready = kInitial;
- pa_context_set_state_callback(context_, StateCallback, &pa_ready);
-
- // Try to connect to the context, or return on failure.
- if (pa_context_connect(context_, NULL, pa_context_flags_t(), NULL) < 0) {
- DLOG(ERROR) << "Error connecting to context.";
- return false;
- }
-
- // Wait until the context is ready.
- while (pa_ready == kInitial) {
- pa_mainloop_iterate(mainloop_, 1, NULL);
- }
-
- // Clear the state callback.
- pa_context_set_state_callback(context_, NULL, NULL);
-
- if (pa_ready == kReady) {
- base::Thread::Options options;
- options.priority = base::kThreadPriority_RealtimeAudio;
- pulse_thread_.StartWithOptions(options);
- pulse_thread_.message_loop()->PostTask(FROM_HERE,
- base::Bind(&ShellPulseAudioContext::Iterate, base::Unretained(this)));
- }
-
- return pa_ready == kReady;
-}
-
-pa_context* ShellPulseAudioContext::GetContext() {
- return context_;
-}
-
-void ShellPulseAudioContext::Iterate() {
- base::AutoLock lock(lock_);
- pulse_thread_.message_loop()->PostDelayedTask(
- FROM_HERE, base::Bind(&ShellPulseAudioContext::Iterate,
- base::Unretained(this)),
- base::TimeDelta::FromMilliseconds(5));
- pa_mainloop_iterate(mainloop_, 0, NULL);
-
- for (Streams::iterator iter = streams_.begin();
- iter != streams_.end(); ++iter) {
- (*iter)->RequestFrame();
- }
-}
-
-ShellPulseAudioStream* ShellPulseAudioContext::CreateStream(
- ShellPulseAudioStream::Host* host, int rate, int channel) {
- base::AutoLock lock(lock_);
-
- ShellPulseAudioStream* stream = new ShellPulseAudioStream;
- bool result = stream->Initialize(this, host, rate, channel);
- DCHECK(result);
- streams_.insert(stream);
- return stream;
-}
-
-void ShellPulseAudioContext::DestroyStream(ShellPulseAudioStream* stream) {
- base::AutoLock lock(lock_);
- DCHECK(streams_.find(stream) != streams_.end());
- streams_.erase(streams_.find(stream));
- delete stream;
-}
-
-void ShellPulseAudioContext::StateCallback(pa_context* c, void* userdata) {
- int* pa_ready = static_cast<int*>(userdata);
-
- switch (pa_context_get_state(c)) {
- case PA_CONTEXT_FAILED:
- case PA_CONTEXT_TERMINATED:
- *pa_ready = kError;
- break;
- case PA_CONTEXT_READY:
- *pa_ready = kReady;
- break;
- default:
- break;
- }
-}
-
-} // namespace media
-
diff --git a/src/media/audio/shell_pulse_audio.h b/src/media/audio/shell_pulse_audio.h
deleted file mode 100644
index 6692eba..0000000
--- a/src/media/audio/shell_pulse_audio.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef MEDIA_AUDIO_SHELL_PULSE_AUDIO_H_
-#define MEDIA_AUDIO_SHELL_PULSE_AUDIO_H_
-
-#include <pulse/pulseaudio.h>
-
-#include <set>
-
-#include "base/synchronization/lock.h"
-#include "base/threading/thread.h"
-
-namespace media {
-
-class ShellPulseAudioContext;
-
-class ShellPulseAudioStream {
- public:
- class Host {
- public:
- typedef base::Callback<void(const uint8*, size_t)> WriteFunc;
- virtual ~Host() {}
- virtual void RequestFrame(size_t length, WriteFunc write) = 0;
- };
-
- ShellPulseAudioStream();
- ~ShellPulseAudioStream();
-
- bool Initialize(ShellPulseAudioContext* context, Host* host, int rate,
- int channel);
- bool Play();
- bool Pause();
- uint64 GetPlaybackCursorInMicroSeconds();
- void RequestFrame();
-
- private:
- enum {
- kInitial = 0,
- kSuccess,
- kFailure
- };
-
- static const int kMinLatency = 1000000;
- static const int kMaxLatency = 4000000;
-
- static void RequestCallback(pa_stream* s, size_t length, void* userdata);
- void HandleRequest(size_t length);
- static void UnderflowCallback(pa_stream* s, void* userdata);
- void HandleUnderflow();
- static void SuccessCallback(pa_stream* s, int success, void* userdata);
- bool Cork(bool pause);
-
- void WriteFrame(const uint8* data, size_t size);
-
- ShellPulseAudioContext* context_;
- int latency_;
- pa_buffer_attr buf_attr_;
- pa_sample_spec sample_spec_;
- pa_stream* stream_;
- size_t last_request_size_;
- Host* host_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellPulseAudioStream);
-};
-
-class ShellPulseAudioContext {
- public:
- ShellPulseAudioContext();
- ~ShellPulseAudioContext();
-
- bool Initialize();
- pa_context* GetContext();
- void Iterate();
-
- ShellPulseAudioStream* CreateStream(ShellPulseAudioStream::Host* host,
- int rate, int channel);
- void DestroyStream(ShellPulseAudioStream* stream);
-
- base::Lock& lock() { return lock_; }
-
- private:
- typedef std::set<ShellPulseAudioStream*> Streams;
- enum {
- kInitial = 0,
- kReady,
- kError
- };
-
- static void StateCallback(pa_context* c, void* userdata);
-
- pa_mainloop* mainloop_;
- pa_context* context_;
- base::Thread pulse_thread_;
-
- Streams streams_;
- base::Lock lock_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellPulseAudioContext);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SHELL_PULSE_AUDIO_H_
diff --git a/src/media/audio/shell_wav_test_probe.cc b/src/media/audio/shell_wav_test_probe.cc
deleted file mode 100644
index f4197a1..0000000
--- a/src/media/audio/shell_wav_test_probe.cc
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/audio/shell_wav_test_probe.h"
-
-#include <string>
-
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "base/platform_file.h"
-#include "media/base/endian_util.h"
-#include "media/filters/shell_demuxer.h"
-
-// don't include me in release builds please
-#if !defined(__LB_SHELL__FOR_RELEASE__)
-
-static const uint16 kWavFormatCodePCM = 0x0001;
-static const uint16 kWavFormatCodeIEEEFloat = 0x0003;
-// "RIFF" in ASCII (big-endian)
-static const uint32 kWav_RIFF = 0x52494646;
-// "WAVE" in ASCII (big-endian)
-static const uint32 kWav_WAVE = 0x57415645;
-// "fmt " in ASCII (big-endian)
-static const uint32 kWav_fmt = 0x666d7420;
-// "data" in ASCII (big-endian)
-static const uint32 kWav_data = 0x64617461;
-
-namespace media {
-
-ShellWavTestProbe::ShellWavTestProbe()
- : wav_file_(NULL),
- form_wav_length_bytes_(kWavTotalHeaderLength - 8),
- format_code_(0),
- channels_(0),
- samples_per_second_(0),
- bits_per_sample_(0),
- bytes_per_frame_(0),
- closed_(true),
- close_after_ms_(0) {}
-
-void ShellWavTestProbe::Initialize(const char* file_name,
- int channel_count,
- int samples_per_second,
- int bits_per_sample,
- bool use_floats) {
- // try to open file first
- FilePath base_path;
- bool path_ok = PathService::Get(base::DIR_EXE, &base_path);
- DCHECK(path_ok);
- base_path = base_path.Append(file_name);
- wav_file_ = base::CreatePlatformFile(
- base_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
- NULL, NULL);
- DCHECK_NE(wav_file_, base::kInvalidPlatformFileValue);
- closed_ = false;
-
- if (use_floats)
- format_code_ = kWavFormatCodeIEEEFloat;
- else
- format_code_ = kWavFormatCodePCM;
-
- channels_ = channel_count;
-
- bits_per_sample_ = (uint16)bits_per_sample;
- samples_per_second_ = samples_per_second;
-
- bytes_per_frame_ = (bits_per_sample_ / 8) * channels_;
-
- // write temporary header, it's incomplete until we know the whole length
- // of the sample stream, but this will advance file pointer to start of
- // audio data
- WriteHeader();
-}
-
-// see: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
-void ShellWavTestProbe::WriteHeader() {
- // first four bytes are FORM RIFF header
- endian_util::store_uint32_big_endian(kWav_RIFF, wav_header_buffer_);
- // next for are length of file - FORM header, uint32 little-endian
- endian_util::store_uint32_little_endian(form_wav_length_bytes_,
- wav_header_buffer_ + 4);
- // then WAVE header
- endian_util::store_uint32_big_endian(kWav_WAVE, wav_header_buffer_ + 8);
- // start common chunk with format "fmt " header
- endian_util::store_uint32_big_endian(kWav_fmt, wav_header_buffer_ + 12);
- // length of format chunk, uint32 little-endian
- endian_util::store_uint32_little_endian(kWavFormatChunkLength - 8,
- wav_header_buffer_ + 16);
- // format code, uint16 little-endian
- endian_util::store_uint16_little_endian(format_code_,
- wav_header_buffer_ + 20);
- // number of channels, uint16 little-endian
- endian_util::store_uint16_little_endian(channels_, wav_header_buffer_ + 22);
- // sample rate, uint32 little-endian
- endian_util::store_uint32_little_endian(samples_per_second_,
- wav_header_buffer_ + 24);
- // average bytes per second, uint32 little-endian, derived
- uint32 bytes_per_second = samples_per_second_ * bytes_per_frame_;
- endian_util::store_uint32_little_endian(bytes_per_second,
- wav_header_buffer_ + 28);
- // "block align", reading as bytes per frame, uint16 little-endian
- endian_util::store_uint16_little_endian(bytes_per_frame_,
- wav_header_buffer_ + 32);
- // bits per sample, uint16 little-endian
- endian_util::store_uint16_little_endian(bits_per_sample_,
- wav_header_buffer_ + 34);
- // size of extension format chunk header, uint16 little-endian
- // always 22 bytes for us so we can do > 2 channels audio
- endian_util::store_uint16_little_endian(22, wav_header_buffer_ + 36);
- // valid bits per sample, always same as bits per sample
- endian_util::store_uint16_little_endian(bits_per_sample_,
- wav_header_buffer_ + 38);
- // channel mask, 4 bytes, set to all zeroes to keep default channel layout
- endian_util::store_uint32_little_endian(0, wav_header_buffer_ + 40);
- // subformat guid, 16 bytes, first two bytes are format code again, rest
- // are a magic number 00 00 00 00 10 00 80 00 00 aa 00 38 9b 71
- uint64 magic_msd = ((uint64)format_code_ << 48) | 0x0000000000001000;
- endian_util::store_uint64_big_endian(magic_msd, wav_header_buffer_ + 44);
- endian_util::store_uint64_big_endian(0x800000aa00389b71,
- wav_header_buffer_ + 52);
- // start the data chunk with "data" header
- endian_util::store_uint32_big_endian(kWav_data, wav_header_buffer_ + 60);
- // data chunk size is form wav length minus the rest of the header bytes
- // uint32 little-endian
- uint32 data_chunk_size = form_wav_length_bytes_ - (kWavTotalHeaderLength - 8);
- endian_util::store_uint32_little_endian(data_chunk_size,
- wav_header_buffer_ + 64);
- // alright, aiff header buffer is current, now we can write it into the file
- // jump to start of file
- int result =
- base::SeekPlatformFile(wav_file_, base::PLATFORM_FILE_FROM_BEGIN, 0);
- // write buffer
- result = base::WritePlatformFileAtCurrentPos(
- wav_file_, reinterpret_cast<const char*>(wav_header_buffer_),
- kWavTotalHeaderLength);
- DCHECK_EQ(result, kWavTotalHeaderLength);
-}
-
-void ShellWavTestProbe::CloseAfter(uint64 milliseconds) {
- close_after_ms_ = milliseconds;
-}
-
-void ShellWavTestProbe::AddData(const uint8* data,
- uint32 length,
- uint64 timestamp) {
-#if defined(__LB_SHELL__BIG_ENDIAN__) || \
- (defined(OS_STARBOARD) && defined(SB_IS_BIG_ENDIAN) && SB_IS_BIG_ENDIAN)
- uint8* reverse_buffer = (uint8*)malloc(length);
- uint16 bytes_per_sample = bits_per_sample_ / 8;
- int num_words = length / bytes_per_sample;
- for (int i = 0; i < num_words; i++) {
- uint8* out = reverse_buffer + (i * bytes_per_sample);
- if (bytes_per_sample == 2) {
- endian_util::store_uint16_little_endian(((uint16*)data)[i], out);
- } else if (bytes_per_sample == 4) {
- endian_util::store_uint32_little_endian(((uint32*)data)[i], out);
- } else {
- DLOG(ERROR) << "Failed to add data";
- }
- }
- AddDataLittleEndian(reverse_buffer, length, timestamp);
- free(reverse_buffer);
-#else
- AddDataLittleEndian(data, length, timestamp);
-#endif
-}
-
-void ShellWavTestProbe::AddDataLittleEndian(const uint8* data,
- uint32 length,
- uint64 timestamp) {
- if (closed_)
- return;
- if (!length)
- return;
-
- int result = base::WritePlatformFileAtCurrentPos(
- wav_file_, reinterpret_cast<const char*>(data), length);
- DCHECK_EQ(result, length);
- base::FlushPlatformFile(wav_file_);
-
- // update our counters
- form_wav_length_bytes_ += length;
-
- if (close_after_ms_ > 0) {
- if (timestamp == 0) {
- // guess at timestamp based on total file size
- timestamp = (((uint64)form_wav_length_bytes_ -
- (uint64)(kWavTotalHeaderLength - 8)) *
- 1000ULL) /
- (uint64)(samples_per_second_ * bytes_per_frame_);
- }
- if (timestamp > close_after_ms_) {
- Close();
- }
- }
-}
-
-void ShellWavTestProbe::AddData(const scoped_refptr<Buffer>& buffer) {
- uint64 timestamp = 0;
- if (buffer->GetTimestamp() != kNoTimestamp()) {
- timestamp = buffer->GetTimestamp().InMilliseconds();
- }
- AddData(buffer->GetData(), buffer->GetDataSize(), timestamp);
-}
-
-void ShellWavTestProbe::Close() {
- if (closed_)
- return;
-
- closed_ = true;
- // write the header again now that we know the lengths
- WriteHeader();
- // close the file
- base::ClosePlatformFile(wav_file_);
- wav_file_ = NULL;
-}
-
-} // namespace media
-
-#endif // __LB_SHELL__FOR_RELEASE__
diff --git a/src/media/audio/shell_wav_test_probe.h b/src/media/audio/shell_wav_test_probe.h
deleted file mode 100644
index db37a8d..0000000
--- a/src/media/audio/shell_wav_test_probe.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_AUDIO_SHELL_WAV_TEST_PROBE_H_
-#define MEDIA_AUDIO_SHELL_WAV_TEST_PROBE_H_
-
-#include "base/platform_file.h"
-#include "media/base/buffers.h"
-
-// don't include me in release builds please
-#if !defined(__LB_SHELL__FOR_RELEASE__)
-
-static const uint32 kFormWavHeaderLength = 12;
-static const uint32 kWavFormatChunkLength = 48;
-static const uint32 kWavDataChunkHeaderLength = 8;
-static const uint32 kWavTotalHeaderLength =
- kFormWavHeaderLength + kWavFormatChunkLength + kWavDataChunkHeaderLength;
-
-namespace media {
-
-// Utility class for saving decoded audio bytes into a WAV file
-class MEDIA_EXPORT ShellWavTestProbe {
- public:
- ShellWavTestProbe();
- // if use_floats is true then the data is written as floating point,
- // if false it is assumed to be PCM unsigned ints
- void Initialize(const char* file_name,
- int channel_count,
- int samples_per_second,
- int bits_per_sample,
- bool use_floats);
- // automatically close the file after arg milliseconds added to file,
- // Close() will happen on first call to AddData() with timestamp past argument
- void CloseAfter(uint64 milliseconds);
- void AddData(const scoped_refptr<Buffer>& buffer);
- // timestamp can be zero, in which case we will guess at timestamp based on
- // number of bytes written, size of samples, and sample rate
- void AddData(const uint8* data, uint32 length, uint64 timestamp);
- void AddDataLittleEndian(const uint8* data, uint32 length, uint64 timestamp);
- void Close();
-
- private:
- // take the current state variables below and use them to write the
- // WAV header at the top of the file. Moves the file pointer.
- void WriteHeader();
-
- base::PlatformFile wav_file_;
- // wav header state variables
- uint32 form_wav_length_bytes_;
- uint16 format_code_;
- uint16 channels_;
- uint32 samples_per_second_;
- uint16 bits_per_sample_;
- uint8 wav_header_buffer_[kWavTotalHeaderLength];
- uint32 bytes_per_frame_;
- // other state
- bool closed_;
- uint64 close_after_ms_;
-};
-
-} // namespace media
-
-#endif // __LB_SHELL__FOR_RELEASE__
-
-#endif // MEDIA_AUDIO_SHELL_WAV_TEST_PROBE_H_
diff --git a/src/media/audio/simple_sources.cc b/src/media/audio/simple_sources.cc
deleted file mode 100644
index aeac86d..0000000
--- a/src/media/audio/simple_sources.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-// MSVC++ requires this to be set before any other includes to get M_PI.
-#define _USE_MATH_DEFINES
-#include <cmath>
-
-#include "media/audio/simple_sources.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "media/audio/audio_util.h"
-
-namespace media {
-
-//////////////////////////////////////////////////////////////////////////////
-// SineWaveAudioSource implementation.
-
-SineWaveAudioSource::SineWaveAudioSource(int channels,
- double freq, double sample_freq)
- : channels_(channels),
- f_(freq / sample_freq),
- time_state_(0),
- cap_(0),
- callbacks_(0),
- errors_(0) {
-}
-
-// The implementation could be more efficient if a lookup table is constructed
-// but it is efficient enough for our simple needs.
-int SineWaveAudioSource::OnMoreData(AudioBus* audio_bus,
- AudioBuffersState audio_buffers) {
- base::AutoLock auto_lock(time_lock_);
- callbacks_++;
-
- // The table is filled with s(t) = kint16max*sin(Theta*t),
- // where Theta = 2*PI*fs.
- // We store the discrete time value |t| in a member to ensure that the
- // next pass starts at a correct state.
- int max_frames = cap_ > 0 ?
- std::min(audio_bus->frames(), cap_ - time_state_) : audio_bus->frames();
- for (int i = 0; i < max_frames; ++i)
- audio_bus->channel(0)[i] = sin(2.0 * M_PI * f_ * time_state_++);
- for (int i = 1; i < audio_bus->channels(); ++i) {
- memcpy(audio_bus->channel(i), audio_bus->channel(0),
- max_frames * sizeof(*audio_bus->channel(i)));
- }
- return max_frames;
-}
-
-int SineWaveAudioSource::OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState audio_buffers) {
- return OnMoreData(dest, audio_buffers);
-}
-
-void SineWaveAudioSource::OnError(AudioOutputStream* stream, int code) {
- errors_++;
-}
-
-void SineWaveAudioSource::CapSamples(int cap) {
- base::AutoLock auto_lock(time_lock_);
- DCHECK_GT(cap, 0);
- cap_ = cap;
-}
-
-void SineWaveAudioSource::Reset() {
- base::AutoLock auto_lock(time_lock_);
- time_state_ = 0;
-}
-
-} // namespace media
diff --git a/src/media/audio/simple_sources.h b/src/media/audio/simple_sources.h
deleted file mode 100644
index 80bd517..0000000
--- a/src/media/audio/simple_sources.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_SIMPLE_SOURCES_H_
-#define MEDIA_AUDIO_SIMPLE_SOURCES_H_
-
-#include "base/synchronization/lock.h"
-#include "media/audio/audio_io.h"
-#include "media/base/seekable_buffer.h"
-
-namespace media {
-
-// An audio source that produces a pure sinusoidal tone.
-class MEDIA_EXPORT SineWaveAudioSource
- : public AudioOutputStream::AudioSourceCallback {
- public:
- // |channels| is the number of audio channels, |freq| is the frequency in
- // hertz and it has to be less than half of the sampling frequency
- // |sample_freq| or else you will get aliasing.
- SineWaveAudioSource(int channels, double freq, double sample_freq);
- virtual ~SineWaveAudioSource() {}
-
- // Return up to |cap| samples of data via OnMoreData(). Use Reset() to
- // allow more data to be served.
- void CapSamples(int cap);
- void Reset();
-
- // Implementation of AudioSourceCallback.
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState audio_buffers) OVERRIDE;
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState audio_buffers) OVERRIDE;
- virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
-
- // The number of OnMoreData()+OnMoreIOData() and OnError() calls respectively.
- int callbacks() { return callbacks_; }
- int errors() { return errors_; }
-
- protected:
- int channels_;
- double f_;
- int time_state_;
- int cap_;
- int callbacks_;
- int errors_;
- base::Lock time_lock_;
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_SIMPLE_SOURCES_H_
diff --git a/src/media/audio/simple_sources_unittest.cc b/src/media/audio/simple_sources_unittest.cc
deleted file mode 100644
index cee5d8a..0000000
--- a/src/media/audio/simple_sources_unittest.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <limits>
-
-#include "base/logging.h"
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_parameters.h"
-#include "media/audio/simple_sources.h"
-#include "media/base/audio_bus.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Validate that the SineWaveAudioSource writes the expected values.
-TEST(SimpleSources, SineWaveAudioSource) {
- static const uint32 samples = 1024;
- static const uint32 bytes_per_sample = 2;
- static const int freq = 200;
-
- AudioParameters params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kTelephoneSampleRate, bytes_per_sample * 8, samples);
-
- SineWaveAudioSource source(1, freq, params.sample_rate());
- scoped_ptr<AudioBus> audio_bus = AudioBus::Create(params);
- source.OnMoreData(audio_bus.get(), AudioBuffersState());
- EXPECT_EQ(1, source.callbacks());
- EXPECT_EQ(0, source.errors());
-
- uint32 half_period = AudioParameters::kTelephoneSampleRate / (freq * 2);
-
- // Spot test positive incursion of sine wave.
- EXPECT_NEAR(0, audio_bus->channel(0)[0],
- std::numeric_limits<float>::epsilon());
- EXPECT_FLOAT_EQ(0.15643446f, audio_bus->channel(0)[1]);
- EXPECT_LT(audio_bus->channel(0)[1], audio_bus->channel(0)[2]);
- EXPECT_LT(audio_bus->channel(0)[2], audio_bus->channel(0)[3]);
- // Spot test negative incursion of sine wave.
- EXPECT_NEAR(0, audio_bus->channel(0)[half_period],
- std::numeric_limits<float>::epsilon());
- EXPECT_FLOAT_EQ(-0.15643446f, audio_bus->channel(0)[half_period + 1]);
- EXPECT_GT(audio_bus->channel(0)[half_period + 1],
- audio_bus->channel(0)[half_period + 2]);
- EXPECT_GT(audio_bus->channel(0)[half_period + 2],
- audio_bus->channel(0)[half_period + 3]);
-}
-
-TEST(SimpleSources, SineWaveAudioCapped) {
- SineWaveAudioSource source(1, 200, AudioParameters::kTelephoneSampleRate);
-
- static const int kSampleCap = 100;
- source.CapSamples(kSampleCap);
-
- scoped_ptr<AudioBus> audio_bus = AudioBus::Create(1, 2 * kSampleCap);
- EXPECT_EQ(source.OnMoreData(
- audio_bus.get(), AudioBuffersState()), kSampleCap);
- EXPECT_EQ(1, source.callbacks());
- EXPECT_EQ(source.OnMoreData(audio_bus.get(), AudioBuffersState()), 0);
- EXPECT_EQ(2, source.callbacks());
- source.Reset();
- EXPECT_EQ(source.OnMoreData(
- audio_bus.get(), AudioBuffersState()), kSampleCap);
- EXPECT_EQ(3, source.callbacks());
- EXPECT_EQ(0, source.errors());
-}
-
-TEST(SimpleSources, OnError) {
- SineWaveAudioSource source(1, 200, AudioParameters::kTelephoneSampleRate);
- source.OnError(NULL, 0);
- EXPECT_EQ(1, source.errors());
- source.OnError(NULL, 0);
- EXPECT_EQ(2, source.errors());
-}
-
-} // namespace media
diff --git a/src/media/audio/test_audio_input_controller_factory.cc b/src/media/audio/test_audio_input_controller_factory.cc
deleted file mode 100644
index 64bfb9f..0000000
--- a/src/media/audio/test_audio_input_controller_factory.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/test_audio_input_controller_factory.h"
-#include "media/audio/audio_io.h"
-
-namespace media {
-
-TestAudioInputController::TestAudioInputController(
- TestAudioInputControllerFactory* factory,
- AudioManager* audio_manager,
- const AudioParameters& audio_parameters,
- EventHandler* event_handler,
- SyncWriter* sync_writer)
- : AudioInputController(event_handler, sync_writer),
- audio_parameters_(audio_parameters),
- factory_(factory),
- event_handler_(event_handler) {
- message_loop_ = audio_manager->GetMessageLoop();
-}
-
-TestAudioInputController::~TestAudioInputController() {
- // Inform the factory so that it allows creating new instances in future.
- factory_->OnTestAudioInputControllerDestroyed(this);
-}
-
-void TestAudioInputController::Record() {
- if (factory_->delegate_)
- factory_->delegate_->TestAudioControllerOpened(this);
-}
-
-void TestAudioInputController::Close(const base::Closure& closed_task) {
- message_loop_->PostTask(FROM_HERE, closed_task);
- if (factory_->delegate_)
- factory_->delegate_->TestAudioControllerClosed(this);
-}
-
-TestAudioInputControllerFactory::TestAudioInputControllerFactory()
- : controller_(NULL),
- delegate_(NULL) {
-}
-
-TestAudioInputControllerFactory::~TestAudioInputControllerFactory() {
- DCHECK(!controller_);
-}
-
-AudioInputController* TestAudioInputControllerFactory::Create(
- AudioManager* audio_manager,
- AudioInputController::EventHandler* event_handler,
- AudioParameters params) {
- DCHECK(!controller_); // Only one test instance managed at a time.
- controller_ = new TestAudioInputController(this, audio_manager, params,
- event_handler, NULL);
- return controller_;
-}
-
-void TestAudioInputControllerFactory::SetDelegateForTests(
- TestAudioInputControllerDelegate* delegate) {
- delegate_ = delegate;
-}
-
-void TestAudioInputControllerFactory::OnTestAudioInputControllerDestroyed(
- TestAudioInputController* controller) {
- DCHECK_EQ(controller_, controller);
- controller_ = NULL;
-}
-
-} // namespace media
diff --git a/src/media/audio/test_audio_input_controller_factory.h b/src/media/audio/test_audio_input_controller_factory.h
deleted file mode 100644
index 0a17947..0000000
--- a/src/media/audio/test_audio_input_controller_factory.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_TEST_AUDIO_INPUT_CONTROLLER_FACTORY_H_
-#define MEDIA_AUDIO_TEST_AUDIO_INPUT_CONTROLLER_FACTORY_H_
-
-#include "base/bind.h"
-#include "media/audio/audio_input_controller.h"
-
-namespace media {
-
-class TestAudioInputControllerFactory;
-
-// TestAudioInputController and TestAudioInputControllerFactory are used for
-// testing consumers of AudioInputController. TestAudioInputControllerFactory
-// is a AudioInputController::Factory that creates TestAudioInputControllers.
-//
-// TestAudioInputController::Record and Close are overriden to do nothing. It is
-// expected that you'll grab the EventHandler from the TestAudioInputController
-// and invoke the callback methods when appropriate. In this way it's easy to
-// mock a AudioInputController.
-//
-// Typical usage:
-// // Create and register factory.
-// TestAudioInputControllerFactory factory;
-// AudioInputController::set_factory_for_testing(&factory);
-//
-// // Do something that triggers creation of an AudioInputController.
-// TestAudioInputController* controller = factory.last_controller();
-// DCHECK(controller);
-//
-// // Notify event handler with whatever data you want.
-// controller->event_handler()->OnCreated(...);
-//
-// // Do something that triggers AudioInputController::Record to be called.
-// controller->event_handler()->OnData(...);
-// controller->event_handler()->OnError(...);
-//
-// // Make sure consumer of AudioInputController does the right thing.
-// ...
-// // Reset factory.
-// AudioInputController::set_factory_for_testing(NULL);
-
-class TestAudioInputController : public AudioInputController {
- public:
- class Delegate {
- public:
- virtual void TestAudioControllerOpened(
- TestAudioInputController* controller) = 0;
- virtual void TestAudioControllerClosed(
- TestAudioInputController* controller) = 0;
- };
-
- TestAudioInputController(TestAudioInputControllerFactory* factory,
- AudioManager* audio_manager,
- const AudioParameters& audio_parameters,
- EventHandler* event_handler,
- SyncWriter* sync_writer);
-
- // Returns the event handler installed on the AudioInputController.
- EventHandler* event_handler() const { return event_handler_; }
-
- // Notifies the TestAudioControllerOpened() event to the delegate (if any).
- virtual void Record() OVERRIDE;
-
- // Ensure that the closure is run on the audio-manager thread.
- virtual void Close(const base::Closure& closed_task) OVERRIDE;
-
- protected:
- virtual ~TestAudioInputController();
-
- private:
- AudioParameters audio_parameters_;
-
- // These are not owned by us and expected to be valid for this object's
- // lifetime.
- TestAudioInputControllerFactory* factory_;
- EventHandler* event_handler_;
-
- DISALLOW_COPY_AND_ASSIGN(TestAudioInputController);
-};
-
-typedef TestAudioInputController::Delegate TestAudioInputControllerDelegate;
-
-// Simple AudioInputController::Factory method that creates
-// TestAudioInputControllers.
-class TestAudioInputControllerFactory : public AudioInputController::Factory {
- public:
- TestAudioInputControllerFactory();
- virtual ~TestAudioInputControllerFactory();
-
- // AudioInputController::Factory methods.
- virtual AudioInputController* Create(
- AudioManager* audio_manager,
- AudioInputController::EventHandler* event_handler,
- AudioParameters params) OVERRIDE;
-
- void SetDelegateForTests(TestAudioInputControllerDelegate* delegate);
-
- TestAudioInputController* controller() const { return controller_; }
-
- private:
- friend class TestAudioInputController;
-
- // Invoked by a TestAudioInputController when it gets destroyed.
- void OnTestAudioInputControllerDestroyed(
- TestAudioInputController* controller);
-
- // The caller of Create owns this object.
- TestAudioInputController* controller_;
-
- // The delegate for tests for receiving audio controller events.
- TestAudioInputControllerDelegate* delegate_;
-
- DISALLOW_COPY_AND_ASSIGN(TestAudioInputControllerFactory);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_TEST_AUDIO_INPUT_CONTROLLER_FACTORY_H_
diff --git a/src/media/audio/virtual_audio_input_stream.cc b/src/media/audio/virtual_audio_input_stream.cc
deleted file mode 100644
index 4f2f9bd..0000000
--- a/src/media/audio/virtual_audio_input_stream.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/virtual_audio_input_stream.h"
-
-#include <algorithm>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "media/audio/virtual_audio_output_stream.h"
-
-namespace media {
-
-// LoopbackAudioConverter works similar to AudioConverter and converts input
-// streams to different audio parameters. Then, the LoopbackAudioConverter can
-// be used as an input to another AudioConverter. This allows us to
-// use converted audio from AudioOutputStreams as input to an AudioConverter.
-// For example, this allows converting multiple streams into a common format and
-// using the converted audio as input to another AudioConverter (i.e. a mixer).
-class LoopbackAudioConverter : public AudioConverter::InputCallback {
- public:
- LoopbackAudioConverter(const AudioParameters& input_params,
- const AudioParameters& output_params)
- : audio_converter_(input_params, output_params, false) {}
-
- virtual ~LoopbackAudioConverter() {}
-
- void AddInput(AudioConverter::InputCallback* input) {
- audio_converter_.AddInput(input);
- }
-
- void RemoveInput(AudioConverter::InputCallback* input) {
- audio_converter_.RemoveInput(input);
- }
-
- private:
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE {
- audio_converter_.Convert(audio_bus);
- return 1.0;
- }
-
- AudioConverter audio_converter_;
-
- DISALLOW_COPY_AND_ASSIGN(LoopbackAudioConverter);
-};
-
-VirtualAudioInputStream* VirtualAudioInputStream::MakeStream(
- AudioManagerBase* manager, const AudioParameters& params,
- base::MessageLoopProxy* message_loop) {
- return new VirtualAudioInputStream(manager, params, message_loop);
-}
-
-VirtualAudioInputStream::VirtualAudioInputStream(
- AudioManagerBase* manager, const AudioParameters& params,
- base::MessageLoopProxy* message_loop)
- : audio_manager_(manager),
- message_loop_(message_loop),
- callback_(NULL),
- buffer_duration_ms_(base::TimeDelta::FromMilliseconds(
- params.frames_per_buffer() * base::Time::kMillisecondsPerSecond /
- static_cast<float>(params.sample_rate()))),
- buffer_(new uint8[params.GetBytesPerBuffer()]),
- params_(params),
- audio_bus_(AudioBus::Create(params_)),
- mixer_(params_, params_, false),
- num_attached_outputs_streams_(0) {
-}
-
-VirtualAudioInputStream::~VirtualAudioInputStream() {
- for (AudioConvertersMap::iterator it = converters_.begin();
- it != converters_.end(); ++it)
- delete it->second;
-
- DCHECK_EQ(0, num_attached_outputs_streams_);
-}
-
-bool VirtualAudioInputStream::Open() {
- memset(buffer_.get(), 0, params_.GetBytesPerBuffer());
- return true;
-}
-
-void VirtualAudioInputStream::Start(AudioInputCallback* callback) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- callback_ = callback;
- on_more_data_cb_.Reset(base::Bind(&VirtualAudioInputStream::ReadAudio,
- base::Unretained(this)));
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE,
- on_more_data_cb_.callback());
-}
-
-void VirtualAudioInputStream::Stop() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- on_more_data_cb_.Cancel();
-}
-
-void VirtualAudioInputStream::AddOutputStream(
- VirtualAudioOutputStream* stream, const AudioParameters& output_params) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- AudioConvertersMap::iterator converter = converters_.find(output_params);
- if (converter == converters_.end()) {
- std::pair<AudioConvertersMap::iterator, bool> result = converters_.insert(
- std::make_pair(output_params,
- new LoopbackAudioConverter(output_params, params_)));
- converter = result.first;
-
- // Add to main mixer if we just added a new AudioTransform.
- mixer_.AddInput(converter->second);
- }
- converter->second->AddInput(stream);
- ++num_attached_outputs_streams_;
-}
-
-void VirtualAudioInputStream::RemoveOutputStream(
- VirtualAudioOutputStream* stream, const AudioParameters& output_params) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- DCHECK(converters_.find(output_params) != converters_.end());
- converters_[output_params]->RemoveInput(stream);
-
- --num_attached_outputs_streams_;
-}
-
-void VirtualAudioInputStream::ReadAudio() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(callback_);
-
- mixer_.Convert(audio_bus_.get());
- audio_bus_->ToInterleaved(params_.frames_per_buffer(),
- params_.bits_per_sample() / 8,
- buffer_.get());
-
- callback_->OnData(this,
- buffer_.get(),
- params_.GetBytesPerBuffer(),
- params_.GetBytesPerBuffer(),
- 1.0);
-
- message_loop_->PostDelayedTask(FROM_HERE,
- on_more_data_cb_.callback(),
- buffer_duration_ms_);
-}
-
-void VirtualAudioInputStream::Close() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (callback_) {
- DCHECK(on_more_data_cb_.IsCancelled());
- callback_->OnClose(this);
- callback_ = NULL;
- }
- audio_manager_->ReleaseInputStream(this);
-}
-
-double VirtualAudioInputStream::GetMaxVolume() {
- return 1.0;
-}
-
-void VirtualAudioInputStream::SetVolume(double volume) {}
-
-double VirtualAudioInputStream::GetVolume() {
- return 1.0;
-}
-
-void VirtualAudioInputStream::SetAutomaticGainControl(bool enabled) {}
-
-bool VirtualAudioInputStream::GetAutomaticGainControl() {
- return false;
-}
-
-} // namespace media
diff --git a/src/media/audio/virtual_audio_input_stream.h b/src/media/audio/virtual_audio_input_stream.h
deleted file mode 100644
index fcb87a4..0000000
--- a/src/media/audio/virtual_audio_input_stream.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_VIRTUAL_AUDIO_INPUT_STREAM_H_
-#define MEDIA_AUDIO_VIRTUAL_AUDIO_INPUT_STREAM_H_
-
-#include <map>
-#include <set>
-
-#include "base/cancelable_callback.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_converter.h"
-
-namespace media {
-
-class LoopbackAudioConverter;
-class VirtualAudioOutputStream;
-
-// VirtualAudioInputStream converts and mixes audio from attached
-// VirtualAudioOutputStreams into a single stream. It will continuously render
-// audio until this VirtualAudioInputStream is stopped and closed.
-class MEDIA_EXPORT VirtualAudioInputStream : public AudioInputStream {
- public:
- static VirtualAudioInputStream* MakeStream(
- AudioManagerBase* manager,
- const AudioParameters& params,
- base::MessageLoopProxy* message_loop);
-
- virtual ~VirtualAudioInputStream();
-
- // AudioInputStream:
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- // Attaches a VirtualAudioOutputStream to be used as input. This
- // VirtualAudioInputStream must outlive all attached streams, so any attached
- // stream must be closed (which causes a detach) before
- // VirtualAudioInputStream is destroyed.
- virtual void AddOutputStream(VirtualAudioOutputStream* stream,
- const AudioParameters& output_params);
-
- // Detaches a VirtualAudioOutputStream and removes it as input.
- virtual void RemoveOutputStream(VirtualAudioOutputStream* stream,
- const AudioParameters& output_params);
-
- protected:
- friend class VirtualAudioInputStreamTest;
- FRIEND_TEST_ALL_PREFIXES(AudioOutputControllerTest,
- VirtualStreamsTriggerDeviceChange);
-
- typedef std::map<AudioParameters, LoopbackAudioConverter*> AudioConvertersMap;
-
- VirtualAudioInputStream(AudioManagerBase* manager,
- const AudioParameters& params,
- base::MessageLoopProxy* message_loop);
-
- // When Start() is called on this class, we continuously schedule this
- // callback to render audio using any attached VirtualAudioOutputStreams until
- // Stop() is called.
- void ReadAudio();
-
- AudioManagerBase* audio_manager_;
- base::MessageLoopProxy* message_loop_;
- AudioInputCallback* callback_;
-
- // Non-const for testing.
- base::TimeDelta buffer_duration_ms_;
- scoped_array<uint8> buffer_;
- AudioParameters params_;
- scoped_ptr<AudioBus> audio_bus_;
- base::CancelableClosure on_more_data_cb_;
-
- // AudioConverters associated with the attached VirtualAudioOutputStreams,
- // partitioned by common AudioParameters.
- AudioConvertersMap converters_;
-
- // AudioConverter that takes all the audio converters and mixes them into one
- // final audio stream.
- AudioConverter mixer_;
-
- // Number of currently attached VirtualAudioOutputStreams.
- int num_attached_outputs_streams_;
-
- DISALLOW_COPY_AND_ASSIGN(VirtualAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_VIRTUAL_AUDIO_INPUT_STREAM_H_
diff --git a/src/media/audio/virtual_audio_input_stream_unittest.cc b/src/media/audio/virtual_audio_input_stream_unittest.cc
deleted file mode 100644
index eb65b96..0000000
--- a/src/media/audio/virtual_audio_input_stream_unittest.cc
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <vector>
-
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "media/audio/virtual_audio_input_stream.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class MockInputCallback : public AudioInputStream::AudioInputCallback {
- public:
- MockInputCallback() {}
- virtual void OnData(AudioInputStream* stream, const uint8* data,
- uint32 size, uint32 hardware_delay_bytes,
- double volume) {}
- virtual void OnClose(AudioInputStream* stream) {}
- virtual void OnError(AudioInputStream* stream, int code) {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockInputCallback);
-};
-
-class VirtualAudioInputStreamTest : public testing::Test {
- public:
- VirtualAudioInputStreamTest()
- : audio_manager_(AudioManager::Create()),
- params_(
- AudioParameters::AUDIO_VIRTUAL,CHANNEL_LAYOUT_MONO, 8000, 8, 128),
- output_params_(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, 8000, 8,
- 128),
- stream_(NULL),
- source_(CHANNEL_LAYOUT_STEREO, 200.0, 128),
- done_(false, false) {
- }
-
- void StartStreamAndRunTestsOnAudioThread(int num_output_streams,
- int num_callback_iterations,
- int num_streams_removed_per_round,
- int num_expected_source_callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_->Open();
- stream_->Start(&input_callback_);
- AddStreamsAndDoCallbacks(num_output_streams,
- num_callback_iterations,
- num_streams_removed_per_round,
- num_expected_source_callbacks);
- }
-
- void AddStreamsAndDoCallbacks(int num_output_streams,
- int num_callback_iterations,
- int num_streams_removed_per_round,
- int num_expected_source_callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- for (int i = 0; i < num_output_streams; ++i) {
- AudioOutputStream* output_stream =
- audio_manager_->MakeAudioOutputStream(output_params_);
- DCHECK(output_stream);
- output_streams_.push_back(output_stream);
-
- output_stream->Open();
- output_stream->Start(&source_);
- }
-
- if (num_output_streams == 0 && num_streams_removed_per_round > 0) {
- AudioOutputStream* output_stream = output_streams_.back();
- output_streams_.pop_back();
- output_stream->Stop();
- output_stream->Close();
- }
-
- if (num_callback_iterations > 0) {
- // Force the next callback to be immediate.
- stream_->buffer_duration_ms_ = base::TimeDelta();
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::AddStreamsAndDoCallbacks,
- base::Unretained(this),
- 0,
- --num_callback_iterations,
- num_streams_removed_per_round,
- num_expected_source_callbacks));
- } else {
- // Finish the test.
- EXPECT_EQ(num_expected_source_callbacks, source_.callbacks());
- EXPECT_EQ(0, source_.errors());
-
- for (std::vector<AudioOutputStream*>::iterator it =
- output_streams_.begin(); it != output_streams_.end(); ++it)
- (*it)->Stop();
-
- stream_->Stop();
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(&VirtualAudioInputStreamTest::EndTest,
- base::Unretained(this)));
- }
- }
-
- void OpenAndCloseOnAudioThread() {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_->Open();
-
- // Create 2 output streams, which we just open and close without starting.
- const int num_output_stream = 2;
-
- for (int i = 0; i < num_output_stream; ++i) {
- AudioOutputStream* output_stream =
- audio_manager_->MakeAudioOutputStream(output_params_);
- DCHECK(output_stream);
- output_streams_.push_back(output_stream);
-
- output_stream->Open();
- }
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(&VirtualAudioInputStreamTest::EndTest,
- base::Unretained(this)));
- }
-
- void StartStopOnAudioThread(int num_output_streams,
- int num_callback_iterations,
- int num_expected_source_callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_->Open();
- stream_->Start(&input_callback_);
- StartStopCallback(true, num_output_streams, num_callback_iterations,
- num_expected_source_callbacks);
- }
-
- void StartStopCallback(bool init,
- int num_output_streams,
- int num_callback_iterations,
- int num_expected_source_callbacks) {
- ASSERT_TRUE(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
-
- if (init) {
- for (int i = 0; i < num_output_streams; ++i) {
- AudioOutputStream* output_stream =
- audio_manager_->MakeAudioOutputStream(output_params_);
- DCHECK(output_stream);
- output_streams_.push_back(output_stream);
-
- output_stream->Open();
- output_stream->Start(&source_);
- }
-
- // Start with an odd iteration number so we call Stop() first below.
- DCHECK_NE(0, num_callback_iterations % 2);
- }
-
- // Start or stop half the streams.
- for (int i = 0; i < num_output_streams / 2; ++i) {
- if (num_callback_iterations % 2 != 0)
- output_streams_[i]->Stop();
- else
- output_streams_[i]->Start(&source_);
- }
-
- if (num_callback_iterations > 0) {
- // Force the next callback to be immediate.
- stream_->buffer_duration_ms_ = base::TimeDelta::FromMilliseconds(0);
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::StartStopCallback,
- base::Unretained(this),
- false,
- num_output_streams,
- --num_callback_iterations,
- num_expected_source_callbacks));
- } else {
- // Finish the test.
- EXPECT_EQ(num_expected_source_callbacks, source_.callbacks());
- EXPECT_EQ(0, source_.errors());
-
- for (std::vector<AudioOutputStream*>::iterator it =
- output_streams_.begin(); it != output_streams_.end(); ++it)
- (*it)->Stop();
-
- stream_->Stop();
-
- audio_manager_->GetMessageLoop()->PostTask(FROM_HERE,
- base::Bind(&VirtualAudioInputStreamTest::EndTest,
- base::Unretained(this)));
- }
- }
-
- void EndTest() {
- for (std::vector<AudioOutputStream*>::iterator it =
- output_streams_.begin(); it != output_streams_.end(); ++it)
- (*it)->Close();
-
- stream_->Close();
-
- done_.Signal();
- }
-
- protected:
- scoped_ptr<AudioManager> audio_manager_;
- AudioParameters params_;
- AudioParameters output_params_;
- VirtualAudioInputStream* stream_;
- MockInputCallback input_callback_;
- std::vector<AudioOutputStream*> output_streams_;
- SineWaveAudioSource source_;
- base::WaitableEvent done_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VirtualAudioInputStreamTest);
-};
-
-TEST_F(VirtualAudioInputStreamTest, AttachAndDriveSingleStream) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- const int num_output_streams = 1;
- const int num_callback_iterations = 1;
- const int num_streams_removed_per_round = 0;
- const int num_expected_source_callbacks = 1;
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::StartStreamAndRunTestsOnAudioThread,
- base::Unretained(this),
- num_output_streams,
- num_callback_iterations,
- num_streams_removed_per_round,
- num_expected_source_callbacks));
-
- done_.Wait();
-}
-
-TEST_F(VirtualAudioInputStreamTest, AttachAndDriveMultipleStreams) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- const int num_output_streams = 5;
- const int num_callback_iterations = 5;
- const int num_streams_removed_per_round = 0;
- const int num_expected_source_callbacks = 25;
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::StartStreamAndRunTestsOnAudioThread,
- base::Unretained(this),
- num_output_streams,
- num_callback_iterations,
- num_streams_removed_per_round,
- num_expected_source_callbacks));
-
- done_.Wait();
-}
-
-TEST_F(VirtualAudioInputStreamTest, AttachAndRemoveStreams) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- const int num_output_streams = 8;
- const int num_callback_iterations = 5;
- const int num_streams_removed_per_round = 1;
- const int num_expected_source_callbacks = 8 + 7 + 6 + 5 + 4;
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::StartStreamAndRunTestsOnAudioThread,
- base::Unretained(this),
- num_output_streams,
- num_callback_iterations,
- num_streams_removed_per_round,
- num_expected_source_callbacks));
-
- done_.Wait();
-}
-
-// Opens/closes a VirtualAudioInputStream and a number of attached
-// VirtualAudioOutputStreams without calling Start()/Stop().
-TEST_F(VirtualAudioInputStreamTest, OpenAndClose) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::OpenAndCloseOnAudioThread,
- base::Unretained(this)));
-
- done_.Wait();
-}
-
-// Creates and closes and VirtualAudioInputStream.
-TEST_F(VirtualAudioInputStreamTest, CreateAndClose) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(&VirtualAudioInputStreamTest::EndTest,
- base::Unretained(this)));
-
- done_.Wait();
-}
-
-// Starts and stops VirtualAudioOutputStreams while attached to a
-// VirtualAudioInputStream.
-TEST_F(VirtualAudioInputStreamTest, AttachAndStartStopStreams) {
- stream_ = static_cast<VirtualAudioInputStream*>(
- audio_manager_->MakeAudioInputStream(params_, "1"));
- DCHECK(stream_);
-
- const int num_output_streams = 4;
- const int num_callback_iterations = 5;
- const int num_expected_source_callbacks = 2 + 4 + 2 + 4 + 2;
-
- audio_manager_->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioInputStreamTest::StartStopOnAudioThread,
- base::Unretained(this),
- num_output_streams,
- num_callback_iterations,
- num_expected_source_callbacks));
-
- done_.Wait();
-}
-
-} // namespace media
diff --git a/src/media/audio/virtual_audio_output_stream.cc b/src/media/audio/virtual_audio_output_stream.cc
deleted file mode 100644
index aacc667..0000000
--- a/src/media/audio/virtual_audio_output_stream.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/virtual_audio_output_stream.h"
-
-#include "base/message_loop.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/virtual_audio_input_stream.h"
-
-namespace media {
-
-// static
-VirtualAudioOutputStream* VirtualAudioOutputStream::MakeStream(
- AudioManagerBase* manager, const AudioParameters& params,
- base::MessageLoopProxy* message_loop, VirtualAudioInputStream* target) {
- return new VirtualAudioOutputStream(manager, params, message_loop, target);
-}
-
-VirtualAudioOutputStream::VirtualAudioOutputStream(
- AudioManagerBase* manager, const AudioParameters& params,
- base::MessageLoopProxy* message_loop, VirtualAudioInputStream* target)
- : audio_manager_(manager), message_loop_(message_loop), callback_(NULL),
- params_(params), target_input_stream_(target), volume_(1.0f),
- attached_(false) {
-}
-
-VirtualAudioOutputStream::~VirtualAudioOutputStream() {
- DCHECK(!callback_);
- DCHECK(!attached_);
-}
-
-bool VirtualAudioOutputStream::Open() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- return true;
-}
-
-void VirtualAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!attached_);
- callback_ = callback;
- target_input_stream_->AddOutputStream(this, params_);
- attached_ = true;
-}
-
-void VirtualAudioOutputStream::Stop() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(attached_);
- callback_ = NULL;
- target_input_stream_->RemoveOutputStream(this, params_);
- attached_ = false;
-}
-
-void VirtualAudioOutputStream::Close() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- audio_manager_->ReleaseOutputStream(this);
-}
-
-void VirtualAudioOutputStream::SetVolume(double volume) {
- volume_ = volume;
-}
-
-void VirtualAudioOutputStream::GetVolume(double* volume) {
- *volume = volume_;
-}
-
-double VirtualAudioOutputStream::ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(callback_);
-
- int frames = callback_->OnMoreData(audio_bus, AudioBuffersState());
- if (frames < audio_bus->frames())
- audio_bus->ZeroFramesPartial(frames, audio_bus->frames() - frames);
-
- return frames > 0 ? volume_ : 0;
-}
-
-} // namespace media
diff --git a/src/media/audio/virtual_audio_output_stream.h b/src/media/audio/virtual_audio_output_stream.h
deleted file mode 100644
index 0c2969d..0000000
--- a/src/media/audio/virtual_audio_output_stream.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_VIRTUAL_AUDIO_OUTPUT_STREAM_H_
-#define MEDIA_AUDIO_VIRTUAL_AUDIO_OUTPUT_STREAM_H_
-
-#include "base/message_loop_proxy.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_converter.h"
-
-namespace media {
-
-class AudioManagerBase;
-class VirtualAudioInputStream;
-
-// VirtualAudioOutputStream attaches to a VirtualAudioInputStream when Start()
-// is called and is used as an audio source. VirtualAudioOutputStream also
-// implements an interface so it can be used as an input to AudioConverter so
-// that we can get audio frames that match the AudioParameters that
-// VirtualAudioInputStream expects.
-class MEDIA_EXPORT VirtualAudioOutputStream
- : public AudioOutputStream,
- public AudioConverter::InputCallback {
- public:
- static VirtualAudioOutputStream* MakeStream(
- AudioManagerBase* manager,
- const AudioParameters& params,
- base::MessageLoopProxy* message_loop,
- VirtualAudioInputStream* target);
-
- virtual ~VirtualAudioOutputStream();
-
- // AudioOutputStream:
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
- virtual void Close() OVERRIDE;
-
- protected:
- VirtualAudioOutputStream(AudioManagerBase* manager,
- const AudioParameters& params,
- base::MessageLoopProxy* message_loop,
- VirtualAudioInputStream* target);
-
- private:
- // AudioConverter::InputCallback:
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE;
-
- AudioManagerBase* audio_manager_;
- base::MessageLoopProxy* message_loop_;
- AudioSourceCallback* callback_;
- AudioParameters params_;
-
- // Pointer to the VirtualAudioInputStream to attach to when Start() is called.
- // This pointer should always be valid because VirtualAudioInputStream should
- // outlive this class.
- VirtualAudioInputStream* target_input_stream_;
- double volume_;
- bool attached_;
-
- DISALLOW_COPY_AND_ASSIGN(VirtualAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_VIRTUAL_AUDIO_OUTPUT_STREAM_H_
diff --git a/src/media/audio/virtual_audio_output_stream_unittest.cc b/src/media/audio/virtual_audio_output_stream_unittest.cc
deleted file mode 100644
index ae267f5..0000000
--- a/src/media/audio/virtual_audio_output_stream_unittest.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "media/audio/virtual_audio_input_stream.h"
-#include "media/audio/virtual_audio_output_stream.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-
-namespace media {
-
-class MockVirtualAudioInputStream : public VirtualAudioInputStream {
- public:
- MockVirtualAudioInputStream(AudioManagerBase* manager,
- AudioParameters params,
- base::MessageLoopProxy* message_loop)
- : VirtualAudioInputStream(manager, params, message_loop) {}
- ~MockVirtualAudioInputStream() {}
-
- MOCK_METHOD2(AddOutputStream, void(VirtualAudioOutputStream* stream,
- const AudioParameters& output_params));
- MOCK_METHOD2(RemoveOutputStream, void(VirtualAudioOutputStream* stream,
- const AudioParameters& output_params));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockVirtualAudioInputStream);
-};
-
-class MockAudioDeviceListener : public AudioManager::AudioDeviceListener {
- public:
- MOCK_METHOD0(OnDeviceChange, void());
-};
-
-class VirtualAudioOutputStreamTest : public testing::Test {
- public:
- void ListenAndCreateVirtualOnAudioThread(
- AudioManager* manager, AudioManager::AudioDeviceListener* listener) {
- manager->AddOutputDeviceChangeListener(listener);
-
- AudioParameters params(
- AudioParameters::AUDIO_VIRTUAL, CHANNEL_LAYOUT_MONO, 8000, 8, 128);
- AudioInputStream* stream = manager->MakeAudioInputStream(params, "1");
- stream->Close();
- signal_.Signal();
- }
-
- void RemoveListenerOnAudioThread(
- AudioManager* manager, AudioManager::AudioDeviceListener* listener) {
- manager->RemoveOutputDeviceChangeListener(listener);
- signal_.Signal();
- }
-
- protected:
- VirtualAudioOutputStreamTest() : signal_(false, false) {}
-
- base::WaitableEvent signal_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VirtualAudioOutputStreamTest);
-};
-
-TEST_F(VirtualAudioOutputStreamTest, StartStopStartStop) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
-
- MessageLoop message_loop;
-
- AudioParameters params(
- AudioParameters::AUDIO_VIRTUAL, CHANNEL_LAYOUT_MONO, 8000, 8, 128);
- AudioParameters output_params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, 8000, 8, 128);
-
- MockVirtualAudioInputStream input_stream(
- static_cast<AudioManagerBase*>(audio_manager.get()),
- params,
- message_loop.message_loop_proxy());
-
- EXPECT_CALL(input_stream, AddOutputStream(_, _)).Times(2);
- EXPECT_CALL(input_stream, RemoveOutputStream(_, _)).Times(2);
-
- scoped_ptr<VirtualAudioOutputStream> output_stream(
- VirtualAudioOutputStream::MakeStream(
- static_cast<AudioManagerBase*>(audio_manager.get()),
- output_params,
- message_loop.message_loop_proxy(),
- &input_stream));
-
- SineWaveAudioSource source(CHANNEL_LAYOUT_STEREO, 200.0, 128);
- output_stream->Start(&source);
- output_stream->Stop();
- output_stream->Start(&source);
- output_stream->Stop();
- // Can't Close() here because we didn't create this output stream is not owned
- // by the audio manager.
-}
-
-// Tests that we get notifications to reattach output streams when we create a
-// VirtualAudioInputStream.
-TEST_F(VirtualAudioOutputStreamTest, OutputStreamsNotified) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
-
- MockAudioDeviceListener mock_listener;
- EXPECT_CALL(mock_listener, OnDeviceChange()).Times(2);
-
- audio_manager->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioOutputStreamTest::ListenAndCreateVirtualOnAudioThread,
- base::Unretained(this),
- audio_manager.get(),
- &mock_listener));
-
- signal_.Wait();
-
- audio_manager->GetMessageLoop()->PostTask(
- FROM_HERE, base::Bind(
- &VirtualAudioOutputStreamTest::RemoveListenerOnAudioThread,
- base::Unretained(this),
- audio_manager.get(),
- &mock_listener));
-
- signal_.Wait();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_device_listener_win.cc b/src/media/audio/win/audio_device_listener_win.cc
deleted file mode 100644
index 6664498..0000000
--- a/src/media/audio/win/audio_device_listener_win.cc
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/audio_device_listener_win.h"
-
-#include <Audioclient.h>
-
-#include "base/logging.h"
-#include "base/system_monitor/system_monitor.h"
-#include "base/utf_string_conversions.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/windows_version.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/core_audio_util_win.h"
-
-using base::win::ScopedCoMem;
-
-namespace media {
-
-AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb)
- : listener_cb_(listener_cb) {
- CHECK(CoreAudioUtil::IsSupported());
-
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator(
- CoreAudioUtil::CreateDeviceEnumerator());
- if (!device_enumerator)
- return;
-
- HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this);
- if (FAILED(hr)) {
- DLOG(ERROR) << "RegisterEndpointNotificationCallback failed: "
- << std::hex << hr;
- return;
- }
-
- device_enumerator_ = device_enumerator;
-
- ScopedComPtr<IMMDevice> device =
- CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- if (!device) {
- // Most probable reason for ending up here is that all audio devices are
- // disabled or unplugged.
- DVLOG(1) << "CoreAudioUtil::CreateDefaultDevice failed. No device?";
- return;
- }
-
- AudioDeviceName device_name;
- hr = CoreAudioUtil::GetDeviceName(device, &device_name);
- if (FAILED(hr)) {
- DVLOG(1) << "Failed to retrieve the device id: " << std::hex << hr;
- return;
- }
- default_render_device_id_ = device_name.unique_id;
-}
-
-AudioDeviceListenerWin::~AudioDeviceListenerWin() {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (device_enumerator_) {
- HRESULT hr =
- device_enumerator_->UnregisterEndpointNotificationCallback(this);
- DLOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() "
- << "failed: " << std::hex << hr;
- }
-}
-
-STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
- return 1;
-}
-
-STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() {
- return 1;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) {
- if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) {
- *object = static_cast<IMMNotificationClient*>(this);
- return S_OK;
- }
-
- *object = NULL;
- return E_NOINTERFACE;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged(
- LPCWSTR device_id, const PROPERTYKEY key) {
- // TODO(dalecurtis): We need to handle changes for the current default device
- // here. It's tricky because this method may be called many (20+) times for
- // a single change like sample rate. http://crbug.com/153056
- return S_OK;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
- // We don't care when devices are added.
- return S_OK;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
- // We don't care when devices are removed.
- return S_OK;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id,
- DWORD new_state) {
- if (new_state != DEVICE_STATE_ACTIVE && new_state != DEVICE_STATE_NOTPRESENT)
- return S_OK;
-
- base::SystemMonitor* monitor = base::SystemMonitor::Get();
- if (monitor)
- monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
-
- return S_OK;
-}
-
-STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged(
- EDataFlow flow, ERole role, LPCWSTR new_default_device_id) {
- // Only listen for output device changes right now...
- if (flow != eConsole && role != eRender)
- return S_OK;
-
- // If no device is now available, |new_default_device_id| will be NULL.
- std::string new_device_id = "";
- if (new_default_device_id)
- new_device_id = WideToUTF8(new_default_device_id);
-
- // Only fire a state change event if the device has actually changed.
- // TODO(dalecurtis): This still seems to fire an extra event on my machine for
- // an unplug event (probably others too); e.g., we get two transitions to a
- // new default device id.
- if (new_device_id.compare(default_render_device_id_) == 0)
- return S_OK;
-
- default_render_device_id_ = new_device_id;
- listener_cb_.Run();
-
- return S_OK;
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_device_listener_win.h b/src/media/audio/win/audio_device_listener_win.h
deleted file mode 100644
index 6a31251..0000000
--- a/src/media/audio/win/audio_device_listener_win.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
-#define MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
-
-#include <MMDeviceAPI.h>
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/callback.h"
-#include "base/threading/thread_checker.h"
-#include "base/win/scoped_comptr.h"
-#include "media/base/media_export.h"
-
-using base::win::ScopedComPtr;
-
-namespace media {
-
-// IMMNotificationClient implementation for listening for default device changes
-// and forwarding to AudioManagerWin so it can notify downstream clients. Only
-// output (eRender) device changes are supported currently. Core Audio support
-// is required to construct this object. Must be constructed and destructed on
-// a single COM initialized thread.
-// TODO(dalecurtis, henrika): Support input device changes.
-class MEDIA_EXPORT AudioDeviceListenerWin : public IMMNotificationClient {
- public:
- // The listener callback will be called from a system level multimedia thread,
- // thus the callee must be thread safe. |listener| is a permanent callback
- // and must outlive AudioDeviceListenerWin.
- explicit AudioDeviceListenerWin(const base::Closure& listener_cb);
- virtual ~AudioDeviceListenerWin();
-
- private:
- friend class AudioDeviceListenerWinTest;
-
- // IMMNotificationClient implementation.
- STDMETHOD_(ULONG, AddRef)();
- STDMETHOD_(ULONG, Release)();
- STDMETHOD(QueryInterface)(REFIID iid, void** object);
- STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, const PROPERTYKEY key);
- STDMETHOD(OnDeviceAdded)(LPCWSTR device_id);
- STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id);
- STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state);
- STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role,
- LPCWSTR new_default_device_id);
-
- base::Closure listener_cb_;
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator_;
- std::string default_render_device_id_;
-
- // AudioDeviceListenerWin must be constructed and destructed on one thread.
- base::ThreadChecker thread_checker_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWin);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
diff --git a/src/media/audio/win/audio_device_listener_win_unittest.cc b/src/media/audio/win/audio_device_listener_win_unittest.cc
deleted file mode 100644
index 989f8a6..0000000
--- a/src/media/audio/win/audio_device_listener_win_unittest.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/win/scoped_com_initializer.h"
-#include "base/utf_string_conversions.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/win/audio_device_listener_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-static const char* kNoDevice = "";
-static const char* kFirstTestDevice = "test_device_0";
-static const char* kSecondTestDevice = "test_device_1";
-
-class AudioDeviceListenerWinTest : public testing::Test {
- public:
- AudioDeviceListenerWinTest()
- : com_init_(ScopedCOMInitializer::kMTA) {
- }
-
- virtual void SetUp() {
- if (!CoreAudioUtil::IsSupported())
- return;
-
- output_device_listener_.reset(new AudioDeviceListenerWin(base::Bind(
- &AudioDeviceListenerWinTest::OnDeviceChange, base::Unretained(this))));
- }
-
- // Simulate a device change where no output devices are available.
- bool SimulateNullDefaultOutputDeviceChange() {
- return output_device_listener_->OnDefaultDeviceChanged(
- static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender),
- NULL) == S_OK;
- }
-
- bool SimulateDefaultOutputDeviceChange(const char* new_device_id) {
- return output_device_listener_->OnDefaultDeviceChanged(
- static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender),
- ASCIIToWide(new_device_id).c_str()) == S_OK;
- }
-
- void SetOutputDeviceId(std::string new_device_id) {
- output_device_listener_->default_render_device_id_ = new_device_id;
- }
-
- MOCK_METHOD0(OnDeviceChange, void());
-
- private:
- ScopedCOMInitializer com_init_;
- scoped_ptr<AudioDeviceListenerWin> output_device_listener_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWinTest);
-};
-
-// Simulate a device change events and ensure we get the right callbacks.
-TEST_F(AudioDeviceListenerWinTest, OutputDeviceChange) {
- if (!CoreAudioUtil::IsSupported())
- return;
-
- SetOutputDeviceId(kNoDevice);
- EXPECT_CALL(*this, OnDeviceChange()).Times(1);
- ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
-
- testing::Mock::VerifyAndClear(this);
- EXPECT_CALL(*this, OnDeviceChange()).Times(1);
- ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
-
- // The second device event should be ignored since the device id has not
- // changed.
- ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
-}
-
-// Ensure that null output device changes don't crash. Simulates the situation
-// where we have no output devices.
-TEST_F(AudioDeviceListenerWinTest, NullOutputDeviceChange) {
- if (!CoreAudioUtil::IsSupported())
- return;
-
- SetOutputDeviceId(kNoDevice);
- EXPECT_CALL(*this, OnDeviceChange()).Times(0);
- ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
-
- testing::Mock::VerifyAndClear(this);
- EXPECT_CALL(*this, OnDeviceChange()).Times(1);
- ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
-
- testing::Mock::VerifyAndClear(this);
- EXPECT_CALL(*this, OnDeviceChange()).Times(1);
- ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_low_latency_input_win.cc b/src/media/audio/win/audio_low_latency_input_win.cc
deleted file mode 100644
index 1e5464f..0000000
--- a/src/media/audio/win/audio_low_latency_input_win.cc
+++ /dev/null
@@ -1,635 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/audio_low_latency_input_win.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/utf_string_conversions.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/avrt_wrapper_win.h"
-
-using base::win::ScopedComPtr;
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-WASAPIAudioInputStream::WASAPIAudioInputStream(
- AudioManagerWin* manager, const AudioParameters& params,
- const std::string& device_id)
- : manager_(manager),
- capture_thread_(NULL),
- opened_(false),
- started_(false),
- endpoint_buffer_size_frames_(0),
- device_id_(device_id),
- sink_(NULL) {
- DCHECK(manager_);
-
- // Load the Avrt DLL if not already loaded. Required to support MMCSS.
- bool avrt_init = avrt::Initialize();
- DCHECK(avrt_init) << "Failed to load the Avrt.dll";
-
- // Set up the desired capture format specified by the client.
- format_.nSamplesPerSec = params.sample_rate();
- format_.wFormatTag = WAVE_FORMAT_PCM;
- format_.wBitsPerSample = params.bits_per_sample();
- format_.nChannels = params.channels();
- format_.nBlockAlign = (format_.wBitsPerSample / 8) * format_.nChannels;
- format_.nAvgBytesPerSec = format_.nSamplesPerSec * format_.nBlockAlign;
- format_.cbSize = 0;
-
- // Size in bytes of each audio frame.
- frame_size_ = format_.nBlockAlign;
- // Store size of audio packets which we expect to get from the audio
- // endpoint device in each capture event.
- packet_size_frames_ = params.GetBytesPerBuffer() / format_.nBlockAlign;
- packet_size_bytes_ = params.GetBytesPerBuffer();
- DVLOG(1) << "Number of bytes per audio frame : " << frame_size_;
- DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_;
-
- // All events are auto-reset events and non-signaled initially.
-
- // Create the event which the audio engine will signal each time
- // a buffer becomes ready to be processed by the client.
- audio_samples_ready_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
- DCHECK(audio_samples_ready_event_.IsValid());
-
- // Create the event which will be set in Stop() when capturing shall stop.
- stop_capture_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
- DCHECK(stop_capture_event_.IsValid());
-
- ms_to_frame_count_ = static_cast<double>(params.sample_rate()) / 1000.0;
-
- LARGE_INTEGER performance_frequency;
- if (QueryPerformanceFrequency(&performance_frequency)) {
- perf_count_to_100ns_units_ =
- (10000000.0 / static_cast<double>(performance_frequency.QuadPart));
- } else {
- LOG(ERROR) << "High-resolution performance counters are not supported.";
- perf_count_to_100ns_units_ = 0.0;
- }
-}
-
-WASAPIAudioInputStream::~WASAPIAudioInputStream() {}
-
-bool WASAPIAudioInputStream::Open() {
- DCHECK(CalledOnValidThread());
- // Verify that we are not already opened.
- if (opened_)
- return false;
-
- // Obtain a reference to the IMMDevice interface of the capturing
- // device with the specified unique identifier or role which was
- // set at construction.
- HRESULT hr = SetCaptureDevice();
- if (FAILED(hr))
- return false;
-
- // Obtain an IAudioClient interface which enables us to create and initialize
- // an audio stream between an audio application and the audio engine.
- hr = ActivateCaptureDevice();
- if (FAILED(hr))
- return false;
-
- // Retrieve the stream format which the audio engine uses for its internal
- // processing/mixing of shared-mode streams. This function call is for
- // diagnostic purposes only and only in debug mode.
-#ifndef NDEBUG
- hr = GetAudioEngineStreamFormat();
-#endif
-
- // Verify that the selected audio endpoint supports the specified format
- // set during construction.
- if (!DesiredFormatIsSupported()) {
- return false;
- }
-
- // Initialize the audio stream between the client and the device using
- // shared mode and a lowest possible glitch-free latency.
- hr = InitializeAudioEngine();
-
- opened_ = SUCCEEDED(hr);
- return opened_;
-}
-
-void WASAPIAudioInputStream::Start(AudioInputCallback* callback) {
- DCHECK(CalledOnValidThread());
- DCHECK(callback);
- DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully";
- if (!opened_)
- return;
-
- if (started_)
- return;
-
- sink_ = callback;
-
- // Create and start the thread that will drive the capturing by waiting for
- // capture events.
- capture_thread_ =
- new base::DelegateSimpleThread(this, "wasapi_capture_thread");
- capture_thread_->Start();
-
- // Start streaming data between the endpoint buffer and the audio engine.
- HRESULT hr = audio_client_->Start();
- DLOG_IF(ERROR, FAILED(hr)) << "Failed to start input streaming.";
-
- started_ = SUCCEEDED(hr);
-}
-
-void WASAPIAudioInputStream::Stop() {
- DCHECK(CalledOnValidThread());
- if (!started_)
- return;
-
- // Shut down the capture thread.
- if (stop_capture_event_.IsValid()) {
- SetEvent(stop_capture_event_.Get());
- }
-
- // Stop the input audio streaming.
- HRESULT hr = audio_client_->Stop();
- if (FAILED(hr)) {
- LOG(ERROR) << "Failed to stop input streaming.";
- }
-
- // Wait until the thread completes and perform cleanup.
- if (capture_thread_) {
- SetEvent(stop_capture_event_.Get());
- capture_thread_->Join();
- capture_thread_ = NULL;
- }
-
- started_ = false;
-}
-
-void WASAPIAudioInputStream::Close() {
- // It is valid to call Close() before calling open or Start().
- // It is also valid to call Close() after Start() has been called.
- Stop();
- if (sink_) {
- sink_->OnClose(this);
- sink_ = NULL;
- }
-
- // Inform the audio manager that we have been closed. This will cause our
- // destruction.
- manager_->ReleaseInputStream(this);
-}
-
-double WASAPIAudioInputStream::GetMaxVolume() {
- // Verify that Open() has been called succesfully, to ensure that an audio
- // session exists and that an ISimpleAudioVolume interface has been created.
- DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully";
- if (!opened_)
- return 0.0;
-
- // The effective volume value is always in the range 0.0 to 1.0, hence
- // we can return a fixed value (=1.0) here.
- return 1.0;
-}
-
-void WASAPIAudioInputStream::SetVolume(double volume) {
- DVLOG(1) << "SetVolume(volume=" << volume << ")";
- DCHECK(CalledOnValidThread());
- DCHECK_GE(volume, 0.0);
- DCHECK_LE(volume, 1.0);
-
- DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully";
- if (!opened_)
- return;
-
- // Set a new master volume level. Valid volume levels are in the range
- // 0.0 to 1.0. Ignore volume-change events.
- HRESULT hr = simple_audio_volume_->SetMasterVolume(static_cast<float>(volume),
- NULL);
- DLOG_IF(WARNING, FAILED(hr)) << "Failed to set new input master volume.";
-
- // Update the AGC volume level based on the last setting above. Note that,
- // the volume-level resolution is not infinite and it is therefore not
- // possible to assume that the volume provided as input parameter can be
- // used directly. Instead, a new query to the audio hardware is required.
- // This method does nothing if AGC is disabled.
- UpdateAgcVolume();
-}
-
-double WASAPIAudioInputStream::GetVolume() {
- DLOG_IF(ERROR, !opened_) << "Open() has not been called successfully";
- if (!opened_)
- return 0.0;
-
- // Retrieve the current volume level. The value is in the range 0.0 to 1.0.
- float level = 0.0f;
- HRESULT hr = simple_audio_volume_->GetMasterVolume(&level);
- DLOG_IF(WARNING, FAILED(hr)) << "Failed to get input master volume.";
-
- return static_cast<double>(level);
-}
-
-// static
-int WASAPIAudioInputStream::HardwareSampleRate(
- const std::string& device_id) {
- base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format;
- HRESULT hr = GetMixFormat(device_id, &audio_engine_mix_format);
- if (FAILED(hr))
- return 0;
-
- return static_cast<int>(audio_engine_mix_format->nSamplesPerSec);
-}
-
-// static
-uint32 WASAPIAudioInputStream::HardwareChannelCount(
- const std::string& device_id) {
- base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format;
- HRESULT hr = GetMixFormat(device_id, &audio_engine_mix_format);
- if (FAILED(hr))
- return 0;
-
- return static_cast<uint32>(audio_engine_mix_format->nChannels);
-}
-
-// static
-HRESULT WASAPIAudioInputStream::GetMixFormat(const std::string& device_id,
- WAVEFORMATEX** device_format) {
- // It is assumed that this static method is called from a COM thread, i.e.,
- // CoInitializeEx() is not called here to avoid STA/MTA conflicts.
- ScopedComPtr<IMMDeviceEnumerator> enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- enumerator.ReceiveVoid());
- if (FAILED(hr))
- return hr;
-
- ScopedComPtr<IMMDevice> endpoint_device;
- if (device_id == AudioManagerBase::kDefaultDeviceId) {
- // Retrieve the default capture audio endpoint.
- hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole,
- endpoint_device.Receive());
- } else {
- // Retrieve a capture endpoint device that is specified by an endpoint
- // device-identification string.
- hr = enumerator->GetDevice(UTF8ToUTF16(device_id).c_str(),
- endpoint_device.Receive());
- }
- if (FAILED(hr))
- return hr;
-
- ScopedComPtr<IAudioClient> audio_client;
- hr = endpoint_device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client.ReceiveVoid());
- return SUCCEEDED(hr) ? audio_client->GetMixFormat(device_format) : hr;
-}
-
-void WASAPIAudioInputStream::Run() {
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Increase the thread priority.
- capture_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio);
-
- // Enable MMCSS to ensure that this thread receives prioritized access to
- // CPU resources.
- DWORD task_index = 0;
- HANDLE mm_task = avrt::AvSetMmThreadCharacteristics(L"Pro Audio",
- &task_index);
- bool mmcss_is_ok =
- (mm_task && avrt::AvSetMmThreadPriority(mm_task, AVRT_PRIORITY_CRITICAL));
- if (!mmcss_is_ok) {
- // Failed to enable MMCSS on this thread. It is not fatal but can lead
- // to reduced QoS at high load.
- DWORD err = GetLastError();
- LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ").";
- }
-
- // Allocate a buffer with a size that enables us to take care of cases like:
- // 1) The recorded buffer size is smaller, or does not match exactly with,
- // the selected packet size used in each callback.
- // 2) The selected buffer size is larger than the recorded buffer size in
- // each event.
- size_t buffer_frame_index = 0;
- size_t capture_buffer_size = std::max(
- 2 * endpoint_buffer_size_frames_ * frame_size_,
- 2 * packet_size_frames_ * frame_size_);
- scoped_array<uint8> capture_buffer(new uint8[capture_buffer_size]);
-
- LARGE_INTEGER now_count;
- bool recording = true;
- bool error = false;
- double volume = GetVolume();
- HANDLE wait_array[2] = {stop_capture_event_, audio_samples_ready_event_};
-
- while (recording && !error) {
- HRESULT hr = S_FALSE;
-
- // Wait for a close-down event or a new capture event.
- DWORD wait_result = WaitForMultipleObjects(2, wait_array, FALSE, INFINITE);
- switch (wait_result) {
- case WAIT_FAILED:
- error = true;
- break;
- case WAIT_OBJECT_0 + 0:
- // |stop_capture_event_| has been set.
- recording = false;
- break;
- case WAIT_OBJECT_0 + 1:
- {
- // |audio_samples_ready_event_| has been set.
- BYTE* data_ptr = NULL;
- UINT32 num_frames_to_read = 0;
- DWORD flags = 0;
- UINT64 device_position = 0;
- UINT64 first_audio_frame_timestamp = 0;
-
- // Retrieve the amount of data in the capture endpoint buffer,
- // replace it with silence if required, create callbacks for each
- // packet and store non-delivered data for the next event.
- hr = audio_capture_client_->GetBuffer(&data_ptr,
- &num_frames_to_read,
- &flags,
- &device_position,
- &first_audio_frame_timestamp);
- if (FAILED(hr)) {
- DLOG(ERROR) << "Failed to get data from the capture buffer";
- continue;
- }
-
- if (num_frames_to_read != 0) {
- size_t pos = buffer_frame_index * frame_size_;
- size_t num_bytes = num_frames_to_read * frame_size_;
- DCHECK_GE(capture_buffer_size, pos + num_bytes);
-
- if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
- // Clear out the local buffer since silence is reported.
- memset(&capture_buffer[pos], 0, num_bytes);
- } else {
- // Copy captured data from audio engine buffer to local buffer.
- memcpy(&capture_buffer[pos], data_ptr, num_bytes);
- }
-
- buffer_frame_index += num_frames_to_read;
- }
-
- hr = audio_capture_client_->ReleaseBuffer(num_frames_to_read);
- DLOG_IF(ERROR, FAILED(hr)) << "Failed to release capture buffer";
-
- // Derive a delay estimate for the captured audio packet.
- // The value contains two parts (A+B), where A is the delay of the
- // first audio frame in the packet and B is the extra delay
- // contained in any stored data. Unit is in audio frames.
- QueryPerformanceCounter(&now_count);
- double audio_delay_frames =
- ((perf_count_to_100ns_units_ * now_count.QuadPart -
- first_audio_frame_timestamp) / 10000.0) * ms_to_frame_count_ +
- buffer_frame_index - num_frames_to_read;
-
- // Update the AGC volume level once every second. Note that,
- // |volume| is also updated each time SetVolume() is called
- // through IPC by the render-side AGC.
- QueryAgcVolume(&volume);
-
- // Deliver captured data to the registered consumer using a packet
- // size which was specified at construction.
- uint32 delay_frames = static_cast<uint32>(audio_delay_frames + 0.5);
- while (buffer_frame_index >= packet_size_frames_) {
- uint8* audio_data =
- reinterpret_cast<uint8*>(capture_buffer.get());
-
- // Deliver data packet, delay estimation and volume level to
- // the user.
- sink_->OnData(this,
- audio_data,
- packet_size_bytes_,
- delay_frames * frame_size_,
- volume);
-
- // Store parts of the recorded data which can't be delivered
- // using the current packet size. The stored section will be used
- // either in the next while-loop iteration or in the next
- // capture event.
- memmove(&capture_buffer[0],
- &capture_buffer[packet_size_bytes_],
- (buffer_frame_index - packet_size_frames_) * frame_size_);
-
- buffer_frame_index -= packet_size_frames_;
- delay_frames -= packet_size_frames_;
- }
- }
- break;
- default:
- error = true;
- break;
- }
- }
-
- if (recording && error) {
- // TODO(henrika): perhaps it worth improving the cleanup here by e.g.
- // stopping the audio client, joining the thread etc.?
- NOTREACHED() << "WASAPI capturing failed with error code "
- << GetLastError();
- }
-
- // Disable MMCSS.
- if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
- PLOG(WARNING) << "Failed to disable MMCSS";
- }
-}
-
-void WASAPIAudioInputStream::HandleError(HRESULT err) {
- NOTREACHED() << "Error code: " << err;
- if (sink_)
- sink_->OnError(this, static_cast<int>(err));
-}
-
-HRESULT WASAPIAudioInputStream::SetCaptureDevice() {
- ScopedComPtr<IMMDeviceEnumerator> enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- enumerator.ReceiveVoid());
- if (SUCCEEDED(hr)) {
- // Retrieve the IMMDevice by using the specified role or the specified
- // unique endpoint device-identification string.
- // TODO(henrika): possibly add support for the eCommunications as well.
- if (device_id_ == AudioManagerBase::kDefaultDeviceId) {
- // Retrieve the default capture audio endpoint for the specified role.
- // Note that, in Windows Vista, the MMDevice API supports device roles
- // but the system-supplied user interface programs do not.
- hr = enumerator->GetDefaultAudioEndpoint(eCapture,
- eConsole,
- endpoint_device_.Receive());
- } else {
- // Retrieve a capture endpoint device that is specified by an endpoint
- // device-identification string.
- hr = enumerator->GetDevice(UTF8ToUTF16(device_id_).c_str(),
- endpoint_device_.Receive());
- }
-
- if (FAILED(hr))
- return hr;
-
- // Verify that the audio endpoint device is active, i.e., the audio
- // adapter that connects to the endpoint device is present and enabled.
- DWORD state = DEVICE_STATE_DISABLED;
- hr = endpoint_device_->GetState(&state);
- if (SUCCEEDED(hr)) {
- if (!(state & DEVICE_STATE_ACTIVE)) {
- DLOG(ERROR) << "Selected capture device is not active.";
- hr = E_ACCESSDENIED;
- }
- }
- }
-
- return hr;
-}
-
-HRESULT WASAPIAudioInputStream::ActivateCaptureDevice() {
- // Creates and activates an IAudioClient COM object given the selected
- // capture endpoint device.
- HRESULT hr = endpoint_device_->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client_.ReceiveVoid());
- return hr;
-}
-
-HRESULT WASAPIAudioInputStream::GetAudioEngineStreamFormat() {
- HRESULT hr = S_OK;
-#ifndef NDEBUG
- // The GetMixFormat() method retrieves the stream format that the
- // audio engine uses for its internal processing of shared-mode streams.
- // The method always uses a WAVEFORMATEXTENSIBLE structure, instead
- // of a stand-alone WAVEFORMATEX structure, to specify the format.
- // An WAVEFORMATEXTENSIBLE structure can specify both the mapping of
- // channels to speakers and the number of bits of precision in each sample.
- base::win::ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex;
- hr = audio_client_->GetMixFormat(
- reinterpret_cast<WAVEFORMATEX**>(&format_ex));
-
- // See http://msdn.microsoft.com/en-us/windows/hardware/gg463006#EFH
- // for details on the WAVE file format.
- WAVEFORMATEX format = format_ex->Format;
- DVLOG(2) << "WAVEFORMATEX:";
- DVLOG(2) << " wFormatTags : 0x" << std::hex << format.wFormatTag;
- DVLOG(2) << " nChannels : " << format.nChannels;
- DVLOG(2) << " nSamplesPerSec : " << format.nSamplesPerSec;
- DVLOG(2) << " nAvgBytesPerSec: " << format.nAvgBytesPerSec;
- DVLOG(2) << " nBlockAlign : " << format.nBlockAlign;
- DVLOG(2) << " wBitsPerSample : " << format.wBitsPerSample;
- DVLOG(2) << " cbSize : " << format.cbSize;
-
- DVLOG(2) << "WAVEFORMATEXTENSIBLE:";
- DVLOG(2) << " wValidBitsPerSample: " <<
- format_ex->Samples.wValidBitsPerSample;
- DVLOG(2) << " dwChannelMask : 0x" << std::hex <<
- format_ex->dwChannelMask;
- if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
- DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_PCM";
- else if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
- DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_IEEE_FLOAT";
- else if (format_ex->SubFormat == KSDATAFORMAT_SUBTYPE_WAVEFORMATEX)
- DVLOG(2) << " SubFormat : KSDATAFORMAT_SUBTYPE_WAVEFORMATEX";
-#endif
- return hr;
-}
-
-bool WASAPIAudioInputStream::DesiredFormatIsSupported() {
- // An application that uses WASAPI to manage shared-mode streams can rely
- // on the audio engine to perform only limited format conversions. The audio
- // engine can convert between a standard PCM sample size used by the
- // application and the floating-point samples that the engine uses for its
- // internal processing. However, the format for an application stream
- // typically must have the same number of channels and the same sample
- // rate as the stream format used by the device.
- // Many audio devices support both PCM and non-PCM stream formats. However,
- // the audio engine can mix only PCM streams.
- base::win::ScopedCoMem<WAVEFORMATEX> closest_match;
- HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
- &format_,
- &closest_match);
- DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
- << "but a closest match exists.";
- return (hr == S_OK);
-}
-
-HRESULT WASAPIAudioInputStream::InitializeAudioEngine() {
- // Initialize the audio stream between the client and the device.
- // We connect indirectly through the audio engine by using shared mode
- // and WASAPI is initialized in an event driven mode.
- // Note that, |hnsBufferDuration| is set of 0, which ensures that the
- // buffer is never smaller than the minimum buffer size needed to ensure
- // that glitches do not occur between the periodic processing passes.
- // This setting should lead to lowest possible latency.
- HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
- 0, // hnsBufferDuration
- 0,
- &format_,
- NULL);
- if (FAILED(hr))
- return hr;
-
- // Retrieve the length of the endpoint buffer shared between the client
- // and the audio engine. The buffer length determines the maximum amount
- // of capture data that the audio engine can read from the endpoint buffer
- // during a single processing pass.
- // A typical value is 960 audio frames <=> 20ms @ 48kHz sample rate.
- hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_);
- if (FAILED(hr))
- return hr;
- DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_
- << " [frames]";
-
-#ifndef NDEBUG
- // The period between processing passes by the audio engine is fixed for a
- // particular audio endpoint device and represents the smallest processing
- // quantum for the audio engine. This period plus the stream latency between
- // the buffer and endpoint device represents the minimum possible latency
- // that an audio application can achieve.
- // TODO(henrika): possibly remove this section when all parts are ready.
- REFERENCE_TIME device_period_shared_mode = 0;
- REFERENCE_TIME device_period_exclusive_mode = 0;
- HRESULT hr_dbg = audio_client_->GetDevicePeriod(
- &device_period_shared_mode, &device_period_exclusive_mode);
- if (SUCCEEDED(hr_dbg)) {
- DVLOG(1) << "device period: "
- << static_cast<double>(device_period_shared_mode / 10000.0)
- << " [ms]";
- }
-
- REFERENCE_TIME latency = 0;
- hr_dbg = audio_client_->GetStreamLatency(&latency);
- if (SUCCEEDED(hr_dbg)) {
- DVLOG(1) << "stream latency: " << static_cast<double>(latency / 10000.0)
- << " [ms]";
- }
-#endif
-
- // Set the event handle that the audio engine will signal each time
- // a buffer becomes ready to be processed by the client.
- hr = audio_client_->SetEventHandle(audio_samples_ready_event_.Get());
- if (FAILED(hr))
- return hr;
-
- // Get access to the IAudioCaptureClient interface. This interface
- // enables us to read input data from the capture endpoint buffer.
- hr = audio_client_->GetService(__uuidof(IAudioCaptureClient),
- audio_capture_client_.ReceiveVoid());
- if (FAILED(hr))
- return hr;
-
- // Obtain a reference to the ISimpleAudioVolume interface which enables
- // us to control the master volume level of an audio session.
- hr = audio_client_->GetService(__uuidof(ISimpleAudioVolume),
- simple_audio_volume_.ReceiveVoid());
- return hr;
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_low_latency_input_win.h b/src/media/audio/win/audio_low_latency_input_win.h
deleted file mode 100644
index e83fc92..0000000
--- a/src/media/audio/win/audio_low_latency_input_win.h
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Implementation of AudioInputStream for Windows using Windows Core Audio
-// WASAPI for low latency capturing.
-//
-// Overview of operation:
-//
-// - An object of WASAPIAudioInputStream is created by the AudioManager
-// factory.
-// - Next some thread will call Open(), at that point the underlying
-// Core Audio APIs are utilized to create two WASAPI interfaces called
-// IAudioClient and IAudioCaptureClient.
-// - Then some thread will call Start(sink).
-// A thread called "wasapi_capture_thread" is started and this thread listens
-// on an event signal which is set periodically by the audio engine for
-// each recorded data packet. As a result, data samples will be provided
-// to the registered sink.
-// - At some point, a thread will call Stop(), which stops and joins the
-// capture thread and at the same time stops audio streaming.
-// - The same thread that called stop will call Close() where we cleanup
-// and notify the audio manager, which likely will destroy this object.
-//
-// Implementation notes:
-//
-// - The minimum supported client is Windows Vista.
-// - This implementation is single-threaded, hence:
-// o Construction and destruction must take place from the same thread.
-// o It is recommended to call all APIs from the same thread as well.
-// - It is recommended to first acquire the native sample rate of the default
-// input device and then use the same rate when creating this object. Use
-// WASAPIAudioInputStream::HardwareSampleRate() to retrieve the sample rate.
-// - Calling Close() also leads to self destruction.
-//
-// Core Audio API details:
-//
-// - Utilized MMDevice interfaces:
-// o IMMDeviceEnumerator
-// o IMMDevice
-// - Utilized WASAPI interfaces:
-// o IAudioClient
-// o IAudioCaptureClient
-// - The stream is initialized in shared mode and the processing of the
-// audio buffer is event driven.
-// - The Multimedia Class Scheduler service (MMCSS) is utilized to boost
-// the priority of the capture thread.
-// - Audio applications that use the MMDevice API and WASAPI typically use
-// the ISimpleAudioVolume interface to manage stream volume levels on a
-// per-session basis. It is also possible to use of the IAudioEndpointVolume
-// interface to control the master volume level of an audio endpoint device.
-// This implementation is using the ISimpleAudioVolume interface.
-// MSDN states that "In rare cases, a specialized audio application might
-// require the use of the IAudioEndpointVolume".
-//
-#ifndef MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_INPUT_WIN_H_
-#define MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_INPUT_WIN_H_
-
-#include <Audioclient.h>
-#include <MMDeviceAPI.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/threading/non_thread_safe.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/simple_thread.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_com_initializer.h"
-#include "base/win/scoped_comptr.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/audio_input_stream_impl.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioManagerWin;
-
-// AudioInputStream implementation using Windows Core Audio APIs.
-class MEDIA_EXPORT WASAPIAudioInputStream
- : public AudioInputStreamImpl,
- public base::DelegateSimpleThread::Delegate,
- NON_EXPORTED_BASE(public base::NonThreadSafe) {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- WASAPIAudioInputStream(AudioManagerWin* manager,
- const AudioParameters& params,
- const std::string& device_id);
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioInputStream::Close().
- virtual ~WASAPIAudioInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
-
- // Retrieves the sample rate used by the audio engine for its internal
- // processing/mixing of shared-mode streams given a specifed device.
- static int HardwareSampleRate(const std::string& device_id);
-
- // Retrieves the number of audio channels used by the audio engine for its
- // internal processing/mixing of shared-mode streams given a specified device.
- static uint32 HardwareChannelCount(const std::string& device_id);
-
- bool started() const { return started_; }
-
- private:
- // DelegateSimpleThread::Delegate implementation.
- virtual void Run() OVERRIDE;
-
- // Issues the OnError() callback to the |sink_|.
- void HandleError(HRESULT err);
-
- // The Open() method is divided into these sub methods.
- HRESULT SetCaptureDevice();
- HRESULT ActivateCaptureDevice();
- HRESULT GetAudioEngineStreamFormat();
- bool DesiredFormatIsSupported();
- HRESULT InitializeAudioEngine();
-
- // Retrieves the stream format that the audio engine uses for its internal
- // processing/mixing of shared-mode streams.
- static HRESULT GetMixFormat(const std::string& device_id,
- WAVEFORMATEX** device_format);
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerWin* manager_;
-
- // Capturing is driven by this thread (which has no message loop).
- // All OnData() callbacks will be called from this thread.
- base::DelegateSimpleThread* capture_thread_;
-
- // Contains the desired audio format which is set up at construction.
- WAVEFORMATEX format_;
-
- bool opened_;
- bool started_;
-
- // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM)
- size_t frame_size_;
-
- // Size in audio frames of each audio packet where an audio packet
- // is defined as the block of data which the user received in each
- // OnData() callback.
- size_t packet_size_frames_;
-
- // Size in bytes of each audio packet.
- size_t packet_size_bytes_;
-
- // Length of the audio endpoint buffer.
- size_t endpoint_buffer_size_frames_;
-
- // Contains the unique name of the selected endpoint device.
- // Note that AudioManagerBase::kDefaultDeviceId represents the default
- // device role and is not a valid ID as such.
- std::string device_id_;
-
- // Conversion factor used in delay-estimation calculations.
- // Converts a raw performance counter value to 100-nanosecond unit.
- double perf_count_to_100ns_units_;
-
- // Conversion factor used in delay-estimation calculations.
- // Converts from milliseconds to audio frames.
- double ms_to_frame_count_;
-
- // Pointer to the object that will receive the recorded audio samples.
- AudioInputCallback* sink_;
-
- // Windows Multimedia Device (MMDevice) API interfaces.
-
- // An IMMDevice interface which represents an audio endpoint device.
- base::win::ScopedComPtr<IMMDevice> endpoint_device_;
-
- // Windows Audio Session API (WASAP) interfaces.
-
- // An IAudioClient interface which enables a client to create and initialize
- // an audio stream between an audio application and the audio engine.
- base::win::ScopedComPtr<IAudioClient> audio_client_;
-
- // The IAudioCaptureClient interface enables a client to read input data
- // from a capture endpoint buffer.
- base::win::ScopedComPtr<IAudioCaptureClient> audio_capture_client_;
-
- // The ISimpleAudioVolume interface enables a client to control the
- // master volume level of an audio session.
- // The volume-level is a value in the range 0.0 to 1.0.
- // This interface does only work with shared-mode streams.
- base::win::ScopedComPtr<ISimpleAudioVolume> simple_audio_volume_;
-
- // The audio engine will signal this event each time a buffer has been
- // recorded.
- base::win::ScopedHandle audio_samples_ready_event_;
-
- // This event will be signaled when capturing shall stop.
- base::win::ScopedHandle stop_capture_event_;
-
- DISALLOW_COPY_AND_ASSIGN(WASAPIAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_INPUT_WIN_H_
diff --git a/src/media/audio/win/audio_low_latency_input_win_unittest.cc b/src/media/audio/win/audio_low_latency_input_win_unittest.cc
deleted file mode 100644
index 6eaa352..0000000
--- a/src/media/audio/win/audio_low_latency_input_win_unittest.cc
+++ /dev/null
@@ -1,405 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <windows.h>
-#include <mmsystem.h>
-
-#include "base/basictypes.h"
-#include "base/environment.h"
-#include "base/file_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/path_service.h"
-#include "base/test/test_timeouts.h"
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager_base.h"
-#include "media/audio/win/audio_low_latency_input_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "media/base/seekable_buffer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::win::ScopedCOMInitializer;
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::AtLeast;
-using ::testing::Gt;
-using ::testing::NotNull;
-
-namespace media {
-
-ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) {
- if (++*count >= limit) {
- loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
- }
-}
-
-class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
- public:
- MOCK_METHOD5(OnData, void(AudioInputStream* stream,
- const uint8* src, uint32 size,
- uint32 hardware_delay_bytes, double volume));
- MOCK_METHOD1(OnClose, void(AudioInputStream* stream));
- MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code));
-};
-
-// This audio sink implementation should be used for manual tests only since
-// the recorded data is stored on a raw binary data file.
-class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
- public:
- // Allocate space for ~10 seconds of data @ 48kHz in stereo:
- // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
- static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
-
- explicit WriteToFileAudioSink(const char* file_name)
- : buffer_(0, kMaxBufferSize),
- bytes_to_write_(0) {
- FilePath file_path;
- EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_path));
- file_path = file_path.AppendASCII(file_name);
- binary_file_ = file_util::OpenFile(file_path, "wb");
- DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file.";
- LOG(INFO) << ">> Output file: " << file_path.value()
- << " has been created.";
- }
-
- virtual ~WriteToFileAudioSink() {
- size_t bytes_written = 0;
- while (bytes_written < bytes_to_write_) {
- const uint8* chunk;
- int chunk_size;
-
- // Stop writing if no more data is available.
- if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
- break;
-
- // Write recorded data chunk to the file and prepare for next chunk.
- fwrite(chunk, 1, chunk_size, binary_file_);
- buffer_.Seek(chunk_size);
- bytes_written += chunk_size;
- }
- file_util::CloseFile(binary_file_);
- }
-
- // AudioInputStream::AudioInputCallback implementation.
- virtual void OnData(AudioInputStream* stream,
- const uint8* src,
- uint32 size,
- uint32 hardware_delay_bytes,
- double volume) {
- // Store data data in a temporary buffer to avoid making blocking
- // fwrite() calls in the audio callback. The complete buffer will be
- // written to file in the destructor.
- if (buffer_.Append(src, size)) {
- bytes_to_write_ += size;
- }
- }
-
- virtual void OnClose(AudioInputStream* stream) {}
- virtual void OnError(AudioInputStream* stream, int code) {}
-
- private:
- media::SeekableBuffer buffer_;
- FILE* binary_file_;
- size_t bytes_to_write_;
-};
-
-// Convenience method which ensures that we are not running on the build
-// bots and that at least one valid input device can be found. We also
-// verify that we are not running on XP since the low-latency (WASAPI-
-// based) version requires Windows Vista or higher.
-static bool CanRunAudioTests(AudioManager* audio_man) {
- if (!CoreAudioUtil::IsSupported()) {
- LOG(WARNING) << "This tests requires Windows Vista or higher.";
- return false;
- }
- // TODO(henrika): note that we use Wave today to query the number of
- // existing input devices.
- bool input = audio_man->HasAudioInputDevices();
- LOG_IF(WARNING, !input) << "No input device detected.";
- return input;
-}
-
-// Convenience method which creates a default AudioInputStream object but
-// also allows the user to modify the default settings.
-class AudioInputStreamWrapper {
- public:
- explicit AudioInputStreamWrapper(AudioManager* audio_manager)
- : com_init_(ScopedCOMInitializer::kMTA),
- audio_man_(audio_manager),
- format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
- channel_layout_(CHANNEL_LAYOUT_STEREO),
- bits_per_sample_(16) {
- // Use native/mixing sample rate and 10ms frame size as default.
- sample_rate_ = static_cast<int>(
- WASAPIAudioInputStream::HardwareSampleRate(
- AudioManagerBase::kDefaultDeviceId));
- samples_per_packet_ = sample_rate_ / 100;
- }
-
- ~AudioInputStreamWrapper() {}
-
- // Creates AudioInputStream object using default parameters.
- AudioInputStream* Create() {
- return CreateInputStream();
- }
-
- // Creates AudioInputStream object using non-default parameters where the
- // frame size is modified.
- AudioInputStream* Create(int samples_per_packet) {
- samples_per_packet_ = samples_per_packet;
- return CreateInputStream();
- }
-
- AudioParameters::Format format() const { return format_; }
- int channels() const {
- return ChannelLayoutToChannelCount(channel_layout_);
- }
- int bits_per_sample() const { return bits_per_sample_; }
- int sample_rate() const { return sample_rate_; }
- int samples_per_packet() const { return samples_per_packet_; }
-
- private:
- AudioInputStream* CreateInputStream() {
- AudioInputStream* ais = audio_man_->MakeAudioInputStream(
- AudioParameters(format_, channel_layout_, sample_rate_,
- bits_per_sample_, samples_per_packet_),
- AudioManagerBase::kDefaultDeviceId);
- EXPECT_TRUE(ais);
- return ais;
- }
-
- ScopedCOMInitializer com_init_;
- AudioManager* audio_man_;
- AudioParameters::Format format_;
- ChannelLayout channel_layout_;
- int bits_per_sample_;
- int sample_rate_;
- int samples_per_packet_;
-};
-
-// Convenience method which creates a default AudioInputStream object.
-static AudioInputStream* CreateDefaultAudioInputStream(
- AudioManager* audio_manager) {
- AudioInputStreamWrapper aisw(audio_manager);
- AudioInputStream* ais = aisw.Create();
- return ais;
-}
-
-// Verify that we can retrieve the current hardware/mixing sample rate
-// for all available input devices.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Retrieve a list of all available input devices.
- media::AudioDeviceNames device_names;
- audio_manager->GetAudioInputDeviceNames(&device_names);
-
- // Scan all available input devices and repeat the same test for all of them.
- for (media::AudioDeviceNames::const_iterator it = device_names.begin();
- it != device_names.end(); ++it) {
- // Retrieve the hardware sample rate given a specified audio input device.
- // TODO(tommi): ensure that we don't have to cast here.
- int fs = static_cast<int>(WASAPIAudioInputStream::HardwareSampleRate(
- it->unique_id));
- EXPECT_GE(fs, 0);
- }
-}
-
-// Test Create(), Close() calling sequence.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get());
- ais->Close();
-}
-
-// Test Open(), Close() calling sequence.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get());
- EXPECT_TRUE(ais->Open());
- ais->Close();
-}
-
-// Test Open(), Start(), Close() calling sequence.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get());
- EXPECT_TRUE(ais->Open());
- MockAudioInputCallback sink;
- ais->Start(&sink);
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Test Open(), Start(), Stop(), Close() calling sequence.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get());
- EXPECT_TRUE(ais->Open());
- MockAudioInputCallback sink;
- ais->Start(&sink);
- ais->Stop();
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// Test some additional calling sequences.
-TEST(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get());
- WASAPIAudioInputStream* wais = static_cast<WASAPIAudioInputStream*>(ais);
-
- // Open(), Open() should fail the second time.
- EXPECT_TRUE(ais->Open());
- EXPECT_FALSE(ais->Open());
-
- MockAudioInputCallback sink;
-
- // Start(), Start() is a valid calling sequence (second call does nothing).
- ais->Start(&sink);
- EXPECT_TRUE(wais->started());
- ais->Start(&sink);
- EXPECT_TRUE(wais->started());
-
- // Stop(), Stop() is a valid calling sequence (second call does nothing).
- ais->Stop();
- EXPECT_FALSE(wais->started());
- ais->Stop();
- EXPECT_FALSE(wais->started());
-
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- int count = 0;
- MessageLoopForUI loop;
-
- // 10 ms packet size.
-
- // Create default WASAPI input stream which records in stereo using
- // the shared mixing rate. The default buffer size is 10ms.
- AudioInputStreamWrapper aisw(audio_manager.get());
- AudioInputStream* ais = aisw.Create();
- EXPECT_TRUE(ais->Open());
-
- MockAudioInputCallback sink;
-
- // Derive the expected size in bytes of each recorded packet.
- uint32 bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
- (aisw.bits_per_sample() / 8);
-
- // We use 10ms packets and will run the test until ten packets are received.
- // All should contain valid packets of the same size and a valid delay
- // estimate.
- EXPECT_CALL(sink, OnData(
- ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
- ais->Start(&sink);
- loop.Run();
- ais->Stop();
-
- // Store current packet size (to be used in the subsequent tests).
- int samples_per_packet_10ms = aisw.samples_per_packet();
-
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-
- // 20 ms packet size.
-
- count = 0;
- ais = aisw.Create(2 * samples_per_packet_10ms);
- EXPECT_TRUE(ais->Open());
- bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
- (aisw.bits_per_sample() / 8);
-
- EXPECT_CALL(sink, OnData(
- ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
- ais->Start(&sink);
- loop.Run();
- ais->Stop();
-
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-
- // 5 ms packet size.
-
- count = 0;
- ais = aisw.Create(samples_per_packet_10ms / 2);
- EXPECT_TRUE(ais->Open());
- bytes_per_packet = aisw.channels() * aisw.samples_per_packet() *
- (aisw.bits_per_sample() / 8);
-
- EXPECT_CALL(sink, OnData(
- ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _))
- .Times(AtLeast(10))
- .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop));
- ais->Start(&sink);
- loop.Run();
- ais->Stop();
-
- EXPECT_CALL(sink, OnClose(ais))
- .Times(1);
- ais->Close();
-}
-
-// This test is intended for manual tests and should only be enabled
-// when it is required to store the captured data on a local file.
-// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
-// To include disabled tests in test execution, just invoke the test program
-// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
-// environment variable to a value greater than 0.
-TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- // Name of the output PCM file containing captured data. The output file
- // will be stored in the directory containing 'media_unittests.exe'.
- // Example of full name: \src\build\Debug\out_stereo_10sec.pcm.
- const char* file_name = "out_stereo_10sec.pcm";
-
- AudioInputStreamWrapper aisw(audio_manager.get());
- AudioInputStream* ais = aisw.Create();
- EXPECT_TRUE(ais->Open());
-
- LOG(INFO) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]";
- WriteToFileAudioSink file_sink(file_name);
- LOG(INFO) << ">> Speak into the default microphone while recording.";
- ais->Start(&file_sink);
- base::PlatformThread::Sleep(TestTimeouts::action_timeout());
- ais->Stop();
- LOG(INFO) << ">> Recording has stopped.";
- ais->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_low_latency_output_win.cc b/src/media/audio/win/audio_low_latency_output_win.cc
deleted file mode 100644
index 3037589..0000000
--- a/src/media/audio/win/audio_low_latency_output_win.cc
+++ /dev/null
@@ -1,956 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/audio_low_latency_output_win.h"
-
-#include <Functiondiscoverykeys_devpkey.h>
-
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/metrics/histogram.h"
-#include "base/utf_string_conversions.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/avrt_wrapper_win.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-using base::win::ScopedComPtr;
-using base::win::ScopedCOMInitializer;
-using base::win::ScopedCoMem;
-
-namespace media {
-
-typedef uint32 ChannelConfig;
-
-// Retrieves the stream format that the audio engine uses for its internal
-// processing/mixing of shared-mode streams.
-static HRESULT GetMixFormat(ERole device_role, WAVEFORMATEX** device_format) {
- // Note that we are using the IAudioClient::GetMixFormat() API to get the
- // device format in this function. It is in fact possible to be "more native",
- // and ask the endpoint device directly for its properties. Given a reference
- // to the IMMDevice interface of an endpoint object, a client can obtain a
- // reference to the endpoint object's property store by calling the
- // IMMDevice::OpenPropertyStore() method. However, I have not been able to
- // access any valuable information using this method on my HP Z600 desktop,
- // hence it feels more appropriate to use the IAudioClient::GetMixFormat()
- // approach instead.
-
- // Calling this function only makes sense for shared mode streams, since
- // if the device will be opened in exclusive mode, then the application
- // specified format is used instead. However, the result of this method can
- // be useful for testing purposes so we don't DCHECK here.
- DLOG_IF(WARNING, WASAPIAudioOutputStream::GetShareMode() ==
- AUDCLNT_SHAREMODE_EXCLUSIVE) <<
- "The mixing sample rate will be ignored for exclusive-mode streams.";
-
- // It is assumed that this static method is called from a COM thread, i.e.,
- // CoInitializeEx() is not called here again to avoid STA/MTA conflicts.
- ScopedComPtr<IMMDeviceEnumerator> enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- enumerator.ReceiveVoid());
- if (FAILED(hr))
- return hr;
-
- ScopedComPtr<IMMDevice> endpoint_device;
- hr = enumerator->GetDefaultAudioEndpoint(eRender,
- device_role,
- endpoint_device.Receive());
- if (FAILED(hr))
- return hr;
-
- ScopedComPtr<IAudioClient> audio_client;
- hr = endpoint_device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client.ReceiveVoid());
- return SUCCEEDED(hr) ? audio_client->GetMixFormat(device_format) : hr;
-}
-
-// Retrieves an integer mask which corresponds to the channel layout the
-// audio engine uses for its internal processing/mixing of shared-mode
-// streams. This mask indicates which channels are present in the multi-
-// channel stream. The least significant bit corresponds with the Front Left
-// speaker, the next least significant bit corresponds to the Front Right
-// speaker, and so on, continuing in the order defined in KsMedia.h.
-// See http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx
-// for more details.
-static ChannelConfig GetChannelConfig() {
- // Use a WAVEFORMATEXTENSIBLE structure since it can specify both the
- // number of channels and the mapping of channels to speakers for
- // multichannel devices.
- base::win::ScopedCoMem<WAVEFORMATPCMEX> format_ex;
- HRESULT hr = S_FALSE;
- hr = GetMixFormat(eConsole, reinterpret_cast<WAVEFORMATEX**>(&format_ex));
- if (FAILED(hr))
- return 0;
-
- // The dwChannelMask member specifies which channels are present in the
- // multichannel stream. The least significant bit corresponds to the
- // front left speaker, the next least significant bit corresponds to the
- // front right speaker, and so on.
- // See http://msdn.microsoft.com/en-us/library/windows/desktop/dd757714(v=vs.85).aspx
- // for more details on the channel mapping.
- DVLOG(2) << "dwChannelMask: 0x" << std::hex << format_ex->dwChannelMask;
-
-#if !defined(NDEBUG)
- // See http://en.wikipedia.org/wiki/Surround_sound for more details on
- // how to name various speaker configurations. The list below is not complete.
- const char* speaker_config = "Undefined";
- switch (format_ex->dwChannelMask) {
- case KSAUDIO_SPEAKER_MONO:
- speaker_config = "Mono";
- break;
- case KSAUDIO_SPEAKER_STEREO:
- speaker_config = "Stereo";
- break;
- case KSAUDIO_SPEAKER_5POINT1_SURROUND:
- speaker_config = "5.1 surround";
- break;
- case KSAUDIO_SPEAKER_5POINT1:
- speaker_config = "5.1";
- break;
- case KSAUDIO_SPEAKER_7POINT1_SURROUND:
- speaker_config = "7.1 surround";
- break;
- case KSAUDIO_SPEAKER_7POINT1:
- speaker_config = "7.1";
- break;
- default:
- break;
- }
- DVLOG(2) << "speaker configuration: " << speaker_config;
-#endif
-
- return static_cast<ChannelConfig>(format_ex->dwChannelMask);
-}
-
-// Converts Microsoft's channel configuration to ChannelLayout.
-// This mapping is not perfect but the best we can do given the current
-// ChannelLayout enumerator and the Windows-specific speaker configurations
-// defined in ksmedia.h. Don't assume that the channel ordering in
-// ChannelLayout is exactly the same as the Windows specific configuration.
-// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to
-// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R
-// speakers are different in these two definitions.
-static ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) {
- switch (config) {
- case KSAUDIO_SPEAKER_DIRECTOUT:
- return CHANNEL_LAYOUT_NONE;
- case KSAUDIO_SPEAKER_MONO:
- return CHANNEL_LAYOUT_MONO;
- case KSAUDIO_SPEAKER_STEREO:
- return CHANNEL_LAYOUT_STEREO;
- case KSAUDIO_SPEAKER_QUAD:
- return CHANNEL_LAYOUT_QUAD;
- case KSAUDIO_SPEAKER_SURROUND:
- return CHANNEL_LAYOUT_4_0;
- case KSAUDIO_SPEAKER_5POINT1:
- return CHANNEL_LAYOUT_5_1_BACK;
- case KSAUDIO_SPEAKER_5POINT1_SURROUND:
- return CHANNEL_LAYOUT_5_1;
- case KSAUDIO_SPEAKER_7POINT1:
- return CHANNEL_LAYOUT_7_1_WIDE;
- case KSAUDIO_SPEAKER_7POINT1_SURROUND:
- return CHANNEL_LAYOUT_7_1;
- default:
- DVLOG(1) << "Unsupported channel layout: " << config;
- return CHANNEL_LAYOUT_UNSUPPORTED;
- }
-}
-
-// static
-AUDCLNT_SHAREMODE WASAPIAudioOutputStream::GetShareMode() {
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
- return AUDCLNT_SHAREMODE_EXCLUSIVE;
- return AUDCLNT_SHAREMODE_SHARED;
-}
-
-WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager,
- const AudioParameters& params,
- ERole device_role)
- : creating_thread_id_(base::PlatformThread::CurrentId()),
- manager_(manager),
- opened_(false),
- restart_rendering_mode_(false),
- volume_(1.0),
- endpoint_buffer_size_frames_(0),
- device_role_(device_role),
- share_mode_(GetShareMode()),
- client_channel_count_(params.channels()),
- num_written_frames_(0),
- source_(NULL),
- audio_bus_(AudioBus::Create(params)) {
- DCHECK(manager_);
-
- // Load the Avrt DLL if not already loaded. Required to support MMCSS.
- bool avrt_init = avrt::Initialize();
- DCHECK(avrt_init) << "Failed to load the avrt.dll";
-
- if (share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE) {
- VLOG(1) << ">> Note that EXCLUSIVE MODE is enabled <<";
- }
-
- // Set up the desired render format specified by the client. We use the
- // WAVE_FORMAT_EXTENSIBLE structure to ensure that multiple channel ordering
- // and high precision data can be supported.
-
- // Begin with the WAVEFORMATEX structure that specifies the basic format.
- WAVEFORMATEX* format = &format_.Format;
- format->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- format->nChannels = client_channel_count_;
- format->nSamplesPerSec = params.sample_rate();
- format->wBitsPerSample = params.bits_per_sample();
- format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels;
- format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
- format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
-
- // Add the parts which are unique to WAVE_FORMAT_EXTENSIBLE.
- format_.Samples.wValidBitsPerSample = params.bits_per_sample();
- format_.dwChannelMask = GetChannelConfig();
- format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-
- // Size in bytes of each audio frame.
- frame_size_ = format->nBlockAlign;
-
- // Store size (in different units) of audio packets which we expect to
- // get from the audio endpoint device in each render event.
- packet_size_frames_ = params.GetBytesPerBuffer() / format->nBlockAlign;
- packet_size_bytes_ = params.GetBytesPerBuffer();
- packet_size_ms_ = (1000.0 * packet_size_frames_) / params.sample_rate();
- DVLOG(1) << "Number of bytes per audio frame : " << frame_size_;
- DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_;
- DVLOG(1) << "Number of bytes per packet : " << packet_size_bytes_;
- DVLOG(1) << "Number of milliseconds per packet: " << packet_size_ms_;
-
- // All events are auto-reset events and non-signaled initially.
-
- // Create the event which the audio engine will signal each time
- // a buffer becomes ready to be processed by the client.
- audio_samples_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
- DCHECK(audio_samples_render_event_.IsValid());
-
- // Create the event which will be set in Stop() when capturing shall stop.
- stop_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
- DCHECK(stop_render_event_.IsValid());
-}
-
-WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {}
-
-bool WASAPIAudioOutputStream::Open() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- if (opened_)
- return true;
-
- // Channel mixing is not supported, it must be handled by ChannelMixer.
- if (format_.Format.nChannels != client_channel_count_) {
- LOG(ERROR) << "Channel down-mixing is not supported.";
- return false;
- }
-
- // Create an IMMDeviceEnumerator interface and obtain a reference to
- // the IMMDevice interface of the default rendering device with the
- // specified role.
- HRESULT hr = SetRenderDevice();
- if (FAILED(hr)) {
- return false;
- }
-
- // Obtain an IAudioClient interface which enables us to create and initialize
- // an audio stream between an audio application and the audio engine.
- hr = ActivateRenderDevice();
- if (FAILED(hr)) {
- return false;
- }
-
- // Verify that the selected audio endpoint supports the specified format
- // set during construction.
- // In exclusive mode, the client can choose to open the stream in any audio
- // format that the endpoint device supports. In shared mode, the client must
- // open the stream in the mix format that is currently in use by the audio
- // engine (or a format that is similar to the mix format). The audio engine's
- // input streams and the output mix from the engine are all in this format.
- if (!DesiredFormatIsSupported()) {
- return false;
- }
-
- // Initialize the audio stream between the client and the device using
- // shared or exclusive mode and a lowest possible glitch-free latency.
- // We will enter different code paths depending on the specified share mode.
- hr = InitializeAudioEngine();
- if (FAILED(hr)) {
- return false;
- }
-
- opened_ = true;
- return true;
-}
-
-void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- CHECK(callback);
- CHECK(opened_);
-
- if (render_thread_.get()) {
- CHECK_EQ(callback, source_);
- return;
- }
-
- if (restart_rendering_mode_) {
- // The selected audio device has been removed or disabled and a new
- // default device has been enabled instead. The current implementation
- // does not to support this sequence of events. Given that Open()
- // and Start() are usually called in one sequence; it should be a very
- // rare event.
- // TODO(henrika): it is possible to extend the functionality here.
- LOG(ERROR) << "Unable to start since the selected default device has "
- "changed since Open() was called.";
- return;
- }
-
- source_ = callback;
-
- // Avoid start-up glitches by filling up the endpoint buffer with "silence"
- // before starting the stream.
- BYTE* data_ptr = NULL;
- HRESULT hr = audio_render_client_->GetBuffer(endpoint_buffer_size_frames_,
- &data_ptr);
- if (FAILED(hr)) {
- DLOG(ERROR) << "Failed to use rendering audio buffer: " << std::hex << hr;
- return;
- }
-
- // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
- // explicitly write silence data to the rendering buffer.
- audio_render_client_->ReleaseBuffer(endpoint_buffer_size_frames_,
- AUDCLNT_BUFFERFLAGS_SILENT);
- num_written_frames_ = endpoint_buffer_size_frames_;
-
- // Sanity check: verify that the endpoint buffer is filled with silence.
- UINT32 num_queued_frames = 0;
- audio_client_->GetCurrentPadding(&num_queued_frames);
- DCHECK(num_queued_frames == num_written_frames_);
-
- // Create and start the thread that will drive the rendering by waiting for
- // render events.
- render_thread_.reset(
- new base::DelegateSimpleThread(this, "wasapi_render_thread"));
- render_thread_->Start();
-
- // Start streaming data between the endpoint buffer and the audio engine.
- hr = audio_client_->Start();
- if (FAILED(hr)) {
- SetEvent(stop_render_event_.Get());
- render_thread_->Join();
- render_thread_.reset();
- HandleError(hr);
- }
-}
-
-void WASAPIAudioOutputStream::Stop() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- if (!render_thread_.get())
- return;
-
- // Stop output audio streaming.
- HRESULT hr = audio_client_->Stop();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to stop output streaming: " << std::hex << hr;
- }
-
- // Wait until the thread completes and perform cleanup.
- SetEvent(stop_render_event_.Get());
- render_thread_->Join();
- render_thread_.reset();
-
- // Ensure that we don't quit the main thread loop immediately next
- // time Start() is called.
- ResetEvent(stop_render_event_.Get());
-
- // Clear source callback, it'll be set again on the next Start() call.
- source_ = NULL;
-
- // Flush all pending data and reset the audio clock stream position to 0.
- hr = audio_client_->Reset();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to reset streaming: " << std::hex << hr;
- }
-
- // Extra safety check to ensure that the buffers are cleared.
- // If the buffers are not cleared correctly, the next call to Start()
- // would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer().
- // This check is is only needed for shared-mode streams.
- if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
- UINT32 num_queued_frames = 0;
- audio_client_->GetCurrentPadding(&num_queued_frames);
- DCHECK_EQ(0u, num_queued_frames);
- }
-}
-
-void WASAPIAudioOutputStream::Close() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
-
- // It is valid to call Close() before calling open or Start().
- // It is also valid to call Close() after Start() has been called.
- Stop();
-
- // Inform the audio manager that we have been closed. This will cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void WASAPIAudioOutputStream::SetVolume(double volume) {
- DVLOG(1) << "SetVolume(volume=" << volume << ")";
- float volume_float = static_cast<float>(volume);
- if (volume_float < 0.0f || volume_float > 1.0f) {
- return;
- }
- volume_ = volume_float;
-}
-
-void WASAPIAudioOutputStream::GetVolume(double* volume) {
- DVLOG(1) << "GetVolume()";
- *volume = static_cast<double>(volume_);
-}
-
-// static
-int WASAPIAudioOutputStream::HardwareChannelCount() {
- // Use a WAVEFORMATEXTENSIBLE structure since it can specify both the
- // number of channels and the mapping of channels to speakers for
- // multichannel devices.
- base::win::ScopedCoMem<WAVEFORMATPCMEX> format_ex;
- HRESULT hr = GetMixFormat(
- eConsole, reinterpret_cast<WAVEFORMATEX**>(&format_ex));
- if (FAILED(hr))
- return 0;
-
- // Number of channels in the stream. Corresponds to the number of bits
- // set in the dwChannelMask.
- DVLOG(1) << "endpoint channels (out): " << format_ex->Format.nChannels;
-
- return static_cast<int>(format_ex->Format.nChannels);
-}
-
-// static
-ChannelLayout WASAPIAudioOutputStream::HardwareChannelLayout() {
- return ChannelConfigToChannelLayout(GetChannelConfig());
-}
-
-// static
-int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) {
- base::win::ScopedCoMem<WAVEFORMATEX> format;
- HRESULT hr = GetMixFormat(device_role, &format);
- if (FAILED(hr))
- return 0;
-
- DVLOG(2) << "nSamplesPerSec: " << format->nSamplesPerSec;
- return static_cast<int>(format->nSamplesPerSec);
-}
-
-void WASAPIAudioOutputStream::Run() {
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Increase the thread priority.
- render_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio);
-
- // Enable MMCSS to ensure that this thread receives prioritized access to
- // CPU resources.
- DWORD task_index = 0;
- HANDLE mm_task = avrt::AvSetMmThreadCharacteristics(L"Pro Audio",
- &task_index);
- bool mmcss_is_ok =
- (mm_task && avrt::AvSetMmThreadPriority(mm_task, AVRT_PRIORITY_CRITICAL));
- if (!mmcss_is_ok) {
- // Failed to enable MMCSS on this thread. It is not fatal but can lead
- // to reduced QoS at high load.
- DWORD err = GetLastError();
- LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ").";
- }
-
- HRESULT hr = S_FALSE;
-
- bool playing = true;
- bool error = false;
- HANDLE wait_array[] = { stop_render_event_,
- audio_samples_render_event_ };
- UINT64 device_frequency = 0;
-
- // The IAudioClock interface enables us to monitor a stream's data
- // rate and the current position in the stream. Allocate it before we
- // start spinning.
- ScopedComPtr<IAudioClock> audio_clock;
- hr = audio_client_->GetService(__uuidof(IAudioClock),
- audio_clock.ReceiveVoid());
- if (SUCCEEDED(hr)) {
- // The device frequency is the frequency generated by the hardware clock in
- // the audio device. The GetFrequency() method reports a constant frequency.
- hr = audio_clock->GetFrequency(&device_frequency);
- }
- error = FAILED(hr);
- PLOG_IF(ERROR, error) << "Failed to acquire IAudioClock interface: "
- << std::hex << hr;
-
- // Keep rendering audio until the stop event or the stream-switch event
- // is signaled. An error event can also break the main thread loop.
- while (playing && !error) {
- // Wait for a close-down event, stream-switch event or a new render event.
- DWORD wait_result = WaitForMultipleObjects(arraysize(wait_array),
- wait_array,
- FALSE,
- INFINITE);
-
- switch (wait_result) {
- case WAIT_OBJECT_0 + 0:
- // |stop_render_event_| has been set.
- playing = false;
- break;
- case WAIT_OBJECT_0 + 1:
- {
- // |audio_samples_render_event_| has been set.
- UINT32 num_queued_frames = 0;
- uint8* audio_data = NULL;
-
- // Contains how much new data we can write to the buffer without
- // the risk of overwriting previously written data that the audio
- // engine has not yet read from the buffer.
- size_t num_available_frames = 0;
-
- if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
- // Get the padding value which represents the amount of rendering
- // data that is queued up to play in the endpoint buffer.
- hr = audio_client_->GetCurrentPadding(&num_queued_frames);
- num_available_frames =
- endpoint_buffer_size_frames_ - num_queued_frames;
- } else {
- // While the stream is running, the system alternately sends one
- // buffer or the other to the client. This form of double buffering
- // is referred to as "ping-ponging". Each time the client receives
- // a buffer from the system (triggers this event) the client must
- // process the entire buffer. Calls to the GetCurrentPadding method
- // are unnecessary because the packet size must always equal the
- // buffer size. In contrast to the shared mode buffering scheme,
- // the latency for an event-driven, exclusive-mode stream depends
- // directly on the buffer size.
- num_available_frames = endpoint_buffer_size_frames_;
- }
-
- // Check if there is enough available space to fit the packet size
- // specified by the client.
- if (FAILED(hr) || (num_available_frames < packet_size_frames_))
- continue;
-
- // Derive the number of packets we need get from the client to
- // fill up the available area in the endpoint buffer.
- // |num_packets| will always be one for exclusive-mode streams.
- size_t num_packets = (num_available_frames / packet_size_frames_);
-
- // Get data from the client/source.
- for (size_t n = 0; n < num_packets; ++n) {
- // Grab all available space in the rendering endpoint buffer
- // into which the client can write a data packet.
- hr = audio_render_client_->GetBuffer(packet_size_frames_,
- &audio_data);
- if (FAILED(hr)) {
- DLOG(ERROR) << "Failed to use rendering audio buffer: "
- << std::hex << hr;
- continue;
- }
-
- // Derive the audio delay which corresponds to the delay between
- // a render event and the time when the first audio sample in a
- // packet is played out through the speaker. This delay value
- // can typically be utilized by an acoustic echo-control (AEC)
- // unit at the render side.
- UINT64 position = 0;
- int audio_delay_bytes = 0;
- hr = audio_clock->GetPosition(&position, NULL);
- if (SUCCEEDED(hr)) {
- // Stream position of the sample that is currently playing
- // through the speaker.
- double pos_sample_playing_frames = format_.Format.nSamplesPerSec *
- (static_cast<double>(position) / device_frequency);
-
- // Stream position of the last sample written to the endpoint
- // buffer. Note that, the packet we are about to receive in
- // the upcoming callback is also included.
- size_t pos_last_sample_written_frames =
- num_written_frames_ + packet_size_frames_;
-
- // Derive the actual delay value which will be fed to the
- // render client using the OnMoreData() callback.
- audio_delay_bytes = (pos_last_sample_written_frames -
- pos_sample_playing_frames) * frame_size_;
- }
-
- // Read a data packet from the registered client source and
- // deliver a delay estimate in the same callback to the client.
- // A time stamp is also stored in the AudioBuffersState. This
- // time stamp can be used at the client side to compensate for
- // the delay between the usage of the delay value and the time
- // of generation.
-
- uint32 num_filled_bytes = 0;
- const int bytes_per_sample = format_.Format.wBitsPerSample >> 3;
-
- int frames_filled = source_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(0, audio_delay_bytes));
- num_filled_bytes = frames_filled * frame_size_;
- DCHECK_LE(num_filled_bytes, packet_size_bytes_);
- // Note: If this ever changes to output raw float the data must be
- // clipped and sanitized since it may come from an untrusted
- // source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, bytes_per_sample, audio_data);
-
- // Perform in-place, software-volume adjustments.
- media::AdjustVolume(audio_data,
- num_filled_bytes,
- audio_bus_->channels(),
- bytes_per_sample,
- volume_);
-
- // Zero out the part of the packet which has not been filled by
- // the client. Using silence is the least bad option in this
- // situation.
- if (num_filled_bytes < packet_size_bytes_) {
- memset(&audio_data[num_filled_bytes], 0,
- (packet_size_bytes_ - num_filled_bytes));
- }
-
- // Release the buffer space acquired in the GetBuffer() call.
- DWORD flags = 0;
- audio_render_client_->ReleaseBuffer(packet_size_frames_,
- flags);
-
- num_written_frames_ += packet_size_frames_;
- }
- }
- break;
- default:
- error = true;
- break;
- }
- }
-
- if (playing && error) {
- // Stop audio rendering since something has gone wrong in our main thread
- // loop. Note that, we are still in a "started" state, hence a Stop() call
- // is required to join the thread properly.
- audio_client_->Stop();
- PLOG(ERROR) << "WASAPI rendering failed.";
- }
-
- // Disable MMCSS.
- if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
- PLOG(WARNING) << "Failed to disable MMCSS";
- }
-}
-
-void WASAPIAudioOutputStream::HandleError(HRESULT err) {
- CHECK((started() && GetCurrentThreadId() == render_thread_->tid()) ||
- (!started() && GetCurrentThreadId() == creating_thread_id_));
- NOTREACHED() << "Error code: " << std::hex << err;
- if (source_)
- source_->OnError(this, static_cast<int>(err));
-}
-
-HRESULT WASAPIAudioOutputStream::SetRenderDevice() {
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
- ScopedComPtr<IMMDevice> endpoint_device;
-
- // Create the IMMDeviceEnumerator interface.
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- device_enumerator.ReceiveVoid());
- if (SUCCEEDED(hr)) {
- // Retrieve the default render audio endpoint for the specified role.
- // Note that, in Windows Vista, the MMDevice API supports device roles
- // but the system-supplied user interface programs do not.
- hr = device_enumerator->GetDefaultAudioEndpoint(
- eRender, device_role_, endpoint_device.Receive());
- if (FAILED(hr))
- return hr;
-
- // Verify that the audio endpoint device is active. That is, the audio
- // adapter that connects to the endpoint device is present and enabled.
- DWORD state = DEVICE_STATE_DISABLED;
- hr = endpoint_device->GetState(&state);
- if (SUCCEEDED(hr)) {
- if (!(state & DEVICE_STATE_ACTIVE)) {
- DLOG(ERROR) << "Selected render device is not active.";
- hr = E_ACCESSDENIED;
- }
- }
- }
-
- if (SUCCEEDED(hr)) {
- device_enumerator_ = device_enumerator;
- endpoint_device_ = endpoint_device;
- }
-
- return hr;
-}
-
-HRESULT WASAPIAudioOutputStream::ActivateRenderDevice() {
- ScopedComPtr<IAudioClient> audio_client;
-
- // Creates and activates an IAudioClient COM object given the selected
- // render endpoint device.
- HRESULT hr = endpoint_device_->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client.ReceiveVoid());
- if (SUCCEEDED(hr)) {
- // Retrieve the stream format that the audio engine uses for its internal
- // processing/mixing of shared-mode streams.
- audio_engine_mix_format_.Reset(NULL);
- hr = audio_client->GetMixFormat(
- reinterpret_cast<WAVEFORMATEX**>(&audio_engine_mix_format_));
-
- if (SUCCEEDED(hr)) {
- audio_client_ = audio_client;
- }
- }
-
- return hr;
-}
-
-bool WASAPIAudioOutputStream::DesiredFormatIsSupported() {
- // Determine, before calling IAudioClient::Initialize(), whether the audio
- // engine supports a particular stream format.
- // In shared mode, the audio engine always supports the mix format,
- // which is stored in the |audio_engine_mix_format_| member and it is also
- // possible to receive a proposed (closest) format if the current format is
- // not supported.
- base::win::ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match;
- HRESULT hr = audio_client_->IsFormatSupported(
- share_mode_, reinterpret_cast<WAVEFORMATEX*>(&format_),
- reinterpret_cast<WAVEFORMATEX**>(&closest_match));
-
- // This log can only be triggered for shared mode.
- DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
- << "but a closest match exists.";
- // This log can be triggered both for shared and exclusive modes.
- DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
- if (hr == S_FALSE) {
- DVLOG(1) << "wFormatTag : " << closest_match->Format.wFormatTag;
- DVLOG(1) << "nChannels : " << closest_match->Format.nChannels;
- DVLOG(1) << "nSamplesPerSec: " << closest_match->Format.nSamplesPerSec;
- DVLOG(1) << "wBitsPerSample: " << closest_match->Format.wBitsPerSample;
- }
-
- return (hr == S_OK);
-}
-
-HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() {
-#if !defined(NDEBUG)
- // The period between processing passes by the audio engine is fixed for a
- // particular audio endpoint device and represents the smallest processing
- // quantum for the audio engine. This period plus the stream latency between
- // the buffer and endpoint device represents the minimum possible latency
- // that an audio application can achieve in shared mode.
- {
- REFERENCE_TIME default_device_period = 0;
- REFERENCE_TIME minimum_device_period = 0;
- HRESULT hr_dbg = audio_client_->GetDevicePeriod(&default_device_period,
- &minimum_device_period);
- if (SUCCEEDED(hr_dbg)) {
- // Shared mode device period.
- DVLOG(1) << "shared mode (default) device period: "
- << static_cast<double>(default_device_period / 10000.0)
- << " [ms]";
- // Exclusive mode device period.
- DVLOG(1) << "exclusive mode (minimum) device period: "
- << static_cast<double>(minimum_device_period / 10000.0)
- << " [ms]";
- }
-
- REFERENCE_TIME latency = 0;
- hr_dbg = audio_client_->GetStreamLatency(&latency);
- if (SUCCEEDED(hr_dbg)) {
- DVLOG(1) << "stream latency: " << static_cast<double>(latency / 10000.0)
- << " [ms]";
- }
- }
-#endif
-
- HRESULT hr = S_FALSE;
-
- // Perform different initialization depending on if the device shall be
- // opened in shared mode or in exclusive mode.
- hr = (share_mode_ == AUDCLNT_SHAREMODE_SHARED) ?
- SharedModeInitialization() : ExclusiveModeInitialization();
- if (FAILED(hr)) {
- LOG(WARNING) << "IAudioClient::Initialize() failed: " << std::hex << hr;
- return hr;
- }
-
- // Retrieve the length of the endpoint buffer. The buffer length represents
- // the maximum amount of rendering data that the client can write to
- // the endpoint buffer during a single processing pass.
- // A typical value is 960 audio frames <=> 20ms @ 48kHz sample rate.
- hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_);
- if (FAILED(hr))
- return hr;
- DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_
- << " [frames]";
-
- // The buffer scheme for exclusive mode streams is not designed for max
- // flexibility. We only allow a "perfect match" between the packet size set
- // by the user and the actual endpoint buffer size.
- if (share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE &&
- endpoint_buffer_size_frames_ != packet_size_frames_) {
- hr = AUDCLNT_E_INVALID_SIZE;
- DLOG(ERROR) << "AUDCLNT_E_INVALID_SIZE";
- return hr;
- }
-
- // Set the event handle that the audio engine will signal each time
- // a buffer becomes ready to be processed by the client.
- hr = audio_client_->SetEventHandle(audio_samples_render_event_.Get());
- if (FAILED(hr))
- return hr;
-
- // Get access to the IAudioRenderClient interface. This interface
- // enables us to write output data to a rendering endpoint buffer.
- // The methods in this interface manage the movement of data packets
- // that contain audio-rendering data.
- hr = audio_client_->GetService(__uuidof(IAudioRenderClient),
- audio_render_client_.ReceiveVoid());
- return hr;
-}
-
-HRESULT WASAPIAudioOutputStream::SharedModeInitialization() {
- DCHECK_EQ(share_mode_, AUDCLNT_SHAREMODE_SHARED);
-
- // TODO(henrika): this buffer scheme is still under development.
- // The exact details are yet to be determined based on tests with different
- // audio clients.
- int glitch_free_buffer_size_ms = static_cast<int>(packet_size_ms_ + 0.5);
- if (audio_engine_mix_format_->Format.nSamplesPerSec % 8000 == 0) {
- // Initial tests have shown that we have to add 10 ms extra to
- // ensure that we don't run empty for any packet size.
- glitch_free_buffer_size_ms += 10;
- } else if (audio_engine_mix_format_->Format.nSamplesPerSec % 11025 == 0) {
- // Initial tests have shown that we have to add 20 ms extra to
- // ensure that we don't run empty for any packet size.
- glitch_free_buffer_size_ms += 20;
- } else {
- DLOG(WARNING) << "Unsupported sample rate "
- << audio_engine_mix_format_->Format.nSamplesPerSec << " detected";
- glitch_free_buffer_size_ms += 20;
- }
- DVLOG(1) << "glitch_free_buffer_size_ms: " << glitch_free_buffer_size_ms;
- REFERENCE_TIME requested_buffer_duration =
- static_cast<REFERENCE_TIME>(glitch_free_buffer_size_ms * 10000);
-
- // Initialize the audio stream between the client and the device.
- // We connect indirectly through the audio engine by using shared mode
- // and WASAPI is initialized in an event driven mode.
- // Note that this API ensures that the buffer is never smaller than the
- // minimum buffer size needed to ensure glitch-free rendering.
- // If we requests a buffer size that is smaller than the audio engine's
- // minimum required buffer size, the method sets the buffer size to this
- // minimum buffer size rather than to the buffer size requested.
- HRESULT hr = S_FALSE;
- hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
- requested_buffer_duration,
- 0,
- reinterpret_cast<WAVEFORMATEX*>(&format_),
- NULL);
- return hr;
-}
-
-HRESULT WASAPIAudioOutputStream::ExclusiveModeInitialization() {
- DCHECK_EQ(share_mode_, AUDCLNT_SHAREMODE_EXCLUSIVE);
-
- float f = (1000.0 * packet_size_frames_) / format_.Format.nSamplesPerSec;
- REFERENCE_TIME requested_buffer_duration =
- static_cast<REFERENCE_TIME>(f * 10000.0 + 0.5);
-
- // Initialize the audio stream between the client and the device.
- // For an exclusive-mode stream that uses event-driven buffering, the
- // caller must specify nonzero values for hnsPeriodicity and
- // hnsBufferDuration, and the values of these two parameters must be equal.
- // The Initialize method allocates two buffers for the stream. Each buffer
- // is equal in duration to the value of the hnsBufferDuration parameter.
- // Following the Initialize call for a rendering stream, the caller should
- // fill the first of the two buffers before starting the stream.
- HRESULT hr = S_FALSE;
- hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
- requested_buffer_duration,
- requested_buffer_duration,
- reinterpret_cast<WAVEFORMATEX*>(&format_),
- NULL);
- if (FAILED(hr)) {
- if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
- LOG(ERROR) << "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
-
- UINT32 aligned_buffer_size = 0;
- audio_client_->GetBufferSize(&aligned_buffer_size);
- DVLOG(1) << "Use aligned buffer size instead: " << aligned_buffer_size;
- audio_client_.Release();
-
- // Calculate new aligned periodicity. Each unit of reference time
- // is 100 nanoseconds.
- REFERENCE_TIME aligned_buffer_duration = static_cast<REFERENCE_TIME>(
- (10000000.0 * aligned_buffer_size / format_.Format.nSamplesPerSec)
- + 0.5);
-
- // It is possible to re-activate and re-initialize the audio client
- // at this stage but we bail out with an error code instead and
- // combine it with a log message which informs about the suggested
- // aligned buffer size which should be used instead.
- DVLOG(1) << "aligned_buffer_duration: "
- << static_cast<double>(aligned_buffer_duration / 10000.0)
- << " [ms]";
- } else if (hr == AUDCLNT_E_INVALID_DEVICE_PERIOD) {
- // We will get this error if we try to use a smaller buffer size than
- // the minimum supported size (usually ~3ms on Windows 7).
- LOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD";
- }
- }
-
- return hr;
-}
-
-std::string WASAPIAudioOutputStream::GetDeviceName(LPCWSTR device_id) const {
- std::string name;
- ScopedComPtr<IMMDevice> audio_device;
-
- // Get the IMMDevice interface corresponding to the given endpoint ID string.
- HRESULT hr = device_enumerator_->GetDevice(device_id, audio_device.Receive());
- if (SUCCEEDED(hr)) {
- // Retrieve user-friendly name of endpoint device.
- // Example: "Speakers (Realtek High Definition Audio)".
- ScopedComPtr<IPropertyStore> properties;
- hr = audio_device->OpenPropertyStore(STGM_READ, properties.Receive());
- if (SUCCEEDED(hr)) {
- PROPVARIANT friendly_name;
- PropVariantInit(&friendly_name);
- hr = properties->GetValue(PKEY_Device_FriendlyName, &friendly_name);
- if (SUCCEEDED(hr) && friendly_name.vt == VT_LPWSTR) {
- if (friendly_name.pwszVal)
- name = WideToUTF8(friendly_name.pwszVal);
- }
- PropVariantClear(&friendly_name);
- }
- }
- return name;
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_low_latency_output_win.h b/src/media/audio/win/audio_low_latency_output_win.h
deleted file mode 100644
index fb9aa3d..0000000
--- a/src/media/audio/win/audio_low_latency_output_win.h
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implementation of AudioOutputStream for Windows using Windows Core Audio
-// WASAPI for low latency rendering.
-//
-// Overview of operation and performance:
-//
-// - An object of WASAPIAudioOutputStream is created by the AudioManager
-// factory.
-// - Next some thread will call Open(), at that point the underlying
-// Core Audio APIs are utilized to create two WASAPI interfaces called
-// IAudioClient and IAudioRenderClient.
-// - Then some thread will call Start(source).
-// A thread called "wasapi_render_thread" is started and this thread listens
-// on an event signal which is set periodically by the audio engine to signal
-// render events. As a result, OnMoreData() will be called and the registered
-// client is then expected to provide data samples to be played out.
-// - At some point, a thread will call Stop(), which stops and joins the
-// render thread and at the same time stops audio streaming.
-// - The same thread that called stop will call Close() where we cleanup
-// and notify the audio manager, which likely will destroy this object.
-// - Initial tests on Windows 7 shows that this implementation results in a
-// latency of approximately 35 ms if the selected packet size is less than
-// or equal to 20 ms. Using a packet size of 10 ms does not result in a
-// lower latency but only affects the size of the data buffer in each
-// OnMoreData() callback.
-// - A total typical delay of 35 ms contains three parts:
-// o Audio endpoint device period (~10 ms).
-// o Stream latency between the buffer and endpoint device (~5 ms).
-// o Endpoint buffer (~20 ms to ensure glitch-free rendering).
-// - Note that, if the user selects a packet size of e.g. 100 ms, the total
-// delay will be approximately 115 ms (10 + 5 + 100).
-//
-// Implementation notes:
-//
-// - The minimum supported client is Windows Vista.
-// - This implementation is single-threaded, hence:
-// o Construction and destruction must take place from the same thread.
-// o All APIs must be called from the creating thread as well.
-// - It is recommended to first acquire the native sample rate of the default
-// input device and then use the same rate when creating this object. Use
-// WASAPIAudioOutputStream::HardwareSampleRate() to retrieve the sample rate.
-// - Calling Close() also leads to self destruction.
-// - Stream switching is not supported if the user shifts the audio device
-// after Open() is called but before Start() has been called.
-// - Stream switching can fail if streaming starts on one device with a
-// supported format (X) and the new default device - to which we would like
-// to switch - uses another format (Y), which is not supported given the
-// configured audio parameters.
-// - The audio device must be opened with the same number of channels as it
-// supports natively (see HardwareChannelCount()) otherwise Open() will fail.
-// - Support for 8-bit audio has not yet been verified and tested.
-//
-// Core Audio API details:
-//
-// - The public API methods (Open(), Start(), Stop() and Close()) must be
-// called on constructing thread. The reason is that we want to ensure that
-// the COM environment is the same for all API implementations.
-// - Utilized MMDevice interfaces:
-// o IMMDeviceEnumerator
-// o IMMDevice
-// - Utilized WASAPI interfaces:
-// o IAudioClient
-// o IAudioRenderClient
-// - The stream is initialized in shared mode and the processing of the
-// audio buffer is event driven.
-// - The Multimedia Class Scheduler service (MMCSS) is utilized to boost
-// the priority of the render thread.
-// - Audio-rendering endpoint devices can have three roles:
-// Console (eConsole), Communications (eCommunications), and Multimedia
-// (eMultimedia). Search for "Device Roles" on MSDN for more details.
-//
-// Threading details:
-//
-// - It is assumed that this class is created on the audio thread owned
-// by the AudioManager.
-// - It is a requirement to call the following methods on the same audio
-// thread: Open(), Start(), Stop(), and Close().
-// - Audio rendering is performed on the audio render thread, owned by this
-// class, and the AudioSourceCallback::OnMoreData() method will be called
-// from this thread. Stream switching also takes place on the audio-render
-// thread.
-//
-// Experimental exclusive mode:
-//
-// - It is possible to open up a stream in exclusive mode by using the
-// --enable-exclusive-audio command line flag.
-// - The internal buffering scheme is less flexible for exclusive streams.
-// Hence, some manual tuning will be required before deciding what frame
-// size to use. See the WinAudioOutputTest unit test for more details.
-// - If an application opens a stream in exclusive mode, the application has
-// exclusive use of the audio endpoint device that plays the stream.
-// - Exclusive-mode should only be utilized when the lowest possible latency
-// is important.
-// - In exclusive mode, the client can choose to open the stream in any audio
-// format that the endpoint device supports, i.e. not limited to the device's
-// current (default) configuration.
-// - Initial measurements on Windows 7 (HP Z600 workstation) have shown that
-// the lowest possible latencies we can achieve on this machine are:
-// o ~3.3333ms @ 48kHz <=> 160 audio frames per buffer.
-// o ~3.6281ms @ 44.1kHz <=> 160 audio frames per buffer.
-// - See http://msdn.microsoft.com/en-us/library/windows/desktop/dd370844(v=vs.85).aspx
-// for more details.
-
-#ifndef MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_
-#define MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_
-
-#include <Audioclient.h>
-#include <MMDeviceAPI.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/simple_thread.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_com_initializer.h"
-#include "base/win/scoped_comptr.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioManagerWin;
-
-// AudioOutputStream implementation using Windows Core Audio APIs.
-class MEDIA_EXPORT WASAPIAudioOutputStream :
- public AudioOutputStream,
- public base::DelegateSimpleThread::Delegate {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- WASAPIAudioOutputStream(AudioManagerWin* manager,
- const AudioParameters& params,
- ERole device_role);
-
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~WASAPIAudioOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- // Retrieves the number of channels the audio engine uses for its internal
- // processing/mixing of shared-mode streams for the default endpoint device.
- static int HardwareChannelCount();
-
- // Retrieves the channel layout the audio engine uses for its internal
- // processing/mixing of shared-mode streams for the default endpoint device.
- // Note that we convert an internal channel layout mask (see ChannelMask())
- // into a Chrome-specific channel layout enumerator in this method, hence
- // the match might not be perfect.
- static ChannelLayout HardwareChannelLayout();
-
- // Retrieves the sample rate the audio engine uses for its internal
- // processing/mixing of shared-mode streams for the default endpoint device.
- static int HardwareSampleRate(ERole device_role);
-
- // Returns AUDCLNT_SHAREMODE_EXCLUSIVE if --enable-exclusive-mode is used
- // as command-line flag and AUDCLNT_SHAREMODE_SHARED otherwise (default).
- static AUDCLNT_SHAREMODE GetShareMode();
-
- bool started() const { return render_thread_.get() != NULL; }
-
- // Returns the number of channels the audio engine uses for its internal
- // processing/mixing of shared-mode streams for the default endpoint device.
- int GetEndpointChannelCountForTesting() { return format_.Format.nChannels; }
-
- private:
- // DelegateSimpleThread::Delegate implementation.
- virtual void Run() OVERRIDE;
-
- // Issues the OnError() callback to the |sink_|.
- void HandleError(HRESULT err);
-
- // The Open() method is divided into these sub methods.
- HRESULT SetRenderDevice();
- HRESULT ActivateRenderDevice();
- bool DesiredFormatIsSupported();
- HRESULT InitializeAudioEngine();
-
- // Called when the device will be opened in shared mode and use the
- // internal audio engine's mix format.
- HRESULT SharedModeInitialization();
-
- // Called when the device will be opened in exclusive mode and use the
- // application specified format.
- HRESULT ExclusiveModeInitialization();
-
- // Converts unique endpoint ID to user-friendly device name.
- std::string GetDeviceName(LPCWSTR device_id) const;
-
- // Contains the thread ID of the creating thread.
- base::PlatformThreadId creating_thread_id_;
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerWin* manager_;
-
- // Rendering is driven by this thread (which has no message loop).
- // All OnMoreData() callbacks will be called from this thread.
- scoped_ptr<base::DelegateSimpleThread> render_thread_;
-
- // Contains the desired audio format which is set up at construction.
- // Extended PCM waveform format structure based on WAVEFORMATEXTENSIBLE.
- // Use this for multiple channel and hi-resolution PCM data.
- WAVEFORMATPCMEX format_;
-
- // Copy of the audio format which we know the audio engine supports.
- // It is recommended to ensure that the sample rate in |format_| is identical
- // to the sample rate in |audio_engine_mix_format_|.
- base::win::ScopedCoMem<WAVEFORMATPCMEX> audio_engine_mix_format_;
-
- bool opened_;
-
- // Set to true as soon as a new default device is detected, and cleared when
- // the streaming has switched from using the old device to the new device.
- // All additional device detections during an active state are ignored to
- // ensure that the ongoing switch can finalize without disruptions.
- bool restart_rendering_mode_;
-
- // Volume level from 0 to 1.
- float volume_;
-
- // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM).
- size_t frame_size_;
-
- // Size in audio frames of each audio packet where an audio packet
- // is defined as the block of data which the source is expected to deliver
- // in each OnMoreData() callback.
- size_t packet_size_frames_;
-
- // Size in bytes of each audio packet.
- size_t packet_size_bytes_;
-
- // Size in milliseconds of each audio packet.
- float packet_size_ms_;
-
- // Length of the audio endpoint buffer.
- size_t endpoint_buffer_size_frames_;
-
- // Defines the role that the system has assigned to an audio endpoint device.
- ERole device_role_;
-
- // The sharing mode for the connection.
- // Valid values are AUDCLNT_SHAREMODE_SHARED and AUDCLNT_SHAREMODE_EXCLUSIVE
- // where AUDCLNT_SHAREMODE_SHARED is the default.
- AUDCLNT_SHAREMODE share_mode_;
-
- // The channel count set by the client in |params| which is provided to the
- // constructor. The client must feed the AudioSourceCallback::OnMoreData()
- // callback with PCM-data that contains this number of channels.
- int client_channel_count_;
-
- // Counts the number of audio frames written to the endpoint buffer.
- UINT64 num_written_frames_;
-
- // Pointer to the client that will deliver audio samples to be played out.
- AudioSourceCallback* source_;
-
- // An IMMDeviceEnumerator interface which represents a device enumerator.
- base::win::ScopedComPtr<IMMDeviceEnumerator> device_enumerator_;
-
- // An IMMDevice interface which represents an audio endpoint device.
- base::win::ScopedComPtr<IMMDevice> endpoint_device_;
-
- // An IAudioClient interface which enables a client to create and initialize
- // an audio stream between an audio application and the audio engine.
- base::win::ScopedComPtr<IAudioClient> audio_client_;
-
- // The IAudioRenderClient interface enables a client to write output
- // data to a rendering endpoint buffer.
- base::win::ScopedComPtr<IAudioRenderClient> audio_render_client_;
-
- // The audio engine will signal this event each time a buffer becomes
- // ready to be filled by the client.
- base::win::ScopedHandle audio_samples_render_event_;
-
- // This event will be signaled when rendering shall stop.
- base::win::ScopedHandle stop_render_event_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(WASAPIAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_
diff --git a/src/media/audio/win/audio_low_latency_output_win_unittest.cc b/src/media/audio/win/audio_low_latency_output_win_unittest.cc
deleted file mode 100644
index 9836c09..0000000
--- a/src/media/audio/win/audio_low_latency_output_win_unittest.cc
+++ /dev/null
@@ -1,763 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <windows.h>
-#include <mmsystem.h>
-
-#include "base/basictypes.h"
-#include "base/environment.h"
-#include "base/file_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/test/test_timeouts.h"
-#include "base/time.h"
-#include "base/path_service.h"
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_low_latency_output_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/seekable_buffer.h"
-#include "media/base/test_data_util.h"
-#include "testing/gmock_mutant.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::Between;
-using ::testing::CreateFunctor;
-using ::testing::DoAll;
-using ::testing::Gt;
-using ::testing::InvokeWithoutArgs;
-using ::testing::NotNull;
-using ::testing::Return;
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-static const char kSpeechFile_16b_s_48k[] = "speech_16b_stereo_48kHz.raw";
-static const char kSpeechFile_16b_s_44k[] = "speech_16b_stereo_44kHz.raw";
-static const size_t kFileDurationMs = 20000;
-static const size_t kNumFileSegments = 2;
-static const int kBitsPerSample = 16;
-static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
-static const size_t kMaxDeltaSamples = 1000;
-static const char* kDeltaTimeMsFileName = "delta_times_ms.txt";
-
-MATCHER_P(HasValidDelay, value, "") {
- // It is difficult to come up with a perfect test condition for the delay
- // estimation. For now, verify that the produced output delay is always
- // larger than the selected buffer size.
- return arg.hardware_delay_bytes > value.hardware_delay_bytes;
-}
-
-// Used to terminate a loop from a different thread than the loop belongs to.
-// |loop| should be a MessageLoopProxy.
-ACTION_P(QuitLoop, loop) {
- loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-// This audio source implementation should be used for manual tests only since
-// it takes about 20 seconds to play out a file.
-class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback {
- public:
- explicit ReadFromFileAudioSource(const std::string& name)
- : pos_(0),
- previous_call_time_(base::Time::Now()),
- text_file_(NULL),
- elements_to_write_(0) {
- // Reads a test file from media/test/data directory.
- file_ = ReadTestDataFile(name);
-
- // Creates an array that will store delta times between callbacks.
- // The content of this array will be written to a text file at
- // destruction and can then be used for off-line analysis of the exact
- // timing of callbacks. The text file will be stored in media/test/data.
- delta_times_.reset(new int[kMaxDeltaSamples]);
- }
-
- virtual ~ReadFromFileAudioSource() {
- // Get complete file path to output file in directory containing
- // media_unittests.exe.
- FilePath file_name;
- EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name));
- file_name = file_name.AppendASCII(kDeltaTimeMsFileName);
-
- EXPECT_TRUE(!text_file_);
- text_file_ = file_util::OpenFile(file_name, "wt");
- DLOG_IF(ERROR, !text_file_) << "Failed to open log file.";
-
- // Write the array which contains delta times to a text file.
- size_t elements_written = 0;
- while (elements_written < elements_to_write_) {
- fprintf(text_file_, "%d\n", delta_times_[elements_written]);
- ++elements_written;
- }
-
- file_util::CloseFile(text_file_);
- }
-
- // AudioOutputStream::AudioSourceCallback implementation.
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- // Store time difference between two successive callbacks in an array.
- // These values will be written to a file in the destructor.
- int diff = (base::Time::Now() - previous_call_time_).InMilliseconds();
- previous_call_time_ = base::Time::Now();
- if (elements_to_write_ < kMaxDeltaSamples) {
- delta_times_[elements_to_write_] = diff;
- ++elements_to_write_;
- }
-
- int max_size =
- audio_bus->frames() * audio_bus->channels() * kBitsPerSample / 8;
-
- // Use samples read from a data file and fill up the audio buffer
- // provided to us in the callback.
- if (pos_ + static_cast<int>(max_size) > file_size())
- max_size = file_size() - pos_;
- int frames = max_size / (audio_bus->channels() * kBitsPerSample / 8);
- if (max_size) {
- audio_bus->FromInterleaved(
- file_->GetData() + pos_, frames, kBitsPerSample / 8);
- pos_ += max_size;
- }
- return frames;
- }
-
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) OVERRIDE {
- NOTREACHED();
- return 0;
- }
-
- virtual void OnError(AudioOutputStream* stream, int code) {}
-
- int file_size() { return file_->GetDataSize(); }
-
- private:
- scoped_refptr<DecoderBuffer> file_;
- scoped_array<int> delta_times_;
- int pos_;
- base::Time previous_call_time_;
- FILE* text_file_;
- size_t elements_to_write_;
-};
-
-static bool ExclusiveModeIsEnabled() {
- return (WASAPIAudioOutputStream::GetShareMode() ==
- AUDCLNT_SHAREMODE_EXCLUSIVE);
-}
-
-// Convenience method which ensures that we are not running on the build
-// bots and that at least one valid output device can be found. We also
-// verify that we are not running on XP since the low-latency (WASAPI-
-// based) version requires Windows Vista or higher.
-static bool CanRunAudioTests(AudioManager* audio_man) {
- if (!CoreAudioUtil::IsSupported()) {
- LOG(WARNING) << "This test requires Windows Vista or higher.";
- return false;
- }
-
- // TODO(henrika): note that we use Wave today to query the number of
- // existing output devices.
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output devices detected.";
- return false;
- }
-
- if (WASAPIAudioOutputStream::HardwareChannelLayout() != kChannelLayout) {
- LOG(WARNING) << "This test requires stereo audio output.";
- return false;
- }
-
- return true;
-}
-
-// Convenience method which creates a default AudioOutputStream object but
-// also allows the user to modify the default settings.
-class AudioOutputStreamWrapper {
- public:
- explicit AudioOutputStreamWrapper(AudioManager* audio_manager)
- : com_init_(ScopedCOMInitializer::kMTA),
- audio_man_(audio_manager),
- format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
- channel_layout_(kChannelLayout),
- bits_per_sample_(kBitsPerSample) {
- // Use native/mixing sample rate and 10ms frame size as default.
- sample_rate_ = static_cast<int>(
- WASAPIAudioOutputStream::HardwareSampleRate(eConsole));
- samples_per_packet_ = sample_rate_ / 100;
- DCHECK(sample_rate_);
- }
-
- ~AudioOutputStreamWrapper() {}
-
- // Creates AudioOutputStream object using default parameters.
- AudioOutputStream* Create() {
- return CreateOutputStream();
- }
-
- // Creates AudioOutputStream object using non-default parameters where the
- // frame size is modified.
- AudioOutputStream* Create(int samples_per_packet) {
- samples_per_packet_ = samples_per_packet;
- return CreateOutputStream();
- }
-
- // Creates AudioOutputStream object using non-default parameters where the
- // sample rate and frame size are modified.
- AudioOutputStream* Create(int sample_rate, int samples_per_packet) {
- sample_rate_ = sample_rate;
- samples_per_packet_ = samples_per_packet;
- return CreateOutputStream();
- }
-
- AudioParameters::Format format() const { return format_; }
- int channels() const { return ChannelLayoutToChannelCount(channel_layout_); }
- int bits_per_sample() const { return bits_per_sample_; }
- int sample_rate() const { return sample_rate_; }
- int samples_per_packet() const { return samples_per_packet_; }
-
- private:
- AudioOutputStream* CreateOutputStream() {
- AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(
- AudioParameters(format_, channel_layout_, sample_rate_,
- bits_per_sample_, samples_per_packet_));
- EXPECT_TRUE(aos);
- return aos;
- }
-
- ScopedCOMInitializer com_init_;
- AudioManager* audio_man_;
- AudioParameters::Format format_;
- ChannelLayout channel_layout_;
- int bits_per_sample_;
- int sample_rate_;
- int samples_per_packet_;
-};
-
-// Convenience method which creates a default AudioOutputStream object.
-static AudioOutputStream* CreateDefaultAudioOutputStream(
- AudioManager* audio_manager) {
- AudioOutputStreamWrapper aosw(audio_manager);
- AudioOutputStream* aos = aosw.Create();
- return aos;
-}
-
-// Verify that we can retrieve the current hardware/mixing sample rate
-// for all supported device roles. The ERole enumeration defines constants
-// that indicate the role that the system/user has assigned to an audio
-// endpoint device.
-// TODO(henrika): modify this test when we support full device enumeration.
-TEST(WASAPIAudioOutputStreamTest, HardwareSampleRate) {
- // Skip this test in exclusive mode since the resulting rate is only utilized
- // for shared mode streams.
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()) || ExclusiveModeIsEnabled())
- return;
-
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Default device intended for games, system notification sounds,
- // and voice commands.
- int fs = static_cast<int>(
- WASAPIAudioOutputStream::HardwareSampleRate(eConsole));
- EXPECT_GE(fs, 0);
-
- // Default communication device intended for e.g. VoIP communication.
- fs = static_cast<int>(
- WASAPIAudioOutputStream::HardwareSampleRate(eCommunications));
- EXPECT_GE(fs, 0);
-
- // Multimedia device for music, movies and live music recording.
- fs = static_cast<int>(
- WASAPIAudioOutputStream::HardwareSampleRate(eMultimedia));
- EXPECT_GE(fs, 0);
-}
-
-// Test Create(), Close() calling sequence.
-TEST(WASAPIAudioOutputStreamTest, CreateAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
- aos->Close();
-}
-
-// Verify that the created object is configured to use the same number of
-// audio channels as is reported by the static HardwareChannelCount() method.
-TEST(WASAPIAudioOutputStreamTest, HardwareChannelCount) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // First, verify that we can read a valid native/hardware channel-count.
- int hardware_channel_count = WASAPIAudioOutputStream::HardwareChannelCount();
- EXPECT_GE(hardware_channel_count, 1);
-
- AudioOutputStreamWrapper aosw(audio_manager.get());
- WASAPIAudioOutputStream* aos =
- static_cast<WASAPIAudioOutputStream*>(aosw.Create());
-
- // Next, ensure that the created output stream object is really using the
- // hardware channel-count.
- EXPECT_EQ(hardware_channel_count, aos->GetEndpointChannelCountForTesting());
- aos->Close();
-}
-
-// Test Open(), Close() calling sequence.
-TEST(WASAPIAudioOutputStreamTest, OpenAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
- EXPECT_TRUE(aos->Open());
- aos->Close();
-}
-
-// Test Open(), Start(), Close() calling sequence.
-TEST(WASAPIAudioOutputStreamTest, OpenStartAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
- EXPECT_TRUE(aos->Open());
- MockAudioSourceCallback source;
- EXPECT_CALL(source, OnError(aos, _))
- .Times(0);
- aos->Start(&source);
- aos->Close();
-}
-
-// Test Open(), Start(), Stop(), Close() calling sequence.
-TEST(WASAPIAudioOutputStreamTest, OpenStartStopAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
- EXPECT_TRUE(aos->Open());
- MockAudioSourceCallback source;
- EXPECT_CALL(source, OnError(aos, _))
- .Times(0);
- aos->Start(&source);
- aos->Stop();
- aos->Close();
-}
-
-// Test SetVolume(), GetVolume()
-TEST(WASAPIAudioOutputStreamTest, Volume) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
-
- // Initial volume should be full volume (1.0).
- double volume = 0.0;
- aos->GetVolume(&volume);
- EXPECT_EQ(1.0, volume);
-
- // Verify some valid volume settings.
- aos->SetVolume(0.0);
- aos->GetVolume(&volume);
- EXPECT_EQ(0.0, volume);
-
- aos->SetVolume(0.5);
- aos->GetVolume(&volume);
- EXPECT_EQ(0.5, volume);
-
- aos->SetVolume(1.0);
- aos->GetVolume(&volume);
- EXPECT_EQ(1.0, volume);
-
- // Ensure that invalid volume setting have no effect.
- aos->SetVolume(1.5);
- aos->GetVolume(&volume);
- EXPECT_EQ(1.0, volume);
-
- aos->SetVolume(-0.5);
- aos->GetVolume(&volume);
- EXPECT_EQ(1.0, volume);
-
- aos->Close();
-}
-
-// Test some additional calling sequences.
-TEST(WASAPIAudioOutputStreamTest, MiscCallingSequences) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager.get());
- WASAPIAudioOutputStream* waos = static_cast<WASAPIAudioOutputStream*>(aos);
-
- // Open(), Open() is a valid calling sequence (second call does nothing).
- EXPECT_TRUE(aos->Open());
- EXPECT_TRUE(aos->Open());
-
- MockAudioSourceCallback source;
-
- // Start(), Start() is a valid calling sequence (second call does nothing).
- aos->Start(&source);
- EXPECT_TRUE(waos->started());
- aos->Start(&source);
- EXPECT_TRUE(waos->started());
-
- // Stop(), Stop() is a valid calling sequence (second call does nothing).
- aos->Stop();
- EXPECT_FALSE(waos->started());
- aos->Stop();
- EXPECT_FALSE(waos->started());
-
- // Start(), Stop(), Start(), Stop().
- aos->Start(&source);
- EXPECT_TRUE(waos->started());
- aos->Stop();
- EXPECT_FALSE(waos->started());
- aos->Start(&source);
- EXPECT_TRUE(waos->started());
- aos->Stop();
- EXPECT_FALSE(waos->started());
-
- aos->Close();
-}
-
-// Use default packet size (10ms) and verify that rendering starts.
-TEST(WASAPIAudioOutputStreamTest, PacketSizeInMilliseconds) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- MockAudioSourceCallback source;
-
- // Create default WASAPI output stream which plays out in stereo using
- // the shared mixing rate. The default buffer size is 10ms.
- AudioOutputStreamWrapper aosw(audio_manager.get());
- AudioOutputStream* aos = aosw.Create();
- EXPECT_TRUE(aos->Open());
-
- // Derive the expected size in bytes of each packet.
- uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
- (aosw.bits_per_sample() / 8);
-
- // Set up expected minimum delay estimation.
- AudioBuffersState state(0, bytes_per_packet);
-
- // Wait for the first callback and verify its parameters.
- EXPECT_CALL(source, OnMoreData(NotNull(), HasValidDelay(state)))
- .WillOnce(DoAll(
- QuitLoop(loop.message_loop_proxy()),
- Return(aosw.samples_per_packet())));
-
- aos->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- loop.Run();
- aos->Stop();
- aos->Close();
-}
-
-// Use a fixed packets size (independent of sample rate) and verify
-// that rendering starts.
-TEST(WASAPIAudioOutputStreamTest, PacketSizeInSamples) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- MockAudioSourceCallback source;
-
- // Create default WASAPI output stream which reads data in stereo using
- // the native mixing rate and channel count. The buffer size is set to
- // 1024 samples.
- AudioOutputStreamWrapper aosw(audio_manager.get());
- AudioOutputStream* aos = aosw.Create(1024);
- EXPECT_TRUE(aos->Open());
-
- // Derive the expected size in bytes of each packet.
- uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
- (aosw.bits_per_sample() / 8);
-
- // Set up expected minimum delay estimation.
- AudioBuffersState state(0, bytes_per_packet);
-
- // Ensure that callbacks start correctly.
- EXPECT_CALL(source, OnMoreData(NotNull(), HasValidDelay(state)))
- .WillOnce(DoAll(
- QuitLoop(loop.message_loop_proxy()),
- Return(aosw.samples_per_packet())))
- .WillRepeatedly(Return(aosw.samples_per_packet()));
-
- aos->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- loop.Run();
- aos->Stop();
- aos->Close();
-}
-
-// This test is intended for manual tests and should only be enabled
-// when it is required to play out data from a local PCM file.
-// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
-// To include disabled tests in test execution, just invoke the test program
-// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
-// environment variable to a value greater than 0.
-// The test files are approximately 20 seconds long.
-TEST(WASAPIAudioOutputStreamTest, DISABLED_ReadFromStereoFile) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- AudioOutputStreamWrapper aosw(audio_manager.get());
- AudioOutputStream* aos = aosw.Create();
- EXPECT_TRUE(aos->Open());
-
- std::string file_name;
- if (aosw.sample_rate() == 48000) {
- file_name = kSpeechFile_16b_s_48k;
- } else if (aosw.sample_rate() == 44100) {
- file_name = kSpeechFile_16b_s_44k;
- } else if (aosw.sample_rate() == 96000) {
- // Use 48kHz file at 96kHz as well. Will sound like Donald Duck.
- file_name = kSpeechFile_16b_s_48k;
- } else {
- FAIL() << "This test supports 44.1, 48kHz and 96kHz only.";
- return;
- }
- ReadFromFileAudioSource file_source(file_name);
-
- LOG(INFO) << "File name : " << file_name.c_str();
- LOG(INFO) << "Sample rate : " << aosw.sample_rate();
- LOG(INFO) << "Bits per sample: " << aosw.bits_per_sample();
- LOG(INFO) << "#channels : " << aosw.channels();
- LOG(INFO) << "File size : " << file_source.file_size();
- LOG(INFO) << "#file segments : " << kNumFileSegments;
- LOG(INFO) << ">> Listen to the stereo file while playing...";
-
- for (int i = 0; i < kNumFileSegments; i++) {
- // Each segment will start with a short (~20ms) block of zeros, hence
- // some short glitches might be heard in this test if kNumFileSegments
- // is larger than one. The exact length of the silence period depends on
- // the selected sample rate.
- aos->Start(&file_source);
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(kFileDurationMs / kNumFileSegments));
- aos->Stop();
- }
-
- LOG(INFO) << ">> Stereo file playout has stopped.";
- aos->Close();
-}
-
-// Verify that we can open the output stream in exclusive mode using a
-// certain set of audio parameters and a sample rate of 48kHz.
-// The expected outcomes of each setting in this test has been derived
-// manually using log outputs (--v=1).
-TEST(WASAPIAudioOutputStreamTest, ExclusiveModeBufferSizesAt48kHz) {
- if (!ExclusiveModeIsEnabled())
- return;
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- AudioOutputStreamWrapper aosw(audio_manager.get());
-
- // 10ms @ 48kHz shall work.
- // Note that, this is the same size as we can use for shared-mode streaming
- // but here the endpoint buffer delay is only 10ms instead of 20ms.
- AudioOutputStream* aos = aosw.Create(48000, 480);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-
- // 5ms @ 48kHz does not work due to misalignment.
- // This test will propose an aligned buffer size of 5.3333ms.
- // Note that we must call Close() even is Open() fails since Close() also
- // deletes the object and we want to create a new object in the next test.
- aos = aosw.Create(48000, 240);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 5.3333ms @ 48kHz should work (see test above).
- aos = aosw.Create(48000, 256);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-
- // 2.6667ms is smaller than the minimum supported size (=3ms).
- aos = aosw.Create(48000, 128);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 3ms does not correspond to an aligned buffer size.
- // This test will propose an aligned buffer size of 3.3333ms.
- aos = aosw.Create(48000, 144);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 3.3333ms @ 48kHz <=> smallest possible buffer size we can use.
- aos = aosw.Create(48000, 160);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-}
-
-// Verify that we can open the output stream in exclusive mode using a
-// certain set of audio parameters and a sample rate of 44.1kHz.
-// The expected outcomes of each setting in this test has been derived
-// manually using log outputs (--v=1).
-TEST(WASAPIAudioOutputStreamTest, ExclusiveModeBufferSizesAt44kHz) {
- if (!ExclusiveModeIsEnabled())
- return;
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- AudioOutputStreamWrapper aosw(audio_manager.get());
-
- // 10ms @ 44.1kHz does not work due to misalignment.
- // This test will propose an aligned buffer size of 10.1587ms.
- AudioOutputStream* aos = aosw.Create(44100, 441);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 10.1587ms @ 44.1kHz shall work (see test above).
- aos = aosw.Create(44100, 448);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-
- // 5.8050ms @ 44.1 should work.
- aos = aosw.Create(44100, 256);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-
- // 4.9887ms @ 44.1kHz does not work to misalignment.
- // This test will propose an aligned buffer size of 5.0794ms.
- // Note that we must call Close() even is Open() fails since Close() also
- // deletes the object and we want to create a new object in the next test.
- aos = aosw.Create(44100, 220);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 5.0794ms @ 44.1kHz shall work (see test above).
- aos = aosw.Create(44100, 224);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-
- // 2.9025ms is smaller than the minimum supported size (=3ms).
- aos = aosw.Create(44100, 132);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 3.01587ms is larger than the minimum size but is not aligned.
- // This test will propose an aligned buffer size of 3.6281ms.
- aos = aosw.Create(44100, 133);
- EXPECT_FALSE(aos->Open());
- aos->Close();
-
- // 3.6281ms @ 44.1kHz <=> smallest possible buffer size we can use.
- aos = aosw.Create(44100, 160);
- EXPECT_TRUE(aos->Open());
- aos->Close();
-}
-
-// Verify that we can open and start the output stream in exclusive mode at
-// the lowest possible delay at 48kHz.
-TEST(WASAPIAudioOutputStreamTest, ExclusiveModeMinBufferSizeAt48kHz) {
- if (!ExclusiveModeIsEnabled())
- return;
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- MockAudioSourceCallback source;
-
- // Create exclusive-mode WASAPI output stream which plays out in stereo
- // using the minimum buffer size at 48kHz sample rate.
- AudioOutputStreamWrapper aosw(audio_manager.get());
- AudioOutputStream* aos = aosw.Create(48000, 160);
- EXPECT_TRUE(aos->Open());
-
- // Derive the expected size in bytes of each packet.
- uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
- (aosw.bits_per_sample() / 8);
-
- // Set up expected minimum delay estimation.
- AudioBuffersState state(0, bytes_per_packet);
-
- // Wait for the first callback and verify its parameters.
- EXPECT_CALL(source, OnMoreData(NotNull(), HasValidDelay(state)))
- .WillOnce(DoAll(
- QuitLoop(loop.message_loop_proxy()),
- Return(aosw.samples_per_packet())))
- .WillRepeatedly(Return(aosw.samples_per_packet()));
-
- aos->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- loop.Run();
- aos->Stop();
- aos->Close();
-}
-
-// Verify that we can open and start the output stream in exclusive mode at
-// the lowest possible delay at 44.1kHz.
-TEST(WASAPIAudioOutputStreamTest, ExclusiveModeMinBufferSizeAt44kHz) {
- if (!ExclusiveModeIsEnabled())
- return;
-
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- MockAudioSourceCallback source;
-
- // Create exclusive-mode WASAPI output stream which plays out in stereo
- // using the minimum buffer size at 44.1kHz sample rate.
- AudioOutputStreamWrapper aosw(audio_manager.get());
- AudioOutputStream* aos = aosw.Create(44100, 160);
- EXPECT_TRUE(aos->Open());
-
- // Derive the expected size in bytes of each packet.
- uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() *
- (aosw.bits_per_sample() / 8);
-
- // Set up expected minimum delay estimation.
- AudioBuffersState state(0, bytes_per_packet);
-
- // Wait for the first callback and verify its parameters.
- EXPECT_CALL(source, OnMoreData(NotNull(), HasValidDelay(state)))
- .WillOnce(DoAll(
- QuitLoop(loop.message_loop_proxy()),
- Return(aosw.samples_per_packet())))
- .WillRepeatedly(Return(aosw.samples_per_packet()));
-
- aos->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- loop.Run();
- aos->Stop();
- aos->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_manager_win.cc b/src/media/audio/win/audio_manager_win.cc
deleted file mode 100644
index 9b788bf..0000000
--- a/src/media/audio/win/audio_manager_win.cc
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/audio_io.h"
-
-#include <windows.h>
-#include <objbase.h> // This has to be before initguid.h
-#include <initguid.h>
-#include <mmsystem.h>
-#include <setupapi.h>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/command_line.h"
-#include "base/file_path.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/path_service.h"
-#include "base/process_util.h"
-#include "base/string_number_conversions.h"
-#include "base/string_util.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_device_listener_win.h"
-#include "media/audio/win/audio_low_latency_input_win.h"
-#include "media/audio/win/audio_low_latency_output_win.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/audio_unified_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "media/audio/win/device_enumeration_win.h"
-#include "media/audio/win/wavein_input_win.h"
-#include "media/audio/win/waveout_output_win.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-
-// Libraries required for the SetupAPI and Wbem APIs used here.
-#pragma comment(lib, "setupapi.lib")
-
-// The following are defined in various DDK headers, and we (re)define them here
-// to avoid adding the DDK as a chrome dependency.
-#define DRV_QUERYDEVICEINTERFACE 0x80c
-#define DRVM_MAPPER_PREFERRED_GET 0x2015
-#define DRV_QUERYDEVICEINTERFACESIZE 0x80d
-DEFINE_GUID(AM_KSCATEGORY_AUDIO, 0x6994ad04, 0x93ef, 0x11d0,
- 0xa3, 0xcc, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96);
-
-namespace media {
-
-// Maximum number of output streams that can be open simultaneously.
-static const int kMaxOutputStreams = 50;
-
-// Up to 8 channels can be passed to the driver. This should work, given the
-// right drivers, but graceful error handling is needed.
-static const int kWinMaxChannels = 8;
-
-// We use 3 buffers for recording audio so that if a recording callback takes
-// some time to return we won't lose audio. More buffers while recording are
-// ok because they don't introduce any delay in recording, unlike in playback
-// where you first need to fill in that number of buffers before starting to
-// play.
-static const int kNumInputBuffers = 3;
-
-static int GetVersionPartAsInt(DWORDLONG num) {
- return static_cast<int>(num & 0xffff);
-}
-
-// Returns a string containing the given device's description and installed
-// driver version.
-static string16 GetDeviceAndDriverInfo(HDEVINFO device_info,
- SP_DEVINFO_DATA* device_data) {
- // Save the old install params setting and set a flag for the
- // SetupDiBuildDriverInfoList below to return only the installed drivers.
- SP_DEVINSTALL_PARAMS old_device_install_params;
- old_device_install_params.cbSize = sizeof(old_device_install_params);
- SetupDiGetDeviceInstallParams(device_info, device_data,
- &old_device_install_params);
- SP_DEVINSTALL_PARAMS device_install_params = old_device_install_params;
- device_install_params.FlagsEx |= DI_FLAGSEX_INSTALLEDDRIVER;
- SetupDiSetDeviceInstallParams(device_info, device_data,
- &device_install_params);
-
- SP_DRVINFO_DATA driver_data;
- driver_data.cbSize = sizeof(driver_data);
- string16 device_and_driver_info;
- if (SetupDiBuildDriverInfoList(device_info, device_data,
- SPDIT_COMPATDRIVER)) {
- if (SetupDiEnumDriverInfo(device_info, device_data, SPDIT_COMPATDRIVER, 0,
- &driver_data)) {
- DWORDLONG version = driver_data.DriverVersion;
- device_and_driver_info = string16(driver_data.Description) + L" v" +
- base::IntToString16(GetVersionPartAsInt((version >> 48))) + L"." +
- base::IntToString16(GetVersionPartAsInt((version >> 32))) + L"." +
- base::IntToString16(GetVersionPartAsInt((version >> 16))) + L"." +
- base::IntToString16(GetVersionPartAsInt(version));
- }
- SetupDiDestroyDriverInfoList(device_info, device_data, SPDIT_COMPATDRIVER);
- }
-
- SetupDiSetDeviceInstallParams(device_info, device_data,
- &old_device_install_params);
-
- return device_and_driver_info;
-}
-
-AudioManagerWin::AudioManagerWin() {
- if (!CoreAudioUtil::IsSupported()) {
- // Use the Wave API for device enumeration if XP or lower.
- enumeration_type_ = kWaveEnumeration;
- } else {
- // Use the MMDevice API for device enumeration if Vista or higher.
- enumeration_type_ = kMMDeviceEnumeration;
- }
-
- SetMaxOutputStreamsAllowed(kMaxOutputStreams);
-
- // Task must be posted last to avoid races from handing out "this" to the
- // audio thread.
- GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &AudioManagerWin::CreateDeviceListener, base::Unretained(this)));
-}
-
-AudioManagerWin::~AudioManagerWin() {
- // It's safe to post a task here since Shutdown() will wait for all tasks to
- // complete before returning.
- GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &AudioManagerWin::DestroyDeviceListener, base::Unretained(this)));
- Shutdown();
-}
-
-bool AudioManagerWin::HasAudioOutputDevices() {
- return (::waveOutGetNumDevs() != 0);
-}
-
-bool AudioManagerWin::HasAudioInputDevices() {
- return (::waveInGetNumDevs() != 0);
-}
-
-void AudioManagerWin::CreateDeviceListener() {
- // AudioDeviceListenerWin must be initialized on a COM thread and should only
- // be used if WASAPI / Core Audio is supported.
- if (CoreAudioUtil::IsSupported()) {
- output_device_listener_.reset(new AudioDeviceListenerWin(BindToLoop(
- GetMessageLoop(), base::Bind(
- &AudioManagerWin::NotifyAllOutputDeviceChangeListeners,
- base::Unretained(this)))));
- }
-}
-
-void AudioManagerWin::DestroyDeviceListener() {
- output_device_listener_.reset();
-}
-
-string16 AudioManagerWin::GetAudioInputDeviceModel() {
- // Get the default audio capture device and its device interface name.
- DWORD device_id = 0;
- waveInMessage(reinterpret_cast<HWAVEIN>(WAVE_MAPPER),
- DRVM_MAPPER_PREFERRED_GET,
- reinterpret_cast<DWORD_PTR>(&device_id), NULL);
- ULONG device_interface_name_size = 0;
- waveInMessage(reinterpret_cast<HWAVEIN>(device_id),
- DRV_QUERYDEVICEINTERFACESIZE,
- reinterpret_cast<DWORD_PTR>(&device_interface_name_size), 0);
- size_t bytes_in_char16 = sizeof(string16::value_type);
- DCHECK_EQ(0u, device_interface_name_size % bytes_in_char16);
- if (device_interface_name_size <= bytes_in_char16)
- return string16(); // No audio capture device.
-
- string16 device_interface_name;
- string16::value_type* name_ptr = WriteInto(&device_interface_name,
- device_interface_name_size / bytes_in_char16);
- waveInMessage(reinterpret_cast<HWAVEIN>(device_id),
- DRV_QUERYDEVICEINTERFACE,
- reinterpret_cast<DWORD_PTR>(name_ptr),
- static_cast<DWORD_PTR>(device_interface_name_size));
-
- // Enumerate all audio devices and find the one matching the above device
- // interface name.
- HDEVINFO device_info = SetupDiGetClassDevs(
- &AM_KSCATEGORY_AUDIO, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
- if (device_info == INVALID_HANDLE_VALUE)
- return string16();
-
- DWORD interface_index = 0;
- SP_DEVICE_INTERFACE_DATA interface_data;
- interface_data.cbSize = sizeof(interface_data);
- while (SetupDiEnumDeviceInterfaces(device_info, 0, &AM_KSCATEGORY_AUDIO,
- interface_index++, &interface_data)) {
- // Query the size of the struct, allocate it and then query the data.
- SP_DEVINFO_DATA device_data;
- device_data.cbSize = sizeof(device_data);
- DWORD interface_detail_size = 0;
- SetupDiGetDeviceInterfaceDetail(device_info, &interface_data, 0, 0,
- &interface_detail_size, &device_data);
- if (!interface_detail_size)
- continue;
-
- scoped_array<char> interface_detail_buffer(new char[interface_detail_size]);
- SP_DEVICE_INTERFACE_DETAIL_DATA* interface_detail =
- reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(
- interface_detail_buffer.get());
- interface_detail->cbSize = interface_detail_size;
- if (!SetupDiGetDeviceInterfaceDetail(device_info, &interface_data,
- interface_detail,
- interface_detail_size, NULL,
- &device_data))
- return string16();
-
- bool device_found = (device_interface_name == interface_detail->DevicePath);
-
- if (device_found)
- return GetDeviceAndDriverInfo(device_info, &device_data);
- }
-
- return string16();
-}
-
-bool AudioManagerWin::CanShowAudioInputSettings() {
- return true;
-}
-
-void AudioManagerWin::ShowAudioInputSettings() {
- std::wstring program;
- std::string argument;
- if (!CoreAudioUtil::IsSupported()) {
- program = L"sndvol32.exe";
- argument = "-R";
- } else {
- program = L"control.exe";
- argument = "mmsys.cpl,,1";
- }
-
- FilePath path;
- PathService::Get(base::DIR_SYSTEM, &path);
- path = path.Append(program);
- CommandLine command_line(path);
- command_line.AppendArg(argument);
- base::LaunchProcess(command_line, base::LaunchOptions(), NULL);
-}
-
-void AudioManagerWin::GetAudioInputDeviceNames(
- media::AudioDeviceNames* device_names) {
- DCHECK(enumeration_type() != kUninitializedEnumeration);
- // Enumerate all active audio-endpoint capture devices.
- if (enumeration_type() == kWaveEnumeration) {
- // Utilize the Wave API for Windows XP.
- media::GetInputDeviceNamesWinXP(device_names);
- } else {
- // Utilize the MMDevice API (part of Core Audio) for Vista and higher.
- media::GetInputDeviceNamesWin(device_names);
- }
-
- // Always add default device parameters as first element.
- if (!device_names->empty()) {
- media::AudioDeviceName name;
- name.device_name = AudioManagerBase::kDefaultDeviceName;
- name.unique_id = AudioManagerBase::kDefaultDeviceId;
- device_names->push_front(name);
- }
-}
-
-// Factory for the implementations of AudioOutputStream for AUDIO_PCM_LINEAR
-// mode.
-// - PCMWaveOutAudioOutputStream: Based on the waveOut API.
-AudioOutputStream* AudioManagerWin::MakeLinearOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- if (params.channels() > kWinMaxChannels)
- return NULL;
-
- return new PCMWaveOutAudioOutputStream(this,
- params,
- media::NumberOfWaveOutBuffers(),
- WAVE_MAPPER);
-}
-
-// Factory for the implementations of AudioOutputStream for
-// AUDIO_PCM_LOW_LATENCY mode. Two implementations should suffice most
-// windows user's needs.
-// - PCMWaveOutAudioOutputStream: Based on the waveOut API.
-// - WASAPIAudioOutputStream: Based on Core Audio (WASAPI) API.
-AudioOutputStream* AudioManagerWin::MakeLowLatencyOutputStream(
- const AudioParameters& params) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- if (params.channels() > kWinMaxChannels)
- return NULL;
-
- if (!CoreAudioUtil::IsSupported()) {
- // Fall back to Windows Wave implementation on Windows XP or lower.
- DVLOG(1) << "Using WaveOut since WASAPI requires at least Vista.";
- return new PCMWaveOutAudioOutputStream(this, params, 2, WAVE_MAPPER);
- }
-
- // TODO(henrika): remove once we properly handle input device selection.
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kEnableWebAudioInput)) {
- if (WASAPIUnifiedStream::HasUnifiedDefaultIO()) {
- DVLOG(1) << "WASAPIUnifiedStream is created.";
- return new WASAPIUnifiedStream(this, params);
- }
- LOG(WARNING) << "Unified audio I/O is not supported.";
- }
-
- return new WASAPIAudioOutputStream(this, params, eConsole);
-}
-
-// Factory for the implementations of AudioInputStream for AUDIO_PCM_LINEAR
-// mode.
-AudioInputStream* AudioManagerWin::MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
- return CreatePCMWaveInAudioInputStream(params, device_id);
-}
-
-// Factory for the implementations of AudioInputStream for
-// AUDIO_PCM_LOW_LATENCY mode.
-AudioInputStream* AudioManagerWin::MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) {
- DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- AudioInputStream* stream = NULL;
- if (!CoreAudioUtil::IsSupported()) {
- // Fall back to Windows Wave implementation on Windows XP or lower.
- DVLOG(1) << "Using WaveIn since WASAPI requires at least Vista.";
- stream = CreatePCMWaveInAudioInputStream(params, device_id);
- } else {
- stream = new WASAPIAudioInputStream(this, params, device_id);
- }
-
- return stream;
-}
-
-AudioInputStream* AudioManagerWin::CreatePCMWaveInAudioInputStream(
- const AudioParameters& params,
- const std::string& device_id) {
- std::string xp_device_id = device_id;
- if (device_id != AudioManagerBase::kDefaultDeviceId &&
- enumeration_type_ == kMMDeviceEnumeration) {
- xp_device_id = media::ConvertToWinXPDeviceId(device_id);
- if (xp_device_id.empty()) {
- DLOG(ERROR) << "Cannot find a waveIn device which matches the device ID "
- << device_id;
- return NULL;
- }
- }
-
- return new PCMWaveInAudioInputStream(this, params, kNumInputBuffers,
- xp_device_id);
-}
-
-/// static
-AudioManager* CreateAudioManager() {
- return new AudioManagerWin();
-}
-
-AudioParameters AudioManagerWin::GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) {
- // If WASAPI isn't supported we'll fallback to WaveOut, which will take care
- // of resampling and bits per sample changes. By setting these equal to the
- // input values, AudioOutputResampler will skip resampling and bit per sample
- // differences (since the input parameters will match the output parameters).
- int sample_rate = input_params.sample_rate();
- int bits_per_sample = input_params.bits_per_sample();
- ChannelLayout channel_layout = input_params.channel_layout();
- if (CoreAudioUtil::IsSupported()) {
- sample_rate = GetAudioHardwareSampleRate();
- bits_per_sample = 16;
- channel_layout = WASAPIAudioOutputStream::HardwareChannelLayout();
- }
-
- // TODO(dalecurtis): This should include hardware bits per channel eventually.
- return AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
- sample_rate, bits_per_sample, GetAudioHardwareBufferSize());
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_manager_win.h b/src/media/audio/win/audio_manager_win.h
deleted file mode 100644
index 4ce2fbc..0000000
--- a/src/media/audio/win/audio_manager_win.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_
-#define MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_
-
-#include <string>
-
-#include "media/audio/audio_manager_base.h"
-
-namespace media {
-
-class AudioDeviceListenerWin;
-
-// Windows implementation of the AudioManager singleton. This class is internal
-// to the audio output and only internal users can call methods not exposed by
-// the AudioManager class.
-class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase {
- public:
- AudioManagerWin();
-
- // Implementation of AudioManager.
- virtual bool HasAudioOutputDevices() OVERRIDE;
- virtual bool HasAudioInputDevices() OVERRIDE;
- virtual string16 GetAudioInputDeviceModel() OVERRIDE;
- virtual bool CanShowAudioInputSettings() OVERRIDE;
- virtual void ShowAudioInputSettings() OVERRIDE;
- virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names)
- OVERRIDE;
-
- // Implementation of AudioManagerBase.
- virtual AudioOutputStream* MakeLinearOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioOutputStream* MakeLowLatencyOutputStream(
- const AudioParameters& params) OVERRIDE;
- virtual AudioInputStream* MakeLinearInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioInputStream* MakeLowLatencyInputStream(
- const AudioParameters& params, const std::string& device_id) OVERRIDE;
- virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters(
- const AudioParameters& input_params) OVERRIDE;
-
- protected:
- virtual ~AudioManagerWin();
-
- private:
- enum EnumerationType {
- kUninitializedEnumeration = 0,
- kMMDeviceEnumeration,
- kWaveEnumeration,
- };
-
- // Allow unit test to modify the utilized enumeration API.
- friend class AudioInputDeviceTest;
-
- EnumerationType enumeration_type_;
- EnumerationType enumeration_type() { return enumeration_type_; }
- void SetEnumerationType(EnumerationType type) {
- enumeration_type_ = type;
- }
-
- // Returns a PCMWaveInAudioInputStream instance or NULL on failure.
- // This method converts MMDevice-style device ID to WaveIn-style device ID if
- // necessary.
- // (Please see device_enumeration_win.h for more info about the two kinds of
- // device IDs.)
- AudioInputStream* CreatePCMWaveInAudioInputStream(
- const AudioParameters& params,
- const std::string& device_id);
-
- // Helper methods for constructing AudioDeviceListenerWin on the audio thread.
- void CreateDeviceListener();
- void DestroyDeviceListener();
-
- // Listen for output device changes.
- scoped_ptr<AudioDeviceListenerWin> output_device_listener_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioManagerWin);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_
diff --git a/src/media/audio/win/audio_output_win_unittest.cc b/src/media/audio/win/audio_output_win_unittest.cc
deleted file mode 100644
index 40b4d81..0000000
--- a/src/media/audio/win/audio_output_win_unittest.cc
+++ /dev/null
@@ -1,693 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <windows.h>
-#include <mmsystem.h>
-
-#include "base/basictypes.h"
-#include "base/base_paths.h"
-#include "base/file_util.h"
-#include "base/memory/aligned_memory.h"
-#include "base/path_service.h"
-#include "base/sync_socket.h"
-#include "base/win/scoped_com_initializer.h"
-#include "base/win/windows_version.h"
-#include "media/base/limits.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/simple_sources.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::DoAll;
-using ::testing::Field;
-using ::testing::Invoke;
-using ::testing::InSequence;
-using ::testing::NiceMock;
-using ::testing::NotNull;
-using ::testing::Return;
-
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-static const wchar_t kAudioFile1_16b_m_16K[]
- = L"media\\test\\data\\sweep02_16b_mono_16KHz.raw";
-
-// This class allows to find out if the callbacks are occurring as
-// expected and if any error has been reported.
-class TestSourceBasic : public AudioOutputStream::AudioSourceCallback {
- public:
- explicit TestSourceBasic()
- : callback_count_(0),
- had_error_(0) {
- }
- // AudioSourceCallback::OnMoreData implementation:
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- ++callback_count_;
- // Touch the channel memory value to make sure memory is good.
- audio_bus->Zero();
- return audio_bus->frames();
- }
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) {
- NOTREACHED();
- return 0;
- }
- // AudioSourceCallback::OnError implementation:
- virtual void OnError(AudioOutputStream* stream, int code) {
- ++had_error_;
- }
- // Returns how many times OnMoreData() has been called.
- int callback_count() const {
- return callback_count_;
- }
- // Returns how many times the OnError callback was called.
- int had_error() const {
- return had_error_;
- }
-
- void set_error(bool error) {
- had_error_ += error ? 1 : 0;
- }
-
- private:
- int callback_count_;
- int had_error_;
-};
-
-const int kMaxNumBuffers = 3;
-// Specializes TestSourceBasic to simulate a source that blocks for some time
-// in the OnMoreData callback.
-class TestSourceLaggy : public TestSourceBasic {
- public:
- TestSourceLaggy(int laggy_after_buffer, int lag_in_ms)
- : laggy_after_buffer_(laggy_after_buffer), lag_in_ms_(lag_in_ms) {
- }
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- // Call the base, which increments the callback_count_.
- TestSourceBasic::OnMoreData(audio_bus, buffers_state);
- if (callback_count() > kMaxNumBuffers) {
- ::Sleep(lag_in_ms_);
- }
- return audio_bus->frames();
- }
- private:
- int laggy_after_buffer_;
- int lag_in_ms_;
-};
-
-class MockAudioSource : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-
- static int ClearData(AudioBus* audio_bus, AudioBuffersState buffers_state) {
- audio_bus->Zero();
- return audio_bus->frames();
- }
-};
-
-// Helper class to memory map an entire file. The mapping is read-only. Don't
-// use for gigabyte-sized files. Attempts to write to this memory generate
-// memory access violations.
-class ReadOnlyMappedFile {
- public:
- explicit ReadOnlyMappedFile(const wchar_t* file_name)
- : fmap_(NULL), start_(NULL), size_(0) {
- HANDLE file = ::CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if (INVALID_HANDLE_VALUE == file)
- return;
- fmap_ = ::CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL);
- ::CloseHandle(file);
- if (!fmap_)
- return;
- start_ = reinterpret_cast<char*>(::MapViewOfFile(fmap_, FILE_MAP_READ,
- 0, 0, 0));
- if (!start_)
- return;
- MEMORY_BASIC_INFORMATION mbi = {0};
- ::VirtualQuery(start_, &mbi, sizeof(mbi));
- size_ = mbi.RegionSize;
- }
- ~ReadOnlyMappedFile() {
- if (start_) {
- ::UnmapViewOfFile(start_);
- ::CloseHandle(fmap_);
- }
- }
- // Returns true if the file was successfully mapped.
- bool is_valid() const {
- return ((start_ > 0) && (size_ > 0));
- }
- // Returns the size in bytes of the mapped memory.
- uint32 size() const {
- return size_;
- }
- // Returns the memory backing the file.
- const void* GetChunkAt(uint32 offset) {
- return &start_[offset];
- }
-
- private:
- HANDLE fmap_;
- char* start_;
- uint32 size_;
-};
-
-// ===========================================================================
-// Validation of AudioManager::AUDIO_PCM_LINEAR
-//
-// NOTE:
-// The tests can fail on the build bots when somebody connects to them via
-// remote-desktop and the rdp client installs an audio device that fails to open
-// at some point, possibly when the connection goes idle.
-
-// Test that can it be created and closed.
-TEST(WinAudioTest, PCMWaveStreamGetAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 256));
- ASSERT_TRUE(NULL != oas);
- oas->Close();
-}
-
-// Test that can it be cannot be created with invalid parameters.
-TEST(WinAudioTest, SanityOnMakeParams) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- AudioParameters::Format fmt = AudioParameters::AUDIO_PCM_LINEAR;
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_UNSUPPORTED, 8000, 16, 256)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 1024 * 1024, 16, 256)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, 80, 256)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_UNSUPPORTED, 8000, 16, 256)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, -8000, 16, 256)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16, -100)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16, 0)));
- EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream(
- AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16,
- media::limits::kMaxSamplesPerPacket + 1)));
-}
-
-// Test that it can be opened and closed.
-TEST(WinAudioTest, PCMWaveStreamOpenAndClose) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 256));
- ASSERT_TRUE(NULL != oas);
- EXPECT_TRUE(oas->Open());
- oas->Close();
-}
-
-// Test that it has a maximum packet size.
-TEST(WinAudioTest, PCMWaveStreamOpenLimit) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 8000, 16, 1024 * 1024 * 1024));
- EXPECT_TRUE(NULL == oas);
- if (oas)
- oas->Close();
-}
-
-// Test potential deadlock situation if the source is slow or blocks for some
-// time. The actual EXPECT_GT are mostly meaningless and the real test is that
-// the test completes in reasonable time.
-TEST(WinAudioTest, PCMWaveSlowSource) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 16000, 16, 256));
- ASSERT_TRUE(NULL != oas);
- TestSourceLaggy test_laggy(2, 90);
- EXPECT_TRUE(oas->Open());
- // The test parameters cause a callback every 32 ms and the source is
- // sleeping for 90 ms, so it is guaranteed that we run out of ready buffers.
- oas->Start(&test_laggy);
- ::Sleep(500);
- EXPECT_GT(test_laggy.callback_count(), 2);
- EXPECT_FALSE(test_laggy.had_error());
- oas->Stop();
- ::Sleep(500);
- oas->Close();
-}
-
-// Test another potential deadlock situation if the thread that calls Start()
-// gets paused. This test is best when run over RDP with audio enabled. See
-// bug 19276 for more details.
-TEST(WinAudioTest, PCMWaveStreamPlaySlowLoop) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, samples_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
-
- EXPECT_TRUE(oas->Open());
- oas->SetVolume(1.0);
-
- for (int ix = 0; ix != 5; ++ix) {
- oas->Start(&source);
- ::Sleep(10);
- oas->Stop();
- }
- oas->Close();
-}
-
-
-// This test produces actual audio for .5 seconds on the default wave
-// device at 44.1K s/sec. Parameters have been chosen carefully so you should
-// not hear pops or noises while the sound is playing.
-TEST(WinAudioTest, PCMWaveStreamPlay200HzTone44Kss) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, samples_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
-
- EXPECT_TRUE(oas->Open());
- oas->SetVolume(1.0);
- oas->Start(&source);
- ::Sleep(500);
- oas->Stop();
- oas->Close();
-}
-
-// This test produces actual audio for for .5 seconds on the default wave
-// device at 22K s/sec. Parameters have been chosen carefully so you should
-// not hear pops or noises while the sound is playing. The audio also should
-// sound with a lower volume than PCMWaveStreamPlay200HzTone44Kss.
-TEST(WinAudioTest, PCMWaveStreamPlay200HzTone22Kss) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 20;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate / 2, 16,
- samples_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate/2);
-
- EXPECT_TRUE(oas->Open());
-
- oas->SetVolume(0.5);
- oas->Start(&source);
- ::Sleep(500);
-
- // Test that the volume is within the set limits.
- double volume = 0.0;
- oas->GetVolume(&volume);
- EXPECT_LT(volume, 0.51);
- EXPECT_GT(volume, 0.49);
- oas->Stop();
- oas->Close();
-}
-
-// Uses a restricted source to play ~2 seconds of audio for about 5 seconds. We
-// try hard to generate situation where the two threads are accessing the
-// object roughly at the same time.
-TEST(WinAudioTest, PushSourceFile16KHz) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- static const int kSampleRate = 16000;
- SineWaveAudioSource source(1, 200.0, kSampleRate);
- // Compute buffer size for 100ms of audio.
- const uint32 kSamples100ms = (kSampleRate / 1000) * 100;
- // Restrict SineWaveAudioSource to 100ms of samples.
- source.CapSamples(kSamples100ms);
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- kSampleRate, 16, kSamples100ms));
- ASSERT_TRUE(NULL != oas);
-
- EXPECT_TRUE(oas->Open());
-
- oas->SetVolume(1.0);
- oas->Start(&source);
-
- // We buffer and play at the same time, buffering happens every ~10ms and the
- // consuming of the buffer happens every ~100ms. We do 100 buffers which
- // effectively wrap around the file more than once.
- for (uint32 ix = 0; ix != 100; ++ix) {
- ::Sleep(10);
- source.Reset();
- }
-
- // Play a little bit more of the file.
- ::Sleep(500);
-
- oas->Stop();
- oas->Close();
-}
-
-// This test is to make sure an AudioOutputStream can be started after it was
-// stopped. You will here two .5 seconds wave signal separated by 0.5 seconds
-// of silence.
-TEST(WinAudioTest, PCMWaveStreamPlayTwice200HzTone44Kss) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, samples_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
- EXPECT_TRUE(oas->Open());
- oas->SetVolume(1.0);
-
- // Play the wave for .5 seconds.
- oas->Start(&source);
- ::Sleep(500);
- oas->Stop();
-
- // Sleep to give silence after stopping the AudioOutputStream.
- ::Sleep(250);
-
- // Start again and play for .5 seconds.
- oas->Start(&source);
- ::Sleep(500);
- oas->Stop();
-
- oas->Close();
-}
-
-// With the low latency mode, WASAPI is utilized by default for Vista and
-// higher and Wave is used for XP and lower. It is possible to utilize a
-// smaller buffer size for WASAPI than for Wave.
-TEST(WinAudioTest, PCMWaveStreamPlay200HzToneLowLatency) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- // The WASAPI API requires a correct COM environment.
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Use 10 ms buffer size for WASAPI and 50 ms buffer size for Wave.
- // Take the existing native sample rate into account.
- int sample_rate = static_cast<int>(media::GetAudioHardwareSampleRate());
- uint32 samples_10_ms = sample_rate / 100;
- int n = 1;
- (base::win::GetVersion() <= base::win::VERSION_XP) ? n = 5 : n = 1;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- CHANNEL_LAYOUT_MONO, sample_rate,
- 16, n * samples_10_ms));
- ASSERT_TRUE(NULL != oas);
-
- SineWaveAudioSource source(1, 200, sample_rate);
-
- bool opened = oas->Open();
- if (!opened) {
- // It was not possible to open this audio device in mono.
- // No point in continuing the test so let's break here.
- LOG(WARNING) << "Mono is not supported. Skipping test.";
- oas->Close();
- return;
- }
- oas->SetVolume(1.0);
-
- // Play the wave for .8 seconds.
- oas->Start(&source);
- ::Sleep(800);
- oas->Stop();
- oas->Close();
-}
-
-// Check that the pending bytes value is correct what the stream starts.
-TEST(WinAudioTest, PCMWaveStreamPendingBytes) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(
- AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- AudioParameters::kAudioCDSampleRate, 16, samples_100_ms));
- ASSERT_TRUE(NULL != oas);
-
- NiceMock<MockAudioSource> source;
- EXPECT_TRUE(oas->Open());
-
- uint32 bytes_100_ms = samples_100_ms * 2;
-
- // Audio output stream has either a double or triple buffer scheme.
- // We expect the amount of pending bytes will reaching up to 2 times of
- // |bytes_100_ms| depending on number of buffers used.
- // From that it would decrease as we are playing the data but not providing
- // new one. And then we will try to provide zero data so the amount of
- // pending bytes will go down and eventually read zero.
- InSequence s;
-
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes, 0)))
- .WillOnce(Invoke(MockAudioSource::ClearData));
- switch (NumberOfWaveOutBuffers()) {
- case 2:
- break; // Calls are the same as at end of 3-buffer scheme.
- case 3:
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- bytes_100_ms)))
- .WillOnce(Invoke(MockAudioSource::ClearData));
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- 2 * bytes_100_ms)))
- .WillOnce(Invoke(MockAudioSource::ClearData));
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- 2 * bytes_100_ms)))
- .Times(AnyNumber())
- .WillRepeatedly(Return(0));
- break;
- default:
- ASSERT_TRUE(false)
- << "Unexpected number of buffers: " << NumberOfWaveOutBuffers();
- }
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes,
- bytes_100_ms)))
- .Times(AnyNumber())
- .WillRepeatedly(Return(0));
- EXPECT_CALL(source, OnMoreData(NotNull(),
- Field(&AudioBuffersState::pending_bytes, 0)))
- .Times(AnyNumber())
- .WillRepeatedly(Return(0));
-
- oas->Start(&source);
- ::Sleep(500);
- oas->Stop();
- oas->Close();
-}
-
-// Simple source that uses a SyncSocket to retrieve the audio data
-// from a potentially remote thread.
-class SyncSocketSource : public AudioOutputStream::AudioSourceCallback {
- public:
- SyncSocketSource(base::SyncSocket* socket, const AudioParameters& params)
- : socket_(socket) {
- // Setup AudioBus wrapping data we'll receive over the sync socket.
- data_size_ = AudioBus::CalculateMemorySize(params);
- data_.reset(static_cast<float*>(
- base::AlignedAlloc(data_size_, AudioBus::kChannelAlignment)));
- audio_bus_ = AudioBus::WrapMemory(params, data_.get());
- }
- ~SyncSocketSource() {}
-
- // AudioSourceCallback::OnMoreData implementation:
- virtual int OnMoreData(AudioBus* audio_bus,
- AudioBuffersState buffers_state) {
- socket_->Send(&buffers_state, sizeof(buffers_state));
- uint32 size = socket_->Receive(data_.get(), data_size_);
- DCHECK_EQ(static_cast<size_t>(size) % sizeof(*audio_bus_->channel(0)), 0U);
- audio_bus_->CopyTo(audio_bus);
- return audio_bus_->frames();
- }
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) {
- NOTREACHED();
- return 0;
- }
- // AudioSourceCallback::OnError implementation:
- virtual void OnError(AudioOutputStream* stream, int code) {
- }
-
- private:
- base::SyncSocket* socket_;
- int data_size_;
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data_;
- scoped_ptr<AudioBus> audio_bus_;
-};
-
-struct SyncThreadContext {
- base::SyncSocket* socket;
- int sample_rate;
- int channels;
- int frames;
- double sine_freq;
- uint32 packet_size_bytes;
-};
-
-// This thread provides the data that the SyncSocketSource above needs
-// using the other end of a SyncSocket. The protocol is as follows:
-//
-// SyncSocketSource ---send 4 bytes ------------> SyncSocketThread
-// <--- audio packet ----------
-//
-DWORD __stdcall SyncSocketThread(void* context) {
- SyncThreadContext& ctx = *(reinterpret_cast<SyncThreadContext*>(context));
-
- // Setup AudioBus wrapping data we'll pass over the sync socket.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data(static_cast<float*>(
- base::AlignedAlloc(ctx.packet_size_bytes, AudioBus::kChannelAlignment)));
- scoped_ptr<AudioBus> audio_bus = AudioBus::WrapMemory(
- ctx.channels, ctx.frames, data.get());
-
- SineWaveAudioSource sine(1, ctx.sine_freq, ctx.sample_rate);
- const int kTwoSecFrames = ctx.sample_rate * 2;
-
- AudioBuffersState buffers_state;
- int times = 0;
- for (int ix = 0; ix < kTwoSecFrames; ix += ctx.frames) {
- if (ctx.socket->Receive(&buffers_state, sizeof(buffers_state)) == 0)
- break;
- if ((times > 0) && (buffers_state.pending_bytes < 1000)) __debugbreak();
- sine.OnMoreData(audio_bus.get(), buffers_state);
- ctx.socket->Send(data.get(), ctx.packet_size_bytes);
- ++times;
- }
-
- return 0;
-}
-
-// Test the basic operation of AudioOutputStream used with a SyncSocket.
-// The emphasis is to verify that it is possible to feed data to the audio
-// layer using a source based on SyncSocket. In a real situation we would
-// go for the low-latency version in combination with SyncSocket, but to keep
-// the test more simple, AUDIO_PCM_LINEAR is utilized instead. The main
-// principle of the test still remains and we avoid the additional complexity
-// related to the two different audio-layers for AUDIO_PCM_LOW_LATENCY.
-// In this test you should hear a continuous 200Hz tone for 2 seconds.
-TEST(WinAudioTest, SyncSocketBasic) {
- scoped_ptr<AudioManager> audio_man(AudioManager::Create());
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output device detected.";
- return;
- }
-
- static const int sample_rate = AudioParameters::kAudioCDSampleRate;
- static const uint32 kSamples20ms = sample_rate / 50;
- AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
- CHANNEL_LAYOUT_MONO, sample_rate, 16, kSamples20ms);
-
-
- AudioOutputStream* oas = audio_man->MakeAudioOutputStream(params);
- ASSERT_TRUE(NULL != oas);
-
- ASSERT_TRUE(oas->Open());
-
- base::SyncSocket sockets[2];
- ASSERT_TRUE(base::SyncSocket::CreatePair(&sockets[0], &sockets[1]));
-
- SyncSocketSource source(&sockets[0], params);
-
- SyncThreadContext thread_context;
- thread_context.sample_rate = params.sample_rate();
- thread_context.sine_freq = 200.0;
- thread_context.packet_size_bytes = AudioBus::CalculateMemorySize(params);
- thread_context.frames = params.frames_per_buffer();
- thread_context.channels = params.channels();
- thread_context.socket = &sockets[1];
-
- HANDLE thread = ::CreateThread(NULL, 0, SyncSocketThread,
- &thread_context, 0, NULL);
-
- oas->Start(&source);
-
- ::WaitForSingleObject(thread, INFINITE);
- ::CloseHandle(thread);
-
- oas->Stop();
- oas->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_unified_win.cc b/src/media/audio/win/audio_unified_win.cc
deleted file mode 100644
index 677f9e0..0000000
--- a/src/media/audio/win/audio_unified_win.cc
+++ /dev/null
@@ -1,569 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/audio_unified_win.h"
-
-#include <Functiondiscoverykeys_devpkey.h>
-
-#include "base/debug/trace_event.h"
-#include "base/time.h"
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/avrt_wrapper_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-
-using base::win::ScopedComPtr;
-using base::win::ScopedCOMInitializer;
-using base::win::ScopedCoMem;
-
-// Time in milliseconds between two successive delay measurements.
-// We save resources by not updating the delay estimates for each capture
-// event (typically 100Hz rate).
-static const size_t kTimeDiffInMillisecondsBetweenDelayMeasurements = 1000;
-
-// Compare two sets of audio parameters and return true if they are equal.
-// Note that bits_per_sample() is excluded from this comparison since Core
-// Audio can deal with most bit depths. As an example, if the native/mixing
-// bit depth is 32 bits (default), opening at 16 or 24 still works fine and
-// the audio engine will do the required conversion for us.
-static bool CompareAudioParameters(const media::AudioParameters& a,
- const media::AudioParameters& b) {
- return (a.format() == b.format() &&
- a.channels() == b.channels() &&
- a.sample_rate() == b.sample_rate() &&
- a.frames_per_buffer() == b.frames_per_buffer());
-}
-
-// Use the acquired IAudioClock interface to derive a time stamp of the audio
-// sample which is currently playing through the speakers.
-static double SpeakerStreamPosInMilliseconds(IAudioClock* clock) {
- UINT64 device_frequency = 0, position = 0;
- if (FAILED(clock->GetFrequency(&device_frequency)) ||
- FAILED(clock->GetPosition(&position, NULL))) {
- return 0.0;
- }
-
- return base::Time::kMillisecondsPerSecond *
- (static_cast<double>(position) / device_frequency);
-}
-
-// Get a time stamp in milliseconds given number of audio frames in |num_frames|
-// using the current sample rate |fs| as scale factor.
-// Example: |num_frames| = 960 and |fs| = 48000 => 20 [ms].
-static double CurrentStreamPosInMilliseconds(UINT64 num_frames, DWORD fs) {
- return base::Time::kMillisecondsPerSecond *
- (static_cast<double>(num_frames) / fs);
-}
-
-// Convert a timestamp in milliseconds to byte units given the audio format
-// in |format|.
-// Example: |ts_milliseconds| equals 10, sample rate is 48000 and frame size
-// is 4 bytes per audio frame => 480 * 4 = 1920 [bytes].
-static int MillisecondsToBytes(double ts_milliseconds,
- const WAVEFORMATPCMEX& format) {
- double seconds = ts_milliseconds / base::Time::kMillisecondsPerSecond;
- return static_cast<int>(seconds * format.Format.nSamplesPerSec *
- format.Format.nBlockAlign + 0.5);
-}
-
-namespace media {
-
-WASAPIUnifiedStream::WASAPIUnifiedStream(AudioManagerWin* manager,
- const AudioParameters& params)
- : creating_thread_id_(base::PlatformThread::CurrentId()),
- manager_(manager),
- share_mode_(CoreAudioUtil::GetShareMode()),
- audio_io_thread_(NULL),
- opened_(false),
- endpoint_render_buffer_size_frames_(0),
- endpoint_capture_buffer_size_frames_(0),
- num_written_frames_(0),
- total_delay_ms_(0.0),
- source_(NULL),
- capture_bus_(AudioBus::Create(params)),
- render_bus_(AudioBus::Create(params)) {
- DCHECK(manager_);
-
- DVLOG_IF(1, !HasUnifiedDefaultIO()) << "Unified audio I/O is not supported.";
- DVLOG_IF(1, share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE)
- << "Core Audio (WASAPI) EXCLUSIVE MODE is enabled.";
-
-#if !defined(NDEBUG)
- // Add log message if input parameters are not identical to the preferred
- // parameters.
- AudioParameters mix_params;
- HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(
- eRender, eConsole, &mix_params);
- DVLOG_IF(1, SUCCEEDED(hr) && !CompareAudioParameters(params, mix_params)) <<
- "Input and preferred parameters are not identical.";
-#endif
-
- // Load the Avrt DLL if not already loaded. Required to support MMCSS.
- bool avrt_init = avrt::Initialize();
- DCHECK(avrt_init) << "Failed to load the avrt.dll";
-
- // Begin with the WAVEFORMATEX structure that specifies the basic format.
- WAVEFORMATEX* format = &format_.Format;
- format->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- format->nChannels = params.channels();
- format->nSamplesPerSec = params.sample_rate();
- format->wBitsPerSample = params.bits_per_sample();
- format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels;
- format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
- format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
-
- // Add the parts which are unique to WAVE_FORMAT_EXTENSIBLE.
- format_.Samples.wValidBitsPerSample = params.bits_per_sample();
- format_.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
- format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-
- // Store size (in different units) of audio packets which we expect to
- // get from the audio endpoint device in each render event.
- packet_size_frames_ = params.GetBytesPerBuffer() / format->nBlockAlign;
- float packet_size_ms = (1000.0 * packet_size_frames_) / params.sample_rate();
- DVLOG(1) << "Number of bytes per audio frame : " << format->nBlockAlign;
- DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_;
- DVLOG(1) << "Number of milliseconds per packet: " << packet_size_ms;
-
- // All events are auto-reset events and non-signaled initially.
-
- // Create the event which the audio engine will signal each time a buffer
- // has been recorded.
- capture_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
-
- // Create the event which will be set in Stop() when straeming shall stop.
- stop_streaming_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
-}
-
-WASAPIUnifiedStream::~WASAPIUnifiedStream() {
-}
-
-bool WASAPIUnifiedStream::Open() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- if (opened_)
- return true;
-
- if (!HasUnifiedDefaultIO()) {
- LOG(ERROR) << "Unified audio I/O is not supported.";
- return false;
- }
-
- // Render side:
-
- ScopedComPtr<IAudioClient> audio_output_client =
- CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
- if (!audio_output_client)
- return false;
-
- if (!CoreAudioUtil::IsFormatSupported(audio_output_client,
- share_mode_,
- &format_)) {
- return false;
- }
-
- HRESULT hr = S_FALSE;
- if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
- hr = CoreAudioUtil::SharedModeInitialize(
- audio_output_client, &format_, NULL,
- &endpoint_render_buffer_size_frames_);
- } else {
- // TODO(henrika): add support for AUDCLNT_SHAREMODE_EXCLUSIVE.
- }
- if (FAILED(hr))
- return false;
-
- ScopedComPtr<IAudioRenderClient> audio_render_client =
- CoreAudioUtil::CreateRenderClient(audio_output_client);
- if (!audio_render_client)
- return false;
-
- // Capture side:
-
- ScopedComPtr<IAudioClient> audio_input_client =
- CoreAudioUtil::CreateDefaultClient(eCapture, eConsole);
- if (!audio_input_client)
- return false;
-
- if (!CoreAudioUtil::IsFormatSupported(audio_input_client,
- share_mode_,
- &format_)) {
- return false;
- }
-
- if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
- // Include valid event handle for event-driven initialization.
- hr = CoreAudioUtil::SharedModeInitialize(
- audio_input_client, &format_, capture_event_.Get(),
- &endpoint_capture_buffer_size_frames_);
- } else {
- // TODO(henrika): add support for AUDCLNT_SHAREMODE_EXCLUSIVE.
- }
- if (FAILED(hr))
- return false;
-
- ScopedComPtr<IAudioCaptureClient> audio_capture_client =
- CoreAudioUtil::CreateCaptureClient(audio_input_client);
- if (!audio_capture_client)
- return false;
-
- // Store all valid COM interfaces.
- audio_output_client_ = audio_output_client;
- audio_render_client_ = audio_render_client;
- audio_input_client_ = audio_input_client;
- audio_capture_client_ = audio_capture_client;
-
- opened_ = true;
- return SUCCEEDED(hr);
-}
-
-void WASAPIUnifiedStream::Start(AudioSourceCallback* callback) {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- CHECK(callback);
- CHECK(opened_);
-
- if (audio_io_thread_.get()) {
- CHECK_EQ(callback, source_);
- return;
- }
-
- source_ = callback;
-
- // Create and start the thread that will capturing and rendering.
- audio_io_thread_.reset(
- new base::DelegateSimpleThread(this, "wasapi_io_thread"));
- audio_io_thread_->Start();
- if (!audio_io_thread_->HasBeenStarted()) {
- DLOG(ERROR) << "Failed to start WASAPI IO thread.";
- return;
- }
-
- // Start input streaming data between the endpoint buffer and the audio
- // engine.
- HRESULT hr = audio_input_client_->Start();
- if (FAILED(hr)) {
- StopAndJoinThread(hr);
- return;
- }
-
- // Reset the counter for number of rendered frames taking into account the
- // fact that we always initialize the render side with silence.
- UINT32 num_queued_frames = 0;
- audio_output_client_->GetCurrentPadding(&num_queued_frames);
- DCHECK_EQ(num_queued_frames, endpoint_render_buffer_size_frames_);
- num_written_frames_ = num_queued_frames;
-
- // Start output streaming data between the endpoint buffer and the audio
- // engine.
- hr = audio_output_client_->Start();
- if (FAILED(hr)) {
- StopAndJoinThread(hr);
- return;
- }
-}
-
-void WASAPIUnifiedStream::Stop() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
- if (!audio_io_thread_.get())
- return;
-
- // Stop input audio streaming.
- HRESULT hr = audio_input_client_->Stop();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to stop input streaming: " << std::hex << hr;
- }
-
- // Stop output audio streaming.
- hr = audio_output_client_->Stop();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to stop output streaming: " << std::hex << hr;
- }
-
- // Wait until the thread completes and perform cleanup.
- SetEvent(stop_streaming_event_.Get());
- audio_io_thread_->Join();
- audio_io_thread_.reset();
-
- // Ensure that we don't quit the main thread loop immediately next
- // time Start() is called.
- ResetEvent(stop_streaming_event_.Get());
-
- // Clear source callback, it'll be set again on the next Start() call.
- source_ = NULL;
-
- // Flush all pending data and reset the audio clock stream position to 0.
- hr = audio_output_client_->Reset();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to reset output streaming: " << std::hex << hr;
- }
-
- audio_input_client_->Reset();
- if (FAILED(hr)) {
- DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
- << "Failed to reset input streaming: " << std::hex << hr;
- }
-
- // Extra safety check to ensure that the buffers are cleared.
- // If the buffers are not cleared correctly, the next call to Start()
- // would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer().
- // TODO(henrika): this check is is only needed for shared-mode streams.
- UINT32 num_queued_frames = 0;
- audio_output_client_->GetCurrentPadding(&num_queued_frames);
- DCHECK_EQ(0u, num_queued_frames);
-}
-
-void WASAPIUnifiedStream::Close() {
- DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
-
- // It is valid to call Close() before calling open or Start().
- // It is also valid to call Close() after Start() has been called.
- Stop();
-
- // Inform the audio manager that we have been closed. This will cause our
- // destruction.
- manager_->ReleaseOutputStream(this);
-}
-
-void WASAPIUnifiedStream::SetVolume(double volume) {
- NOTIMPLEMENTED();
-}
-
-void WASAPIUnifiedStream::GetVolume(double* volume) {
- NOTIMPLEMENTED();
-}
-
-// static
-bool WASAPIUnifiedStream::HasUnifiedDefaultIO() {
- AudioParameters in_params;
- HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eCapture, eConsole,
- &in_params);
- if (FAILED(hr))
- return false;
-
- AudioParameters out_params;
- hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
- &out_params);
- if (FAILED(hr))
- return false;
-
- return CompareAudioParameters(in_params, out_params);
-}
-
-void WASAPIUnifiedStream::Run() {
- ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
-
- // Increase the thread priority.
- audio_io_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio);
-
- // Enable MMCSS to ensure that this thread receives prioritized access to
- // CPU resources.
- // TODO(henrika): investigate if it is possible to include these additional
- // settings in SetThreadPriority() as well.
- DWORD task_index = 0;
- HANDLE mm_task = avrt::AvSetMmThreadCharacteristics(L"Pro Audio",
- &task_index);
- bool mmcss_is_ok =
- (mm_task && avrt::AvSetMmThreadPriority(mm_task, AVRT_PRIORITY_CRITICAL));
- if (!mmcss_is_ok) {
- // Failed to enable MMCSS on this thread. It is not fatal but can lead
- // to reduced QoS at high load.
- DWORD err = GetLastError();
- LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ").";
- }
-
- // The IAudioClock interface enables us to monitor a stream's data
- // rate and the current position in the stream. Allocate it before we
- // start spinning.
- ScopedComPtr<IAudioClock> audio_output_clock;
- HRESULT hr = audio_output_client_->GetService(
- __uuidof(IAudioClock), audio_output_clock.ReceiveVoid());
- LOG_IF(WARNING, FAILED(hr)) << "Failed to create IAudioClock: "
- << std::hex << hr;
-
- // Stores a delay measurement (unit is in bytes). This variable is not
- // updated at each event, but the update frequency is set by a constant
- // called |kTimeDiffInMillisecondsBetweenDelayMeasurements|.
- int total_delay_bytes = 0;
-
- bool streaming = true;
- bool error = false;
- HANDLE wait_array[] = { stop_streaming_event_,
- capture_event_ };
-
- const int bytes_per_sample = format_.Format.wBitsPerSample >> 3;
-
- // Keep streaming audio until the stop, or error, event is signaled.
- // The current implementation uses capture events as driving mechanism since
- // extensive testing has shown that it gives us a more reliable callback
- // sequence compared with a scheme where both capture and render events are
- // utilized.
- while (streaming && !error) {
- // Wait for a close-down event, or a new capture event.
- DWORD wait_result = WaitForMultipleObjects(arraysize(wait_array),
- wait_array,
- FALSE,
- INFINITE);
- switch (wait_result) {
- case WAIT_OBJECT_0 + 0:
- // |stop_streaming_event_| has been set.
- streaming = false;
- break;
- case WAIT_OBJECT_0 + 1:
- // |capture_event_| has been set
- {
- TRACE_EVENT0("audio", "WASAPIUnifiedStream::Run");
-
- // --- Capture ---
-
- BYTE* data_ptr = NULL;
- UINT32 num_captured_frames = 0;
- DWORD flags = 0;
- UINT64 device_position = 0;
- UINT64 capture_time_stamp = 0;
-
- base::TimeTicks now_tick = base::TimeTicks::HighResNow();
-
- // Retrieve the amount of data in the capture endpoint buffer.
- // |endpoint_capture_time_stamp| is the value of the performance
- // counter at the time that the audio endpoint device recorded
- // the device position of the first audio frame in the data packet.
- hr = audio_capture_client_->GetBuffer(&data_ptr,
- &num_captured_frames,
- &flags,
- &device_position,
- &capture_time_stamp);
- if (FAILED(hr)) {
- DLOG(ERROR) << "Failed to get data from the capture buffer";
- continue;
- }
-
- if (num_captured_frames != 0) {
- if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
- // Clear out the capture buffer since silence is reported.
- capture_bus_->Zero();
- } else {
- // Store captured data in an audio bus after de-interleaving
- // the data to match the audio bus structure.
- capture_bus_->FromInterleaved(
- data_ptr, num_captured_frames, bytes_per_sample);
- }
- }
-
- hr = audio_capture_client_->ReleaseBuffer(num_captured_frames);
- DLOG_IF(ERROR, FAILED(hr)) << "Failed to release capture buffer";
-
- // Save resource by not asking for new delay estimates each time.
- // These estimates are fairly stable and it is perfectly safe to only
- // sample at a rate of ~1Hz.
- // TODO(henrika): it might be possible to use a fixed delay instead.
- if ((now_tick - last_delay_sample_time_).InMilliseconds() >
- kTimeDiffInMillisecondsBetweenDelayMeasurements) {
- // Calculate the estimated capture delay, i.e., the latency between
- // the recording time and the time we when we are notified about
- // the recorded data. Note that the capture time stamp is given in
- // 100-nanosecond (0.1 microseconds) units.
- base::TimeDelta diff = now_tick -
- base::TimeTicks::FromInternalValue(0.1 * capture_time_stamp);
- const double capture_delay_ms = diff.InMillisecondsF();
-
- // Calculate the estimated render delay, i.e., the time difference
- // between the time when data is added to the endpoint buffer and
- // when the data is played out on the actual speaker.
- const double stream_pos = CurrentStreamPosInMilliseconds(
- num_written_frames_ + packet_size_frames_,
- format_.Format.nSamplesPerSec);
- const double speaker_pos =
- SpeakerStreamPosInMilliseconds(audio_output_clock);
- const double render_delay_ms = stream_pos - speaker_pos;
-
- // Derive the total delay, i.e., the sum of the input and output
- // delays. Also convert the value into byte units.
- total_delay_ms_ = capture_delay_ms + render_delay_ms;
- last_delay_sample_time_ = now_tick;
- DVLOG(3) << "total_delay_ms : " << total_delay_ms_;
- total_delay_bytes = MillisecondsToBytes(total_delay_ms_, format_);
- }
-
- // Prepare for rendering by calling OnMoreIOData().
- int frames_filled = source_->OnMoreIOData(
- capture_bus_.get(),
- render_bus_.get(),
- AudioBuffersState(0, total_delay_bytes));
- DCHECK_EQ(frames_filled, render_bus_->frames());
-
- // --- Render ---
-
- // Keep track of number of rendered frames since we need it for
- // our delay calculations.
- num_written_frames_ += frames_filled;
-
- // Derive the the amount of available space in the endpoint buffer.
- // Avoid render attempt if there is no room for a captured packet.
- UINT32 num_queued_frames = 0;
- audio_output_client_->GetCurrentPadding(&num_queued_frames);
- if (endpoint_render_buffer_size_frames_ - num_queued_frames <
- packet_size_frames_)
- continue;
-
- // Grab all available space in the rendering endpoint buffer
- // into which the client can write a data packet.
- uint8* audio_data = NULL;
- hr = audio_render_client_->GetBuffer(packet_size_frames_,
- &audio_data);
- if (FAILED(hr)) {
- DLOG(ERROR) << "Failed to access render buffer";
- continue;
- }
-
- // Convert the audio bus content to interleaved integer data using
- // |audio_data| as destination.
- render_bus_->ToInterleaved(
- packet_size_frames_, bytes_per_sample, audio_data);
-
- // Release the buffer space acquired in the GetBuffer() call.
- audio_render_client_->ReleaseBuffer(packet_size_frames_, 0);
- DLOG_IF(ERROR, FAILED(hr)) << "Failed to release render buffer";
- }
- break;
- default:
- error = true;
- break;
- }
- }
-
- if (streaming && error) {
- // Stop audio streaming since something has gone wrong in our main thread
- // loop. Note that, we are still in a "started" state, hence a Stop() call
- // is required to join the thread properly.
- audio_input_client_->Stop();
- audio_output_client_->Stop();
- PLOG(ERROR) << "WASAPI streaming failed.";
- }
-
- // Disable MMCSS.
- if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
- PLOG(WARNING) << "Failed to disable MMCSS";
- }
-}
-
-void WASAPIUnifiedStream::HandleError(HRESULT err) {
- CHECK((started() && GetCurrentThreadId() == audio_io_thread_->tid()) ||
- (!started() && GetCurrentThreadId() == creating_thread_id_));
- NOTREACHED() << "Error code: " << std::hex << err;
- if (source_)
- source_->OnError(this, static_cast<int>(err));
-}
-
-void WASAPIUnifiedStream::StopAndJoinThread(HRESULT err) {
- CHECK(GetCurrentThreadId() == creating_thread_id_);
- DCHECK(audio_io_thread_.get());
- SetEvent(stop_streaming_event_.Get());
- audio_io_thread_->Join();
- audio_io_thread_.reset();
- HandleError(err);
-}
-
-} // namespace media
diff --git a/src/media/audio/win/audio_unified_win.h b/src/media/audio/win/audio_unified_win.h
deleted file mode 100644
index 0e8e829..0000000
--- a/src/media/audio/win/audio_unified_win.h
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_AUDIO_UNIFIED_WIN_H_
-#define MEDIA_AUDIO_WIN_AUDIO_UNIFIED_WIN_H_
-
-#include <Audioclient.h>
-#include <MMDeviceAPI.h>
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/gtest_prod_util.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/simple_thread.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_comptr.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioManagerWin;
-
-// Implementation of AudioOutputStream for Windows using the Core Audio API
-// where both capturing and rendering takes place on the same thread to enable
-// audio I/O.
-//
-// The user should also ensure that audio I/O is supported by calling
-// HasUnifiedDefaultIO().
-//
-// Implementation notes:
-//
-// - Certain conditions must be fulfilled to support audio I/O:
-// o Both capture and render side must use the same sample rate.
-// o Both capture and render side must use the same channel count.
-// o Both capture and render side must use the same channel configuration.
-// o See HasUnifiedDefaultIO() for more details.
-//
-// TODO(henrika):
-//
-// - Add support for exclusive mode.
-// - Add multi-channel support.
-// - Add support for non-matching sample rates.
-//
-class MEDIA_EXPORT WASAPIUnifiedStream
- : public AudioOutputStream,
- public base::DelegateSimpleThread::Delegate {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object.
- WASAPIUnifiedStream(AudioManagerWin* manager,
- const AudioParameters& params);
-
- // The dtor is typically called by the AudioManager only and it is usually
- // triggered by calling AudioOutputStream::Close().
- virtual ~WASAPIUnifiedStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioSourceCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual void GetVolume(double* volume) OVERRIDE;
-
- // Returns true if all conditions to support audio IO are fulfilled.
- // Input and output sides of the Audio Engine must use the same native
- // device period (requires e.g. identical sample rates) and have the same
- // channel count.
- static bool HasUnifiedDefaultIO();
-
- bool started() const {
- return audio_io_thread_.get() != NULL;
- }
-
- private:
- // DelegateSimpleThread::Delegate implementation.
- virtual void Run() OVERRIDE;
-
- // Issues the OnError() callback to the |source_|.
- void HandleError(HRESULT err);
-
- // Stops and joins the audio thread in case of an error.
- void StopAndJoinThread(HRESULT err);
-
- // Converts unique endpoint ID to user-friendly device name.
- std::string GetDeviceName(LPCWSTR device_id) const;
-
- // Returns the number of channels the audio engine uses for its internal
- // processing/mixing of shared-mode streams for the default endpoint device.
- int endpoint_channel_count() { return format_.Format.nChannels; }
-
- // Contains the thread ID of the creating thread.
- base::PlatformThreadId creating_thread_id_;
-
- // Our creator, the audio manager needs to be notified when we close.
- AudioManagerWin* manager_;
-
- // The sharing mode for the streams.
- // Valid values are AUDCLNT_SHAREMODE_SHARED and AUDCLNT_SHAREMODE_EXCLUSIVE
- // where AUDCLNT_SHAREMODE_SHARED is the default.
- AUDCLNT_SHAREMODE share_mode_;
-
- // Rendering and capturing is driven by this thread (no message loop).
- // All OnMoreIOData() callbacks will be called from this thread.
- scoped_ptr<base::DelegateSimpleThread> audio_io_thread_;
-
- // Contains the desired audio format which is set up at construction.
- // Extended PCM waveform format structure based on WAVEFORMATEXTENSIBLE.
- // Use this for multiple channel and hi-resolution PCM data.
- WAVEFORMATPCMEX format_;
-
- // True when successfully opened.
- bool opened_;
-
- // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM).
- size_t frame_size_;
-
- // Size in audio frames of each audio packet where an audio packet
- // is defined as the block of data which the source is expected to deliver
- // in each OnMoreIOData() callback.
- size_t packet_size_frames_;
-
- // Length of the audio endpoint buffer.
- size_t endpoint_render_buffer_size_frames_;
- size_t endpoint_capture_buffer_size_frames_;
-
- // Counts the number of audio frames written to the endpoint buffer.
- uint64 num_written_frames_;
-
- // Time stamp for last delay measurement.
- base::TimeTicks last_delay_sample_time_;
-
- // Contains the total (sum of render and capture) delay in milliseconds.
- double total_delay_ms_;
-
- // Pointer to the client that will deliver audio samples to be played out.
- AudioSourceCallback* source_;
-
- // IMMDevice interfaces which represents audio endpoint devices.
- base::win::ScopedComPtr<IMMDevice> endpoint_render_device_;
- base::win::ScopedComPtr<IMMDevice> endpoint_capture_device_;
-
- // IAudioClient interfaces which enables a client to create and initialize
- // an audio stream between an audio application and the audio engine.
- base::win::ScopedComPtr<IAudioClient> audio_output_client_;
- base::win::ScopedComPtr<IAudioClient> audio_input_client_;
-
- // IAudioRenderClient interfaces enables a client to write output
- // data to a rendering endpoint buffer.
- base::win::ScopedComPtr<IAudioRenderClient> audio_render_client_;
-
- // IAudioCaptureClient interfaces enables a client to read input
- // data from a capturing endpoint buffer.
- base::win::ScopedComPtr<IAudioCaptureClient> audio_capture_client_;
-
- // The audio engine will signal this event each time a buffer has been
- // recorded.
- base::win::ScopedHandle capture_event_;
-
- // This event will be signaled when streaming shall stop.
- base::win::ScopedHandle stop_streaming_event_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreIOData().
- scoped_ptr<AudioBus> render_bus_;
-
- // Container for sending data to AudioSourceCallback::OnMoreIOData().
- scoped_ptr<AudioBus> capture_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(WASAPIUnifiedStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_AUDIO_UNIFIED_WIN_H_
diff --git a/src/media/audio/win/audio_unified_win_unittest.cc b/src/media/audio/win/audio_unified_win_unittest.cc
deleted file mode 100644
index 4d5f41b..0000000
--- a/src/media/audio/win/audio_unified_win_unittest.cc
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/command_line.h"
-#include "base/file_util.h"
-#include "base/message_loop.h"
-#include "base/path_service.h"
-#include "base/test/test_timeouts.h"
-#include "base/time.h"
-#include "base/win/scoped_com_initializer.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_manager.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_unified_win.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "media/base/media_switches.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AtLeast;
-using ::testing::Between;
-using ::testing::DoAll;
-using ::testing::NotNull;
-using ::testing::Return;
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-static const size_t kMaxDeltaSamples = 1000;
-static const char* kDeltaTimeMsFileName = "unified_delta_times_ms.txt";
-
-// Verify that the delay estimate in the OnMoreIOData() callback is larger
-// than an expected minumum value.
-MATCHER_P(DelayGreaterThan, value, "") {
- return (arg.hardware_delay_bytes > value.hardware_delay_bytes);
-}
-
-// Used to terminate a loop from a different thread than the loop belongs to.
-// |loop| should be a MessageLoopProxy.
-ACTION_P(QuitLoop, loop) {
- loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-class MockUnifiedSourceCallback
- : public AudioOutputStream::AudioSourceCallback {
- public:
- MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
- AudioBuffersState buffers_state));
- MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state));
- MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
-};
-
-// AudioOutputStream::AudioSourceCallback implementation which enables audio
-// play-through. It also creates a text file that contains times between two
-// successive callbacks. Units are in milliseconds. This file can be used for
-// off-line analysis of the callback sequence.
-class UnifiedSourceCallback : public AudioOutputStream::AudioSourceCallback {
- public:
- explicit UnifiedSourceCallback()
- : previous_call_time_(base::Time::Now()),
- text_file_(NULL),
- elements_to_write_(0) {
- delta_times_.reset(new int[kMaxDeltaSamples]);
- }
-
- virtual ~UnifiedSourceCallback() {
- FilePath file_name;
- EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name));
- file_name = file_name.AppendASCII(kDeltaTimeMsFileName);
-
- EXPECT_TRUE(!text_file_);
- text_file_ = file_util::OpenFile(file_name, "wt");
- DLOG_IF(ERROR, !text_file_) << "Failed to open log file.";
- LOG(INFO) << ">> Output file " << file_name.value() << " has been created.";
-
- // Write the array which contains delta times to a text file.
- size_t elements_written = 0;
- while (elements_written < elements_to_write_) {
- fprintf(text_file_, "%d\n", delta_times_[elements_written]);
- ++elements_written;
- }
- file_util::CloseFile(text_file_);
- }
-
- virtual int OnMoreData(AudioBus* dest,
- AudioBuffersState buffers_state) {
- NOTREACHED();
- return 0;
- };
-
- virtual int OnMoreIOData(AudioBus* source,
- AudioBus* dest,
- AudioBuffersState buffers_state) {
- // Store time between this callback and the previous callback.
- int diff = (base::Time::Now() - previous_call_time_).InMilliseconds();
- previous_call_time_ = base::Time::Now();
- if (elements_to_write_ < kMaxDeltaSamples) {
- delta_times_[elements_to_write_] = diff;
- ++elements_to_write_;
- }
-
- // Play out the recorded audio samples in loop back.
- source->CopyTo(dest);
- return source->frames();
- };
-
- virtual void OnError(AudioOutputStream* stream, int code) {
- NOTREACHED();
- }
-
- private:
- base::Time previous_call_time_;
- scoped_array<int> delta_times_;
- FILE* text_file_;
- size_t elements_to_write_;
-};
-
-// Convenience method which ensures that we fulfill all required conditions
-// to run unified audio tests on Windows.
-static bool CanRunUnifiedAudioTests(AudioManager* audio_man) {
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (!cmd_line->HasSwitch(switches::kEnableWebAudioInput)) {
- DVLOG(1) << "--enable-webaudio-input must be defined to run this test.";
- return false;
- }
-
- if (!CoreAudioUtil::IsSupported()) {
- LOG(WARNING) << "This tests requires Windows Vista or higher.";
- return false;
- }
-
- if (!audio_man->HasAudioOutputDevices()) {
- LOG(WARNING) << "No output devices detected.";
- return false;
- }
-
- if (!audio_man->HasAudioInputDevices()) {
- LOG(WARNING) << "No input devices detected.";
- return false;
- }
-
- if (!WASAPIUnifiedStream::HasUnifiedDefaultIO()) {
- LOG(WARNING) << "Audio IO is not supported.";
- return false;
- }
-
- return true;
-}
-
-// Convenience class which simplifies creation of a unified AudioOutputStream
-// object.
-class AudioUnifiedStreamWrapper {
- public:
- explicit AudioUnifiedStreamWrapper(AudioManager* audio_manager)
- : com_init_(ScopedCOMInitializer::kMTA),
- audio_man_(audio_manager) {
- // We open up both both sides (input and output) using the preferred
- // set of audio parameters. These parameters corresponds to the mix format
- // that the audio engine uses internally for processing of shared-mode
- // output streams.
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
- eRender, eConsole, ¶ms_)));
- }
-
- ~AudioUnifiedStreamWrapper() {}
-
- // Creates AudioOutputStream object using default parameters.
- WASAPIUnifiedStream* Create() {
- return static_cast<WASAPIUnifiedStream*> (CreateOutputStream());
- }
-
- AudioParameters::Format format() const { return params_.format(); }
- int channels() const { return params_.channels(); }
- int bits_per_sample() const { return params_.bits_per_sample(); }
- int sample_rate() const { return params_.sample_rate(); }
- int frames_per_buffer() const { return params_.frames_per_buffer(); }
- int bytes_per_buffer() const { return params_.GetBytesPerBuffer(); }
-
- private:
- AudioOutputStream* CreateOutputStream() {
- AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params_);
- EXPECT_TRUE(aos);
- return aos;
- }
-
- ScopedCOMInitializer com_init_;
- AudioManager* audio_man_;
- AudioParameters params_;
-};
-
-// Convenience method which creates a default WASAPIUnifiedStream object.
-static WASAPIUnifiedStream* CreateDefaultUnifiedStream(
- AudioManager* audio_manager) {
- AudioUnifiedStreamWrapper aosw(audio_manager);
- return aosw.Create();
-}
-
-// Test Open(), Close() calling sequence.
-TEST(WASAPIUnifiedStreamTest, OpenAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunUnifiedAudioTests(audio_manager.get()))
- return;
-
- WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get());
- EXPECT_TRUE(wus->Open());
- wus->Close();
-}
-
-// Test Open(), Start(), Close() calling sequence.
-TEST(WASAPIUnifiedStreamTest, OpenStartAndClose) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunUnifiedAudioTests(audio_manager.get()))
- return;
-
- MockUnifiedSourceCallback source;
- AudioUnifiedStreamWrapper ausw(audio_manager.get());
- WASAPIUnifiedStream* wus = ausw.Create();
-
- EXPECT_TRUE(wus->Open());
- EXPECT_CALL(source, OnError(wus, _))
- .Times(0);
- EXPECT_CALL(source, OnMoreIOData(NotNull(), NotNull(), _))
- .Times(Between(0, 1))
- .WillOnce(Return(ausw.frames_per_buffer()));
- wus->Start(&source);
- wus->Close();
-}
-
-// Verify that IO callbacks starts as they should.
-TEST(WASAPIUnifiedStreamTest, StartLoopbackAudio) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunUnifiedAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- MockUnifiedSourceCallback source;
- AudioUnifiedStreamWrapper ausw(audio_manager.get());
- WASAPIUnifiedStream* wus = ausw.Create();
-
- // Set up expected minimum delay estimation where we use a minium delay
- // which is equal to the sum of render and capture sizes. We can never
- // reach a delay lower than this value.
- AudioBuffersState min_total_audio_delay(0, 2 * ausw.bytes_per_buffer());
-
- EXPECT_TRUE(wus->Open());
- EXPECT_CALL(source, OnError(wus, _))
- .Times(0);
- EXPECT_CALL(source, OnMoreIOData(
- NotNull(), NotNull(), DelayGreaterThan(min_total_audio_delay)))
- .Times(AtLeast(2))
- .WillOnce(Return(ausw.frames_per_buffer()))
- .WillOnce(DoAll(
- QuitLoop(loop.message_loop_proxy()),
- Return(ausw.frames_per_buffer())));
- wus->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- TestTimeouts::action_timeout());
- loop.Run();
- wus->Stop();
- wus->Close();
-}
-
-// Perform a real-time test in loopback where the recorded audio is echoed
-// back to the speaker. This test allows the user to verify that the audio
-// sounds OK. A text file with name |kDeltaTimeMsFileName| is also generated.
-TEST(WASAPIUnifiedStreamTest, DISABLED_RealTimePlayThrough) {
- scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
- if (!CanRunUnifiedAudioTests(audio_manager.get()))
- return;
-
- MessageLoopForUI loop;
- UnifiedSourceCallback source;
- WASAPIUnifiedStream* wus = CreateDefaultUnifiedStream(audio_manager.get());
-
- EXPECT_TRUE(wus->Open());
- wus->Start(&source);
- loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
- base::TimeDelta::FromMilliseconds(10000));
- loop.Run();
- wus->Close();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/avrt_wrapper_win.cc b/src/media/audio/win/avrt_wrapper_win.cc
deleted file mode 100644
index c9f1599..0000000
--- a/src/media/audio/win/avrt_wrapper_win.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/audio/win/avrt_wrapper_win.h"
-
-#include "base/logging.h"
-
-namespace avrt {
-
-// Function pointers
-typedef BOOL (WINAPI *AvRevertMmThreadCharacteristicsFn)(HANDLE);
-typedef HANDLE (WINAPI *AvSetMmThreadCharacteristicsFn)(LPCWSTR, LPDWORD);
-typedef BOOL (WINAPI *AvSetMmThreadPriorityFn)(HANDLE, AVRT_PRIORITY);
-
-HMODULE g_avrt = NULL;
-AvRevertMmThreadCharacteristicsFn g_revert_mm_thread_characteristics = NULL;
-AvSetMmThreadCharacteristicsFn g_set_mm_thread_characteristics = NULL;
-AvSetMmThreadPriorityFn g_set_mm_thread_priority = NULL;
-
-bool Initialize() {
- if (!g_set_mm_thread_priority) {
- // The avrt.dll is available on Windows Vista and later.
- wchar_t path[MAX_PATH] = {0};
- ExpandEnvironmentStrings(L"%WINDIR%\\system32\\avrt.dll", path,
- arraysize(path));
- g_avrt = LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
- if (!g_avrt)
- return false;
-
- g_revert_mm_thread_characteristics =
- reinterpret_cast<AvRevertMmThreadCharacteristicsFn>(
- GetProcAddress(g_avrt, "AvRevertMmThreadCharacteristics"));
- g_set_mm_thread_characteristics =
- reinterpret_cast<AvSetMmThreadCharacteristicsFn>(
- GetProcAddress(g_avrt, "AvSetMmThreadCharacteristicsW"));
- g_set_mm_thread_priority = reinterpret_cast<AvSetMmThreadPriorityFn>(
- GetProcAddress(g_avrt, "AvSetMmThreadPriority"));
- }
-
- return (g_avrt && g_revert_mm_thread_characteristics &&
- g_set_mm_thread_characteristics && g_set_mm_thread_priority);
-}
-
-bool AvRevertMmThreadCharacteristics(HANDLE avrt_handle) {
- DCHECK(g_revert_mm_thread_characteristics);
- return (g_revert_mm_thread_characteristics &&
- g_revert_mm_thread_characteristics(avrt_handle));
-}
-
-HANDLE AvSetMmThreadCharacteristics(const wchar_t* task_name,
- DWORD* task_index) {
- DCHECK(g_set_mm_thread_characteristics);
- return (g_set_mm_thread_characteristics ?
- g_set_mm_thread_characteristics(task_name, task_index) : NULL);
-}
-
-bool AvSetMmThreadPriority(HANDLE avrt_handle, AVRT_PRIORITY priority) {
- DCHECK(g_set_mm_thread_priority);
- return (g_set_mm_thread_priority &&
- g_set_mm_thread_priority(avrt_handle, priority));
-}
-
-} // namespace avrt
diff --git a/src/media/audio/win/avrt_wrapper_win.h b/src/media/audio/win/avrt_wrapper_win.h
deleted file mode 100644
index 8127b6b..0000000
--- a/src/media/audio/win/avrt_wrapper_win.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// The avrt namespace encapsulates the details needed to support MMCSS.
-//
-// The Multimedia Class Scheduler service (MMCSS) enables multimedia
-// applications to ensure that their time-sensitive processing receives
-// prioritized access to CPU resources. This service enables multimedia
-// applications to utilize as much of the CPU as possible without denying
-// CPU resources to lower-priority applications.
-// MMCSS requires Windows Vista or higher and that the Avrt DLL is loaded.
-//
-// TODO(henrika): refactor and merge into existing thread implementation
-// for Windows to ensure that MMCSS can be enabled for all threads.
-//
-#ifndef MEDIA_AUDIO_WIN_AVRT_WRAPPER_WIN_H_
-#define MEDIA_AUDIO_WIN_AVRT_WRAPPER_WIN_H_
-
-#include <windows.h>
-#include <avrt.h>
-
-#include "base/basictypes.h"
-
-namespace avrt {
-
-// Loads the Avrt.dll which is available on Windows Vista and later.
-bool Initialize();
-
-// Function wrappers for the underlying MMCSS functions.
-bool AvRevertMmThreadCharacteristics(HANDLE avrt_handle);
-HANDLE AvSetMmThreadCharacteristics(const wchar_t* task_name,
- DWORD* task_index);
-bool AvSetMmThreadPriority(HANDLE avrt_handle, AVRT_PRIORITY priority);
-
-} // namespace avrt
-
-#endif // MEDIA_AUDIO_WIN_AVRT_WRAPPER_WIN_H_
-
diff --git a/src/media/audio/win/core_audio_util_win.cc b/src/media/audio/win/core_audio_util_win.cc
deleted file mode 100644
index b501cbf..0000000
--- a/src/media/audio/win/core_audio_util_win.cc
+++ /dev/null
@@ -1,580 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/core_audio_util_win.h"
-
-#include <Audioclient.h>
-#include <Functiondiscoverykeys_devpkey.h>
-
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/stringprintf.h"
-#include "base/utf_string_conversions.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/windows_version.h"
-#include "media/base/media_switches.h"
-
-using base::win::ScopedCoMem;
-using base::win::ScopedHandle;
-
-namespace media {
-
-typedef uint32 ChannelConfig;
-
-// Converts Microsoft's channel configuration to ChannelLayout.
-// This mapping is not perfect but the best we can do given the current
-// ChannelLayout enumerator and the Windows-specific speaker configurations
-// defined in ksmedia.h. Don't assume that the channel ordering in
-// ChannelLayout is exactly the same as the Windows specific configuration.
-// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to
-// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R
-// speakers are different in these two definitions.
-static ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) {
- switch (config) {
- case KSAUDIO_SPEAKER_DIRECTOUT:
- DVLOG(2) << "KSAUDIO_SPEAKER_DIRECTOUT=>CHANNEL_LAYOUT_NONE";
- return CHANNEL_LAYOUT_NONE;
- case KSAUDIO_SPEAKER_MONO:
- DVLOG(2) << "KSAUDIO_SPEAKER_MONO=>CHANNEL_LAYOUT_MONO";
- return CHANNEL_LAYOUT_MONO;
- case KSAUDIO_SPEAKER_STEREO:
- DVLOG(2) << "KSAUDIO_SPEAKER_STEREO=>CHANNEL_LAYOUT_STEREO";
- return CHANNEL_LAYOUT_STEREO;
- case KSAUDIO_SPEAKER_QUAD:
- DVLOG(2) << "KSAUDIO_SPEAKER_QUAD=>CHANNEL_LAYOUT_QUAD";
- return CHANNEL_LAYOUT_QUAD;
- case KSAUDIO_SPEAKER_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_SURROUND=>CHANNEL_LAYOUT_4_0";
- return CHANNEL_LAYOUT_4_0;
- case KSAUDIO_SPEAKER_5POINT1:
- DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1=>CHANNEL_LAYOUT_5_1_BACK";
- return CHANNEL_LAYOUT_5_1_BACK;
- case KSAUDIO_SPEAKER_5POINT1_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1_SURROUND=>CHANNEL_LAYOUT_5_1";
- return CHANNEL_LAYOUT_5_1;
- case KSAUDIO_SPEAKER_7POINT1:
- DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1=>CHANNEL_LAYOUT_7_1_WIDE";
- return CHANNEL_LAYOUT_7_1_WIDE;
- case KSAUDIO_SPEAKER_7POINT1_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1_SURROUND=>CHANNEL_LAYOUT_7_1";
- return CHANNEL_LAYOUT_7_1;
- default:
- DVLOG(2) << "Unsupported channel layout: " << config;
- return CHANNEL_LAYOUT_UNSUPPORTED;
- }
-}
-
-// Scoped PROPVARIANT class for automatically freeing a COM PROPVARIANT
-// structure at the end of a scope.
-class ScopedPropertyVariant {
- public:
- ScopedPropertyVariant() {
- PropVariantInit(&propvar_);
- }
- ~ScopedPropertyVariant() {
- PropVariantClear(&propvar_);
- }
-
- // Retrieves the pointer address.
- // Used to receive a PROPVARIANT as an out argument (and take ownership).
- PROPVARIANT* Receive() {
- DCHECK_EQ(propvar_.vt, VT_EMPTY);
- return &propvar_;
- }
-
- VARTYPE type() const {
- return propvar_.vt;
- }
-
- LPWSTR as_wide_string() const {
- DCHECK_EQ(type(), VT_LPWSTR);
- return propvar_.pwszVal;
- }
-
- private:
- PROPVARIANT propvar_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedPropertyVariant);
-};
-
-bool CoreAudioUtil::IsSupported() {
- // Microsoft does not plan to make the Core Audio APIs available for use
- // with earlier versions of Windows, including Microsoft Windows Server 2003,
- // Windows XP, Windows Millennium Edition, Windows 2000, and Windows 98.
- return (base::win::GetVersion() >= base::win::VERSION_VISTA);
-}
-
-base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) {
- // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond.
- return base::TimeDelta::FromMicroseconds(0.1 * time + 0.5);
-}
-
-AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() {
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
- return AUDCLNT_SHAREMODE_EXCLUSIVE;
- return AUDCLNT_SHAREMODE_SHARED;
-}
-
-int CoreAudioUtil::NumberOfActiveDevices(EDataFlow data_flow) {
- DCHECK(CoreAudioUtil::IsSupported());
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator)
- return 0;
-
- // Generate a collection of active (present and not disabled) audio endpoint
- // devices for the specified data-flow direction.
- // This method will succeed even if all devices are disabled.
- ScopedComPtr<IMMDeviceCollection> collection;
- HRESULT hr = device_enumerator->EnumAudioEndpoints(data_flow,
- DEVICE_STATE_ACTIVE,
- collection.Receive());
- if (FAILED(hr)) {
- LOG(ERROR) << "IMMDeviceCollection::EnumAudioEndpoints: " << std::hex << hr;
- return 0;
- }
-
- // Retrieve the number of active audio devices for the specified direction
- UINT number_of_active_devices = 0;
- collection->GetCount(&number_of_active_devices);
- DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ")
- << "number of devices: " << number_of_active_devices;
- return static_cast<int>(number_of_active_devices);
-}
-
-ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- device_enumerator.ReceiveVoid());
- // CO_E_NOTINITIALIZED is the most likely reason for failure and if that
- // happens we might as well die here.
- CHECK(SUCCEEDED(hr));
- return device_enumerator;
-}
-
-ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow,
- ERole role) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDevice> endpoint_device;
-
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator)
- return endpoint_device;
-
- // Retrieve the default audio endpoint for the specified data-flow
- // direction and role.
- HRESULT hr = device_enumerator->GetDefaultAudioEndpoint(
- data_flow, role, endpoint_device.Receive());
-
- if (FAILED(hr)) {
- DVLOG(1) << "IMMDeviceEnumerator::GetDefaultAudioEndpoint: "
- << std::hex << hr;
- return endpoint_device;
- }
-
- // Verify that the audio endpoint device is active, i.e., that the audio
- // adapter that connects to the endpoint device is present and enabled.
- DWORD state = DEVICE_STATE_DISABLED;
- hr = endpoint_device->GetState(&state);
- if (SUCCEEDED(hr)) {
- if (!(state & DEVICE_STATE_ACTIVE)) {
- DVLOG(1) << "Selected endpoint device is not active";
- endpoint_device.Release();
- }
- }
- return endpoint_device;
-}
-
-ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDevice(
- const std::string& device_id) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDevice> endpoint_device;
-
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator)
- return endpoint_device;
-
- // Retrieve an audio device specified by an endpoint device-identification
- // string.
- HRESULT hr = device_enumerator->GetDevice(UTF8ToUTF16(device_id).c_str(),
- endpoint_device.Receive());
- DVLOG_IF(1, FAILED(hr)) << "IMMDeviceEnumerator::GetDevice: "
- << std::hex << hr;
- return endpoint_device;
-}
-
-HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- // Retrieve unique name of endpoint device.
- // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
- AudioDeviceName device_name;
- ScopedCoMem<WCHAR> endpoint_device_id;
- HRESULT hr = device->GetId(&endpoint_device_id);
- if (FAILED(hr))
- return hr;
- WideToUTF8(endpoint_device_id, wcslen(endpoint_device_id),
- &device_name.unique_id);
-
- // Retrieve user-friendly name of endpoint device.
- // Example: "Microphone (Realtek High Definition Audio)".
- ScopedComPtr<IPropertyStore> properties;
- hr = device->OpenPropertyStore(STGM_READ, properties.Receive());
- if (FAILED(hr))
- return hr;
- ScopedPropertyVariant friendly_name;
- hr = properties->GetValue(PKEY_Device_FriendlyName, friendly_name.Receive());
- if (FAILED(hr))
- return hr;
- if (friendly_name.as_wide_string()) {
- WideToUTF8(friendly_name.as_wide_string(),
- wcslen(friendly_name.as_wide_string()),
- &device_name.device_name);
- }
-
- *name = device_name;
- DVLOG(2) << "friendly name: " << device_name.device_name;
- DVLOG(2) << "unique id : " << device_name.unique_id;
- return hr;
-}
-
-std::string CoreAudioUtil::GetFriendlyName(const std::string& device_id) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDevice> audio_device = CreateDevice(device_id);
- if (!audio_device)
- return std::string();
-
- AudioDeviceName device_name;
- HRESULT hr = GetDeviceName(audio_device, &device_name);
- if (FAILED(hr))
- return std::string();
-
- return device_name.device_name;
-}
-
-bool CoreAudioUtil::DeviceIsDefault(EDataFlow flow,
- ERole role,
- std::string device_id) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDevice> device = CreateDefaultDevice(flow, role);
- if (!device)
- return false;
-
- ScopedCoMem<WCHAR> default_device_id;
- HRESULT hr = device->GetId(&default_device_id);
- if (FAILED(hr))
- return false;
-
- std::string str_default;
- WideToUTF8(default_device_id, wcslen(default_device_id), &str_default);
- if (device_id.compare(str_default) != 0)
- return false;
- return true;
-}
-
-EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMEndpoint> endpoint;
- HRESULT hr = device->QueryInterface(endpoint.Receive());
- if (FAILED(hr)) {
- DVLOG(1) << "IMMDevice::QueryInterface: " << std::hex << hr;
- return eAll;
- }
-
- EDataFlow data_flow;
- hr = endpoint->GetDataFlow(&data_flow);
- if (FAILED(hr)) {
- DVLOG(1) << "IMMEndpoint::GetDataFlow: " << std::hex << hr;
- return eAll;
- }
- return data_flow;
-}
-
-ScopedComPtr<IAudioClient> CoreAudioUtil::CreateClient(
- IMMDevice* audio_device) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- // Creates and activates an IAudioClient COM object given the selected
- // endpoint device.
- ScopedComPtr<IAudioClient> audio_client;
- HRESULT hr = audio_device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client.ReceiveVoid());
- DVLOG_IF(1, FAILED(hr)) << "IMMDevice::Activate: " << std::hex << hr;
- return audio_client;
-}
-
-ScopedComPtr<IAudioClient> CoreAudioUtil::CreateDefaultClient(
- EDataFlow data_flow, ERole role) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedComPtr<IMMDevice> default_device(CreateDefaultDevice(data_flow, role));
- return (default_device ? CreateClient(default_device) :
- ScopedComPtr<IAudioClient>());
-}
-
-HRESULT CoreAudioUtil::GetSharedModeMixFormat(
- IAudioClient* client, WAVEFORMATPCMEX* format) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedCoMem<WAVEFORMATPCMEX> format_pcmex;
- HRESULT hr = client->GetMixFormat(
- reinterpret_cast<WAVEFORMATEX**>(&format_pcmex));
- if (FAILED(hr))
- return hr;
-
- size_t bytes = sizeof(WAVEFORMATEX) + format_pcmex->Format.cbSize;
- DCHECK_EQ(bytes, sizeof(WAVEFORMATPCMEX));
-
- memcpy(format, format_pcmex, bytes);
-
- DVLOG(2) << "wFormatTag: 0x" << std::hex << format->Format.wFormatTag
- << ", nChannels: " << std::dec << format->Format.nChannels
- << ", nSamplesPerSec: " << format->Format.nSamplesPerSec
- << ", nAvgBytesPerSec: " << format->Format.nAvgBytesPerSec
- << ", nBlockAlign: " << format->Format.nBlockAlign
- << ", wBitsPerSample: " << format->Format.wBitsPerSample
- << ", cbSize: " << format->Format.cbSize
- << ", wValidBitsPerSample: " << format->Samples.wValidBitsPerSample
- << ", dwChannelMask: 0x" << std::hex << format->dwChannelMask;
-
- return hr;
-}
-
-bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- const WAVEFORMATPCMEX* format) {
- DCHECK(CoreAudioUtil::IsSupported());
- ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match;
- HRESULT hr = client->IsFormatSupported(
- share_mode, reinterpret_cast<const WAVEFORMATEX*>(format),
- reinterpret_cast<WAVEFORMATEX**>(&closest_match));
-
- // This log can only be triggered for shared mode.
- DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
- << "but a closest match exists.";
- // This log can be triggered both for shared and exclusive modes.
- DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
- if (hr == S_FALSE) {
- DVLOG(2) << "wFormatTag: " << closest_match->Format.wFormatTag
- << ", nChannels: " << closest_match->Format.nChannels
- << ", nSamplesPerSec: " << closest_match->Format.nSamplesPerSec
- << ", wBitsPerSample: " << closest_match->Format.wBitsPerSample;
- }
-
- return (hr == S_OK);
-}
-
-HRESULT CoreAudioUtil::GetDevicePeriod(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- REFERENCE_TIME* device_period) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- // Get the period of the engine thread.
- REFERENCE_TIME default_period = 0;
- REFERENCE_TIME minimum_period = 0;
- HRESULT hr = client->GetDevicePeriod(&default_period, &minimum_period);
- if (FAILED(hr))
- return hr;
-
- *device_period = (share_mode == AUDCLNT_SHAREMODE_SHARED) ? default_period :
- minimum_period;
- DVLOG(2) << "device_period: "
- << RefererenceTimeToTimeDelta(*device_period).InMillisecondsF()
- << " [ms]";
- return hr;
-}
-
-HRESULT CoreAudioUtil::GetPreferredAudioParameters(
- IAudioClient* client, AudioParameters* params) {
- DCHECK(CoreAudioUtil::IsSupported());
- WAVEFORMATPCMEX format;
- HRESULT hr = GetSharedModeMixFormat(client, &format);
- if (FAILED(hr))
- return hr;
-
- REFERENCE_TIME default_period = 0;
- hr = GetDevicePeriod(client, AUDCLNT_SHAREMODE_SHARED, &default_period);
- if (FAILED(hr))
- return hr;
-
- // Get the integer mask which corresponds to the channel layout the
- // audio engine uses for its internal processing/mixing of shared-mode
- // streams. This mask indicates which channels are present in the multi-
- // channel stream. The least significant bit corresponds with the Front Left
- // speaker, the next least significant bit corresponds to the Front Right
- // speaker, and so on, continuing in the order defined in KsMedia.h.
- // See http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083.aspx
- // for more details.
- ChannelConfig channel_config = format.dwChannelMask;
-
- // Convert Microsoft's channel configuration to genric ChannelLayout.
- ChannelLayout channel_layout = ChannelConfigToChannelLayout(channel_config);
-
- // Store preferred sample rate and buffer size.
- int sample_rate = format.Format.nSamplesPerSec;
- int frames_per_buffer = static_cast<int>(sample_rate *
- RefererenceTimeToTimeDelta(default_period).InSecondsF() + 0.5);
-
- // TODO(henrika): possibly use format.Format.wBitsPerSample here instead.
- // We use a hard-coded value of 16 bits per sample today even if most audio
- // engines does the actual mixing in 32 bits per sample.
- int bits_per_sample = 16;
-
- DVLOG(2) << "channel_layout : " << channel_layout;
- DVLOG(2) << "sample_rate : " << sample_rate;
- DVLOG(2) << "bits_per_sample : " << bits_per_sample;
- DVLOG(2) << "frames_per_buffer: " << frames_per_buffer;
-
- AudioParameters audio_params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- channel_layout,
- sample_rate,
- bits_per_sample,
- frames_per_buffer);
-
- *params = audio_params;
- return hr;
-}
-
-HRESULT CoreAudioUtil::GetPreferredAudioParameters(
- EDataFlow data_flow, ERole role, AudioParameters* params) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- ScopedComPtr<IAudioClient> client = CreateDefaultClient(data_flow, role);
- if (!client) {
- // Map NULL-pointer to new error code which can be different from the
- // actual error code. The exact value is not important here.
- return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
- }
- return GetPreferredAudioParameters(client, params);
-}
-
-HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client,
- const WAVEFORMATPCMEX* format,
- HANDLE event_handle,
- size_t* endpoint_buffer_size) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
-
- // Enable event-driven streaming if a valid event handle is provided.
- // After the stream starts, the audio engine will signal the event handle
- // to notify the client each time a buffer becomes ready to process.
- // Event-driven buffering is supported for both rendering and capturing.
- // Both shared-mode and exclusive-mode streams can use event-driven buffering.
- bool use_event = (event_handle != NULL &&
- event_handle != INVALID_HANDLE_VALUE);
- if (use_event)
- stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
- DVLOG(2) << "stream_flags: 0x" << std::hex << stream_flags;
-
- // Initialize the shared mode client for minimal delay.
- HRESULT hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED,
- stream_flags,
- 0,
- 0,
- reinterpret_cast<const WAVEFORMATEX*>(format),
- NULL);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::Initialize: " << std::hex << hr;
- return hr;
- }
-
- if (use_event) {
- hr = client->SetEventHandle(event_handle);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::SetEventHandle: " << std::hex << hr;
- return hr;
- }
- }
-
- UINT32 buffer_size_in_frames = 0;
- hr = client->GetBufferSize(&buffer_size_in_frames);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetBufferSize: " << std::hex << hr;
- return hr;
- }
-
- *endpoint_buffer_size = static_cast<size_t>(buffer_size_in_frames);
- DVLOG(2) << "endpoint buffer size: " << buffer_size_in_frames;
-
- // TODO(henrika): utilize when delay measurements are added.
- REFERENCE_TIME latency = 0;
- hr = client->GetStreamLatency(&latency);
- DVLOG(2) << "stream latency: "
- << RefererenceTimeToTimeDelta(latency).InMillisecondsF() << " [ms]";
- return hr;
-}
-
-ScopedComPtr<IAudioRenderClient> CoreAudioUtil::CreateRenderClient(
- IAudioClient* client) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- // Get access to the IAudioRenderClient interface. This interface
- // enables us to write output data to a rendering endpoint buffer.
- ScopedComPtr<IAudioRenderClient> audio_render_client;
- HRESULT hr = client->GetService(__uuidof(IAudioRenderClient),
- audio_render_client.ReceiveVoid());
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
- return ScopedComPtr<IAudioRenderClient>();
- }
-
- // TODO(henrika): verify that this scheme is the same for shared mode and
- // exclusive mode streams.
-
- // Avoid start-up glitches by filling up the endpoint buffer with "silence"
- // before starting the stream.
- UINT32 endpoint_buffer_size = 0;
- hr = client->GetBufferSize(&endpoint_buffer_size);
- DVLOG_IF(1, FAILED(hr)) << "IAudioClient::GetBufferSize: " << std::hex << hr;
-
- BYTE* data = NULL;
- hr = audio_render_client->GetBuffer(endpoint_buffer_size, &data);
- DVLOG_IF(1, FAILED(hr)) << "IAudioRenderClient::GetBuffer: "
- << std::hex << hr;
- if (SUCCEEDED(hr)) {
- // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
- // explicitly write silence data to the rendering buffer.
- hr = audio_render_client->ReleaseBuffer(endpoint_buffer_size,
- AUDCLNT_BUFFERFLAGS_SILENT);
- DVLOG_IF(1, FAILED(hr)) << "IAudioRenderClient::ReleaseBuffer: "
- << std::hex << hr;
- }
-
- // Sanity check: verify that the endpoint buffer is filled with silence.
- UINT32 num_queued_frames = 0;
- client->GetCurrentPadding(&num_queued_frames);
- DCHECK(num_queued_frames == endpoint_buffer_size);
-
- return audio_render_client;
-}
-
-ScopedComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
- IAudioClient* client) {
- DCHECK(CoreAudioUtil::IsSupported());
-
- // Get access to the IAudioCaptureClient interface. This interface
- // enables us to read input data from a capturing endpoint buffer.
- ScopedComPtr<IAudioCaptureClient> audio_capture_client;
- HRESULT hr = client->GetService(__uuidof(IAudioCaptureClient),
- audio_capture_client.ReceiveVoid());
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
- return ScopedComPtr<IAudioCaptureClient>();
- }
- return audio_capture_client;
-}
-
-} // namespace media
diff --git a/src/media/audio/win/core_audio_util_win.h b/src/media/audio/win/core_audio_util_win.h
deleted file mode 100644
index c8a37d6..0000000
--- a/src/media/audio/win/core_audio_util_win.h
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Utility methods for the Core Audio API on Windows.
-// Always ensure that Core Audio is supported before using these methods.
-// Use media::CoreAudioIsSupported() for this purpose.
-// Also, all methods must be called on a valid COM thread. This can be done
-// by using the base::win::ScopedCOMInitializer helper class.
-
-#ifndef MEDIA_AUDIO_WIN_CORE_AUDIO_UTIL_WIN_H_
-#define MEDIA_AUDIO_WIN_CORE_AUDIO_UTIL_WIN_H_
-
-#include <audioclient.h>
-#include <mmdeviceapi.h>
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/time.h"
-#include "base/win/scoped_comptr.h"
-#include "media/audio/audio_device_name.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/media_export.h"
-
-using base::win::ScopedComPtr;
-
-namespace media {
-
-class MEDIA_EXPORT CoreAudioUtil {
- public:
- // Returns true if Windows Core Audio is supported.
- // Always verify that this method returns true before using any of the
- // methods in this class.
- static bool IsSupported();
-
- // Converts between reference time to base::TimeDelta.
- // One reference-time unit is 100 nanoseconds.
- // Example: double s = RefererenceTimeToTimeDelta(t).InMillisecondsF();
- static base::TimeDelta RefererenceTimeToTimeDelta(REFERENCE_TIME time);
-
- // Returns AUDCLNT_SHAREMODE_EXCLUSIVE if --enable-exclusive-mode is used
- // as command-line flag and AUDCLNT_SHAREMODE_SHARED otherwise (default).
- static AUDCLNT_SHAREMODE GetShareMode();
-
- // The Windows Multimedia Device (MMDevice) API enables audio clients to
- // discover audio endpoint devices and determine their capabilities.
-
- // Number of active audio devices in the specified flow data flow direction.
- // Set |data_flow| to eAll to retrieve the total number of active audio
- // devices.
- static int NumberOfActiveDevices(EDataFlow data_flow);
-
- // Creates an IMMDeviceEnumerator interface which provides methods for
- // enumerating audio endpoint devices.
- static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumerator();
-
- // Creates a default endpoint device that is specified by a data-flow
- // direction and role, e.g. default render device.
- static ScopedComPtr<IMMDevice> CreateDefaultDevice(
- EDataFlow data_flow, ERole role);
-
- // Creates an endpoint device that is specified by a unique endpoint device-
- // identification string.
- static ScopedComPtr<IMMDevice> CreateDevice(const std::string& device_id);
-
- // Returns the unique ID and user-friendly name of a given endpoint device.
- // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}", and
- // "Microphone (Realtek High Definition Audio)".
- static HRESULT GetDeviceName(IMMDevice* device, AudioDeviceName* name);
-
- // Gets the user-friendly name of the endpoint device which is represented
- // by a unique id in |device_id|.
- static std::string GetFriendlyName(const std::string& device_id);
-
- // Returns true if the provided unique |device_id| corresponds to the current
- // default device for the specified by a data-flow direction and role.
- static bool DeviceIsDefault(
- EDataFlow flow, ERole role, std::string device_id);
-
- // Query if the audio device is a rendering device or a capture device.
- static EDataFlow GetDataFlow(IMMDevice* device);
-
- // The Windows Audio Session API (WASAPI) enables client applications to
- // manage the flow of audio data between the application and an audio endpoint
- // device.
-
- // Create an IAudioClient interface for the default IMMDevice where
- // flow direction and role is define by |data_flow| and |role|.
- // The IAudioClient interface enables a client to create and initialize an
- // audio stream between an audio application and the audio engine (for a
- // shared-mode stream) or the hardware buffer of an audio endpoint device
- // (for an exclusive-mode stream).
- static ScopedComPtr<IAudioClient> CreateDefaultClient(EDataFlow data_flow,
- ERole role);
-
- // Create an IAudioClient interface for an existing IMMDevice given by
- // |audio_device|. Flow direction and role is define by the |audio_device|.
- static ScopedComPtr<IAudioClient> CreateClient(IMMDevice* audio_device);
-
- // Get the mix format that the audio engine uses internally for processing
- // of shared-mode streams. This format is not necessarily a format that the
- // audio endpoint device supports. Thus, the caller might not succeed in
- // creating an exclusive-mode stream with a format obtained by this method.
- static HRESULT GetSharedModeMixFormat(IAudioClient* client,
- WAVEFORMATPCMEX* format);
-
- // Returns true if the specified |client| supports the format in |format|
- // for the given |share_mode| (shared or exclusive).
- static bool IsFormatSupported(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- const WAVEFORMATPCMEX* format);
-
- // For a shared-mode stream, the audio engine periodically processes the
- // data in the endpoint buffer at the period obtained in |device_period|.
- // For an exclusive mode stream, |device_period| corresponds to the minimum
- // time interval between successive processing by the endpoint device.
- // This period plus the stream latency between the buffer and endpoint device
- // represents the minimum possible latency that an audio application can
- // achieve. The time in |device_period| is expressed in 100-nanosecond units.
- static HRESULT GetDevicePeriod(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- REFERENCE_TIME* device_period);
-
- // Get the preferred audio parameters for the specified |client| or the
- // given direction and role is define by |data_flow| and |role|.
- // The acquired values should only be utilized for shared mode streamed since
- // there are no preferred settings for an exclusive mode stream.
- static HRESULT GetPreferredAudioParameters(IAudioClient* client,
- AudioParameters* params);
- static HRESULT GetPreferredAudioParameters(EDataFlow data_flow, ERole role,
- AudioParameters* params);
-
- // After activating an IAudioClient interface on an audio endpoint device,
- // the client must initialize it once, and only once, to initialize the audio
- // stream between the client and the device. In shared mode, the client
- // connects indirectly through the audio engine which does the mixing.
- // In exclusive mode, the client connects directly to the audio hardware.
- // If a valid event is provided in |event_handle|, the client will be
- // initialized for event-driven buffer handling. If |event_handle| is set to
- // NULL, event-driven buffer handling is not utilized.
- static HRESULT SharedModeInitialize(IAudioClient* client,
- const WAVEFORMATPCMEX* format,
- HANDLE event_handle,
- size_t* endpoint_buffer_size);
- // TODO(henrika): add ExclusiveModeInitialize(...)
-
- // Create an IAudioRenderClient client for an existing IAudioClient given by
- // |client|. The IAudioRenderClient interface enables a client to write
- // output data to a rendering endpoint buffer.
- static ScopedComPtr<IAudioRenderClient> CreateRenderClient(
- IAudioClient* client);
-
- // Create an IAudioCaptureClient client for an existing IAudioClient given by
- // |client|. The IAudioCaptureClient interface enables a client to read
- // input data from a capture endpoint buffer.
- static ScopedComPtr<IAudioCaptureClient> CreateCaptureClient(
- IAudioClient* client);
-
- private:
- CoreAudioUtil() {}
- ~CoreAudioUtil() {}
- DISALLOW_COPY_AND_ASSIGN(CoreAudioUtil);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_CORE_AUDIO_UTIL_WIN_H_
diff --git a/src/media/audio/win/core_audio_util_win_unittest.cc b/src/media/audio/win/core_audio_util_win_unittest.cc
deleted file mode 100644
index b1edf47..0000000
--- a/src/media/audio/win/core_audio_util_win_unittest.cc
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/memory/scoped_ptr.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/win/scoped_com_initializer.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/win/core_audio_util_win.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::win::ScopedCOMInitializer;
-
-namespace media {
-
-class CoreAudioUtilWinTest : public ::testing::Test {
- protected:
- // The test runs on a COM thread in the multithreaded apartment (MTA).
- // If we don't initialize the COM library on a thread before using COM,
- // all function calls will return CO_E_NOTINITIALIZED.
- CoreAudioUtilWinTest()
- : com_init_(ScopedCOMInitializer::kMTA) {
- DCHECK(com_init_.succeeded());
- }
- virtual ~CoreAudioUtilWinTest() {}
-
- bool CanRunAudioTest() {
- bool core_audio = CoreAudioUtil::IsSupported();
- if (!core_audio)
- return false;
- int capture_devices = CoreAudioUtil::NumberOfActiveDevices(eCapture);
- int render_devices = CoreAudioUtil::NumberOfActiveDevices(eRender);
- return ((capture_devices > 0) && (render_devices > 0));
- }
-
- ScopedCOMInitializer com_init_;
-};
-
-TEST_F(CoreAudioUtilWinTest, NumberOfActiveDevices) {
- if (!CanRunAudioTest())
- return;
-
- int render_devices = CoreAudioUtil::NumberOfActiveDevices(eRender);
- EXPECT_GT(render_devices, 0);
- int capture_devices = CoreAudioUtil::NumberOfActiveDevices(eCapture);
- EXPECT_GT(capture_devices, 0);
- int total_devices = CoreAudioUtil::NumberOfActiveDevices(eAll);
- EXPECT_EQ(total_devices, render_devices + capture_devices);
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateDeviceEnumerator) {
- if (!CanRunAudioTest())
- return;
-
- ScopedComPtr<IMMDeviceEnumerator> enumerator =
- CoreAudioUtil::CreateDeviceEnumerator();
- EXPECT_TRUE(enumerator);
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateDefaultDevice) {
- if (!CanRunAudioTest())
- return;
-
- struct {
- EDataFlow flow;
- ERole role;
- } data[] = {
- {eRender, eConsole},
- {eRender, eCommunications},
- {eRender, eMultimedia},
- {eCapture, eConsole},
- {eCapture, eCommunications},
- {eCapture, eMultimedia}
- };
-
- // Create default devices for all flow/role combinations above.
- ScopedComPtr<IMMDevice> audio_device;
- for (int i = 0; i < arraysize(data); ++i) {
- audio_device =
- CoreAudioUtil::CreateDefaultDevice(data[i].flow, data[i].role);
- EXPECT_TRUE(audio_device);
- EXPECT_EQ(data[i].flow, CoreAudioUtil::GetDataFlow(audio_device));
- }
-
- // Only eRender and eCapture are allowed as flow parameter.
- audio_device = CoreAudioUtil::CreateDefaultDevice(eAll, eConsole);
- EXPECT_FALSE(audio_device);
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateDevice) {
- if (!CanRunAudioTest())
- return;
-
- // Get name and ID of default device used for playback.
- ScopedComPtr<IMMDevice> default_render_device =
- CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- AudioDeviceName default_render_name;
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(default_render_device,
- &default_render_name)));
-
- // Use the uniqe ID as input to CreateDevice() and create a corresponding
- // IMMDevice.
- ScopedComPtr<IMMDevice> audio_device =
- CoreAudioUtil::CreateDevice(default_render_name.unique_id);
- EXPECT_TRUE(audio_device);
-
- // Verify that the two IMMDevice interfaces represents the same endpoint
- // by comparing their unique IDs.
- AudioDeviceName device_name;
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device,
- &device_name)));
- EXPECT_EQ(default_render_name.unique_id, device_name.unique_id);
-}
-
-TEST_F(CoreAudioUtilWinTest, GetDefaultDeviceName) {
- if (!CanRunAudioTest())
- return;
-
- struct {
- EDataFlow flow;
- ERole role;
- } data[] = {
- {eRender, eConsole},
- {eRender, eCommunications},
- {eCapture, eConsole},
- {eCapture, eCommunications}
- };
-
- // Get name and ID of default devices for all flow/role combinations above.
- ScopedComPtr<IMMDevice> audio_device;
- AudioDeviceName device_name;
- for (int i = 0; i < arraysize(data); ++i) {
- audio_device =
- CoreAudioUtil::CreateDefaultDevice(data[i].flow, data[i].role);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device,
- &device_name)));
- EXPECT_FALSE(device_name.device_name.empty());
- EXPECT_FALSE(device_name.unique_id.empty());
- }
-}
-
-TEST_F(CoreAudioUtilWinTest, GetFriendlyName) {
- if (!CanRunAudioTest())
- return;
-
- // Get name and ID of default device used for recording.
- ScopedComPtr<IMMDevice> audio_device =
- CoreAudioUtil::CreateDefaultDevice(eCapture, eConsole);
- AudioDeviceName device_name;
- HRESULT hr = CoreAudioUtil::GetDeviceName(audio_device, &device_name);
- EXPECT_TRUE(SUCCEEDED(hr));
-
- // Use unique ID as input to GetFriendlyName() and compare the result
- // with the already obtained friendly name for the default capture device.
- std::string friendly_name = CoreAudioUtil::GetFriendlyName(
- device_name.unique_id);
- EXPECT_EQ(friendly_name, device_name.device_name);
-
- // Same test as above but for playback.
- audio_device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- hr = CoreAudioUtil::GetDeviceName(audio_device, &device_name);
- EXPECT_TRUE(SUCCEEDED(hr));
- friendly_name = CoreAudioUtil::GetFriendlyName(device_name.unique_id);
- EXPECT_EQ(friendly_name, device_name.device_name);
-}
-
-TEST_F(CoreAudioUtilWinTest, DeviceIsDefault) {
- if (!CanRunAudioTest())
- return;
-
- // Verify that the default render device is correctly identified as a
- // default device.
- ScopedComPtr<IMMDevice> audio_device =
- CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- AudioDeviceName name;
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device, &name)));
- const std::string id = name.unique_id;
- EXPECT_TRUE(CoreAudioUtil::DeviceIsDefault(eRender, eConsole, id));
- EXPECT_FALSE(CoreAudioUtil::DeviceIsDefault(eCapture, eConsole, id));
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateDefaultClient) {
- if (!CanRunAudioTest())
- return;
-
- EDataFlow data[] = {eRender, eCapture};
-
- for (int i = 0; i < arraysize(data); ++i) {
- ScopedComPtr<IAudioClient> client;
- client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
- EXPECT_TRUE(client);
- }
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateClient) {
- if (!CanRunAudioTest())
- return;
-
- EDataFlow data[] = {eRender, eCapture};
-
- for (int i = 0; i < arraysize(data); ++i) {
- ScopedComPtr<IMMDevice> device;
- ScopedComPtr<IAudioClient> client;
- device = CoreAudioUtil::CreateDefaultDevice(data[i], eConsole);
- EXPECT_TRUE(device);
- EXPECT_EQ(data[i], CoreAudioUtil::GetDataFlow(device));
- client = CoreAudioUtil::CreateClient(device);
- EXPECT_TRUE(client);
- }
-}
-
-TEST_F(CoreAudioUtilWinTest, GetSharedModeMixFormat) {
- if (!CanRunAudioTest())
- return;
-
- ScopedComPtr<IMMDevice> device;
- ScopedComPtr<IAudioClient> client;
- device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- EXPECT_TRUE(device);
- client = CoreAudioUtil::CreateClient(device);
- EXPECT_TRUE(client);
-
- // Perform a simple sanity test of the aquired format structure.
- WAVEFORMATPCMEX format;
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
- &format)));
- EXPECT_GE(format.Format.nChannels, 1);
- EXPECT_GE(format.Format.nSamplesPerSec, 8000u);
- EXPECT_GE(format.Format.wBitsPerSample, 16);
- EXPECT_GE(format.Samples.wValidBitsPerSample, 16);
- EXPECT_EQ(format.Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE);
-}
-
-TEST_F(CoreAudioUtilWinTest, GetDevicePeriod) {
- if (!CanRunAudioTest())
- return;
-
- EDataFlow data[] = {eRender, eCapture};
-
- // Verify that the device periods are valid for the default render and
- // capture devices.
- for (int i = 0; i < arraysize(data); ++i) {
- ScopedComPtr<IAudioClient> client;
- REFERENCE_TIME shared_time_period = 0;
- REFERENCE_TIME exclusive_time_period = 0;
- client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
- EXPECT_TRUE(client);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDevicePeriod(
- client, AUDCLNT_SHAREMODE_SHARED, &shared_time_period)));
- EXPECT_GT(shared_time_period, 0);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDevicePeriod(
- client, AUDCLNT_SHAREMODE_EXCLUSIVE, &exclusive_time_period)));
- EXPECT_GT(exclusive_time_period, 0);
- EXPECT_LE(exclusive_time_period, shared_time_period);
- }
-}
-
-TEST_F(CoreAudioUtilWinTest, GetPreferredAudioParameters) {
- if (!CanRunAudioTest())
- return;
-
- EDataFlow data[] = {eRender, eCapture};
-
- // Verify that the preferred audio parameters are OK for the default render
- // and capture devices.
- for (int i = 0; i < arraysize(data); ++i) {
- ScopedComPtr<IAudioClient> client;
- AudioParameters params;
- client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
- EXPECT_TRUE(client);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(client,
- ¶ms)));
- EXPECT_TRUE(params.IsValid());
- }
-}
-
-TEST_F(CoreAudioUtilWinTest, SharedModeInitialize) {
- if (!CanRunAudioTest())
- return;
-
- ScopedComPtr<IAudioClient> client;
- client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
- EXPECT_TRUE(client);
-
- WAVEFORMATPCMEX format;
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
- &format)));
-
- // Perform a shared-mode initialization without event-driven buffer handling.
- size_t endpoint_buffer_size = 0;
- HRESULT hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- EXPECT_TRUE(SUCCEEDED(hr));
- EXPECT_GT(endpoint_buffer_size, 0u);
-
- // It is only possible to create a client once.
- hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- EXPECT_FALSE(SUCCEEDED(hr));
- EXPECT_EQ(hr, AUDCLNT_E_ALREADY_INITIALIZED);
-
- // Verify that it is possible to reinitialize the client after releasing it.
- client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
- EXPECT_TRUE(client);
- hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- EXPECT_TRUE(SUCCEEDED(hr));
- EXPECT_GT(endpoint_buffer_size, 0u);
-
- // Use a non-supported format and verify that initialization fails.
- // A simple way to emulate an invalid format is to use the shared-mode
- // mixing format and modify the preferred sample.
- client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
- EXPECT_TRUE(client);
- format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1;
- EXPECT_FALSE(CoreAudioUtil::IsFormatSupported(
- client, AUDCLNT_SHAREMODE_SHARED, &format));
- hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- EXPECT_TRUE(FAILED(hr));
- EXPECT_EQ(hr, E_INVALIDARG);
-
- // Finally, perform a shared-mode initialization using event-driven buffer
- // handling. The event handle will be signaled when an audio buffer is ready
- // to be processed by the client (not verified here).
- // The event handle should be in the nonsignaled state.
- base::win::ScopedHandle event_handle(::CreateEvent(NULL, TRUE, FALSE, NULL));
- client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
- EXPECT_TRUE(client);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
- &format)));
- EXPECT_TRUE(CoreAudioUtil::IsFormatSupported(
- client, AUDCLNT_SHAREMODE_SHARED, &format));
- hr = CoreAudioUtil::SharedModeInitialize(client, &format, event_handle.Get(),
- &endpoint_buffer_size);
- EXPECT_TRUE(SUCCEEDED(hr));
- EXPECT_GT(endpoint_buffer_size, 0u);
-}
-
-TEST_F(CoreAudioUtilWinTest, CreateRenderAndCaptureClients) {
- if (!CanRunAudioTest())
- return;
-
- EDataFlow data[] = {eRender, eCapture};
-
- WAVEFORMATPCMEX format;
- size_t endpoint_buffer_size = 0;
-
- for (int i = 0; i < arraysize(data); ++i) {
- ScopedComPtr<IAudioClient> client;
- ScopedComPtr<IAudioRenderClient> render_client;
- ScopedComPtr<IAudioCaptureClient> capture_client;
-
- client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
- EXPECT_TRUE(client);
- EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
- &format)));
- if (data[i] == eRender) {
- // It is not possible to create a render client using an unitialized
- // client interface.
- render_client = CoreAudioUtil::CreateRenderClient(client);
- EXPECT_FALSE(render_client);
-
- // Do a proper initialization and verify that it works this time.
- CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- render_client = CoreAudioUtil::CreateRenderClient(client);
- EXPECT_TRUE(render_client);
- EXPECT_GT(endpoint_buffer_size, 0u);
- } else if (data[i] == eCapture) {
- // It is not possible to create a capture client using an unitialized
- // client interface.
- capture_client = CoreAudioUtil::CreateCaptureClient(client);
- EXPECT_FALSE(capture_client);
-
- // Do a proper initialization and verify that it works this time.
- CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
- &endpoint_buffer_size);
- capture_client = CoreAudioUtil::CreateCaptureClient(client);
- EXPECT_TRUE(capture_client);
- EXPECT_GT(endpoint_buffer_size, 0u);
- }
- }
-}
-
-//
-
-} // namespace media
diff --git a/src/media/audio/win/device_enumeration_win.cc b/src/media/audio/win/device_enumeration_win.cc
deleted file mode 100644
index 46aacb0..0000000
--- a/src/media/audio/win/device_enumeration_win.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <MMDeviceAPI.h>
-#include <mmsystem.h>
-#include <Functiondiscoverykeys_devpkey.h> // MMDeviceAPI.h must come first
-
-#include "media/audio/win/audio_manager_win.h"
-
-#include "base/logging.h"
-#include "base/utf_string_conversions.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_comptr.h"
-
-using media::AudioDeviceNames;
-using base::win::ScopedComPtr;
-using base::win::ScopedCoMem;
-
-// Taken from Mmddk.h.
-#define DRV_RESERVED 0x0800
-#define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17)
-#define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)
-
-namespace media {
-
-bool GetInputDeviceNamesWin(AudioDeviceNames* device_names) {
- // It is assumed that this method is called from a COM thread, i.e.,
- // CoInitializeEx() is not called here again to avoid STA/MTA conflicts.
- ScopedComPtr<IMMDeviceEnumerator> enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator),
- enumerator.ReceiveVoid());
- DCHECK_NE(CO_E_NOTINITIALIZED, hr);
- if (FAILED(hr)) {
- LOG(WARNING) << "Failed to create IMMDeviceEnumerator: " << std::hex << hr;
- return false;
- }
-
- // Generate a collection of active audio capture endpoint devices.
- // This method will succeed even if all devices are disabled.
- ScopedComPtr<IMMDeviceCollection> collection;
- hr = enumerator->EnumAudioEndpoints(eCapture,
- DEVICE_STATE_ACTIVE,
- collection.Receive());
- if (FAILED(hr))
- return false;
-
- // Retrieve the number of active capture devices.
- UINT number_of_active_devices = 0;
- collection->GetCount(&number_of_active_devices);
- if (number_of_active_devices == 0)
- return true;
-
- media::AudioDeviceName device;
-
- // Loop over all active capture devices and add friendly name and
- // unique ID to the |device_names| list.
- for (UINT i = 0; i < number_of_active_devices; ++i) {
- // Retrieve unique name of endpoint device.
- // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
- ScopedComPtr<IMMDevice> audio_device;
- hr = collection->Item(i, audio_device.Receive());
- if (FAILED(hr))
- continue;
-
- // Store the unique name.
- ScopedCoMem<WCHAR> endpoint_device_id;
- audio_device->GetId(&endpoint_device_id);
- device.unique_id = WideToUTF8(static_cast<WCHAR*>(endpoint_device_id));
-
- // Retrieve user-friendly name of endpoint device.
- // Example: "Microphone (Realtek High Definition Audio)".
- ScopedComPtr<IPropertyStore> properties;
- hr = audio_device->OpenPropertyStore(STGM_READ, properties.Receive());
- if (SUCCEEDED(hr)) {
- PROPVARIANT friendly_name;
- PropVariantInit(&friendly_name);
- hr = properties->GetValue(PKEY_Device_FriendlyName, &friendly_name);
-
- // Store the user-friendly name.
- if (SUCCEEDED(hr) &&
- friendly_name.vt == VT_LPWSTR && friendly_name.pwszVal) {
- device.device_name = WideToUTF8(friendly_name.pwszVal);
- }
- PropVariantClear(&friendly_name);
- }
-
- // Add combination of user-friendly and unique name to the output list.
- device_names->push_back(device);
- }
-
- return true;
-}
-
-bool GetInputDeviceNamesWinXP(AudioDeviceNames* device_names) {
- // Retrieve the number of active waveform input devices.
- UINT number_of_active_devices = waveInGetNumDevs();
- if (number_of_active_devices == 0)
- return true;
-
- media::AudioDeviceName device;
- WAVEINCAPS capabilities;
- MMRESULT err = MMSYSERR_NOERROR;
-
- // Loop over all active capture devices and add friendly name and
- // unique ID to the |device_names| list. Note that, for Wave on XP,
- // the "unique" name will simply be a copy of the friendly name since
- // there is no safe method to retrieve a unique device name on XP.
- for (UINT i = 0; i < number_of_active_devices; ++i) {
- // Retrieve the capabilities of the specified waveform-audio input device.
- err = waveInGetDevCaps(i, &capabilities, sizeof(capabilities));
- if (err != MMSYSERR_NOERROR)
- continue;
-
- // Store the user-friendly name. Max length is MAXPNAMELEN(=32)
- // characters and the name cane be truncated on XP.
- // Example: "Microphone (Realtek High Defini".
- device.device_name = WideToUTF8(capabilities.szPname);
-
- // Store the "unique" name (we use same as friendly name on Windows XP).
- device.unique_id = WideToUTF8(capabilities.szPname);
-
- // Add combination of user-friendly and unique name to the output list.
- device_names->push_back(device);
- }
-
- return true;
-}
-
-std::string ConvertToWinXPDeviceId(const std::string& device_id) {
- UINT number_of_active_devices = waveInGetNumDevs();
- MMRESULT result = MMSYSERR_NOERROR;
-
- UINT i = 0;
- for (; i < number_of_active_devices; ++i) {
- size_t size = 0;
- // Get the size (including the terminating NULL) of the endpoint ID of the
- // waveIn device.
- result = waveInMessage(reinterpret_cast<HWAVEIN>(i),
- DRV_QUERYFUNCTIONINSTANCEIDSIZE,
- reinterpret_cast<DWORD_PTR>(&size), NULL);
- if (result != MMSYSERR_NOERROR)
- continue;
-
- ScopedCoMem<WCHAR> id;
- id.Reset(static_cast<WCHAR*>(CoTaskMemAlloc(size)));
- if (!id)
- continue;
-
- // Get the endpoint ID string for this waveIn device.
- result = waveInMessage(
- reinterpret_cast<HWAVEIN>(i), DRV_QUERYFUNCTIONINSTANCEID,
- reinterpret_cast<DWORD_PTR>(static_cast<WCHAR*>(id)), size);
- if (result != MMSYSERR_NOERROR)
- continue;
-
- std::string utf8_id = WideToUTF8(static_cast<WCHAR*>(id));
- // Check whether the endpoint ID string of this waveIn device matches that
- // of the audio endpoint device.
- if (device_id == utf8_id)
- break;
- }
-
- // If a matching waveIn device was found, convert the unique endpoint ID
- // string to a standard friendly name with max 32 characters.
- if (i < number_of_active_devices) {
- WAVEINCAPS capabilities;
-
- result = waveInGetDevCaps(i, &capabilities, sizeof(capabilities));
- if (result == MMSYSERR_NOERROR)
- return WideToUTF8(capabilities.szPname);
- }
-
- return std::string();
-}
-
-} // namespace media
diff --git a/src/media/audio/win/device_enumeration_win.h b/src/media/audio/win/device_enumeration_win.h
deleted file mode 100644
index 3d44670..0000000
--- a/src/media/audio/win/device_enumeration_win.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_
-#define MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_
-
-#include <string>
-
-#include "media/audio/audio_device_name.h"
-
-namespace media {
-
-// Returns a list of audio input device structures (name and unique device ID)
-// using the MMDevice API which is supported on Windows Vista and higher.
-// Example record in the output list:
-// - device_name: "Microphone (Realtek High Definition Audio)".
-// - unique_id: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}"
-// This method must be called from a COM thread using MTA.
-bool GetInputDeviceNamesWin(media::AudioDeviceNames* device_names);
-
-// Returns a list of audio input device structures (name and unique device ID)
-// using the WaveIn API which is supported on Windows XP and higher.
-// Example record in the output list:
-// - device_name: "Microphone (Realtek High Defini".
-// - unique_id: "Microphone (Realtek High Defini" (same as friendly name).
-bool GetInputDeviceNamesWinXP(media::AudioDeviceNames* device_names);
-
-// Converts a device ID generated by |GetInputDeviceNamesWin()| to the
-// corresponding ID by |GetInputDeviceNamesWinXP()|. Returns an empty string on
-// failure.
-// Example input and output:
-// - input ID: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}"
-// - output ID: "Microphone (Realtek High Defini"
-std::string ConvertToWinXPDeviceId(const std::string& device_id);
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_DEVICE_ENUMERATION_WIN_H_
-
diff --git a/src/media/audio/win/wavein_input_win.cc b/src/media/audio/win/wavein_input_win.cc
deleted file mode 100644
index a625707..0000000
--- a/src/media/audio/win/wavein_input_win.cc
+++ /dev/null
@@ -1,302 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/wavein_input_win.h"
-
-#pragma comment(lib, "winmm.lib")
-
-#include "base/logging.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_manager_win.h"
-#include "media/audio/win/device_enumeration_win.h"
-
-namespace {
-const int kStopInputStreamCallbackTimeout = 3000; // Three seconds.
-}
-
-namespace media {
-
-// Our sound buffers are allocated once and kept in a linked list using the
-// the WAVEHDR::dwUser variable. The last buffer points to the first buffer.
-static WAVEHDR* GetNextBuffer(WAVEHDR* current) {
- return reinterpret_cast<WAVEHDR*>(current->dwUser);
-}
-
-PCMWaveInAudioInputStream::PCMWaveInAudioInputStream(
- AudioManagerWin* manager, const AudioParameters& params, int num_buffers,
- const std::string& device_id)
- : state_(kStateEmpty),
- manager_(manager),
- device_id_(device_id),
- wavein_(NULL),
- callback_(NULL),
- num_buffers_(num_buffers),
- buffer_(NULL),
- channels_(params.channels()) {
- DCHECK_GT(num_buffers_, 0);
- format_.wFormatTag = WAVE_FORMAT_PCM;
- format_.nChannels = params.channels() > 2 ? 2 : params.channels();
- format_.nSamplesPerSec = params.sample_rate();
- format_.wBitsPerSample = params.bits_per_sample();
- format_.cbSize = 0;
- format_.nBlockAlign = (format_.nChannels * format_.wBitsPerSample) / 8;
- format_.nAvgBytesPerSec = format_.nBlockAlign * format_.nSamplesPerSec;
- buffer_size_ = params.frames_per_buffer() * format_.nBlockAlign;
- // If we don't have a packet size we use 100ms.
- if (!buffer_size_)
- buffer_size_ = format_.nAvgBytesPerSec / 10;
- // The event is auto-reset.
- stopped_event_.Set(::CreateEventW(NULL, FALSE, FALSE, NULL));
-}
-
-PCMWaveInAudioInputStream::~PCMWaveInAudioInputStream() {
- DCHECK(NULL == wavein_);
-}
-
-bool PCMWaveInAudioInputStream::Open() {
- if (state_ != kStateEmpty)
- return false;
- if (num_buffers_ < 2 || num_buffers_ > 10)
- return false;
-
- // Convert the stored device id string into an unsigned integer
- // corresponding to the selected device.
- UINT device_id = WAVE_MAPPER;
- if (!GetDeviceId(&device_id)) {
- return false;
- }
-
- // Open the specified input device for recording.
- MMRESULT result = MMSYSERR_NOERROR;
- result = ::waveInOpen(&wavein_, device_id, &format_,
- reinterpret_cast<DWORD_PTR>(WaveCallback),
- reinterpret_cast<DWORD_PTR>(this),
- CALLBACK_FUNCTION);
- if (result != MMSYSERR_NOERROR)
- return false;
-
- SetupBuffers();
- state_ = kStateReady;
- return true;
-}
-
-void PCMWaveInAudioInputStream::SetupBuffers() {
- WAVEHDR* last = NULL;
- WAVEHDR* first = NULL;
- for (int ix = 0; ix != num_buffers_; ++ix) {
- uint32 sz = sizeof(WAVEHDR) + buffer_size_;
- buffer_ = reinterpret_cast<WAVEHDR*>(new char[sz]);
- buffer_->lpData = reinterpret_cast<char*>(buffer_) + sizeof(WAVEHDR);
- buffer_->dwBufferLength = buffer_size_;
- buffer_->dwBytesRecorded = 0;
- buffer_->dwUser = reinterpret_cast<DWORD_PTR>(last);
- buffer_->dwFlags = WHDR_DONE;
- buffer_->dwLoops = 0;
- if (ix == 0)
- first = buffer_;
- last = buffer_;
- ::waveInPrepareHeader(wavein_, buffer_, sizeof(WAVEHDR));
- }
- // Fix the first buffer to point to the last one.
- first->dwUser = reinterpret_cast<DWORD_PTR>(last);
-}
-
-void PCMWaveInAudioInputStream::FreeBuffers() {
- WAVEHDR* current = buffer_;
- for (int ix = 0; ix != num_buffers_; ++ix) {
- WAVEHDR* next = GetNextBuffer(current);
- if (current->dwFlags & WHDR_PREPARED)
- ::waveInUnprepareHeader(wavein_, current, sizeof(WAVEHDR));
- delete[] reinterpret_cast<char*>(current);
- current = next;
- }
- buffer_ = NULL;
-}
-
-void PCMWaveInAudioInputStream::Start(AudioInputCallback* callback) {
- if (state_ != kStateReady)
- return;
-
- callback_ = callback;
- state_ = kStateRecording;
-
- WAVEHDR* buffer = buffer_;
- for (int ix = 0; ix != num_buffers_; ++ix) {
- QueueNextPacket(buffer);
- buffer = GetNextBuffer(buffer);
- }
- buffer = buffer_;
-
- MMRESULT result = ::waveInStart(wavein_);
- if (result != MMSYSERR_NOERROR) {
- HandleError(result);
- state_ = kStateReady;
- } else {
- manager_->IncreaseActiveInputStreamCount();
- }
-}
-
-// Stopping is tricky. First, no buffer should be locked by the audio driver
-// or else the waveInReset() will deadlock and secondly, the callback should
-// not be inside the AudioInputCallback's OnData because waveInReset()
-// forcefully kills the callback thread.
-void PCMWaveInAudioInputStream::Stop() {
- if (state_ != kStateRecording)
- return;
- state_ = kStateStopping;
- // Wait for the callback to finish, it will signal us when ready to be reset.
- if (WAIT_OBJECT_0 !=
- ::WaitForSingleObject(stopped_event_, kStopInputStreamCallbackTimeout)) {
- HandleError(::GetLastError());
- return;
- }
- // Stop is always called before Close. In case of error, this will be
- // also called when closing the input controller.
- manager_->DecreaseActiveInputStreamCount();
-
- state_ = kStateStopped;
- MMRESULT res = ::waveInReset(wavein_);
- if (res != MMSYSERR_NOERROR) {
- state_ = kStateRecording;
- HandleError(res);
- return;
- }
- state_ = kStateReady;
-}
-
-// We can Close in any state except that when trying to close a stream that is
-// recording Windows generates an error, which we propagate to the source.
-void PCMWaveInAudioInputStream::Close() {
- if (wavein_) {
- // waveInClose generates a callback with WIM_CLOSE id in the same thread.
- MMRESULT res = ::waveInClose(wavein_);
- if (res != MMSYSERR_NOERROR) {
- HandleError(res);
- return;
- }
- state_ = kStateClosed;
- wavein_ = NULL;
- FreeBuffers();
- }
- // Tell the audio manager that we have been released. This can result in
- // the manager destroying us in-place so this needs to be the last thing
- // we do on this function.
- manager_->ReleaseInputStream(this);
-}
-
-double PCMWaveInAudioInputStream::GetMaxVolume() {
- // TODO(henrika): Add volume support using the Audio Mixer API.
- return 0.0;
-}
-
-void PCMWaveInAudioInputStream::SetVolume(double volume) {
- // TODO(henrika): Add volume support using the Audio Mixer API.
-}
-
-double PCMWaveInAudioInputStream::GetVolume() {
- // TODO(henrika): Add volume support using the Audio Mixer API.
- return 0.0;
-}
-
-void PCMWaveInAudioInputStream::SetAutomaticGainControl(bool enabled) {
- // TODO(henrika): Add AGC support when volume control has been added.
- NOTIMPLEMENTED();
-}
-
-bool PCMWaveInAudioInputStream::GetAutomaticGainControl() {
- // TODO(henrika): Add AGC support when volume control has been added.
- NOTIMPLEMENTED();
- return false;
-}
-
-void PCMWaveInAudioInputStream::HandleError(MMRESULT error) {
- DLOG(WARNING) << "PCMWaveInAudio error " << error;
- callback_->OnError(this, error);
-}
-
-void PCMWaveInAudioInputStream::QueueNextPacket(WAVEHDR *buffer) {
- MMRESULT res = ::waveInAddBuffer(wavein_, buffer, sizeof(WAVEHDR));
- if (res != MMSYSERR_NOERROR)
- HandleError(res);
-}
-
-bool PCMWaveInAudioInputStream::GetDeviceId(UINT* device_index) {
- // Deliver the default input device id (WAVE_MAPPER) if the default
- // device has been selected.
- if (device_id_ == AudioManagerBase::kDefaultDeviceId) {
- *device_index = WAVE_MAPPER;
- return true;
- }
-
- // Get list of all available and active devices.
- AudioDeviceNames device_names;
- if (!media::GetInputDeviceNamesWinXP(&device_names))
- return false;
-
- if (device_names.empty())
- return false;
-
- // Search the full list of devices and compare with the specified
- // device id which was specified in the constructor. Stop comparing
- // when a match is found and return the corresponding index.
- UINT index = 0;
- bool found_device = false;
- AudioDeviceNames::const_iterator it = device_names.begin();
- while (it != device_names.end()) {
- if (it->unique_id.compare(device_id_) == 0) {
- *device_index = index;
- found_device = true;
- break;
- }
- ++index;
- ++it;
- }
-
- return found_device;
-}
-
-// Windows calls us back in this function when some events happen. Most notably
-// when it has an audio buffer with recorded data.
-void PCMWaveInAudioInputStream::WaveCallback(HWAVEIN hwi, UINT msg,
- DWORD_PTR instance,
- DWORD_PTR param1, DWORD_PTR) {
- PCMWaveInAudioInputStream* obj =
- reinterpret_cast<PCMWaveInAudioInputStream*>(instance);
-
- if (msg == WIM_DATA) {
- // WIM_DONE indicates that the driver is done with our buffer. We pass it
- // to the callback and check if we need to stop playing.
- // It should be OK to assume the data in the buffer is what has been
- // recorded in the soundcard.
- // TODO(henrika): the |volume| parameter is always set to zero since there
- // is currently no support for controlling the microphone volume level.
- WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1);
- obj->callback_->OnData(obj, reinterpret_cast<const uint8*>(buffer->lpData),
- buffer->dwBytesRecorded,
- buffer->dwBytesRecorded,
- 0.0);
-
- if (obj->state_ == kStateStopping) {
- // The main thread has called Stop() and is waiting to issue waveOutReset
- // which will kill this thread. We should not enter AudioSourceCallback
- // code anymore.
- ::SetEvent(obj->stopped_event_);
- } else if (obj->state_ == kStateStopped) {
- // Not sure if ever hit this but just in case.
- } else {
- // Queue the finished buffer back with the audio driver. Since we are
- // reusing the same buffers we can get away without calling
- // waveInPrepareHeader.
- obj->QueueNextPacket(buffer);
- }
- } else if (msg == WIM_CLOSE) {
- // We can be closed before calling Start, so it is possible to have a
- // null callback at this point.
- if (obj->callback_)
- obj->callback_->OnClose(obj);
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/win/wavein_input_win.h b/src/media/audio/win/wavein_input_win.h
deleted file mode 100644
index 82f1f55..0000000
--- a/src/media/audio/win/wavein_input_win.h
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_
-#define MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_
-
-#include <string>
-
-#include <windows.h>
-#include <mmsystem.h>
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerWin;
-
-class PCMWaveInAudioInputStream : public AudioInputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the
- // the audio manager who is creating this object and |device_id| which
- // is provided by the operating system.
- PCMWaveInAudioInputStream(AudioManagerWin* manager,
- const AudioParameters& params,
- int num_buffers,
- const std::string& device_id);
- virtual ~PCMWaveInAudioInputStream();
-
- // Implementation of AudioInputStream.
- virtual bool Open() OVERRIDE;
- virtual void Start(AudioInputCallback* callback) OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Close() OVERRIDE;
- // TODO(henrika): Add volume support using the Audio Mixer API.
- virtual double GetMaxVolume() OVERRIDE;
- virtual void SetVolume(double volume) OVERRIDE;
- virtual double GetVolume() OVERRIDE;
- virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
- virtual bool GetAutomaticGainControl() OVERRIDE;
-
- private:
- enum State {
- kStateEmpty, // Initial state.
- kStateReady, // Device obtained and ready to record.
- kStateRecording, // Recording audio.
- kStateStopping, // Trying to stop, waiting for callback to finish.
- kStateStopped, // Stopped. Device was reset.
- kStateClosed // Device has been released.
- };
-
- // Allow unit tests to query the device ID.
- friend class AudioInputDeviceTest;
-
- // Windows calls us back with the recorded audio data here. See msdn
- // documentation for 'waveInProc' for details about the parameters.
- static void CALLBACK WaveCallback(HWAVEIN hwi, UINT msg, DWORD_PTR instance,
- DWORD_PTR param1, DWORD_PTR param2);
-
- // If windows reports an error this function handles it and passes it to
- // the attached AudioInputCallback::OnError().
- void HandleError(MMRESULT error);
-
- // Allocates and prepares the memory that will be used for recording.
- void SetupBuffers();
-
- // Deallocates the memory allocated in SetupBuffers.
- void FreeBuffers();
-
- // Sends a buffer to the audio driver for recording.
- void QueueNextPacket(WAVEHDR* buffer);
-
- // Converts the stored device id string into an unsigned integer which
- // can be used by waveInOpen() to open the specified capture device.
- bool GetDeviceId(UINT* device_index);
-
- // Reader beware. Visual C has stronger guarantees on volatile vars than
- // most people expect. In fact, it has release semantics on write and
- // acquire semantics on reads. See the msdn documentation.
- volatile State state_;
-
- // The audio manager that created this input stream. We notify it when
- // we close so it can release its own resources.
- AudioManagerWin* manager_;
-
- // We use the callback mostly to periodically give the recorded audio data.
- AudioInputCallback* callback_;
-
- // The number of buffers of size |buffer_size_| each to use.
- const int num_buffers_;
-
- // The size in bytes of each audio buffer.
- uint32 buffer_size_;
-
- // Channels, 1 or 2.
- const int channels_;
-
- // Contains the unique name of the selected endpoint device.
- // Note that AudioManagerBase::kDefaultDeviceId represents the default
- // device role and is not a valid ID as such.
- std::string device_id_;
-
- // Windows native structure to encode the format parameters.
- WAVEFORMATEX format_;
-
- // Handle to the instance of the wave device.
- HWAVEIN wavein_;
-
- // Pointer to the first allocated audio buffer. This object owns it.
- WAVEHDR* buffer_;
-
- // An event that is signaled when the callback thread is ready to stop.
- base::win::ScopedHandle stopped_event_;
-
- DISALLOW_COPY_AND_ASSIGN(PCMWaveInAudioInputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_
diff --git a/src/media/audio/win/waveout_output_win.cc b/src/media/audio/win/waveout_output_win.cc
deleted file mode 100644
index f18b2fa..0000000
--- a/src/media/audio/win/waveout_output_win.cc
+++ /dev/null
@@ -1,422 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/audio/win/waveout_output_win.h"
-
-#include <windows.h>
-#include <mmsystem.h>
-#pragma comment(lib, "winmm.lib")
-
-#include "base/basictypes.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_util.h"
-#include "media/audio/win/audio_manager_win.h"
-
-namespace media {
-
-// Some general thoughts about the waveOut API which is badly documented :
-// - We use CALLBACK_EVENT mode in which XP signals events such as buffer
-// releases.
-// - We use RegisterWaitForSingleObject() so one of threads in thread pool
-// automatically calls our callback that feeds more data to Windows.
-// - Windows does not provide a way to query if the device is playing or paused
-// thus it forces you to maintain state, which naturally is not exactly
-// synchronized to the actual device state.
-
-// Sixty four MB is the maximum buffer size per AudioOutputStream.
-static const uint32 kMaxOpenBufferSize = 1024 * 1024 * 64;
-
-// See Also
-// http://www.thx.com/consumer/home-entertainment/home-theater/surround-sound-speaker-set-up/
-// http://en.wikipedia.org/wiki/Surround_sound
-
-static const int kMaxChannelsToMask = 8;
-static const unsigned int kChannelsToMask[kMaxChannelsToMask + 1] = {
- 0,
- // 1 = Mono
- SPEAKER_FRONT_CENTER,
- // 2 = Stereo
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
- // 3 = Stereo + Center
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER,
- // 4 = Quad
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
- SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
- // 5 = 5.0
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
- SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
- // 6 = 5.1
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
- // 7 = 6.1
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
- SPEAKER_BACK_CENTER,
- // 8 = 7.1
- SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
- SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT
- // TODO(fbarchard): Add additional masks for 7.2 and beyond.
-};
-
-inline size_t PCMWaveOutAudioOutputStream::BufferSize() const {
- // Round size of buffer up to the nearest 16 bytes.
- return (sizeof(WAVEHDR) + buffer_size_ + 15u) & static_cast<size_t>(~15);
-}
-
-inline WAVEHDR* PCMWaveOutAudioOutputStream::GetBuffer(int n) const {
- DCHECK_GE(n, 0);
- DCHECK_LT(n, num_buffers_);
- return reinterpret_cast<WAVEHDR*>(&buffers_[n * BufferSize()]);
-}
-
-PCMWaveOutAudioOutputStream::PCMWaveOutAudioOutputStream(
- AudioManagerWin* manager, const AudioParameters& params, int num_buffers,
- UINT device_id)
- : state_(PCMA_BRAND_NEW),
- manager_(manager),
- device_id_(device_id),
- waveout_(NULL),
- callback_(NULL),
- num_buffers_(num_buffers),
- buffer_size_(params.GetBytesPerBuffer()),
- volume_(1),
- channels_(params.channels()),
- pending_bytes_(0),
- waiting_handle_(NULL),
- audio_bus_(AudioBus::Create(params)) {
- format_.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- format_.Format.nChannels = params.channels();
- format_.Format.nSamplesPerSec = params.sample_rate();
- format_.Format.wBitsPerSample = params.bits_per_sample();
- format_.Format.cbSize = sizeof(format_) - sizeof(WAVEFORMATEX);
- // The next are computed from above.
- format_.Format.nBlockAlign = (format_.Format.nChannels *
- format_.Format.wBitsPerSample) / 8;
- format_.Format.nAvgBytesPerSec = format_.Format.nBlockAlign *
- format_.Format.nSamplesPerSec;
- if (params.channels() > kMaxChannelsToMask) {
- format_.dwChannelMask = kChannelsToMask[kMaxChannelsToMask];
- } else {
- format_.dwChannelMask = kChannelsToMask[params.channels()];
- }
- format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- format_.Samples.wValidBitsPerSample = params.bits_per_sample();
-}
-
-PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() {
- DCHECK(NULL == waveout_);
-}
-
-bool PCMWaveOutAudioOutputStream::Open() {
- if (state_ != PCMA_BRAND_NEW)
- return false;
- if (BufferSize() * num_buffers_ > kMaxOpenBufferSize)
- return false;
- if (num_buffers_ < 2 || num_buffers_ > 5)
- return false;
-
- // Create buffer event.
- buffer_event_.Set(::CreateEvent(NULL, // Security attributes.
- FALSE, // It will auto-reset.
- FALSE, // Initial state.
- NULL)); // No name.
- if (!buffer_event_.Get())
- return false;
-
- // Open the device.
- // We'll be getting buffer_event_ events when it's time to refill the buffer.
- MMRESULT result = ::waveOutOpen(
- &waveout_,
- device_id_,
- reinterpret_cast<LPCWAVEFORMATEX>(&format_),
- reinterpret_cast<DWORD_PTR>(buffer_event_.Get()),
- NULL,
- CALLBACK_EVENT);
- if (result != MMSYSERR_NOERROR)
- return false;
-
- SetupBuffers();
- state_ = PCMA_READY;
- return true;
-}
-
-void PCMWaveOutAudioOutputStream::SetupBuffers() {
- buffers_.reset(new char[BufferSize() * num_buffers_]);
- for (int ix = 0; ix != num_buffers_; ++ix) {
- WAVEHDR* buffer = GetBuffer(ix);
- buffer->lpData = reinterpret_cast<char*>(buffer) + sizeof(WAVEHDR);
- buffer->dwBufferLength = buffer_size_;
- buffer->dwBytesRecorded = 0;
- buffer->dwFlags = WHDR_DONE;
- buffer->dwLoops = 0;
- // Tell windows sound drivers about our buffers. Not documented what
- // this does but we can guess that causes the OS to keep a reference to
- // the memory pages so the driver can use them without worries.
- ::waveOutPrepareHeader(waveout_, buffer, sizeof(WAVEHDR));
- }
-}
-
-void PCMWaveOutAudioOutputStream::FreeBuffers() {
- for (int ix = 0; ix != num_buffers_; ++ix) {
- ::waveOutUnprepareHeader(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
- }
- buffers_.reset(NULL);
-}
-
-// Initially we ask the source to fill up all audio buffers. If we don't do
-// this then we would always get the driver callback when it is about to run
-// samples and that would leave too little time to react.
-void PCMWaveOutAudioOutputStream::Start(AudioSourceCallback* callback) {
- if (state_ != PCMA_READY)
- return;
- callback_ = callback;
-
- // Reset buffer event, it can be left in the arbitrary state if we
- // previously stopped the stream. Can happen because we are stopping
- // callbacks before stopping playback itself.
- if (!::ResetEvent(buffer_event_.Get())) {
- HandleError(MMSYSERR_ERROR);
- return;
- }
-
- // Start watching for buffer events.
- if (!::RegisterWaitForSingleObject(&waiting_handle_,
- buffer_event_.Get(),
- &BufferCallback,
- this,
- INFINITE,
- WT_EXECUTEDEFAULT)) {
- HandleError(MMSYSERR_ERROR);
- waiting_handle_ = NULL;
- return;
- }
-
- state_ = PCMA_PLAYING;
-
- // Queue the buffers.
- pending_bytes_ = 0;
- for (int ix = 0; ix != num_buffers_; ++ix) {
- WAVEHDR* buffer = GetBuffer(ix);
- // Caller waits for 1st packet to become available, but not for others,
- // so we wait for them here.
- if (ix != 0)
- callback_->WaitTillDataReady();
- QueueNextPacket(buffer); // Read more data.
- pending_bytes_ += buffer->dwBufferLength;
- }
-
- // From now on |pending_bytes_| would be accessed by callback thread.
- // Most likely waveOutPause() or waveOutRestart() has its own memory barrier,
- // but issuing our own is safer.
- MemoryBarrier();
-
- MMRESULT result = ::waveOutPause(waveout_);
- if (result != MMSYSERR_NOERROR) {
- HandleError(result);
- return;
- }
-
- // Send the buffers to the audio driver. Note that the device is paused
- // so we avoid entering the callback method while still here.
- for (int ix = 0; ix != num_buffers_; ++ix) {
- result = ::waveOutWrite(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
- if (result != MMSYSERR_NOERROR) {
- HandleError(result);
- break;
- }
- }
- result = ::waveOutRestart(waveout_);
- if (result != MMSYSERR_NOERROR) {
- HandleError(result);
- return;
- }
-}
-
-// Stopping is tricky if we want it be fast.
-// For now just do it synchronously and avoid all the complexities.
-// TODO(enal): if we want faster Stop() we can create singleton that keeps track
-// of all currently playing streams. Then you don't have to wait
-// till all callbacks are completed. Of course access to singleton
-// should be under its own lock, and checking the liveness and
-// acquiring the lock on stream should be done atomically.
-void PCMWaveOutAudioOutputStream::Stop() {
- if (state_ != PCMA_PLAYING)
- return;
- state_ = PCMA_STOPPING;
- MemoryBarrier();
-
- // Stop watching for buffer event, wait till all the callbacks are complete.
- // Should be done before ::waveOutReset() call to avoid race condition when
- // callback that is currently active and already checked that stream is still
- // being played calls ::waveOutWrite() after ::waveOutReset() returns, later
- // causing ::waveOutClose() to fail with WAVERR_STILLPLAYING.
- // TODO(enal): that delays actual stopping of playback. Alternative can be
- // to call ::waveOutReset() twice, once before
- // ::UnregisterWaitEx() and once after.
- if (waiting_handle_) {
- if (!::UnregisterWaitEx(waiting_handle_, INVALID_HANDLE_VALUE)) {
- state_ = PCMA_PLAYING;
- HandleError(MMSYSERR_ERROR);
- return;
- }
- waiting_handle_ = NULL;
- }
-
- // Stop playback.
- MMRESULT res = ::waveOutReset(waveout_);
- if (res != MMSYSERR_NOERROR) {
- state_ = PCMA_PLAYING;
- HandleError(res);
- return;
- }
-
- // Wait for lock to ensure all outstanding callbacks have completed.
- base::AutoLock auto_lock(lock_);
-
- // waveOutReset() leaves buffers in the unpredictable state, causing
- // problems if we want to close, release, or reuse them. Fix the states.
- for (int ix = 0; ix != num_buffers_; ++ix) {
- GetBuffer(ix)->dwFlags = WHDR_PREPARED;
- }
-
- // Don't use callback after Stop().
- callback_ = NULL;
-
- state_ = PCMA_READY;
-}
-
-// We can Close in any state except that trying to close a stream that is
-// playing Windows generates an error. We cannot propagate it to the source,
-// as callback_ is set to NULL. Just print it and hope somebody somehow
-// will find it...
-void PCMWaveOutAudioOutputStream::Close() {
- Stop(); // Just to be sure. No-op if not playing.
- if (waveout_) {
- MMRESULT result = ::waveOutClose(waveout_);
- // If ::waveOutClose() fails we cannot just delete the stream, callback
- // may try to access it and would crash. Better to leak the stream.
- if (result != MMSYSERR_NOERROR) {
- HandleError(result);
- state_ = PCMA_PLAYING;
- return;
- }
- state_ = PCMA_CLOSED;
- waveout_ = NULL;
- FreeBuffers();
- }
- // Tell the audio manager that we have been released. This can result in
- // the manager destroying us in-place so this needs to be the last thing
- // we do on this function.
- manager_->ReleaseOutputStream(this);
-}
-
-void PCMWaveOutAudioOutputStream::SetVolume(double volume) {
- if (!waveout_)
- return;
- volume_ = static_cast<float>(volume);
-}
-
-void PCMWaveOutAudioOutputStream::GetVolume(double* volume) {
- if (!waveout_)
- return;
- *volume = volume_;
-}
-
-void PCMWaveOutAudioOutputStream::HandleError(MMRESULT error) {
- DLOG(WARNING) << "PCMWaveOutAudio error " << error;
- if (callback_)
- callback_->OnError(this, error);
-}
-
-void PCMWaveOutAudioOutputStream::QueueNextPacket(WAVEHDR *buffer) {
- DCHECK_EQ(channels_, format_.Format.nChannels);
- // Call the source which will fill our buffer with pleasant sounds and
- // return to us how many bytes were used.
- // TODO(fbarchard): Handle used 0 by queueing more.
-
- // HACK: Yield if Read() is called too often. On older platforms which are
- // still using the WaveOut backend, we run into synchronization issues where
- // the renderer has not finished filling the shared memory when Read() is
- // called. Reading too early will lead to clicks and pops. See issues:
- // http://crbug.com/161307 and http://crbug.com/61022
- callback_->WaitTillDataReady();
-
- // TODO(sergeyu): Specify correct hardware delay for AudioBuffersState.
- int frames_filled = callback_->OnMoreData(
- audio_bus_.get(), AudioBuffersState(pending_bytes_, 0));
- uint32 used = frames_filled * audio_bus_->channels() *
- format_.Format.wBitsPerSample / 8;
-
- if (used <= buffer_size_) {
- // Note: If this ever changes to output raw float the data must be clipped
- // and sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, format_.Format.wBitsPerSample / 8, buffer->lpData);
-
- buffer->dwBufferLength = used * format_.Format.nChannels / channels_;
- media::AdjustVolume(buffer->lpData, used,
- format_.Format.nChannels,
- format_.Format.wBitsPerSample >> 3,
- volume_);
- } else {
- HandleError(0);
- return;
- }
- buffer->dwFlags = WHDR_PREPARED;
-}
-
-// One of the threads in our thread pool asynchronously calls this function when
-// buffer_event_ is signalled. Search through all the buffers looking for freed
-// ones, fills them with data, and "feed" the Windows.
-// Note: by searching through all the buffers we guarantee that we fill all the
-// buffers, even when "event loss" happens, i.e. if Windows signals event
-// when it did not flip into unsignaled state from the previous signal.
-void NTAPI PCMWaveOutAudioOutputStream::BufferCallback(PVOID lpParameter,
- BOOLEAN timer_fired) {
- TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::BufferCallback");
-
- DCHECK(!timer_fired);
- PCMWaveOutAudioOutputStream* stream =
- reinterpret_cast<PCMWaveOutAudioOutputStream*>(lpParameter);
-
- // Lock the stream so callbacks do not interfere with each other.
- // Several callbacks can be called simultaneously by different threads in the
- // thread pool if some of the callbacks are slow, or system is very busy and
- // scheduled callbacks are not called on time.
- base::AutoLock auto_lock(stream->lock_);
- if (stream->state_ != PCMA_PLAYING)
- return;
-
- for (int ix = 0; ix != stream->num_buffers_; ++ix) {
- WAVEHDR* buffer = stream->GetBuffer(ix);
- if (buffer->dwFlags & WHDR_DONE) {
- // Before we queue the next packet, we need to adjust the number of
- // pending bytes since the last write to hardware.
- stream->pending_bytes_ -= buffer->dwBufferLength;
- stream->QueueNextPacket(buffer);
-
- // QueueNextPacket() can take a long time, especially if several of them
- // were called back-to-back. Check if we are stopping now.
- if (stream->state_ != PCMA_PLAYING)
- return;
-
- // Time to send the buffer to the audio driver. Since we are reusing
- // the same buffers we can get away without calling waveOutPrepareHeader.
- MMRESULT result = ::waveOutWrite(stream->waveout_,
- buffer,
- sizeof(WAVEHDR));
- if (result != MMSYSERR_NOERROR)
- stream->HandleError(result);
- stream->pending_bytes_ += buffer->dwBufferLength;
- }
- }
-}
-
-} // namespace media
diff --git a/src/media/audio/win/waveout_output_win.h b/src/media/audio/win/waveout_output_win.h
deleted file mode 100644
index a62fcfc..0000000
--- a/src/media/audio/win/waveout_output_win.h
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_WAVEOUT_OUTPUT_WIN_H_
-#define MEDIA_AUDIO_WIN_WAVEOUT_OUTPUT_WIN_H_
-
-#include <windows.h>
-#include <mmsystem.h>
-#include <mmreg.h>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/win/scoped_handle.h"
-#include "media/audio/audio_io.h"
-#include "media/audio/audio_parameters.h"
-
-namespace media {
-
-class AudioManagerWin;
-
-// Implements PCM audio output support for Windows using the WaveXXX API.
-// While not as nice as the DirectSound-based API, it should work in all target
-// operating systems regardless or DirectX version installed. It is known that
-// in some machines WaveXXX based audio is better while in others DirectSound
-// is better.
-//
-// Important: the OnXXXX functions in AudioSourceCallback are called by more
-// than one thread so it is important to have some form of synchronization if
-// you are keeping state in it.
-class PCMWaveOutAudioOutputStream : public AudioOutputStream {
- public:
- // The ctor takes all the usual parameters, plus |manager| which is the the
- // audio manager who is creating this object and |device_id| which is provided
- // by the operating system.
- PCMWaveOutAudioOutputStream(AudioManagerWin* manager,
- const AudioParameters& params,
- int num_buffers,
- UINT device_id);
- virtual ~PCMWaveOutAudioOutputStream();
-
- // Implementation of AudioOutputStream.
- virtual bool Open();
- virtual void Close();
- virtual void Start(AudioSourceCallback* callback);
- virtual void Stop();
- virtual void SetVolume(double volume);
- virtual void GetVolume(double* volume);
-
- // Sends a buffer to the audio driver for playback.
- void QueueNextPacket(WAVEHDR* buffer);
-
- private:
- enum State {
- PCMA_BRAND_NEW, // Initial state.
- PCMA_READY, // Device obtained and ready to play.
- PCMA_PLAYING, // Playing audio.
- PCMA_STOPPING, // Audio is stopping, do not "feed" data to Windows.
- PCMA_CLOSED // Device has been released.
- };
-
- // Returns pointer to the n-th buffer.
- inline WAVEHDR* GetBuffer(int n) const;
-
- // Size of one buffer in bytes, rounded up if necessary.
- inline size_t BufferSize() const;
-
- // Windows calls us back asking for more data when buffer_event_ signalled.
- // See MSDN for help on RegisterWaitForSingleObject() and waveOutOpen().
- static void NTAPI BufferCallback(PVOID lpParameter, BOOLEAN timer_fired);
-
- // If windows reports an error this function handles it and passes it to
- // the attached AudioSourceCallback::OnError().
- void HandleError(MMRESULT error);
-
- // Allocates and prepares the memory that will be used for playback.
- void SetupBuffers();
-
- // Deallocates the memory allocated in SetupBuffers.
- void FreeBuffers();
-
- // Reader beware. Visual C has stronger guarantees on volatile vars than
- // most people expect. In fact, it has release semantics on write and
- // acquire semantics on reads. See the msdn documentation.
- volatile State state_;
-
- // The audio manager that created this output stream. We notify it when
- // we close so it can release its own resources.
- AudioManagerWin* manager_;
-
- // We use the callback mostly to periodically request more audio data.
- AudioSourceCallback* callback_;
-
- // The number of buffers of size |buffer_size_| each to use.
- const int num_buffers_;
-
- // The size in bytes of each audio buffer, we usually have two of these.
- uint32 buffer_size_;
-
- // Volume level from 0 to 1.
- float volume_;
-
- // Channels from 0 to 8.
- const int channels_;
-
- // Number of bytes yet to be played in the hardware buffer.
- uint32 pending_bytes_;
-
- // The id assigned by the operating system to the selected wave output
- // hardware device. Usually this is just -1 which means 'default device'.
- UINT device_id_;
-
- // Windows native structure to encode the format parameters.
- WAVEFORMATPCMEX format_;
-
- // Handle to the instance of the wave device.
- HWAVEOUT waveout_;
-
- // Handle to the buffer event.
- base::win::ScopedHandle buffer_event_;
-
- // Handle returned by RegisterWaitForSingleObject().
- HANDLE waiting_handle_;
-
- // Pointer to the allocated audio buffers, we allocate all buffers in one big
- // chunk. This object owns them.
- scoped_array<char> buffers_;
-
- // Lock used to avoid the conflict when callbacks are called simultaneously.
- base::Lock lock_;
-
- // Container for retrieving data from AudioSourceCallback::OnMoreData().
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(PCMWaveOutAudioOutputStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_AUDIO_WIN_WAVEOUT_OUTPUT_WIN_H_
diff --git a/src/media/base/audio_bus.cc b/src/media/base/audio_bus.cc
deleted file mode 100644
index adf7de9..0000000
--- a/src/media/base/audio_bus.cc
+++ /dev/null
@@ -1,358 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_bus.h"
-
-#include <limits>
-
-#include "base/logging.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/limits.h"
-
-namespace media {
-
-static bool IsAligned(void* ptr) {
- return (reinterpret_cast<uintptr_t>(ptr) &
- (AudioBus::kChannelAlignment - 1)) == 0U;
-}
-
-// Calculates the required size for an AudioBus with the given params, sets
-// |aligned_frames| to the actual frame length of each channel array.
-static int CalculateMemorySizeInternal(int channels, int frames,
- int* out_aligned_frames) {
- // Choose a size such that each channel will be aligned by
- // kChannelAlignment when stored in a contiguous block.
- int aligned_frames =
- ((frames * sizeof(float) + AudioBus::kChannelAlignment - 1) &
- ~(AudioBus::kChannelAlignment - 1)) / sizeof(float);
-
- if (out_aligned_frames)
- *out_aligned_frames = aligned_frames;
-
- return sizeof(float) * channels * aligned_frames;
-}
-
-// |Format| is the destination type, |Fixed| is a type larger than |Format|
-// such that operations can be made without overflowing.
-template<class Format, class Fixed>
-static void FromInterleavedInternal(const void* src, int start_frame,
- int frames, AudioBus* dest) {
- const Format* source = static_cast<const Format*>(src);
-
- static const Fixed kBias = std::numeric_limits<Format>::is_signed ? 0 :
- std::numeric_limits<Format>::max() / 2 + 1;
- static const float kMaxScale = 1.0f / (kBias ? kBias - 1 :
- std::numeric_limits<Format>::max());
- static const float kMinScale = 1.0f / (kBias ? kBias :
- -static_cast<Fixed>(std::numeric_limits<Format>::min()));
-
- int channels = dest->channels();
- for (int ch = 0; ch < channels; ++ch) {
- float* channel_data = dest->channel(ch);
- for (int i = start_frame, offset = ch; i < start_frame + frames;
- ++i, offset += channels) {
- Fixed v = static_cast<Fixed>(source[offset]) - kBias;
- channel_data[i] = v * (v < 0 ? kMinScale : kMaxScale);
- }
- }
-}
-
-// |Format| is the destination type, |Fixed| is a type larger than |Format|
-// such that operations can be made without overflowing.
-template<class Format, class Fixed>
-static void ToInterleavedInternal(const AudioBus* source, int start_frame,
- int frames, void* dst) {
- Format* dest = static_cast<Format*>(dst);
-
- static const Format kBias = std::numeric_limits<Format>::is_signed ? 0 :
- std::numeric_limits<Format>::max() / 2 + 1;
- static const Fixed kMaxValue = kBias ? kBias - 1 :
- std::numeric_limits<Format>::max();
- static const Fixed kMinValue = kBias ? -kBias :
- std::numeric_limits<Format>::min();
-
- int channels = source->channels();
- for (int ch = 0; ch < channels; ++ch) {
- const float* channel_data = source->channel(ch);
- for (int i = start_frame, offset = ch; i < frames;
- ++i, offset += channels) {
- float v = channel_data[i];
- Fixed sample = v * (v < 0 ? -kMinValue : kMaxValue);
-
- if (sample > kMaxValue)
- sample = kMaxValue;
- else if (sample < kMinValue)
- sample = kMinValue;
-
- dest[offset] = static_cast<Format>(sample) + kBias;
- }
- }
-}
-
-static void ValidateConfig(int channels, int frames) {
- CHECK_GT(frames, 0);
- CHECK_GT(channels, 0);
- CHECK_LE(channels, limits::kMaxChannels);
-}
-
-static void CheckOverflow(int start_frame, int frames, int total_frames) {
- CHECK_GE(start_frame, 0);
- CHECK_GE(frames, 0);
- CHECK_GT(total_frames, 0);
- int sum = start_frame + frames;
- CHECK_LE(sum, total_frames);
- CHECK_GE(sum, 0);
-}
-
-AudioBus::AudioBus(int channels, int frames)
- : frames_(frames),
- can_set_channel_data_(false) {
- ValidateConfig(channels, frames_);
-
- int aligned_frames = 0;
- int size = CalculateMemorySizeInternal(channels, frames, &aligned_frames);
-
- data_.reset(static_cast<float*>(base::AlignedAlloc(
- size, AudioBus::kChannelAlignment)));
-
- BuildChannelData(channels, aligned_frames, data_.get());
-}
-
-AudioBus::AudioBus(int channels, int frames, float* data)
- : frames_(frames),
- can_set_channel_data_(false) {
- ValidateConfig(channels, frames_);
-
- int aligned_frames = 0;
- CalculateMemorySizeInternal(channels, frames, &aligned_frames);
-
- BuildChannelData(channels, aligned_frames, data);
-}
-
-AudioBus::AudioBus(int frames, const std::vector<float*>& channel_data)
- : channel_data_(channel_data),
- frames_(frames),
- can_set_channel_data_(false) {
- ValidateConfig(channel_data_.size(), frames_);
-
- // Sanity check wrapped vector for alignment and channel count.
- for (size_t i = 0; i < channel_data_.size(); ++i)
- DCHECK(IsAligned(channel_data_[i]));
-}
-
-AudioBus::AudioBus(int channels)
- : channel_data_(channels),
- frames_(0),
- can_set_channel_data_(true) {
- for (size_t i = 0; i < channel_data_.size(); ++i)
- channel_data_[i] = NULL;
-}
-
-AudioBus::~AudioBus() {}
-
-scoped_ptr<AudioBus> AudioBus::Create(int channels, int frames) {
- return scoped_ptr<AudioBus>(new AudioBus(channels, frames));
-}
-
-scoped_ptr<AudioBus> AudioBus::Create(const AudioParameters& params) {
- return scoped_ptr<AudioBus>(new AudioBus(
- params.channels(), params.frames_per_buffer()));
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-scoped_ptr<AudioBus> AudioBus::Create(int channels, int frames_per_channel,
- int bytes_per_frame, bool interleaved) {
- // AudioBus treats everything in float so we have to convert.
- uint32 float_frame_per_channel =
- frames_per_channel * bytes_per_frame / sizeof(float);
- if (interleaved)
- return Create(1, channels * float_frame_per_channel);
-
- return Create(channels, float_frame_per_channel);
-}
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-scoped_ptr<AudioBus> AudioBus::CreateWrapper(int channels) {
- return scoped_ptr<AudioBus>(new AudioBus(channels));
-}
-
-scoped_ptr<AudioBus> AudioBus::WrapVector(
- int frames, const std::vector<float*>& channel_data) {
- return scoped_ptr<AudioBus>(new AudioBus(frames, channel_data));
-}
-
-scoped_ptr<AudioBus> AudioBus::WrapMemory(int channels, int frames,
- void* data) {
- // |data| must be aligned by AudioBus::kChannelAlignment.
- CHECK(IsAligned(data));
- return scoped_ptr<AudioBus>(new AudioBus(
- channels, frames, static_cast<float*>(data)));
-}
-
-scoped_ptr<AudioBus> AudioBus::WrapMemory(const AudioParameters& params,
- void* data) {
- // |data| must be aligned by AudioBus::kChannelAlignment.
- CHECK(IsAligned(data));
- return scoped_ptr<AudioBus>(new AudioBus(
- params.channels(), params.frames_per_buffer(),
- static_cast<float*>(data)));
-}
-
-void AudioBus::SetChannelData(int channel, float* data) {
- CHECK(can_set_channel_data_);
- CHECK_GE(channel, 0);
- CHECK_LT(static_cast<size_t>(channel), channel_data_.size());
- DCHECK(IsAligned(data));
- channel_data_[channel] = data;
-}
-
-void AudioBus::set_frames(int frames) {
- CHECK(can_set_channel_data_);
- frames_ = frames;
-}
-
-void AudioBus::ZeroFramesPartial(int start_frame, int frames) {
- CheckOverflow(start_frame, frames, frames_);
-
- if (frames <= 0)
- return;
-
- for (size_t i = 0; i < channel_data_.size(); ++i) {
- memset(channel_data_[i] + start_frame, 0,
- frames * sizeof(*channel_data_[i]));
- }
-}
-
-void AudioBus::ZeroFrames(int frames) {
- ZeroFramesPartial(0, frames);
-}
-
-void AudioBus::Zero() {
- ZeroFrames(frames_);
-}
-
-int AudioBus::CalculateMemorySize(const AudioParameters& params) {
- return CalculateMemorySizeInternal(
- params.channels(), params.frames_per_buffer(), NULL);
-}
-
-int AudioBus::CalculateMemorySize(int channels, int frames) {
- return CalculateMemorySizeInternal(channels, frames, NULL);
-}
-
-void AudioBus::BuildChannelData(int channels, int aligned_frames, float* data) {
- DCHECK(IsAligned(data));
- DCHECK_EQ(channel_data_.size(), 0U);
- // Separate audio data out into channels for easy lookup later. Figure out
- channel_data_.reserve(channels);
- for (int i = 0; i < channels; ++i)
- channel_data_.push_back(data + i * aligned_frames);
-}
-
-// TODO(dalecurtis): See if intrinsic optimizations help any here.
-void AudioBus::FromInterleavedPartial(const void* source, int start_frame,
- int frames, int bytes_per_sample) {
- CheckOverflow(start_frame, frames, frames_);
- switch (bytes_per_sample) {
- case 1:
- FromInterleavedInternal<uint8, int16>(source, start_frame, frames, this);
- break;
- case 2:
- FromInterleavedInternal<int16, int32>(source, start_frame, frames, this);
- break;
- case 4:
- FromInterleavedInternal<int32, int64>(source, start_frame, frames, this);
- break;
- default:
- NOTREACHED() << "Unsupported bytes per sample encountered.";
- ZeroFramesPartial(start_frame, frames);
- return;
- }
-
- // Don't clear remaining frames if this is a partial deinterleave.
- if (!start_frame) {
- // Zero any remaining frames.
- ZeroFramesPartial(frames, frames_ - frames);
- }
-}
-
-void AudioBus::FromInterleaved(const void* source, int frames,
- int bytes_per_sample) {
- FromInterleavedPartial(source, 0, frames, bytes_per_sample);
-}
-
-void AudioBus::ToInterleaved(int frames, int bytes_per_sample,
- void* dest) const {
- ToInterleavedPartial(0, frames, bytes_per_sample, dest);
-}
-
-// TODO(dalecurtis): See if intrinsic optimizations help any here.
-void AudioBus::ToInterleavedPartial(int start_frame, int frames,
- int bytes_per_sample, void* dest) const {
- CheckOverflow(start_frame, frames, frames_);
- switch (bytes_per_sample) {
- case 1:
- ToInterleavedInternal<uint8, int16>(this, start_frame, frames, dest);
- break;
- case 2:
- ToInterleavedInternal<int16, int32>(this, start_frame, frames, dest);
- break;
- case 4:
- ToInterleavedInternal<int32, int64>(this, start_frame, frames, dest);
- break;
- default:
- NOTREACHED() << "Unsupported bytes per sample encountered.";
- memset(dest, 0, frames * bytes_per_sample);
- return;
- }
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-void AudioBus::FromInterleavedFloat(const float* source, int frames,
- int audio_bus_offset) {
- DCHECK_LE(frames + audio_bus_offset, this->frames());
-
- while (frames > 0) {
- for (int channel = 0; channel < channels(); ++channel) {
- this->channel(channel)[audio_bus_offset] = *source;
- ++source;
- }
- ++audio_bus_offset;
- --frames;
- }
-}
-
-void AudioBus::ToInterleavedFloat(
- int frames, int audio_bus_offset, int extra_channels, float* dest) const {
- DCHECK_LE(frames + audio_bus_offset, this->frames());
- DCHECK_GE(extra_channels, 0);
-
- while (frames > 0) {
- for (int channel = 0; channel < channels(); ++channel) {
- *dest = this->channel(channel)[audio_bus_offset];
- ++dest;
- }
- for (int channel = 0; channel < extra_channels; ++channel) {
- *dest = 0.f;
- ++dest;
- }
- ++audio_bus_offset;
- --frames;
- }
-}
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-void AudioBus::CopyTo(AudioBus* dest) const {
- CHECK_EQ(channels(), dest->channels());
- CHECK_EQ(frames(), dest->frames());
-
- // Since we don't know if the other AudioBus is wrapped or not (and we don't
- // want to care), just copy using the public channel() accessors.
- for (int i = 0; i < channels(); ++i)
- memcpy(dest->channel(i), channel(i), sizeof(*channel(i)) * frames());
-}
-
-} // namespace media
diff --git a/src/media/base/audio_bus.h b/src/media/base/audio_bus.h
deleted file mode 100644
index 67b4ef6..0000000
--- a/src/media/base/audio_bus.h
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_BUS_H_
-#define MEDIA_BASE_AUDIO_BUS_H_
-
-#include <limits>
-#include <vector>
-
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-class AudioParameters;
-
-// Scoped container for "busing" audio channel data around. Each channel is
-// stored in planar format and guaranteed to be aligned by kChannelAlignment.
-// AudioBus objects can be created normally or via wrapping. Normally, AudioBus
-// will dice up a contiguous memory block for channel data. When wrapped,
-// AudioBus instead routes requests for channel data to the wrapped object.
-class MEDIA_EXPORT AudioBus {
- public:
-#if defined(__LB_PS3__)
- // We interleave our data already, but all data is floats, so we require that
- // no frame be smaller than 4 bytes.
- enum { kChannelAlignment = 4 };
-#else
- // Guaranteed alignment of each channel's data; use 16-byte alignment for easy
- // SSE optimizations.
- enum { kChannelAlignment = 16 };
-#endif
-
- // Creates a new AudioBus and allocates |channels| of length |frames|. Uses
- // channels() and frames_per_buffer() from AudioParameters if given.
- static scoped_ptr<AudioBus> Create(int channels, int frames);
- static scoped_ptr<AudioBus> Create(const AudioParameters& params);
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- static scoped_ptr<AudioBus> Create(int channels, int frames_per_channel,
- int bytes_per_frame, bool interleaved);
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // Creates a new AudioBus with the given number of channels, but zero length.
- // It's expected to be used with SetChannelData() and set_frames() to
- // wrap externally allocated memory.
- static scoped_ptr<AudioBus> CreateWrapper(int channels);
-
- // Creates a new AudioBus from an existing channel vector. Does not transfer
- // ownership of |channel_data| to AudioBus; i.e., |channel_data| must outlive
- // the returned AudioBus. Each channel must be aligned by kChannelAlignment.
- static scoped_ptr<AudioBus> WrapVector(
- int frames, const std::vector<float*>& channel_data);
-
- // Creates a new AudioBus by wrapping an existing block of memory. Block must
- // be at least CalculateMemorySize() bytes in size. |data| must outlive the
- // returned AudioBus. |data| must be aligned by kChannelAlignment.
- static scoped_ptr<AudioBus> WrapMemory(int channels, int frames, void* data);
- static scoped_ptr<AudioBus> WrapMemory(const AudioParameters& params,
- void* data);
- // Returns the required memory size to use the WrapMemory() method.
- static int CalculateMemorySize(const AudioParameters& params);
-
- // Calculates the required size for an AudioBus given the number of channels
- // and frames.
- static int CalculateMemorySize(int channels, int frames);
-
- // Helper methods for converting an AudioBus from and to interleaved integer
- // data. Expects interleaving to be [ch0, ch1, ..., chN, ch0, ch1, ...] with
- // |bytes_per_sample| per value. Values are scaled and bias corrected during
- // conversion. ToInterleaved() will also clip values to format range.
- // Handles uint8, int16, and int32 currently. FromInterleaved() will zero out
- // any unfilled frames when |frames| is less than frames().
- void FromInterleaved(const void* source, int frames, int bytes_per_sample);
- void ToInterleaved(int frames, int bytes_per_sample, void* dest) const;
- void ToInterleavedPartial(int start_frame, int frames, int bytes_per_sample,
- void* dest) const;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // The following two functions work on float samples instead of integer
- // samples.
- // FromInterleavedFloat fills the audio bus with interleaved samples. It is
- // possible to fill frames in the middle of the audio bus by using a non-zero
- // "audio_bus_offset". Note that it will not fill the rest samples with 0.
- // "frames" indicates frame count per channel instead of the combined frames.
- void FromInterleavedFloat(const float* source, int frames,
- int audio_bus_offset);
- // ToInterleavedFloat will interleave data from the audio bus and store them
- // into dest.
- // "frames" indicates frame count per channel instead of the combined frames.
- // It is an error if the requested frame is larger than what the audio bus
- // can offer.
- // "extra_channels" has to be greater than or equal to 0. A non-zero value
- // indicates that there are more channels in the "dest" than in this audio bus
- // and they will be filled with 0.
- void ToInterleavedFloat(int frames, int audio_bus_offset, int extra_channels,
- float* dest) const;
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // Similar to FromInterleaved() above, but meant for streaming sources. Does
- // not zero out remaining frames, the caller is responsible for doing so using
- // ZeroFramesPartial(). Frames are deinterleaved from the start of |source|
- // to channel(x)[start_frame].
- void FromInterleavedPartial(const void* source, int start_frame, int frames,
- int bytes_per_sample);
-
- // Helper method for copying channel data from one AudioBus to another. Both
- // AudioBus object must have the same frames() and channels().
- void CopyTo(AudioBus* dest) const;
-
- // Returns a raw pointer to the requested channel. Pointer is guaranteed to
- // have a 16-byte alignment. Warning: Do not rely on having sane (i.e. not
- // inf, nan, or between [-1.0, 1.0]) values in the channel data.
- float* channel(int channel) {
- return channel_data_[static_cast<size_t>(channel)];
- }
- const float* channel(int channel) const {
- return channel_data_[static_cast<size_t>(channel)];
- }
- void SetChannelData(int channel, float* data);
-
- int channels() const {
- DCHECK_LE(channel_data_.size(),
- static_cast<size_t>(std::numeric_limits<int>::max()));
- return static_cast<int>(channel_data_.size());
- }
-
- int frames() const { return frames_; }
- void set_frames(int frames);
-
- // Helper method for zeroing out all channels of audio data.
- void Zero();
- void ZeroFrames(int frames);
- void ZeroFramesPartial(int start_frame, int frames);
-
- private:
- friend class scoped_ptr<AudioBus>;
- ~AudioBus();
-
- AudioBus(int channels, int frames);
- AudioBus(int channels, int frames, float* data);
- AudioBus(int frames, const std::vector<float*>& channel_data);
- explicit AudioBus(int channels);
-
- // Helper method for building |channel_data_| from a block of memory. |data|
- // must be at least BlockSize() bytes in size.
- void BuildChannelData(int channels, int aligned_frame, float* data);
-
- // Contiguous block of channel memory.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data_;
-
- std::vector<float*> channel_data_;
- int frames_;
-
- // Protect SetChannelData() and set_frames() for use by CreateWrapper().
- bool can_set_channel_data_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioBus);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_BUS_H_
diff --git a/src/media/base/audio_bus_unittest.cc b/src/media/base/audio_bus_unittest.cc
deleted file mode 100644
index 7b88019..0000000
--- a/src/media/base/audio_bus_unittest.cc
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <limits>
-
-#include "base/stringprintf.h"
-#include "base/time.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_bus.h"
-#include "media/base/channel_layout.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kChannels = 6;
-static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_5_1;
-// Use a buffer size which is intentionally not a multiple of kChannelAlignment.
-static const int kFrameCount = media::AudioBus::kChannelAlignment * 32 - 1;
-static const int kSampleRate = 48000;
-
-class AudioBusTest : public testing::Test {
- public:
- AudioBusTest() {}
- ~AudioBusTest() {
- for (size_t i = 0; i < data_.size(); ++i)
- base::AlignedFree(data_[i]);
- }
-
- // Validate parameters returned by AudioBus v.s. the constructed parameters.
- void VerifyParams(AudioBus* bus) {
- EXPECT_EQ(kChannels, bus->channels());
- EXPECT_EQ(kFrameCount, bus->frames());
- }
-
- void VerifyValue(const float data[], int size, float value) {
- for (int i = 0; i < size; ++i)
- ASSERT_FLOAT_EQ(value, data[i]) << "i=" << i;
- }
-
- // Verify values for each channel in |result| against |expected|.
- void VerifyBus(const AudioBus* result, const AudioBus* expected) {
- ASSERT_EQ(expected->channels(), result->channels());
- ASSERT_EQ(expected->frames(), result->frames());
- for (int ch = 0; ch < result->channels(); ++ch) {
- for (int i = 0; i < result->frames(); ++i) {
- SCOPED_TRACE(base::StringPrintf("ch=%d, i=%d", ch, i));
- ASSERT_FLOAT_EQ(expected->channel(ch)[i], result->channel(ch)[i]);
- }
- }
- }
-
- // Read and write to the full extent of the allocated channel data. Also test
- // the Zero() method and verify it does as advertised. Also test data if data
- // is 16-byte aligned as advertised (see kChannelAlignment in audio_bus.h).
- void VerifyChannelData(AudioBus* bus) {
- for (int i = 0; i < bus->channels(); ++i) {
- ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(
- bus->channel(i)) & (AudioBus::kChannelAlignment - 1));
- std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i);
- }
-
- for (int i = 0; i < bus->channels(); ++i)
- VerifyValue(bus->channel(i), bus->frames(), i);
-
- bus->Zero();
- for (int i = 0; i < bus->channels(); ++i)
- VerifyValue(bus->channel(i), bus->frames(), 0);
- }
-
- // Verify copying to and from |bus1| and |bus2|.
- void CopyTest(AudioBus* bus1, AudioBus* bus2) {
- // Fill |bus1| with dummy data.
- for (int i = 0; i < bus1->channels(); ++i)
- std::fill(bus1->channel(i), bus1->channel(i) + bus1->frames(), i);
-
- // Verify copy from |bus1| to |bus2|.
- bus2->Zero();
- bus1->CopyTo(bus2);
- VerifyBus(bus1, bus2);
-
- // Verify copy from |bus2| to |bus1|.
- bus1->Zero();
- bus2->CopyTo(bus1);
- VerifyBus(bus2, bus1);
- }
-
- protected:
- std::vector<float*> data_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioBusTest);
-};
-
-// Verify basic Create(...) method works as advertised.
-TEST_F(AudioBusTest, Create) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);
- VerifyParams(bus.get());
- VerifyChannelData(bus.get());
-}
-
-// Verify Create(...) using AudioParameters works as advertised.
-TEST_F(AudioBusTest, CreateUsingAudioParameters) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(AudioParameters(
- AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32,
- kFrameCount));
- VerifyParams(bus.get());
- VerifyChannelData(bus.get());
-}
-
-// Verify an AudioBus created via wrapping a vector works as advertised.
-TEST_F(AudioBusTest, WrapVector) {
- data_.reserve(kChannels);
- for (int i = 0; i < kChannels; ++i) {
- data_.push_back(static_cast<float*>(base::AlignedAlloc(
- sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment)));
- }
-
- scoped_ptr<AudioBus> bus = AudioBus::WrapVector(kFrameCount, data_);
- VerifyParams(bus.get());
- VerifyChannelData(bus.get());
-}
-
-// Verify an AudioBus created via wrapping a memory block works as advertised.
-TEST_F(AudioBusTest, WrapMemory) {
- AudioParameters params(
- AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32,
- kFrameCount);
- int data_size = AudioBus::CalculateMemorySize(params);
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data(static_cast<float*>(
- base::AlignedAlloc(data_size, AudioBus::kChannelAlignment)));
-
- // Fill the memory with a test value we can check for after wrapping.
- static const float kTestValue = 3;
- std::fill(
- data.get(), data.get() + data_size / sizeof(*data.get()), kTestValue);
-
- scoped_ptr<AudioBus> bus = AudioBus::WrapMemory(params, data.get());
- // Verify the test value we filled prior to wrapping.
- for (int i = 0; i < bus->channels(); ++i)
- VerifyValue(bus->channel(i), bus->frames(), kTestValue);
- VerifyParams(bus.get());
- VerifyChannelData(bus.get());
-
- // Verify the channel vectors lie within the provided memory block.
- EXPECT_GE(bus->channel(0), data.get());
- EXPECT_LE(bus->channel(bus->channels() - 1) + bus->frames(),
- data.get() + data_size / sizeof(*data.get()));
-}
-
-// Simulate a shared memory transfer and verify results.
-TEST_F(AudioBusTest, CopyTo) {
- // Create one bus with AudioParameters and the other through direct values to
- // test for parity between the Create() functions.
- AudioParameters params(
- AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32,
- kFrameCount);
- scoped_ptr<AudioBus> bus1 = AudioBus::Create(kChannels, kFrameCount);
- scoped_ptr<AudioBus> bus2 = AudioBus::Create(params);
-
- {
- SCOPED_TRACE("Created");
- CopyTest(bus1.get(), bus2.get());
- }
- {
- SCOPED_TRACE("Wrapped Vector");
- // Try a copy to an AudioBus wrapping a vector.
- data_.reserve(kChannels);
- for (int i = 0; i < kChannels; ++i) {
- data_.push_back(static_cast<float*>(base::AlignedAlloc(
- sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment)));
- }
-
- bus2 = AudioBus::WrapVector(kFrameCount, data_);
- CopyTest(bus1.get(), bus2.get());
- }
- {
- SCOPED_TRACE("Wrapped Memory");
- // Try a copy to an AudioBus wrapping a memory block.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data(
- static_cast<float*>(base::AlignedAlloc(
- AudioBus::CalculateMemorySize(params),
- AudioBus::kChannelAlignment)));
-
- bus2 = AudioBus::WrapMemory(params, data.get());
- CopyTest(bus1.get(), bus2.get());
- }
-}
-
-// Verify Zero() and ZeroFrames(...) utility methods work as advertised.
-TEST_F(AudioBusTest, Zero) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);
-
- // Fill the bus with dummy data.
- for (int i = 0; i < bus->channels(); ++i)
- std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);
-
- // Zero first half the frames of each channel.
- bus->ZeroFrames(kFrameCount / 2);
- for (int i = 0; i < bus->channels(); ++i) {
- SCOPED_TRACE("First Half Zero");
- VerifyValue(bus->channel(i), kFrameCount / 2, 0);
- VerifyValue(bus->channel(i) + kFrameCount / 2,
- kFrameCount - kFrameCount / 2, i + 1);
- }
-
- // Fill the bus with dummy data.
- for (int i = 0; i < bus->channels(); ++i)
- std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);
-
- // Zero the last half of the frames.
- bus->ZeroFramesPartial(kFrameCount / 2, kFrameCount - kFrameCount / 2);
- for (int i = 0; i < bus->channels(); ++i) {
- SCOPED_TRACE("Last Half Zero");
- VerifyValue(bus->channel(i) + kFrameCount / 2,
- kFrameCount - kFrameCount / 2, 0);
- VerifyValue(bus->channel(i), kFrameCount / 2, i + 1);
- }
-
- // Fill the bus with dummy data.
- for (int i = 0; i < bus->channels(); ++i)
- std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);
-
- // Zero all the frames of each channel.
- bus->Zero();
- for (int i = 0; i < bus->channels(); ++i) {
- SCOPED_TRACE("All Zero");
- VerifyValue(bus->channel(i), bus->frames(), 0);
- }
-}
-
-// Each test vector represents two channels of data in the following arbitrary
-// layout: <min, zero, max, min, zero, max, zero, zero>.
-static const int kTestVectorSize = 8;
-static const uint8 kTestVectorUint8[kTestVectorSize] = {
- 0, -kint8min, kuint8max, 0, -kint8min, kuint8max, -kint8min, -kint8min };
-static const int16 kTestVectorInt16[kTestVectorSize] = {
- kint16min, 0, kint16max, kint16min, 0, kint16max, 0, 0 };
-static const int32 kTestVectorInt32[kTestVectorSize] = {
- kint32min, 0, kint32max, kint32min, 0, kint32max, 0, 0 };
-
-// Expected results.
-static const int kTestVectorFrames = kTestVectorSize / 2;
-static const float kTestVectorResult[][kTestVectorFrames] = {
- { -1, 1, 0, 0 }, { 0, -1, 1, 0 }};
-static const int kTestVectorChannels = arraysize(kTestVectorResult);
-
-// Verify FromInterleaved() deinterleaves audio in supported formats correctly.
-TEST_F(AudioBusTest, FromInterleaved) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- scoped_ptr<AudioBus> expected = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- for (int ch = 0; ch < kTestVectorChannels; ++ch) {
- memcpy(expected->channel(ch), kTestVectorResult[ch],
- kTestVectorFrames * sizeof(*expected->channel(ch)));
- }
- {
- SCOPED_TRACE("uint8");
- bus->Zero();
- bus->FromInterleaved(
- kTestVectorUint8, kTestVectorFrames, sizeof(*kTestVectorUint8));
- VerifyBus(bus.get(), expected.get());
- }
- {
- SCOPED_TRACE("int16");
- bus->Zero();
- bus->FromInterleaved(
- kTestVectorInt16, kTestVectorFrames, sizeof(*kTestVectorInt16));
- VerifyBus(bus.get(), expected.get());
- }
- {
- SCOPED_TRACE("int32");
- bus->Zero();
- bus->FromInterleaved(
- kTestVectorInt32, kTestVectorFrames, sizeof(*kTestVectorInt32));
- VerifyBus(bus.get(), expected.get());
- }
-}
-
-// Verify FromInterleavedPartial() deinterleaves audio correctly.
-TEST_F(AudioBusTest, FromInterleavedPartial) {
- // Only deinterleave the middle two frames in each channel.
- static const int kPartialStart = 1;
- static const int kPartialFrames = 2;
- ASSERT_LE(kPartialStart + kPartialFrames, kTestVectorFrames);
-
- scoped_ptr<AudioBus> bus = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- scoped_ptr<AudioBus> expected = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- expected->Zero();
- for (int ch = 0; ch < kTestVectorChannels; ++ch) {
- memcpy(expected->channel(ch) + kPartialStart,
- kTestVectorResult[ch] + kPartialStart,
- kPartialFrames * sizeof(*expected->channel(ch)));
- }
-
- bus->Zero();
- bus->FromInterleavedPartial(
- kTestVectorInt32 + kPartialStart * bus->channels(), kPartialStart,
- kPartialFrames, sizeof(*kTestVectorInt32));
- VerifyBus(bus.get(), expected.get());
-}
-
-// Verify ToInterleaved() interleaves audio in suported formats correctly.
-TEST_F(AudioBusTest, ToInterleaved) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- // Fill the bus with our test vector.
- for (int ch = 0; ch < kTestVectorChannels; ++ch) {
- memcpy(bus->channel(ch), kTestVectorResult[ch],
- kTestVectorFrames * sizeof(*bus->channel(ch)));
- }
- {
- SCOPED_TRACE("uint8");
- uint8 test_array[arraysize(kTestVectorUint8)];
- bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorUint8), test_array);
- ASSERT_EQ(memcmp(
- test_array, kTestVectorUint8, arraysize(kTestVectorUint8)), 0);
- }
- {
- SCOPED_TRACE("int16");
- int16 test_array[arraysize(kTestVectorInt16)];
- bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt16), test_array);
- ASSERT_EQ(memcmp(
- test_array, kTestVectorInt16, arraysize(kTestVectorInt16)), 0);
- }
- {
- SCOPED_TRACE("int32");
- int32 test_array[arraysize(kTestVectorInt32)];
- bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt32), test_array);
- ASSERT_EQ(memcmp(
- test_array, kTestVectorInt32, arraysize(kTestVectorInt32)), 0);
- }
-}
-
-// Verify ToInterleavedPartial() interleaves audio correctly.
-TEST_F(AudioBusTest, ToInterleavedPartial) {
- // Only interleave the middle two frames in each channel.
- static const int kPartialStart = 1;
- static const int kPartialFrames = 2;
- ASSERT_LE(kPartialStart + kPartialFrames, kTestVectorFrames);
-
- scoped_ptr<AudioBus> expected = AudioBus::Create(
- kTestVectorChannels, kTestVectorFrames);
- for (int ch = 0; ch < kTestVectorChannels; ++ch) {
- memcpy(expected->channel(ch), kTestVectorResult[ch],
- kTestVectorFrames * sizeof(*expected->channel(ch)));
- }
-
- int16 test_array[arraysize(kTestVectorInt16)];
- expected->ToInterleavedPartial(
- kPartialStart, kPartialFrames, sizeof(*kTestVectorInt16), test_array);
- ASSERT_EQ(memcmp(
- test_array, kTestVectorInt16 + kPartialStart * kTestVectorChannels,
- kPartialFrames * sizeof(*kTestVectorInt16)), 0);
-}
-
-} // namespace media
diff --git a/src/media/base/audio_capturer_source.h b/src/media/base/audio_capturer_source.h
deleted file mode 100644
index 7611025..0000000
--- a/src/media/base/audio_capturer_source.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_CAPTURER_SOURCE_H_
-#define MEDIA_BASE_AUDIO_CAPTURER_SOURCE_H_
-
-#include <vector>
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_bus.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// AudioCapturerSource is an interface representing the source for
-// captured audio. An implementation will periodically call Capture() on a
-// callback object.
-class AudioCapturerSource
- : public base::RefCountedThreadSafe<media::AudioCapturerSource> {
- public:
- class CaptureCallback {
- public:
- // Callback to deliver the captured data from the OS.
- virtual void Capture(AudioBus* audio_source,
- int audio_delay_milliseconds,
- double volume) = 0;
-
- // Signals an error has occurred.
- virtual void OnCaptureError() = 0;
-
- protected:
- virtual ~CaptureCallback() {}
- };
-
- class CaptureEventHandler {
- public:
- // Notification to the client that the device with the specific |device_id|
- // has been started.
- virtual void OnDeviceStarted(const std::string& device_id) = 0;
-
- // Notification to the client that the device has been stopped.
- virtual void OnDeviceStopped() = 0;
-
- protected:
- virtual ~CaptureEventHandler() {}
- };
-
- // Sets information about the audio stream format and the device
- // to be used. It must be called before any of the other methods.
- // TODO(xians): Add |device_id| to this Initialize() function.
- virtual void Initialize(const AudioParameters& params,
- CaptureCallback* callback,
- CaptureEventHandler* event_handler) = 0;
-
- // Starts the audio recording.
- virtual void Start() = 0;
-
- // Stops the audio recording. This API is synchronous, and no more data
- // callback will be passed to the client after it is being called.
- virtual void Stop() = 0;
-
- // Sets the capture volume, with range [0.0, 1.0] inclusive.
- virtual void SetVolume(double volume) = 0;
-
- // Specifies the |session_id| to query which device to use.
- // TODO(xians): Change the interface to SetDevice(const std::string&).
- virtual void SetDevice(int session_id) = 0;
-
- // Enables or disables the WebRtc AGC control.
- virtual void SetAutomaticGainControl(bool enable) = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<AudioCapturerSource>;
- virtual ~AudioCapturerSource() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_CAPTURER_SOURCE_H_
diff --git a/src/media/base/audio_decoder.cc b/src/media/base/audio_decoder.cc
deleted file mode 100644
index 9390660..0000000
--- a/src/media/base/audio_decoder.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_decoder.h"
-
-namespace media {
-
-AudioDecoder::AudioDecoder() {}
-
-AudioDecoder::~AudioDecoder() {}
-
-} // namespace media
diff --git a/src/media/base/audio_decoder.h b/src/media/base/audio_decoder.h
deleted file mode 100644
index 36cfe4a..0000000
--- a/src/media/base/audio_decoder.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_DECODER_H_
-#define MEDIA_BASE_AUDIO_DECODER_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/channel_layout.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class Buffer;
-class DemuxerStream;
-
-class MEDIA_EXPORT AudioDecoder
- : public base::RefCountedThreadSafe<AudioDecoder> {
- public:
- // Status codes for read operations.
- enum Status {
- kOk,
- kAborted,
- kDecodeError,
- };
- typedef std::vector<scoped_refptr<Buffer> > Buffers;
-
- // Initialize an AudioDecoder with the given DemuxerStream, executing the
- // callback upon completion.
- // statistics_cb is used to update global pipeline statistics.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) = 0;
-
- // Request samples to be decoded and returned via the provided callback.
- // Only one read may be in flight at any given time.
- //
- // Implementations guarantee that the callback will not be called from within
- // this method.
- //
- // Non-NULL sample buffer pointers will contain decoded audio data or may
- // indicate the end of the stream. A NULL buffer pointer indicates an aborted
- // Read(). This can happen if the DemuxerStream gets flushed and doesn't have
- // any more data to return.
-#if !defined(COBALT) && !defined(__LB_SHELL__)
- typedef base::Callback<void(Status, const scoped_refptr<Buffer>&)> ReadCB;
-#else // !defined(COBALT) && !defined(__LB_SHELL__)
- // This is a variant of the above callback that can pass multiple decoded
- // audio buffers at once. Non-empty sample buffers will contain decoded audio
- // data or may indicate the end of the stream. Empty buffers indicate an
- // aborted Read(). This can happen if the DemuxerStream gets flushed and
- // doesn't have any more data to return.
- typedef base::Callback<void(Status, const Buffers&)> ReadCB;
-#endif // !defined(COBALT) && !defined(__LB_SHELL__)
- virtual void Read(const ReadCB& read_cb) = 0;
-
- // Reset decoder state, dropping any queued encoded data.
- virtual void Reset(const base::Closure& closure) = 0;
-
- // Returns various information about the decoded audio format.
- virtual int bits_per_channel() = 0;
- virtual ChannelLayout channel_layout() = 0;
- virtual int samples_per_second() = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<AudioDecoder>;
- virtual ~AudioDecoder();
- AudioDecoder();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_DECODER_H_
diff --git a/src/media/base/audio_decoder_config.cc b/src/media/base/audio_decoder_config.cc
deleted file mode 100644
index 8d335db..0000000
--- a/src/media/base/audio_decoder_config.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_decoder_config.h"
-
-#include <sstream>
-
-#include "base/logging.h"
-#include "base/metrics/histogram.h"
-#include "media/audio/sample_rates.h"
-#include "media/base/limits.h"
-
-namespace media {
-
-AudioDecoderConfig::AudioDecoderConfig()
- : codec_(kUnknownAudioCodec),
- bits_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED),
- samples_per_second_(0),
- bytes_per_frame_(0),
- extra_data_size_(0),
- is_encrypted_(false) {
-}
-
-AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec,
- int bits_per_channel,
- ChannelLayout channel_layout,
- int samples_per_second,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted) {
- Initialize(codec, bits_per_channel, channel_layout, samples_per_second,
- extra_data, extra_data_size, is_encrypted, true);
-}
-
-void AudioDecoderConfig::Initialize(AudioCodec codec,
- int bits_per_channel,
- ChannelLayout channel_layout,
- int samples_per_second,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted,
- bool record_stats) {
- CHECK((extra_data_size != 0) == (extra_data != NULL));
-
- if (record_stats) {
- UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", codec, kAudioCodecMax + 1);
- // Fake enum histogram to get exact integral buckets. Expect to never see
- // any values over 32 and even that is huge.
- UMA_HISTOGRAM_ENUMERATION("Media.AudioBitsPerChannel", bits_per_channel,
- 40);
- UMA_HISTOGRAM_ENUMERATION("Media.AudioChannelLayout", channel_layout,
- CHANNEL_LAYOUT_MAX);
- AudioSampleRate asr = media::AsAudioSampleRate(samples_per_second);
- if (asr != kUnexpectedAudioSampleRate) {
- UMA_HISTOGRAM_ENUMERATION("Media.AudioSamplesPerSecond", asr,
- kUnexpectedAudioSampleRate);
- } else {
- UMA_HISTOGRAM_COUNTS(
- "Media.AudioSamplesPerSecondUnexpected", samples_per_second);
- }
- }
-
- codec_ = codec;
- bits_per_channel_ = bits_per_channel;
- channel_layout_ = channel_layout;
- samples_per_second_ = samples_per_second;
- extra_data_size_ = extra_data_size;
-
- if (extra_data_size_ > 0) {
- extra_data_.reset(new uint8[extra_data_size_]);
- memcpy(extra_data_.get(), extra_data, extra_data_size_);
- } else {
- extra_data_.reset();
- }
-
- is_encrypted_ = is_encrypted;
-
- int channels = ChannelLayoutToChannelCount(channel_layout_);
- bytes_per_frame_ = channels * bits_per_channel_ / 8;
-}
-
-AudioDecoderConfig::~AudioDecoderConfig() {}
-
-bool AudioDecoderConfig::IsValidConfig() const {
- return codec_ != kUnknownAudioCodec &&
- channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED &&
- bits_per_channel_ > 0 &&
- bits_per_channel_ <= limits::kMaxBitsPerSample &&
- samples_per_second_ > 0 &&
- samples_per_second_ <= limits::kMaxSampleRate;
-}
-
-bool AudioDecoderConfig::Matches(const AudioDecoderConfig& config) const {
- return ((codec() == config.codec()) &&
- (bits_per_channel() == config.bits_per_channel()) &&
- (channel_layout() == config.channel_layout()) &&
- (samples_per_second() == config.samples_per_second()) &&
- (extra_data_size() == config.extra_data_size()) &&
- (!extra_data() || !memcmp(extra_data(), config.extra_data(),
- extra_data_size())) &&
- (is_encrypted() == config.is_encrypted()));
-}
-
-std::string AudioDecoderConfig::AsHumanReadableString() const {
- std::ostringstream s;
- s << "codec: " << codec()
- << " bits per channel: " << bits_per_channel()
- << " channel layout: " << channel_layout()
- << " samples per second: " << samples_per_second()
- << " bytes per frame: " << bytes_per_frame()
- << " has extra data? " << (extra_data() ? "true" : "false")
- << " encrypted? " << (is_encrypted() ? "true" : "false");
- return s.str();
-}
-
-void AudioDecoderConfig::CopyFrom(const AudioDecoderConfig& audio_config) {
- Initialize(audio_config.codec(),
- audio_config.bits_per_channel(),
- audio_config.channel_layout(),
- audio_config.samples_per_second(),
- audio_config.extra_data(),
- audio_config.extra_data_size(),
- audio_config.is_encrypted(),
- false);
-}
-
-AudioCodec AudioDecoderConfig::codec() const {
- return codec_;
-}
-
-int AudioDecoderConfig::bits_per_channel() const {
- return bits_per_channel_;
-}
-
-ChannelLayout AudioDecoderConfig::channel_layout() const {
- return channel_layout_;
-}
-
-int AudioDecoderConfig::samples_per_second() const {
- return samples_per_second_;
-}
-
-int AudioDecoderConfig::bytes_per_frame() const {
- return bytes_per_frame_;
-}
-
-uint8* AudioDecoderConfig::extra_data() const {
- return extra_data_.get();
-}
-
-size_t AudioDecoderConfig::extra_data_size() const {
- return extra_data_size_;
-}
-
-bool AudioDecoderConfig::is_encrypted() const {
- return is_encrypted_;
-}
-
-} // namespace media
diff --git a/src/media/base/audio_decoder_config.h b/src/media/base/audio_decoder_config.h
deleted file mode 100644
index dd6a11b..0000000
--- a/src/media/base/audio_decoder_config.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_DECODER_CONFIG_H_
-#define MEDIA_BASE_AUDIO_DECODER_CONFIG_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-enum AudioCodec {
- // These values are histogrammed over time; do not change their ordinal
- // values. When deleting a codec replace it with a dummy value; when adding a
- // codec, do so at the bottom (and update kAudioCodecMax).
- kUnknownAudioCodec = 0,
- kCodecAAC,
- kCodecMP3,
- kCodecPCM,
- kCodecVorbis,
- kCodecFLAC,
- kCodecAMR_NB,
- kCodecAMR_WB,
- kCodecPCM_MULAW,
- kCodecGSM_MS,
- kCodecPCM_S16BE,
- kCodecPCM_S24BE,
- kCodecOpus,
- // DO NOT ADD RANDOM AUDIO CODECS!
- //
- // The only acceptable time to add a new codec is if there is production code
- // that uses said codec in the same CL.
-
- kAudioCodecMax = kCodecOpus // Must equal the last "real" codec above.
-};
-
-// TODO(dalecurtis): FFmpeg API uses |bytes_per_channel| instead of
-// |bits_per_channel|, we should switch over since bits are generally confusing
-// to work with.
-class MEDIA_EXPORT AudioDecoderConfig {
- public:
- // Constructs an uninitialized object. Clients should call Initialize() with
- // appropriate values before using.
- AudioDecoderConfig();
-
- // Constructs an initialized object. It is acceptable to pass in NULL for
- // |extra_data|, otherwise the memory is copied.
- AudioDecoderConfig(AudioCodec codec, int bits_per_channel,
- ChannelLayout channel_layout, int samples_per_second,
- const uint8* extra_data, size_t extra_data_size,
- bool is_encrypted);
-
- ~AudioDecoderConfig();
-
- // Resets the internal state of this object.
- void Initialize(AudioCodec codec, int bits_per_channel,
- ChannelLayout channel_layout, int samples_per_second,
- const uint8* extra_data, size_t extra_data_size,
- bool is_encrypted,
- bool record_stats);
-
- // Deep copies |audio_config|.
- void CopyFrom(const AudioDecoderConfig& audio_config);
-
- // Returns true if this object has appropriate configuration values, false
- // otherwise.
- bool IsValidConfig() const;
-
- // Returns true if all fields in |config| match this config.
- // Note: The contents of |extra_data_| are compared not the raw pointers.
- bool Matches(const AudioDecoderConfig& config) const;
-
- // Returns a human-readable string describing |*this|. For debugging & test
- // output only.
- std::string AsHumanReadableString() const;
-
- AudioCodec codec() const;
- int bits_per_channel() const;
- ChannelLayout channel_layout() const;
- int samples_per_second() const;
- int bytes_per_frame() const;
-
- // Optional byte data required to initialize audio decoders such as Vorbis
- // codebooks.
- uint8* extra_data() const;
- size_t extra_data_size() const;
-
- // Whether the audio stream is potentially encrypted.
- // Note that in a potentially encrypted audio stream, individual buffers
- // can be encrypted or not encrypted.
- bool is_encrypted() const;
-
- private:
- AudioCodec codec_;
- int bits_per_channel_;
- ChannelLayout channel_layout_;
- int samples_per_second_;
- int bytes_per_frame_;
-
- scoped_array<uint8> extra_data_;
- size_t extra_data_size_;
-
- bool is_encrypted_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioDecoderConfig);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_DECODER_CONFIG_H_
diff --git a/src/media/base/audio_fifo.cc b/src/media/base/audio_fifo.cc
deleted file mode 100644
index b6e8f80..0000000
--- a/src/media/base/audio_fifo.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_fifo.h"
-
-#include "base/logging.h"
-
-using base::subtle::Atomic32;
-using base::subtle::NoBarrier_Store;
-
-namespace media {
-
-// Given current position in the FIFO, the maximum number of elements in the
-// FIFO and the size of the input; this method provides two output results:
-// |size| and |wrap_size|. These two results can then be utilized for memcopy
-// operations to and from the FIFO.
-// Under "normal" circumstances, |size| will be equal to |in_size| and
-// |wrap_size| will be zero. This case corresponding to the non-wrapping case
-// where we have not yet reached the "edge" of the FIFO. If |pos| + |in_size|
-// exceeds the total size of the FIFO, we must wrap around and start reusing
-// a part the allocated memory. The size of this part is given by |wrap_size|.
-static void GetSizes(
- int pos, int max_size, int in_size, int* size, int* wrap_size) {
- if (pos + in_size > max_size) {
- // Wrapping is required => derive size of each segment.
- *size = max_size - pos;
- *wrap_size = in_size - *size;
- } else {
- // Wrapping is not required.
- *size = in_size;
- *wrap_size = 0;
- }
-}
-
-// Updates the read/write position with |step| modulo the maximum number of
-// elements in the FIFO to ensure that the position counters wraps around at
-// the endpoint.
-static int UpdatePos(int pos, int step, int max_size) {
- return ((pos + step) % max_size);
-}
-
-AudioFifo::AudioFifo(int channels, int frames)
- : audio_bus_(AudioBus::Create(channels, frames)),
- max_frames_(frames),
- frames_pushed_(0),
- frames_consumed_(0),
- read_pos_(0),
- write_pos_(0) {}
-
-AudioFifo::~AudioFifo() {}
-
-int AudioFifo::frames() const {
- int delta = frames_pushed_ - frames_consumed_;
- base::subtle::MemoryBarrier();
- return delta;
-}
-
-void AudioFifo::Push(const AudioBus* source) {
- DCHECK(source);
- DCHECK_EQ(source->channels(), audio_bus_->channels());
-
- // Ensure that there is space for the new data in the FIFO.
- const int source_size = source->frames();
- CHECK_LE(source_size + frames(), max_frames_);
-
- // Figure out if wrapping is needed and if so what segment sizes we need
- // when adding the new audio bus content to the FIFO.
- int append_size = 0;
- int wrap_size = 0;
- GetSizes(write_pos_, max_frames(), source_size, &append_size, &wrap_size);
-
- // Copy all channels from the source to the FIFO. Wrap around if needed.
- for (int ch = 0; ch < source->channels(); ++ch) {
- float* dest = audio_bus_->channel(ch);
- const float* src = source->channel(ch);
-
- // Append part of (or the complete) source to the FIFO.
- memcpy(&dest[write_pos_], &src[0], append_size * sizeof(src[0]));
- if (wrap_size > 0) {
- // Wrapping is needed: copy remaining part from the source to the FIFO.
- memcpy(&dest[0], &src[append_size], wrap_size * sizeof(src[0]));
- }
- }
-
- // Ensure the data is *really* written before updating |frames_pushed_|.
- base::subtle::MemoryBarrier();
-
- Atomic32 new_frames_pushed = frames_pushed_ + source_size;
- NoBarrier_Store(&frames_pushed_, new_frames_pushed);
-
- DCHECK_LE(frames(), max_frames());
- write_pos_ = UpdatePos(write_pos_, source_size, max_frames());
-}
-
-void AudioFifo::Consume(AudioBus* destination,
- int start_frame,
- int frames_to_consume) {
- DCHECK(destination);
- DCHECK_EQ(destination->channels(), audio_bus_->channels());
-
- // It is not possible to ask for more data than what is available in the FIFO.
- CHECK_LE(frames_to_consume, frames());
-
- // A copy from the FIFO to |destination| will only be performed if the
- // allocated memory in |destination| is sufficient.
- CHECK_LE(frames_to_consume + start_frame, destination->frames());
-
- // Figure out if wrapping is needed and if so what segment sizes we need
- // when removing audio bus content from the FIFO.
- int consume_size = 0;
- int wrap_size = 0;
- GetSizes(read_pos_, max_frames(), frames_to_consume,
- &consume_size, &wrap_size);
-
- // For all channels, remove the requested amount of data from the FIFO
- // and copy the content to the destination. Wrap around if needed.
- for (int ch = 0; ch < destination->channels(); ++ch) {
- float* dest = destination->channel(ch);
- const float* src = audio_bus_->channel(ch);
-
- // Copy a selected part of the FIFO to the destination.
- memcpy(&dest[start_frame], &src[read_pos_], consume_size * sizeof(src[0]));
- if (wrap_size > 0) {
- // Wrapping is needed: copy remaining part to the destination.
- memcpy(&dest[consume_size + start_frame], &src[0],
- wrap_size * sizeof(src[0]));
- }
- }
-
- Atomic32 new_frames_consumed = frames_consumed_ + frames_to_consume;
- NoBarrier_Store(&frames_consumed_, new_frames_consumed);
-
- read_pos_ = UpdatePos(read_pos_, frames_to_consume, max_frames());
-}
-
-void AudioFifo::Clear() {
- frames_pushed_ = 0;
- frames_consumed_ = 0;
- read_pos_ = 0;
- write_pos_ = 0;
-}
-
-} // namespace media
diff --git a/src/media/base/audio_fifo.h b/src/media/base/audio_fifo.h
deleted file mode 100644
index e978ace..0000000
--- a/src/media/base/audio_fifo.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_FIFO_H_
-#define MEDIA_BASE_AUDIO_FIFO_H_
-
-#include "base/atomicops.h"
-#include "media/base/audio_bus.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// First-in first-out container for AudioBus elements.
-// The maximum number of audio frames in the FIFO is set at construction and
-// can not be extended dynamically. The allocated memory is utilized as a
-// ring buffer.
-// This class is thread-safe in the limited sense that one thread may call
-// Push(), while a second thread calls Consume().
-class MEDIA_EXPORT AudioFifo {
- public:
- // Creates a new AudioFifo and allocates |channels| of length |frames|.
- AudioFifo(int channels, int frames);
- virtual ~AudioFifo();
-
- // Pushes all audio channel data from |source| to the FIFO.
- // Push() will crash if the allocated space is insufficient.
- void Push(const AudioBus* source);
-
- // Consumes |frames_to_consume| audio frames from the FIFO and copies
- // them to |destination| starting at position |start_frame|.
- // Consume() will crash if the FIFO does not contain |frames_to_consume|
- // frames or if there is insufficient space in |destination| to store the
- // frames.
- void Consume(AudioBus* destination, int start_frame, int frames_to_consume);
-
- // Empties the FIFO without deallocating any memory.
- void Clear();
-
- // Number of actual audio frames in the FIFO.
- int frames() const;
-
- int max_frames() const { return max_frames_; }
-
- private:
- // The actual FIFO is an audio bus implemented as a ring buffer.
- scoped_ptr<AudioBus> audio_bus_;
-
- // Maximum number of elements the FIFO can contain.
- // This value is set by |frames| in the constructor.
- const int max_frames_;
-
- // Number of actual elements in the FIFO.
- volatile base::subtle::Atomic32 frames_pushed_;
- volatile base::subtle::Atomic32 frames_consumed_;
-
- // Current read position.
- int read_pos_;
-
- // Current write position.
- int write_pos_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioFifo);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_FIFO_H_
diff --git a/src/media/base/audio_fifo_unittest.cc b/src/media/base/audio_fifo_unittest.cc
deleted file mode 100644
index dd5ffd9..0000000
--- a/src/media/base/audio_fifo_unittest.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(henrika): add test which included |start_frame| in Consume() call.
-
-#include "media/base/audio_fifo.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class AudioFifoTest : public testing::Test {
- public:
- AudioFifoTest() {}
- ~AudioFifoTest() {}
-
- void VerifyValue(const float data[], int size, float value) {
- for (int i = 0; i < size; ++i)
- ASSERT_FLOAT_EQ(value, data[i]) << "i=" << i;
- }
-
- protected:
- DISALLOW_COPY_AND_ASSIGN(AudioFifoTest);
-};
-
-// Verify that construction works as intended.
-TEST_F(AudioFifoTest, Construct) {
- static const int kChannels = 6;
- static const int kMaxFrameCount = 128;
- AudioFifo fifo(kChannels, kMaxFrameCount);
- EXPECT_EQ(fifo.frames(), 0);
-}
-
-// Pushes audio bus objects to a FIFO and fill it up to different degrees.
-TEST_F(AudioFifoTest, Push) {
- static const int kChannels = 2;
- static const int kMaxFrameCount = 128;
- AudioFifo fifo(kChannels, kMaxFrameCount);
- {
- SCOPED_TRACE("Push 50%");
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kMaxFrameCount / 2);
- EXPECT_EQ(fifo.frames(), 0);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), bus->frames());
- fifo.Clear();
- }
- {
- SCOPED_TRACE("Push 100%");
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kMaxFrameCount);
- EXPECT_EQ(fifo.frames(), 0);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), bus->frames());
- fifo.Clear();
- }
-}
-
-// Consumes audio bus objects from a FIFO and empty it to different degrees.
-TEST_F(AudioFifoTest, Consume) {
- static const int kChannels = 2;
- static const int kMaxFrameCount = 128;
- AudioFifo fifo(kChannels, kMaxFrameCount);
- {
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kMaxFrameCount);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), kMaxFrameCount);
- }
- {
- SCOPED_TRACE("Consume 50%");
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kMaxFrameCount / 2);
- fifo.Consume(bus.get(), 0, bus->frames());
- EXPECT_TRUE(fifo.frames() == bus->frames());
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), kMaxFrameCount);
- }
- {
- SCOPED_TRACE("Consume 100%");
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kMaxFrameCount);
- fifo.Consume(bus.get(), 0, bus->frames());
- EXPECT_EQ(fifo.frames(), 0);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), kMaxFrameCount);
- }
-}
-
-// Verify that the frames() method of the FIFO works as intended while
-// appending and removing audio bus elements to/from the FIFO.
-TEST_F(AudioFifoTest, FramesInFifo) {
- static const int kChannels = 2;
- static const int kMaxFrameCount = 64;
- AudioFifo fifo(kChannels, kMaxFrameCount);
-
- // Fill up the FIFO and verify that the size grows as it should while adding
- // one audio frame each time.
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, 1);
- int n = 0;
- while (fifo.frames() < kMaxFrameCount) {
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), ++n);
- }
- EXPECT_EQ(fifo.frames(), kMaxFrameCount);
-
- // Empty the FIFO and verify that the size decreases as it should.
- // Reduce the size of the FIFO by one frame each time.
- while (fifo.frames() > 0) {
- fifo.Consume(bus.get(), 0, bus->frames());
- EXPECT_EQ(fifo.frames(), --n);
- }
- EXPECT_EQ(fifo.frames(), 0);
-
- // Verify that a steady-state size of #frames in the FIFO is maintained
- // during a sequence of Push/Consume calls which involves wrapping. We ensure
- // wrapping by selecting a buffer size which does divides the FIFO size
- // with a remainder of one.
- scoped_ptr<AudioBus> bus2 =
- AudioBus::Create(kChannels, (kMaxFrameCount / 4) - 1);
- const int frames_in_fifo = bus2->frames();
- fifo.Push(bus2.get());
- EXPECT_EQ(fifo.frames(), frames_in_fifo);
- for (int n = 0; n < kMaxFrameCount; ++n) {
- fifo.Push(bus2.get());
- fifo.Consume(bus2.get(), 0, frames_in_fifo);
- EXPECT_EQ(fifo.frames(), frames_in_fifo);
- }
-}
-
-// Perform a sequence of Push/Consume calls and verify that the data written
-// to the FIFO is correctly retrieved, i.e., that the order is correct and the
-// values are correct.
-TEST_F(AudioFifoTest, VerifyDataValues) {
- static const int kChannels = 2;
- static const int kFrameCount = 2;
- static const int kFifoFrameCount = 5 * kFrameCount;
-
- AudioFifo fifo(kChannels, kFifoFrameCount);
- scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);
- EXPECT_EQ(fifo.frames(), 0);
- EXPECT_EQ(bus->frames(), kFrameCount);
-
- // Start by filling up the FIFO with audio frames. The first audio frame
- // will contain all 1's, the second all 2's etc. All channels contain the
- // same value.
- int value = 1;
- while (fifo.frames() < kFifoFrameCount) {
- for (int j = 0; j < bus->channels(); ++j)
- std::fill(bus->channel(j), bus->channel(j) + bus->frames(), value);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), bus->frames() * value);
- ++value;
- }
-
- // FIFO should be full now.
- EXPECT_EQ(fifo.frames(), kFifoFrameCount);
-
- // Consume all audio frames in the FIFO and verify that the stored values
- // are correct. In this example, we shall read out: 1, 2, 3, 4, 5 in that
- // order. Note that we set |frames_to_consume| to half the size of the bus.
- // It means that we shall read out the same value two times in row.
- value = 1;
- int n = 1;
- const int frames_to_consume = bus->frames() / 2;
- while (fifo.frames() > 0) {
- fifo.Consume(bus.get(), 0, frames_to_consume);
- for (int j = 0; j < bus->channels(); ++j)
- VerifyValue(bus->channel(j), frames_to_consume, value);
- if (n++ % 2 == 0)
- ++value; // counts 1, 1, 2, 2, 3, 3,...
- }
-
- // FIFO should be empty now.
- EXPECT_EQ(fifo.frames(), 0);
-
- // Push one audio bus to the FIFO and fill it with 1's.
- value = 1;
- for (int j = 0; j < bus->channels(); ++j)
- std::fill(bus->channel(j), bus->channel(j) + bus->frames(), value);
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), bus->frames());
-
- // Keep calling Consume/Push a few rounds and verify that we read out the
- // correct values. The number of elements shall be fixed (kFrameCount) during
- // this phase.
- for (int i = 0; i < 5 * kFifoFrameCount; i++) {
- fifo.Consume(bus.get(), 0, bus->frames());
- for (int j = 0; j < bus->channels(); ++j) {
- VerifyValue(bus->channel(j), bus->channels(), value);
- std::fill(bus->channel(j), bus->channel(j) + bus->frames(), value + 1);
- }
- fifo.Push(bus.get());
- EXPECT_EQ(fifo.frames(), bus->frames());
- ++value;
- }
-}
-
-} // namespace media
diff --git a/src/media/base/audio_pull_fifo.cc b/src/media/base/audio_pull_fifo.cc
deleted file mode 100644
index 4943591..0000000
--- a/src/media/base/audio_pull_fifo.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_pull_fifo.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-
-namespace media {
-
-AudioPullFifo::AudioPullFifo(int channels, int frames, const ReadCB& read_cb)
- : read_cb_(read_cb) {
- fifo_.reset(new AudioFifo(channels, frames));
- bus_ = AudioBus::Create(channels, frames);
-}
-
-AudioPullFifo::~AudioPullFifo() {
- read_cb_.Reset();
-}
-
-void AudioPullFifo::Consume(AudioBus* destination, int frames_to_consume) {
- DCHECK(destination);
- DCHECK_LE(frames_to_consume, destination->frames());
-
- int write_pos = 0;
- int remaining_frames_to_provide = frames_to_consume;
-
- // Try to fulfill the request using what's available in the FIFO.
- ReadFromFifo(destination, &remaining_frames_to_provide, &write_pos);
-
- // Get the remaining audio frames from the producer using the callback.
- while (remaining_frames_to_provide > 0) {
- // Fill up the FIFO by acquiring audio data from the producer.
- read_cb_.Run(write_pos, bus_.get());
- fifo_->Push(bus_.get());
-
- // Try to fulfill the request using what's available in the FIFO.
- ReadFromFifo(destination, &remaining_frames_to_provide, &write_pos);
- }
-}
-
-void AudioPullFifo::Clear() {
- fifo_->Clear();
-}
-
-void AudioPullFifo::ReadFromFifo(AudioBus* destination,
- int* frames_to_provide,
- int* write_pos) {
- DCHECK(frames_to_provide);
- DCHECK(write_pos);
- int frames = std::min(fifo_->frames(), *frames_to_provide);
- fifo_->Consume(destination, *write_pos, frames);
- *write_pos += frames;
- *frames_to_provide -= frames;
-}
-
-} // namespace media
diff --git a/src/media/base/audio_pull_fifo.h b/src/media/base/audio_pull_fifo.h
deleted file mode 100644
index caf73e4..0000000
--- a/src/media/base/audio_pull_fifo.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_PULL_FIFO_H_
-#define MEDIA_BASE_AUDIO_PULL_FIFO_H_
-
-#include "base/callback.h"
-#include "media/base/audio_fifo.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// A FIFO (First In First Out) buffer to handle mismatches in buffer sizes
-// between a producer and consumer. The consumer will pull data from this FIFO.
-// If data is already available in the FIFO, it is provided to the consumer.
-// If insufficient data is available to satisfy the request, the FIFO will ask
-// the producer for more data to fulfill a request.
-class MEDIA_EXPORT AudioPullFifo {
- public:
- // Callback type for providing more data into the FIFO. Expects AudioBus
- // to be completely filled with data upon return; zero padded if not enough
- // frames are available to satisfy the request. |frame_delay| is the number
- // of output frames already processed and can be used to estimate delay.
- typedef base::Callback<void(int frame_delay, AudioBus* audio_bus)> ReadCB;
-
- // Constructs an AudioPullFifo with the specified |read_cb|, which is used to
- // read audio data to the FIFO if data is not already available. The internal
- // FIFO can contain |channel| number of channels, where each channel is of
- // length |frames| audio frames.
- AudioPullFifo(int channels, int frames, const ReadCB& read_cb);
- virtual ~AudioPullFifo();
-
- // Consumes |frames_to_consume| audio frames from the FIFO and copies
- // them to |destination|. If the FIFO does not have enough data, we ask
- // the producer to give us more data to fulfill the request using the
- // ReadCB implementation.
- void Consume(AudioBus* destination, int frames_to_consume);
-
- // Empties the FIFO without deallocating any memory.
- void Clear();
-
- private:
- // Attempt to fulfill the request using what is available in the FIFO.
- // Append new data to the |destination| starting at |write_pos|.
- void ReadFromFifo(
- AudioBus* destination, int* frames_to_provide, int* write_pos);
-
- // Source of data to the FIFO.
- ReadCB read_cb_;
-
- // The actual FIFO.
- scoped_ptr<AudioFifo> fifo_;
-
- // Temporary audio bus to hold the data from the producer.
- scoped_ptr<AudioBus> bus_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioPullFifo);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_PULL_FIFO_H_
diff --git a/src/media/base/audio_pull_fifo_unittest.cc b/src/media/base/audio_pull_fifo_unittest.cc
deleted file mode 100644
index cec4d35..0000000
--- a/src/media/base/audio_pull_fifo_unittest.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/stringprintf.h"
-#include "media/base/audio_pull_fifo.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Block diagram of a possible real-world usage:
-//
-// | Producer | ----> | AudioPullFifo | ----> | Consumer |
-// push pull
-// 2048 ----> (2048) ----> ~512
-
-// Number of channels in each audio bus.
-static int kChannels = 2;
-
-// Max number of audio framed the FIFO can contain.
-static const int kMaxFramesInFifo = 2048;
-
-class AudioPullFifoTest
- : public testing::TestWithParam<int> {
- public:
- AudioPullFifoTest()
- : pull_fifo_(kChannels, kMaxFramesInFifo, base::Bind(
- &AudioPullFifoTest::ProvideInput, base::Unretained(this))),
- audio_bus_(AudioBus::Create(kChannels, kMaxFramesInFifo)),
- fill_value_(0),
- last_frame_delay_(-1) {}
- virtual ~AudioPullFifoTest() {}
-
- void VerifyValue(const float data[], int size, float start_value) {
- float value = start_value;
- for (int i = 0; i < size; ++i) {
- ASSERT_FLOAT_EQ(value++, data[i]) << "i=" << i;
- }
- }
-
- // Consume data using different sizes, acquire audio frames from the FIFO
- // and verify that the retrieved values matches the values written by the
- // producer.
- void ConsumeTest(int frames_to_consume) {
- int start_value = 0;
- SCOPED_TRACE(base::StringPrintf("Checking frames_to_consume %d",
- frames_to_consume));
- pull_fifo_.Consume(audio_bus_.get(), frames_to_consume);
- for (int j = 0; j < kChannels; ++j) {
- VerifyValue(audio_bus_->channel(j), frames_to_consume, start_value);
- }
- start_value += frames_to_consume;
- EXPECT_LT(last_frame_delay_, audio_bus_->frames());
- }
-
- // AudioPullFifo::ReadCB implementation where we increase a value for each
- // audio frame that we provide. Note that all channels are given the same
- // value to simplify the verification.
- virtual void ProvideInput(int frame_delay, AudioBus* audio_bus) {
- ASSERT_GT(frame_delay, last_frame_delay_);
- last_frame_delay_ = frame_delay;
-
- EXPECT_EQ(audio_bus->channels(), audio_bus_->channels());
- EXPECT_EQ(audio_bus->frames(), kMaxFramesInFifo);
- for (int i = 0; i < audio_bus->frames(); ++i) {
- for (int j = 0; j < audio_bus->channels(); ++j) {
- // Store same value in all channels.
- audio_bus->channel(j)[i] = fill_value_;
- }
- fill_value_++;
- }
- }
-
- protected:
- AudioPullFifo pull_fifo_;
- scoped_ptr<AudioBus> audio_bus_;
- int fill_value_;
- int last_frame_delay_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioPullFifoTest);
-};
-
-TEST_P(AudioPullFifoTest, Consume) {
- ConsumeTest(GetParam());
-}
-
-// Test common |frames_to_consume| values which will be used as input
-// parameter to AudioPullFifo::Consume() when the consumer asks for data.
-INSTANTIATE_TEST_CASE_P(
- AudioPullFifoTest, AudioPullFifoTest,
- testing::Values(544, 512, 512, 512, 512, 2048, 544, 441, 440, 433, 500));
-
-} // namespace media
diff --git a/src/media/base/audio_renderer.cc b/src/media/base/audio_renderer.cc
deleted file mode 100644
index e7b737e..0000000
--- a/src/media/base/audio_renderer.cc
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_renderer.h"
-
-namespace media {
-
-AudioRenderer::AudioRenderer() {}
-AudioRenderer::~AudioRenderer() {}
-
-} // namespace media
diff --git a/src/media/base/audio_renderer.h b/src/media/base/audio_renderer.h
deleted file mode 100644
index 19459ac..0000000
--- a/src/media/base/audio_renderer.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_RENDERER_H_
-#define MEDIA_BASE_AUDIO_RENDERER_H_
-
-#include <list>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/base/pipeline_status.h"
-
-namespace media {
-
-class AudioDecoder;
-class DemuxerStream;
-
-class MEDIA_EXPORT AudioRenderer
- : public base::RefCountedThreadSafe<AudioRenderer> {
- public:
- typedef std::list<scoped_refptr<AudioDecoder> > AudioDecoderList;
-
- // First parameter is the current time that has been rendered.
- // Second parameter is the maximum time value that the clock cannot exceed.
- typedef base::Callback<void(base::TimeDelta, base::TimeDelta)> TimeCB;
-
- // Initialize a AudioRenderer with the given AudioDecoder, executing the
- // |init_cb| upon completion.
- //
- // |statistics_cb| is executed periodically with audio rendering stats.
- //
- // |underflow_cb| is executed when the renderer runs out of data to pass to
- // the audio card during playback. ResumeAfterUnderflow() must be called
- // to resume playback. Pause(), Preroll(), or Stop() cancels the underflow
- // condition.
- //
- // |time_cb| is executed whenever time has advanced by way of audio rendering.
- //
- // |ended_cb| is executed when audio rendering has reached the end of stream.
- //
- // |disabled_cb| is executed when audio rendering has been disabled due to
- // external factors (i.e., device was removed). |time_cb| will no longer be
- // executed.
- //
- // |error_cb| is executed if an error was encountered.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb) = 0;
-
- // Start audio decoding and rendering at the current playback rate, executing
- // |callback| when playback is underway.
- virtual void Play(const base::Closure& callback) = 0;
-
- // Temporarily suspend decoding and rendering audio, executing |callback| when
- // playback has been suspended.
- virtual void Pause(const base::Closure& callback) = 0;
-
- // Discard any audio data, executing |callback| when completed.
- virtual void Flush(const base::Closure& callback) = 0;
-
- // Start prerolling audio data for samples starting at |time|, executing
- // |callback| when completed.
- //
- // Only valid to call after a successful Initialize() or Flush().
- virtual void Preroll(base::TimeDelta time,
- const PipelineStatusCB& callback) = 0;
-
- // Stop all operations in preparation for being deleted, executing |callback|
- // when complete.
- virtual void Stop(const base::Closure& callback) = 0;
-
- // Updates the current playback rate.
- virtual void SetPlaybackRate(float playback_rate) = 0;
-
- // Sets the output volume.
- virtual void SetVolume(float volume) = 0;
-
- // Resumes playback after underflow occurs.
- //
- // |buffer_more_audio| is set to true if you want to increase the size of the
- // decoded audio buffer.
- virtual void ResumeAfterUnderflow(bool buffer_more_audio) = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<AudioRenderer>;
-
- AudioRenderer();
- virtual ~AudioRenderer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioRenderer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_RENDERER_H_
diff --git a/src/media/base/audio_renderer_mixer_input.cc b/src/media/base/audio_renderer_mixer_input.cc
deleted file mode 100644
index 569b126..0000000
--- a/src/media/base/audio_renderer_mixer_input.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_renderer_mixer_input.h"
-
-#include "base/logging.h"
-#include "media/base/audio_renderer_mixer.h"
-
-namespace media {
-
-AudioRendererMixerInput::AudioRendererMixerInput(
- const GetMixerCB& get_mixer_cb, const RemoveMixerCB& remove_mixer_cb)
- : playing_(false),
- initialized_(false),
- volume_(1.0f),
- get_mixer_cb_(get_mixer_cb),
- remove_mixer_cb_(remove_mixer_cb),
- mixer_(NULL),
- callback_(NULL),
- current_audio_delay_milliseconds_(0) {
-}
-
-AudioRendererMixerInput::~AudioRendererMixerInput() {
- // Mixer is no longer safe to use after |remove_mixer_cb_| has been called.
- if (initialized_)
- remove_mixer_cb_.Run(params_);
-}
-
-void AudioRendererMixerInput::Initialize(
- const AudioParameters& params,
- AudioRendererSink::RenderCallback* callback) {
- DCHECK(!initialized_);
- params_ = params;
- mixer_ = get_mixer_cb_.Run(params_);
- callback_ = callback;
- initialized_ = true;
-}
-
-void AudioRendererMixerInput::Start() {
- DCHECK(initialized_);
- DCHECK(!playing_);
-}
-
-void AudioRendererMixerInput::Stop() {
- // Stop() may be called at any time, if Pause() hasn't been called we need to
- // remove our mixer input before shutdown.
- if (!playing_)
- return;
-
- mixer_->RemoveMixerInput(this);
- playing_ = false;
-}
-
-void AudioRendererMixerInput::Play() {
- DCHECK(initialized_);
-
- if (playing_)
- return;
-
- mixer_->AddMixerInput(this);
- playing_ = true;
-}
-
-void AudioRendererMixerInput::Pause(bool /* flush */) {
- DCHECK(initialized_);
-
- if (!playing_)
- return;
-
- mixer_->RemoveMixerInput(this);
- playing_ = false;
-}
-
-bool AudioRendererMixerInput::SetVolume(double volume) {
- volume_ = volume;
- return true;
-}
-
-double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) {
- int frames_filled = callback_->Render(
- audio_bus,
- current_audio_delay_milliseconds_ + buffer_delay.InMilliseconds());
-
- // AudioConverter expects unfilled frames to be zeroed.
- if (frames_filled < audio_bus->frames()) {
- audio_bus->ZeroFramesPartial(
- frames_filled, audio_bus->frames() - frames_filled);
- }
-
- return frames_filled > 0 ? volume_ : 0;
-}
-
-void AudioRendererMixerInput::OnRenderError() {
- callback_->OnRenderError();
-}
-
-} // namespace media
diff --git a/src/media/base/audio_renderer_mixer_input.h b/src/media/base/audio_renderer_mixer_input.h
deleted file mode 100644
index a08b108..0000000
--- a/src/media/base/audio_renderer_mixer_input.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_
-#define MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "media/base/audio_converter.h"
-#include "media/base/audio_renderer_sink.h"
-
-namespace media {
-
-class AudioRendererMixer;
-
-class MEDIA_EXPORT AudioRendererMixerInput
- : NON_EXPORTED_BASE(public AudioRendererSink),
- public AudioConverter::InputCallback {
- public:
- typedef base::Callback<AudioRendererMixer*(
- const AudioParameters& params)> GetMixerCB;
- typedef base::Callback<void(const AudioParameters& params)> RemoveMixerCB;
-
- AudioRendererMixerInput(
- const GetMixerCB& get_mixer_cb, const RemoveMixerCB& remove_mixer_cb);
-
- // AudioRendererSink implementation.
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void Play() OVERRIDE;
- virtual void Pause(bool flush) OVERRIDE;
- virtual bool SetVolume(double volume) OVERRIDE;
- virtual void Initialize(const AudioParameters& params,
- AudioRendererSink::RenderCallback* renderer) OVERRIDE;
-
- // Called by AudioRendererMixer when new delay information is available.
- void set_audio_delay_milliseconds(int audio_delay_milliseconds) {
- current_audio_delay_milliseconds_ = audio_delay_milliseconds;
- }
-
- // Called by AudioRendererMixer when an error occurs.
- void OnRenderError();
-
- protected:
- virtual ~AudioRendererMixerInput();
-
- private:
- friend class AudioRendererMixerInputTest;
-
- bool playing_;
- bool initialized_;
- double volume_;
-
- // AudioConverter::InputCallback implementation.
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE;
-
- // Callbacks provided during construction which allow AudioRendererMixerInput
- // to retrieve a mixer during Initialize() and notify when it's done with it.
- GetMixerCB get_mixer_cb_;
- RemoveMixerCB remove_mixer_cb_;
-
- // AudioParameters received during Initialize().
- AudioParameters params_;
-
- // AudioRendererMixer provided through |get_mixer_cb_| during Initialize(),
- // guaranteed to live (at least) until |remove_mixer_cb_| is called.
- AudioRendererMixer* mixer_;
-
- // Source of audio data which is provided to the mixer.
- AudioRendererSink::RenderCallback* callback_;
-
- // The current audio delay as last provided by AudioRendererMixer.
- int current_audio_delay_milliseconds_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerInput);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_
diff --git a/src/media/base/audio_renderer_mixer_input_unittest.cc b/src/media/base/audio_renderer_mixer_input_unittest.cc
deleted file mode 100644
index ad13db1..0000000
--- a/src/media/base/audio_renderer_mixer_input_unittest.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/bind_helpers.h"
-#include "media/base/audio_renderer_mixer.h"
-#include "media/base/audio_renderer_mixer_input.h"
-#include "media/base/fake_audio_render_callback.h"
-#include "media/base/mock_audio_renderer_sink.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kBitsPerChannel = 16;
-static const int kSampleRate = 48000;
-static const int kBufferSize = 8192;
-static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
-
-class AudioRendererMixerInputTest : public testing::Test {
- public:
- AudioRendererMixerInputTest() {
- audio_parameters_ = AudioParameters(
- AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate,
- kBitsPerChannel, kBufferSize);
-
- CreateMixerInput();
- fake_callback_.reset(new FakeAudioRenderCallback(0));
- mixer_input_->Initialize(audio_parameters_, fake_callback_.get());
- EXPECT_CALL(*this, RemoveMixer(testing::_));
- audio_bus_ = AudioBus::Create(audio_parameters_);
- }
-
- void CreateMixerInput() {
- mixer_input_ = new AudioRendererMixerInput(
- base::Bind(
- &AudioRendererMixerInputTest::GetMixer, base::Unretained(this)),
- base::Bind(
- &AudioRendererMixerInputTest::RemoveMixer, base::Unretained(this)));
- }
-
- AudioRendererMixer* GetMixer(const AudioParameters& params) {
- if (!mixer_.get()) {
- scoped_refptr<MockAudioRendererSink> sink = new MockAudioRendererSink();
- EXPECT_CALL(*sink, Start());
- EXPECT_CALL(*sink, Stop());
-
- mixer_.reset(new AudioRendererMixer(
- audio_parameters_, audio_parameters_, sink));
- }
- return mixer_.get();
- }
-
- double ProvideInput() {
- return mixer_input_->ProvideInput(audio_bus_.get(), base::TimeDelta());
- }
-
- int GetAudioDelayMilliseconds() {
- return mixer_input_->current_audio_delay_milliseconds_;
- }
-
- MOCK_METHOD1(RemoveMixer, void(const AudioParameters&));
-
- protected:
- virtual ~AudioRendererMixerInputTest() {}
-
- AudioParameters audio_parameters_;
- scoped_ptr<AudioRendererMixer> mixer_;
- scoped_refptr<AudioRendererMixerInput> mixer_input_;
- scoped_ptr<FakeAudioRenderCallback> fake_callback_;
- scoped_ptr<AudioBus> audio_bus_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerInputTest);
-};
-
-// Test that getting and setting the volume work as expected. The volume is
-// returned from ProvideInput() only when playing.
-TEST_F(AudioRendererMixerInputTest, GetSetVolume) {
- mixer_input_->Start();
- mixer_input_->Play();
-
- // Starting volume should be 1.0.
- EXPECT_DOUBLE_EQ(ProvideInput(), 1);
-
- const double kVolume = 0.5;
- EXPECT_TRUE(mixer_input_->SetVolume(kVolume));
- EXPECT_DOUBLE_EQ(ProvideInput(), kVolume);
-
- mixer_input_->Stop();
-}
-
-// Test Start()/Play()/Pause()/Stop()/playing() all work as expected. Also
-// implicitly tests that AddMixerInput() and RemoveMixerInput() work without
-// crashing; functional tests for these methods are in AudioRendererMixerTest.
-TEST_F(AudioRendererMixerInputTest, StartPlayPauseStopPlaying) {
- mixer_input_->Start();
- mixer_input_->Play();
- EXPECT_DOUBLE_EQ(ProvideInput(), 1);
- mixer_input_->Pause(false);
- mixer_input_->Play();
- EXPECT_DOUBLE_EQ(ProvideInput(), 1);
- mixer_input_->Stop();
-}
-
-// Test that Stop() can be called before Initialize() and Start().
-TEST_F(AudioRendererMixerInputTest, StopBeforeInitializeOrStart) {
- // |mixer_input_| was initialized during construction.
- mixer_input_->Stop();
-
- // Verify Stop() works without Initialize() or Start().
- CreateMixerInput();
- mixer_input_->Stop();
-}
-
-} // namespace media
diff --git a/src/media/base/audio_renderer_sink.h b/src/media/base/audio_renderer_sink.h
deleted file mode 100644
index 5043b85..0000000
--- a/src/media/base/audio_renderer_sink.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_RENDERER_SINK_H_
-#define MEDIA_BASE_AUDIO_RENDERER_SINK_H_
-
-#include <vector>
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_bus.h"
-#include "media/base/media_export.h"
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#endif // defined(OS_STARBOARD)
-
-namespace media {
-
-// AudioRendererSink is an interface representing the end-point for
-// rendered audio. An implementation is expected to
-// periodically call Render() on a callback object.
-
-class MEDIA_EXPORT AudioRendererSink
- : public base::RefCountedThreadSafe<media::AudioRendererSink> {
- public:
- class MEDIA_EXPORT RenderCallback {
- public:
- // Attempts to completely fill all channels of |dest|, returns actual
- // number of frames filled.
- virtual int Render(AudioBus* dest, int audio_delay_milliseconds) = 0;
-
- // Synchronized audio I/O - see InitializeIO() below.
- virtual void RenderIO(AudioBus* /* source */,
- AudioBus* /* dest */,
- int /* audio_delay_milliseconds */) {}
-
- // Signals an error has occurred.
- virtual void OnRenderError() = 0;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // Callback from the sink to the renderer to indicate that it is currently
- // full and will not be requesting additional data until some is consumed.
- virtual void SinkFull() = 0;
-
-#if defined(OS_STARBOARD)
-#if SB_IS(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
- // Callback from the sink to the renderer to indicate that it has not
- // enough data to continue playback without playing past the end of
- // buffered data.
- virtual void SinkUnderflow() = 0;
-#endif // SB_IS(MEDIA_UNDERFLOW_DETECTED_BY_AUDIO_SINK)
-#endif // defined(OS_STARBOARD)
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- protected:
- virtual ~RenderCallback() {}
- };
-
- // Sets important information about the audio stream format.
- // It must be called before any of the other methods.
- virtual void Initialize(const AudioParameters& params,
- RenderCallback* callback) = 0;
-
- // InitializeIO() may be called instead of Initialize() for clients who wish
- // to have synchronized input and output. |input_channels| specifies the
- // number of input channels which will be at the same sample-rate
- // and buffer-size as the output as specified in |params|.
- // The callback's RenderIO() method will be called instead of Render(),
- // providing the synchronized input data at the same time as when new
- // output data is to be rendered.
- virtual void InitializeIO(const AudioParameters& /* params */,
- int /* input_channels */,
- RenderCallback* /* callback */) {}
-
- // Starts audio playback.
- virtual void Start() = 0;
-
- // Stops audio playback.
- virtual void Stop() = 0;
-
- // Pauses playback.
- virtual void Pause(bool flush) = 0;
-
- // Resumes playback after calling Pause().
- virtual void Play() = 0;
-
- // Sets the playback volume, with range [0.0, 1.0] inclusive.
- // Returns |true| on success.
- virtual bool SetVolume(double volume) = 0;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // To avoid duplication of audio data and additional copies our Sink
- // implementation is responsible for buffering rendered audio. As a
- // result the renderer relays the message to buffer more audio back
- // to the Sink.
- virtual void ResumeAfterUnderflow(bool buffer_more_audio) = 0;
-#endif
-
- protected:
- friend class base::RefCountedThreadSafe<AudioRendererSink>;
- virtual ~AudioRendererSink() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_AUDIO_RENDERER_SINK_H_
diff --git a/src/media/base/audio_splicer.cc b/src/media/base/audio_splicer.cc
deleted file mode 100644
index 2efbba9..0000000
--- a/src/media/base/audio_splicer.cc
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_splicer.h"
-
-#include <cstdlib>
-
-#include "base/logging.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/buffers.h"
-#include "media/base/data_buffer.h"
-
-namespace media {
-
-// Largest gap or overlap allowed by this class. Anything
-// larger than this will trigger an error.
-// This is an arbitrary value, but the initial selection of 50ms
-// roughly represents the duration of 2 compressed AAC or MP3 frames.
-static const int kMaxTimeDeltaInMilliseconds = 50;
-
-AudioSplicer::AudioSplicer(int bytes_per_frame, int samples_per_second)
- : output_timestamp_helper_(bytes_per_frame, samples_per_second),
- min_gap_size_(2 * bytes_per_frame),
- received_end_of_stream_(false) {
-}
-
-AudioSplicer::~AudioSplicer() {
-}
-
-void AudioSplicer::Reset() {
- output_timestamp_helper_.SetBaseTimestamp(kNoTimestamp());
- output_buffers_.clear();
- received_end_of_stream_ = false;
-}
-
-bool AudioSplicer::AddInput(const scoped_refptr<Buffer>& input){
- DCHECK(!received_end_of_stream_ || input->IsEndOfStream());
-
- if (input->IsEndOfStream()) {
- output_buffers_.push_back(input);
- received_end_of_stream_ = true;
- return true;
- }
-
- DCHECK(input->GetTimestamp() != kNoTimestamp());
- DCHECK(input->GetDuration() > base::TimeDelta());
- DCHECK_GT(input->GetDataSize(), 0);
-
- if (output_timestamp_helper_.base_timestamp() == kNoTimestamp())
- output_timestamp_helper_.SetBaseTimestamp(input->GetTimestamp());
-
- if (output_timestamp_helper_.base_timestamp() > input->GetTimestamp()) {
- DVLOG(1) << "Input timestamp is before the base timestamp.";
- return false;
- }
-
- base::TimeDelta timestamp = input->GetTimestamp();
- base::TimeDelta expected_timestamp = output_timestamp_helper_.GetTimestamp();
- base::TimeDelta delta = timestamp - expected_timestamp;
-
- if (std::abs(delta.InMilliseconds()) > kMaxTimeDeltaInMilliseconds) {
- DVLOG(1) << "Timestamp delta too large: " << delta.InMicroseconds() << "us";
- return false;
- }
-
- int bytes_to_fill = 0;
- if (delta != base::TimeDelta())
- bytes_to_fill = output_timestamp_helper_.GetBytesToTarget(timestamp);
-
- if (bytes_to_fill == 0 || std::abs(bytes_to_fill) < min_gap_size_) {
- AddOutputBuffer(input);
- return true;
- }
-
- if (bytes_to_fill > 0) {
- DVLOG(1) << "Gap detected @ " << expected_timestamp.InMicroseconds()
- << " us: " << delta.InMicroseconds() << " us";
-
- // Create a buffer with enough silence samples to fill the gap and
- // add it to the output buffer.
- scoped_refptr<DataBuffer> gap = new DataBuffer(bytes_to_fill);
- gap->SetDataSize(bytes_to_fill);
- memset(gap->GetWritableData(), 0, bytes_to_fill);
- gap->SetTimestamp(expected_timestamp);
- gap->SetDuration(output_timestamp_helper_.GetDuration(bytes_to_fill));
- AddOutputBuffer(gap);
-
- // Add the input buffer now that the gap has been filled.
- AddOutputBuffer(input);
- return true;
- }
-
- int bytes_to_skip = -bytes_to_fill;
-
- DVLOG(1) << "Overlap detected @ " << expected_timestamp.InMicroseconds()
- << " us: " << -delta.InMicroseconds() << " us";
-
- if (input->GetDataSize() <= bytes_to_skip) {
- DVLOG(1) << "Dropping whole buffer";
- return true;
- }
-
- // Copy the trailing samples that do not overlap samples already output
- // into a new buffer. Add this new buffer to the output queue.
- //
- // TODO(acolwell): Implement a cross-fade here so the transition is less
- // jarring.
- int new_buffer_size = input->GetDataSize() - bytes_to_skip;
-
- scoped_refptr<DataBuffer> new_buffer = new DataBuffer(new_buffer_size);
- new_buffer->SetDataSize(new_buffer_size);
- memcpy(new_buffer->GetWritableData(),
- input->GetData() + bytes_to_skip,
- new_buffer_size);
- new_buffer->SetTimestamp(expected_timestamp);
- new_buffer->SetDuration(
- output_timestamp_helper_.GetDuration(new_buffer_size));
- AddOutputBuffer(new_buffer);
- return true;
-}
-
-bool AudioSplicer::HasNextBuffer() const {
- return !output_buffers_.empty();
-}
-
-scoped_refptr<Buffer> AudioSplicer::GetNextBuffer() {
- scoped_refptr<Buffer> ret = output_buffers_.front();
- output_buffers_.pop_front();
- return ret;
-}
-
-void AudioSplicer::AddOutputBuffer(const scoped_refptr<Buffer>& buffer) {
- output_timestamp_helper_.AddBytes(buffer->GetDataSize());
- output_buffers_.push_back(buffer);
-}
-
-} // namespace media
diff --git a/src/media/base/audio_splicer.h b/src/media/base/audio_splicer.h
deleted file mode 100644
index aa97fae..0000000
--- a/src/media/base/audio_splicer.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_SPLICER_H_
-#define MEDIA_BASE_AUDIO_SPLICER_H_
-
-#include <deque>
-
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioDecoderConfig;
-class Buffer;
-
-// Helper class that handles filling gaps and resolving overlaps.
-class MEDIA_EXPORT AudioSplicer {
- public:
- AudioSplicer(int bytes_per_frame, int samples_per_second);
- ~AudioSplicer();
-
- // Resets the splicer state by clearing the output buffers queue,
- // and resetting the timestamp helper.
- void Reset();
-
- // Adds a new buffer full of samples or end of stream buffer to the splicer.
- // Returns true if the buffer was accepted. False is returned if an error
- // occurred.
- bool AddInput(const scoped_refptr<Buffer>& input);
-
- // Returns true if the splicer has a buffer to return.
- bool HasNextBuffer() const;
-
- // Removes the next buffer from the output buffer queue and returns it.
- // This should only be called if HasNextBuffer() returns true.
- scoped_refptr<Buffer> GetNextBuffer();
-
- private:
- void AddOutputBuffer(const scoped_refptr<Buffer>& buffer);
-
- AudioTimestampHelper output_timestamp_helper_;
-
- // Minimum gap size needed before the splicer will take action to
- // fill a gap. This avoids periodically inserting and then dropping samples
- // when the buffer timestamps are slightly off because of timestamp rounding
- // in the source content.
- int min_gap_size_;
-
- std::deque<scoped_refptr<Buffer> > output_buffers_;
- bool received_end_of_stream_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSplicer);
-};
-
-} // namespace media
-
-#endif
diff --git a/src/media/base/audio_splicer_unittest.cc b/src/media/base/audio_splicer_unittest.cc
deleted file mode 100644
index 2096789..0000000
--- a/src/media/base/audio_splicer_unittest.cc
+++ /dev/null
@@ -1,366 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/memory/scoped_ptr.h"
-#include "media/base/audio_splicer.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/buffers.h"
-#include "media/base/data_buffer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kBytesPerFrame = 4;
-static const int kDefaultSampleRate = 44100;
-static const int kDefaultBufferSize = 100 * kBytesPerFrame;
-
-class AudioSplicerTest : public ::testing::Test {
- public:
- AudioSplicerTest()
- : splicer_(kBytesPerFrame, kDefaultSampleRate),
- input_timestamp_helper_(kBytesPerFrame, kDefaultSampleRate) {
- input_timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
- }
-
- scoped_refptr<Buffer> GetNextInputBuffer(uint8 value) {
- return GetNextInputBuffer(value, kDefaultBufferSize);
- }
-
- scoped_refptr<Buffer> GetNextInputBuffer(uint8 value, int size) {
- scoped_refptr<DataBuffer> buffer = new DataBuffer(size);
- buffer->SetDataSize(size);
- memset(buffer->GetWritableData(), value, buffer->GetDataSize());
- buffer->SetTimestamp(input_timestamp_helper_.GetTimestamp());
- buffer->SetDuration(
- input_timestamp_helper_.GetDuration(buffer->GetDataSize()));
- input_timestamp_helper_.AddBytes(buffer->GetDataSize());
- return buffer;
- }
-
- bool VerifyData(const uint8* data, int size, int value) {
- for (int i = 0; i < size; ++i) {
- if (data[i] != value)
- return false;
- }
- return true;
- }
-
- protected:
- AudioSplicer splicer_;
- AudioTimestampHelper input_timestamp_helper_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioSplicerTest);
-};
-
-TEST_F(AudioSplicerTest, PassThru) {
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Test single buffer pass-thru behavior.
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.HasNextBuffer());
-
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
-
- // Test that multiple buffers can be queued in the splicer.
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2);
- scoped_refptr<Buffer> input_3 = GetNextInputBuffer(3);
- EXPECT_TRUE(splicer_.AddInput(input_2));
- EXPECT_TRUE(splicer_.AddInput(input_3));
- EXPECT_TRUE(splicer_.HasNextBuffer());
-
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_TRUE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_2->GetTimestamp(), output_2->GetTimestamp());
- EXPECT_EQ(input_2->GetDuration(), output_2->GetDuration());
- EXPECT_EQ(input_2->GetDataSize(), output_2->GetDataSize());
-
- scoped_refptr<Buffer> output_3 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_3->GetTimestamp(), output_3->GetTimestamp());
- EXPECT_EQ(input_3->GetDuration(), output_3->GetDuration());
- EXPECT_EQ(input_3->GetDataSize(), output_3->GetDataSize());
-}
-
-TEST_F(AudioSplicerTest, Reset) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.HasNextBuffer());
-
- splicer_.Reset();
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Add some bytes to the timestamp helper so that the
- // next buffer starts many frames beyond the end of
- // |input_1|. This is to make sure that Reset() actually
- // clears its state and doesn't try to insert a gap.
- input_timestamp_helper_.AddBytes(100 * kBytesPerFrame);
-
- // Verify that a new input buffer passes through as expected.
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2);
- EXPECT_TRUE(splicer_.AddInput(input_2));
- EXPECT_TRUE(splicer_.HasNextBuffer());
-
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_2->GetTimestamp(), output_2->GetTimestamp());
- EXPECT_EQ(input_2->GetDuration(), output_2->GetDuration());
- EXPECT_EQ(input_2->GetDataSize(), output_2->GetDataSize());
-}
-
-TEST_F(AudioSplicerTest, EndOfStream) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
- scoped_refptr<Buffer> input_2 = new DataBuffer(0); // End of stream.
- scoped_refptr<Buffer> input_3 = GetNextInputBuffer(2);
- EXPECT_TRUE(input_2->IsEndOfStream());
-
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.AddInput(input_2));
- EXPECT_TRUE(splicer_.HasNextBuffer());
-
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
-
- EXPECT_TRUE(output_2->IsEndOfStream());
-
- // Verify that buffers can be added again after Reset().
- splicer_.Reset();
- EXPECT_TRUE(splicer_.AddInput(input_3));
- scoped_refptr<Buffer> output_3 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_3->GetTimestamp(), output_3->GetTimestamp());
- EXPECT_EQ(input_3->GetDuration(), output_3->GetDuration());
- EXPECT_EQ(input_3->GetDataSize(), output_3->GetDataSize());
-}
-
-
-// Test the gap insertion code.
-// +--------------+ +--------------+
-// |11111111111111| |22222222222222|
-// +--------------+ +--------------+
-// Results in:
-// +--------------+----+--------------+
-// |11111111111111|0000|22222222222222|
-// +--------------+----+--------------+
-TEST_F(AudioSplicerTest, GapInsertion) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
-
- // Add bytes to the timestamp helper so that the next buffer
- // will have a starting timestamp that indicates a gap is
- // present.
- const int kGapSize = 7 * kBytesPerFrame;
- input_timestamp_helper_.AddBytes(kGapSize);
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2);
-
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.AddInput(input_2));
-
- // Verify that a gap buffer is generated.
- EXPECT_TRUE(splicer_.HasNextBuffer());
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- scoped_refptr<Buffer> output_3 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Verify that the first input buffer passed through unmodified.
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
- EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1));
-
- // Verify the contents of the gap buffer.
- base::TimeDelta gap_timestamp =
- input_1->GetTimestamp() + input_1->GetDuration();
- base::TimeDelta gap_duration = input_2->GetTimestamp() - gap_timestamp;
- EXPECT_GT(gap_duration, base::TimeDelta());
- EXPECT_EQ(gap_timestamp, output_2->GetTimestamp());
- EXPECT_EQ(gap_duration, output_2->GetDuration());
- EXPECT_EQ(kGapSize, output_2->GetDataSize());
- EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 0));
-
- // Verify that the second input buffer passed through unmodified.
- EXPECT_EQ(input_2->GetTimestamp(), output_3->GetTimestamp());
- EXPECT_EQ(input_2->GetDuration(), output_3->GetDuration());
- EXPECT_EQ(input_2->GetDataSize(), output_3->GetDataSize());
- EXPECT_TRUE(VerifyData(output_3->GetData(), output_3->GetDataSize(), 2));
-}
-
-
-// Test that an error is signalled when the gap between input buffers is
-// too large.
-TEST_F(AudioSplicerTest, GapTooLarge) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
-
- // Add a seconds worth of bytes so that an unacceptably large
- // gap exists between |input_1| and |input_2|.
- const int kGapSize = kDefaultSampleRate * kBytesPerFrame;
- input_timestamp_helper_.AddBytes(kGapSize);
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2);
-
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_FALSE(splicer_.AddInput(input_2));
-
- EXPECT_TRUE(splicer_.HasNextBuffer());
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
-
- // Verify that the first input buffer passed through unmodified.
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
- EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1));
-
- // Verify that the second buffer is not available.
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Reset the timestamp helper so it can generate a buffer that is
- // right after |input_1|.
- input_timestamp_helper_.SetBaseTimestamp(
- input_1->GetTimestamp() + input_1->GetDuration());
-
- // Verify that valid buffers are still accepted.
- scoped_refptr<Buffer> input_3 = GetNextInputBuffer(3);
- EXPECT_TRUE(splicer_.AddInput(input_3));
- EXPECT_TRUE(splicer_.HasNextBuffer());
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
- EXPECT_EQ(input_3->GetTimestamp(), output_2->GetTimestamp());
- EXPECT_EQ(input_3->GetDuration(), output_2->GetDuration());
- EXPECT_EQ(input_3->GetDataSize(), output_2->GetDataSize());
- EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 3));
-}
-
-
-// Verifies that an error is signalled if AddInput() is called
-// with a timestamp that is earlier than the first buffer added.
-TEST_F(AudioSplicerTest, BufferAddedBeforeBase) {
- input_timestamp_helper_.SetBaseTimestamp(
- base::TimeDelta::FromMicroseconds(10));
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
-
- // Reset the timestamp helper so the next buffer will have a timestamp earlier
- // than |input_1|.
- input_timestamp_helper_.SetBaseTimestamp(base::TimeDelta::FromSeconds(0));
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(1);
-
- EXPECT_GT(input_1->GetTimestamp(), input_2->GetTimestamp());
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_FALSE(splicer_.AddInput(input_2));
-}
-
-
-// Test when one buffer partially overlaps another.
-// +--------------+
-// |11111111111111|
-// +--------------+
-// +--------------+
-// |22222222222222|
-// +--------------+
-// Results in:
-// +--------------+----------+
-// |11111111111111|2222222222|
-// +--------------+----------+
-TEST_F(AudioSplicerTest, PartialOverlap) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
-
- // Reset timestamp helper so that the next buffer will have a
- // timestamp that starts in the middle of |input_1|.
- const int kOverlapSize = input_1->GetDataSize() / 4;
- input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp());
- input_timestamp_helper_.AddBytes(input_1->GetDataSize() - kOverlapSize);
-
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2);
-
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.AddInput(input_2));
-
- EXPECT_TRUE(splicer_.HasNextBuffer());
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Verify that the first input buffer passed through unmodified.
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
- EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1));
-
-
- // Verify that the second input buffer was truncated to only contain
- // the samples that are after the end of |input_1|.
- base::TimeDelta expected_timestamp =
- input_1->GetTimestamp() + input_1->GetDuration();
- base::TimeDelta expected_duration =
- (input_2->GetTimestamp() + input_2->GetDuration()) - expected_timestamp;
- EXPECT_EQ(expected_timestamp, output_2->GetTimestamp());
- EXPECT_EQ(expected_duration, output_2->GetDuration());
- EXPECT_EQ(input_2->GetDataSize() - kOverlapSize, output_2->GetDataSize());
- EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 2));
-}
-
-
-// Test that an input buffer that is completely overlapped by a buffer
-// that was already added is dropped.
-// +--------------+
-// |11111111111111|
-// +--------------+
-// +-----+
-// |22222|
-// +-----+
-// +-------------+
-// |3333333333333|
-// +-------------+
-// Results in:
-// +--------------+-------------+
-// |11111111111111|3333333333333|
-// +--------------+-------------+
-TEST_F(AudioSplicerTest, DropBuffer) {
- scoped_refptr<Buffer> input_1 = GetNextInputBuffer(1);
-
- // Reset timestamp helper so that the next buffer will have a
- // timestamp that starts in the middle of |input_1|.
- const int kOverlapOffset = input_1->GetDataSize() / 2;
- const int kOverlapSize = input_1->GetDataSize() / 4;
- input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp());
- input_timestamp_helper_.AddBytes(kOverlapOffset);
-
- scoped_refptr<Buffer> input_2 = GetNextInputBuffer(2, kOverlapSize);
-
- // Reset the timestamp helper so the next buffer will be right after
- // |input_1|.
- input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp());
- input_timestamp_helper_.AddBytes(input_1->GetDataSize());
- scoped_refptr<Buffer> input_3 = GetNextInputBuffer(3);
-
- EXPECT_TRUE(splicer_.AddInput(input_1));
- EXPECT_TRUE(splicer_.AddInput(input_2));
- EXPECT_TRUE(splicer_.AddInput(input_3));
-
- EXPECT_TRUE(splicer_.HasNextBuffer());
- scoped_refptr<Buffer> output_1 = splicer_.GetNextBuffer();
- scoped_refptr<Buffer> output_2 = splicer_.GetNextBuffer();
- EXPECT_FALSE(splicer_.HasNextBuffer());
-
- // Verify that the first input buffer passed through unmodified.
- EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp());
- EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration());
- EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize());
- EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1));
-
- // Verify that the second output buffer only contains
- // the samples that are in |input_3|.
- EXPECT_EQ(input_3->GetTimestamp(), output_2->GetTimestamp());
- EXPECT_EQ(input_3->GetDuration(), output_2->GetDuration());
- EXPECT_EQ(input_3->GetDataSize(), output_2->GetDataSize());
- EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 3));
-}
-
-} // namespace media
diff --git a/src/media/base/audio_timestamp_helper.cc b/src/media/base/audio_timestamp_helper.cc
deleted file mode 100644
index a3f37c4..0000000
--- a/src/media/base/audio_timestamp_helper.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_timestamp_helper.h"
-
-#include "base/logging.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-AudioTimestampHelper::AudioTimestampHelper(int bytes_per_frame,
- int samples_per_second)
- : bytes_per_frame_(bytes_per_frame),
- base_timestamp_(kNoTimestamp()),
- frame_count_(0) {
- DCHECK_GT(bytes_per_frame, 0);
- DCHECK_GT(samples_per_second, 0);
- double fps = samples_per_second;
- microseconds_per_frame_ = base::Time::kMicrosecondsPerSecond / fps;
-}
-
-void AudioTimestampHelper::SetBaseTimestamp(base::TimeDelta base_timestamp) {
- base_timestamp_ = base_timestamp;
- frame_count_ = 0;
-}
-
-base::TimeDelta AudioTimestampHelper::base_timestamp() const {
- return base_timestamp_;
-}
-
-void AudioTimestampHelper::AddBytes(int byte_count) {
- DCHECK_GE(byte_count, 0);
- DCHECK(base_timestamp_ != kNoTimestamp());
- DCHECK_EQ(byte_count % bytes_per_frame_, 0);
- frame_count_ += byte_count / bytes_per_frame_;
-}
-
-base::TimeDelta AudioTimestampHelper::GetTimestamp() const {
- return ComputeTimestamp(frame_count_);
-}
-
-base::TimeDelta AudioTimestampHelper::GetDuration(int byte_count) const {
- DCHECK_GE(byte_count, 0);
- DCHECK_EQ(byte_count % bytes_per_frame_, 0);
- int frames = byte_count / bytes_per_frame_;
- base::TimeDelta end_timestamp = ComputeTimestamp(frame_count_ + frames);
- return end_timestamp - GetTimestamp();
-}
-
-int64 AudioTimestampHelper::GetBytesToTarget(
- base::TimeDelta target) const {
- DCHECK(base_timestamp_ != kNoTimestamp());
- DCHECK(target >= base_timestamp_);
-
- int64 delta_in_us = (target - GetTimestamp()).InMicroseconds();
- if (delta_in_us == 0)
- return 0;
-
- // Compute a timestamp relative to |base_timestamp_| since timestamps
- // created from |frame_count_| are computed relative to this base.
- // This ensures that the time to frame computation here is the proper inverse
- // of the frame to time computation in ComputeTimestamp().
- base::TimeDelta delta_from_base = target - base_timestamp_;
-
- // Compute frame count for the time delta. This computation rounds to
- // the nearest whole number of frames.
- double threshold = microseconds_per_frame_ / 2;
- int64 target_frame_count =
- (delta_from_base.InMicroseconds() + threshold) / microseconds_per_frame_;
- return bytes_per_frame_ * (target_frame_count - frame_count_);
-}
-
-base::TimeDelta AudioTimestampHelper::ComputeTimestamp(
- int64 frame_count) const {
- DCHECK_GE(frame_count, 0);
- DCHECK(base_timestamp_ != kNoTimestamp());
- double frames_us = microseconds_per_frame_ * frame_count;
- return base_timestamp_ + base::TimeDelta::FromMicroseconds(frames_us);
-}
-
-} // namespace media
diff --git a/src/media/base/audio_timestamp_helper.h b/src/media/base/audio_timestamp_helper.h
deleted file mode 100644
index 4b38be7..0000000
--- a/src/media/base/audio_timestamp_helper.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_AUDIO_TIMESTAMP_HELPER_H_
-#define MEDIA_BASE_AUDIO_TIMESTAMP_HELPER_H_
-
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Generates timestamps for a sequence of audio sample bytes. This class should
-// be used any place timestamps need to be calculated for a sequence of audio
-// samples. It helps avoid timestamps inaccuracies caused by rounding/truncation
-// in repeated sample count to timestamp conversions.
-//
-// The class is constructed with bytes per frame and samples_per_second
-// information so that it can convert audio sample byte counts into timestamps.
-// After the object is constructed, SetBaseTimestamp() must be called to specify
-// the starting timestamp of the audio sequence. As audio samples are received,
-// their byte counts are added to AddBytes(). These byte counts are
-// accumulated by this class so GetTimestamp() can be used to determine the
-// timestamp for the samples that have been added. GetDuration() calculates
-// the proper duration values for samples added to the current timestamp.
-// GetBytesToTarget() determines the number of bytes that need to be
-// added/removed from the accumulated bytes to reach a target timestamp.
-class MEDIA_EXPORT AudioTimestampHelper {
- public:
- AudioTimestampHelper(int bytes_per_frame, int samples_per_second);
-
- // Sets the base timestamp to |base_timestamp| and the sets count to 0.
- void SetBaseTimestamp(base::TimeDelta base_timestamp);
-
- base::TimeDelta base_timestamp() const;
-
- // Adds sample bytes to the frame counter.
- //
- // Note: SetBaseTimestamp() must be called with a value other than
- // kNoTimestamp() before this method can be called.
- void AddBytes(int byte_count);
-
- // Get the current timestamp. This value is computed from the base_timestamp()
- // and the number of sample bytes that have been added so far.
- base::TimeDelta GetTimestamp() const;
-
- // Gets the duration if |byte_count| bytes were added to the current
- // timestamp reported by GetTimestamp(). This method ensures that
- // (GetTimestamp() + GetDuration(n)) will equal the timestamp that
- // GetTimestamp() will return if AddBytes(n) is called.
- base::TimeDelta GetDuration(int byte_count) const;
-
- // Returns the number of bytes needed to reach the target timestamp.
- //
- // Note: |target| must be >= |base_timestamp_|.
- int64 GetBytesToTarget(base::TimeDelta target) const;
-
- private:
- base::TimeDelta ComputeTimestamp(int64 frame_count) const;
-
- int bytes_per_frame_;
- double microseconds_per_frame_;
-
- base::TimeDelta base_timestamp_;
-
- // Number of frames accumulated by byte counts passed to AddBytes() calls.
- int64 frame_count_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(AudioTimestampHelper);
-};
-
-} // namespace media
-
-#endif
diff --git a/src/media/base/audio_timestamp_helper_unittest.cc b/src/media/base/audio_timestamp_helper_unittest.cc
deleted file mode 100644
index 5f5bb4e..0000000
--- a/src/media/base/audio_timestamp_helper_unittest.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/audio_timestamp_helper.h"
-#include "media/base/buffers.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kBytesPerFrame = 4;
-static const int kDefaultSampleRate = 44100;
-
-class AudioTimestampHelperTest : public ::testing::Test {
- public:
- AudioTimestampHelperTest()
- : helper_(kBytesPerFrame, kDefaultSampleRate) {
- helper_.SetBaseTimestamp(base::TimeDelta());
- }
-
- // Adds bytes to the helper and returns the current timestamp in microseconds.
- int64 AddBytes(int bytes) {
- helper_.AddBytes(bytes);
- return helper_.GetTimestamp().InMicroseconds();
- }
-
- int64 BytesToTarget(int target_in_microseconds) {
- return helper_.GetBytesToTarget(
- base::TimeDelta::FromMicroseconds(target_in_microseconds));
- }
-
- void TestGetBytesToTargetRange(int byte_count, int start, int end) {
- for (int i = start; i <= end; ++i)
- EXPECT_EQ(byte_count,BytesToTarget(i)) << " Failure for timestamp "
- << i << " us.";
- }
-
- protected:
- AudioTimestampHelper helper_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioTimestampHelperTest);
-};
-
-TEST_F(AudioTimestampHelperTest, Basic) {
- EXPECT_EQ(0, helper_.GetTimestamp().InMicroseconds());
-
- // Verify that the output timestamp is always rounded down to the
- // nearest microsecond. 1 frame @ 44100 is ~22.67573 microseconds,
- // which is why the timestamp sometimes increments by 23 microseconds
- // and other times it increments by 22 microseconds.
- EXPECT_EQ(0, AddBytes(0));
- EXPECT_EQ(22, AddBytes(kBytesPerFrame));
- EXPECT_EQ(45, AddBytes(kBytesPerFrame));
- EXPECT_EQ(68, AddBytes(kBytesPerFrame));
- EXPECT_EQ(90, AddBytes(kBytesPerFrame));
- EXPECT_EQ(113, AddBytes(kBytesPerFrame));
-
- // Verify that adding bytes one frame at a time matches the timestamp returned
- // if the same number of bytes are added all at once.
- base::TimeDelta timestamp_1 = helper_.GetTimestamp();
- helper_.SetBaseTimestamp(kNoTimestamp());
- EXPECT_TRUE(kNoTimestamp() == helper_.base_timestamp());
- helper_.SetBaseTimestamp(base::TimeDelta());
- EXPECT_EQ(0, helper_.GetTimestamp().InMicroseconds());
-
- helper_.AddBytes(5 * kBytesPerFrame);
- EXPECT_EQ(113, helper_.GetTimestamp().InMicroseconds());
- EXPECT_TRUE(timestamp_1 == helper_.GetTimestamp());
-}
-
-
-TEST_F(AudioTimestampHelperTest, GetDuration) {
- helper_.SetBaseTimestamp(base::TimeDelta::FromMicroseconds(100));
-
- int byte_count = 5 * kBytesPerFrame;
- int64 expected_durations[] = { 113, 113, 114, 113, 113, 114 };
- for (size_t i = 0; i < arraysize(expected_durations); ++i) {
- base::TimeDelta duration = helper_.GetDuration(byte_count);
- EXPECT_EQ(expected_durations[i], duration.InMicroseconds());
-
- base::TimeDelta timestamp_1 = helper_.GetTimestamp() + duration;
- helper_.AddBytes(byte_count);
- base::TimeDelta timestamp_2 = helper_.GetTimestamp();
- EXPECT_TRUE(timestamp_1 == timestamp_2);
- }
-}
-
-TEST_F(AudioTimestampHelperTest, GetBytesToTarget) {
- // Verify GetBytesToTarget() rounding behavior.
- // 1 frame @ 44100 is ~22.67573 microseconds,
-
- // Test values less than half of the frame duration.
- TestGetBytesToTargetRange(0, 0, 11);
-
- // Test values between half the frame duration & the
- // full frame duration.
- TestGetBytesToTargetRange(kBytesPerFrame, 12, 22);
-
- // Verify that the same number of bytes is returned up
- // to the next half a frame.
- TestGetBytesToTargetRange(kBytesPerFrame, 23, 34);
-
- // Verify the next 3 ranges.
- TestGetBytesToTargetRange(2 * kBytesPerFrame, 35, 56);
- TestGetBytesToTargetRange(3 * kBytesPerFrame, 57, 79);
- TestGetBytesToTargetRange(4 * kBytesPerFrame, 80, 102);
- TestGetBytesToTargetRange(5 * kBytesPerFrame, 103, 124);
-
-
- // Add bytes to the helper so negative byte counts can
- // be tested.
- helper_.AddBytes(5 * kBytesPerFrame);
-
- // Note: The timestamp ranges must match the positive values
- // tested above to verify that the code is rounding properly.
- TestGetBytesToTargetRange(0 * kBytesPerFrame, 103, 124);
- TestGetBytesToTargetRange(-1 * kBytesPerFrame, 80, 102);
- TestGetBytesToTargetRange(-2 * kBytesPerFrame, 57, 79);
- TestGetBytesToTargetRange(-3 * kBytesPerFrame, 35, 56);
- TestGetBytesToTargetRange(-4 * kBytesPerFrame, 12, 34);
- TestGetBytesToTargetRange(-5 * kBytesPerFrame, 0, 11);
-}
-
-} // namespace media
diff --git a/src/media/base/bind_to_loop.h b/src/media/base/bind_to_loop.h
deleted file mode 100644
index 9938fad..0000000
--- a/src/media/base/bind_to_loop.h
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file was GENERATED by command:
-// pump.py bind_to_loop.h.pump
-// DO NOT EDIT BY HAND!!!
-
-
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_BIND_TO_LOOP_H_
-#define MEDIA_BASE_BIND_TO_LOOP_H_
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-
-// This is a helper utility for base::Bind()ing callbacks on to particular
-// MessageLoops. A typical use is when |a| (of class |A|) wants to hand a
-// callback such as base::Bind(&A::AMethod, a) to |b|, but needs to ensure that
-// when |b| executes the callback, it does so on a particular MessageLoop.
-//
-// Typical usage: request to be called back on the current thread:
-// other->StartAsyncProcessAndCallMeBack(
-// media::BindToLoop(MessageLoopProxy::current(),
-// base::Bind(&MyClass::MyMethod, this)));
-//
-// Note that like base::Bind(), BindToLoop() can't bind non-constant references,
-// and that *unlike* base::Bind(), BindToLoop() makes copies of its arguments,
-// and thus can't be used with arrays.
-
-namespace media {
-
-// Mimic base::internal::CallbackForward, replacing p.Pass() with
-// base::Passed(&p) to account for the extra layer of indirection.
-namespace internal {
-template <typename T>
-T& TrampolineForward(T& t) { return t; }
-
-template <typename T>
-base::internal::PassedWrapper<scoped_ptr<T> > TrampolineForward(
- scoped_ptr<T>& p) { return base::Passed(&p); }
-
-template <typename T>
-base::internal::PassedWrapper<scoped_array<T> > TrampolineForward(
- scoped_array<T>& p) { return base::Passed(&p); }
-
-template <typename T, typename R>
-base::internal::PassedWrapper<scoped_ptr_malloc<T, R> > TrampolineForward(
- scoped_ptr_malloc<T, R>& p) { return base::Passed(&p); }
-
-template <typename T>
-base::internal::PassedWrapper<ScopedVector<T> > TrampolineForward(
- ScopedVector<T>& p) { return base::Passed(&p); }
-
-template <typename T> struct TrampolineHelper;
-
-template <>
-struct TrampolineHelper<void()> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void()>& cb) {
- loop->PostTask(FROM_HERE, base::Bind(cb));
- }
-};
-
-
-template <typename A1>
-struct TrampolineHelper<void(A1)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1)>& cb, A1 a1) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1)));
- }
-};
-
-
-template <typename A1, typename A2>
-struct TrampolineHelper<void(A1, A2)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2)>& cb, A1 a1, A2 a2) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2)));
- }
-};
-
-
-template <typename A1, typename A2, typename A3>
-struct TrampolineHelper<void(A1, A2, A3)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2, A3)>& cb, A1 a1, A2 a2, A3 a3) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2), internal::TrampolineForward(a3)));
- }
-};
-
-
-template <typename A1, typename A2, typename A3, typename A4>
-struct TrampolineHelper<void(A1, A2, A3, A4)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2, A3, A4)>& cb, A1 a1, A2 a2, A3 a3,
- A4 a4) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2), internal::TrampolineForward(a3),
- internal::TrampolineForward(a4)));
- }
-};
-
-
-template <typename A1, typename A2, typename A3, typename A4, typename A5>
-struct TrampolineHelper<void(A1, A2, A3, A4, A5)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2, A3, A4, A5)>& cb, A1 a1, A2 a2, A3 a3,
- A4 a4, A5 a5) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2), internal::TrampolineForward(a3),
- internal::TrampolineForward(a4), internal::TrampolineForward(a5)));
- }
-};
-
-
-template <typename A1, typename A2, typename A3, typename A4, typename A5,
- typename A6>
-struct TrampolineHelper<void(A1, A2, A3, A4, A5, A6)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2, A3, A4, A5, A6)>& cb, A1 a1, A2 a2,
- A3 a3, A4 a4, A5 a5, A6 a6) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2), internal::TrampolineForward(a3),
- internal::TrampolineForward(a4), internal::TrampolineForward(a5),
- internal::TrampolineForward(a6)));
- }
-};
-
-
-template <typename A1, typename A2, typename A3, typename A4, typename A5,
- typename A6, typename A7>
-struct TrampolineHelper<void(A1, A2, A3, A4, A5, A6, A7)> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void(A1, A2, A3, A4, A5, A6, A7)>& cb, A1 a1, A2 a2,
- A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) {
- loop->PostTask(FROM_HERE, base::Bind(cb, internal::TrampolineForward(a1),
- internal::TrampolineForward(a2), internal::TrampolineForward(a3),
- internal::TrampolineForward(a4), internal::TrampolineForward(a5),
- internal::TrampolineForward(a6), internal::TrampolineForward(a7)));
- }
-};
-
-
-} // namespace internal
-
-template<typename T>
-static base::Callback<T> BindToLoop(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<T>& cb) {
- return base::Bind(&internal::TrampolineHelper<T>::Run, loop, cb);
-}
-
-template<typename T>
-static base::Callback<T> BindToCurrentLoop(
- const base::Callback<T>& cb) {
- return BindToLoop(base::MessageLoopProxy::current(), cb);
-}
-
-} // namespace media
-
-#endif // MEDIA_BASE_BIND_TO_LOOP_H_
diff --git a/src/media/base/bind_to_loop.h.pump b/src/media/base/bind_to_loop.h.pump
deleted file mode 100644
index 1a1ae12..0000000
--- a/src/media/base/bind_to_loop.h.pump
+++ /dev/null
@@ -1,100 +0,0 @@
-$$ This is a pump file for generating file templates. Pump is a python
-$$ script that is part of the Google Test suite of utilities. Description
-$$ can be found here:
-$$
-$$ http://code.google.com/p/googletest/wiki/PumpManual
-$$
-
-$$ See comment for MAX_ARITY in base/bind.h.pump.
-$var MAX_ARITY = 7
-
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_BIND_TO_LOOP_H_
-#define MEDIA_BASE_BIND_TO_LOOP_H_
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-
-// This is a helper utility for base::Bind()ing callbacks on to particular
-// MessageLoops. A typical use is when |a| (of class |A|) wants to hand a
-// callback such as base::Bind(&A::AMethod, a) to |b|, but needs to ensure that
-// when |b| executes the callback, it does so on a particular MessageLoop.
-//
-// Typical usage: request to be called back on the current thread:
-// other->StartAsyncProcessAndCallMeBack(
-// media::BindToLoop(MessageLoopProxy::current(),
-// base::Bind(&MyClass::MyMethod, this)));
-//
-// Note that like base::Bind(), BindToLoop() can't bind non-constant references,
-// and that *unlike* base::Bind(), BindToLoop() makes copies of its arguments,
-// and thus can't be used with arrays.
-
-namespace media {
-
-// Mimic base::internal::CallbackForward, replacing p.Pass() with
-// base::Passed(&p) to account for the extra layer of indirection.
-namespace internal {
-template <typename T>
-T& TrampolineForward(T& t) { return t; }
-
-template <typename T>
-base::internal::PassedWrapper<scoped_ptr<T> > TrampolineForward(
- scoped_ptr<T>& p) { return base::Passed(&p); }
-
-template <typename T>
-base::internal::PassedWrapper<scoped_array<T> > TrampolineForward(
- scoped_array<T>& p) { return base::Passed(&p); }
-
-template <typename T, typename R>
-base::internal::PassedWrapper<scoped_ptr_malloc<T, R> > TrampolineForward(
- scoped_ptr_malloc<T, R>& p) { return base::Passed(&p); }
-
-template <typename T>
-base::internal::PassedWrapper<ScopedVector<T> > TrampolineForward(
- ScopedVector<T>& p) { return base::Passed(&p); }
-
-template <typename T> struct TrampolineHelper;
-
-$range ARITY 0..MAX_ARITY
-$for ARITY [[
-$range ARG 1..ARITY
-
-template <$for ARG , [[typename A$(ARG)]]>
-struct TrampolineHelper<void($for ARG , [[A$(ARG)]])> {
- static void Run(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<void($for ARG , [[A$(ARG)]])>& cb
-$if ARITY != 0 [[, ]]
-$for ARG , [[A$(ARG) a$(ARG)]]
-) {
- loop->PostTask(FROM_HERE, base::Bind(cb
-$if ARITY != 0 [[, ]]
-$for ARG , [[internal::TrampolineForward(a$(ARG))]]));
- }
-};
-
-
-]] $$ for ARITY
-
-} // namespace internal
-
-template<typename T>
-static base::Callback<T> BindToLoop(
- const scoped_refptr<base::MessageLoopProxy>& loop,
- const base::Callback<T>& cb) {
- return base::Bind(&internal::TrampolineHelper<T>::Run, loop, cb);
-}
-
-template<typename T>
-static base::Callback<T> BindToCurrentLoop(
- const base::Callback<T>& cb) {
- return BindToLoop(base::MessageLoopProxy::current(), cb);
-}
-
-} // namespace media
-
-#endif // MEDIA_BASE_BIND_TO_LOOP_H_
diff --git a/src/media/base/bind_to_loop_unittest.cc b/src/media/base/bind_to_loop_unittest.cc
deleted file mode 100644
index 214147d..0000000
--- a/src/media/base/bind_to_loop_unittest.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/bind_to_loop.h"
-
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-void BoundBoolSet(bool* var, bool val) {
- *var = val;
-}
-
-void BoundBoolSetFromScopedPtr(bool* var, scoped_ptr<bool> val) {
- *var = *val;
-}
-
-void BoundBoolSetFromScopedPtrMalloc(bool* var, scoped_ptr_malloc<bool> val) {
- *var = val;
-}
-
-void BoundBoolSetFromScopedArray(bool* var, scoped_array<bool> val) {
- *var = val[0];
-}
-
-void BoundBoolSetFromConstRef(bool* var, const bool& val) {
- *var = val;
-}
-
-void BoundIntegersSet(int* a_var, int* b_var, int a_val, int b_val) {
- *a_var = a_val;
- *b_var = b_val;
-}
-
-// Various tests that check that the bound function is only actually executed
-// on the message loop, not during the original Run.
-class BindToLoopTest : public ::testing::Test {
- public:
- BindToLoopTest() : proxy_(loop_.message_loop_proxy()) {}
-
- protected:
- MessageLoop loop_;
- scoped_refptr<base::MessageLoopProxy> proxy_;
-};
-
-TEST_F(BindToLoopTest, Closure) {
- // Test the closure is run inside the loop, not outside it.
- base::WaitableEvent waiter(false, false);
- base::Closure cb = BindToLoop(proxy_, base::Bind(
- &base::WaitableEvent::Signal, base::Unretained(&waiter)));
- cb.Run();
- EXPECT_FALSE(waiter.IsSignaled());
- loop_.RunUntilIdle();
- EXPECT_TRUE(waiter.IsSignaled());
-}
-
-TEST_F(BindToLoopTest, Bool) {
- bool bool_var = false;
- base::Callback<void(bool)> cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSet, &bool_var));
- cb.Run(true);
- EXPECT_FALSE(bool_var);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_var);
-}
-
-TEST_F(BindToLoopTest, BoundScopedPtrBool) {
- bool bool_val = false;
- scoped_ptr<bool> scoped_ptr_bool(new bool(true));
- base::Closure cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromScopedPtr, &bool_val, base::Passed(&scoped_ptr_bool)));
- cb.Run();
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, PassedScopedPtrBool) {
- bool bool_val = false;
- scoped_ptr<bool> scoped_ptr_bool(new bool(true));
- base::Callback<void(scoped_ptr<bool>)> cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromScopedPtr, &bool_val));
- cb.Run(scoped_ptr_bool.Pass());
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, BoundScopedArrayBool) {
- bool bool_val = false;
- scoped_array<bool> scoped_array_bool(new bool[1]);
- scoped_array_bool[0] = true;
- base::Closure cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromScopedArray, &bool_val,
- base::Passed(&scoped_array_bool)));
- cb.Run();
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, PassedScopedArrayBool) {
- bool bool_val = false;
- scoped_array<bool> scoped_array_bool(new bool[1]);
- scoped_array_bool[0] = true;
- base::Callback<void(scoped_array<bool>)> cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromScopedArray, &bool_val));
- cb.Run(scoped_array_bool.Pass());
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, BoundScopedPtrMallocBool) {
- bool bool_val = false;
- scoped_ptr_malloc<bool> scoped_ptr_malloc_bool(
- static_cast<bool*>(malloc(sizeof(bool))));
- *scoped_ptr_malloc_bool = true;
- base::Closure cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromScopedPtrMalloc, &bool_val,
- base::Passed(&scoped_ptr_malloc_bool)));
- cb.Run();
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, PassedScopedPtrMallocBool) {
- bool bool_val = false;
- scoped_ptr_malloc<bool> scoped_ptr_malloc_bool(
- static_cast<bool*>(malloc(sizeof(bool))));
- *scoped_ptr_malloc_bool = true;
- base::Callback<void(scoped_ptr_malloc<bool>)> cb = BindToLoop(
- proxy_, base::Bind(&BoundBoolSetFromScopedPtrMalloc, &bool_val));
- cb.Run(scoped_ptr_malloc_bool.Pass());
- EXPECT_FALSE(bool_val);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_val);
-}
-
-TEST_F(BindToLoopTest, BoolConstRef) {
- bool bool_var = false;
- bool true_var = true;
- const bool& true_ref = true_var;
- base::Closure cb = BindToLoop(proxy_, base::Bind(
- &BoundBoolSetFromConstRef, &bool_var, true_ref));
- cb.Run();
- EXPECT_FALSE(bool_var);
- loop_.RunUntilIdle();
- EXPECT_TRUE(bool_var);
-}
-
-TEST_F(BindToLoopTest, Integers) {
- int a = 0;
- int b = 0;
- base::Callback<void(int, int)> cb = BindToLoop(proxy_, base::Bind(
- &BoundIntegersSet, &a, &b));
- cb.Run(1, -1);
- EXPECT_EQ(a, 0);
- EXPECT_EQ(b, 0);
- loop_.RunUntilIdle();
- EXPECT_EQ(a, 1);
- EXPECT_EQ(b, -1);
-}
-
-} // namespace media
diff --git a/src/media/base/bit_reader.cc b/src/media/base/bit_reader.cc
deleted file mode 100644
index 2ddb8d4..0000000
--- a/src/media/base/bit_reader.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/bit_reader.h"
-
-namespace media {
-
-BitReader::BitReader(const uint8* data, off_t size)
- : data_(data), bytes_left_(size), num_remaining_bits_in_curr_byte_(0) {
- DCHECK(data_ != NULL && bytes_left_ > 0);
-
- UpdateCurrByte();
-}
-
-BitReader::~BitReader() {}
-
-bool BitReader::ReadBitsInternal(int num_bits, uint64* out) {
- DCHECK_LE(num_bits, 64);
-
- *out = 0;
-
- while (num_remaining_bits_in_curr_byte_ != 0 && num_bits != 0) {
- int bits_to_take = std::min(num_remaining_bits_in_curr_byte_, num_bits);
-
- *out <<= bits_to_take;
- *out += curr_byte_ >> (num_remaining_bits_in_curr_byte_ - bits_to_take);
- num_bits -= bits_to_take;
- num_remaining_bits_in_curr_byte_ -= bits_to_take;
- curr_byte_ &= (1 << num_remaining_bits_in_curr_byte_) - 1;
-
- if (num_remaining_bits_in_curr_byte_ == 0)
- UpdateCurrByte();
- }
-
- return num_bits == 0;
-}
-
-void BitReader::UpdateCurrByte() {
- DCHECK_EQ(num_remaining_bits_in_curr_byte_, 0);
-
- if (bytes_left_ == 0)
- return;
-
- // Load a new byte and advance pointers.
- curr_byte_ = *data_;
- ++data_;
- --bytes_left_;
- num_remaining_bits_in_curr_byte_ = 8;
-}
-
-} // namespace media
diff --git a/src/media/base/bit_reader.h b/src/media/base/bit_reader.h
deleted file mode 100644
index 1becf91..0000000
--- a/src/media/base/bit_reader.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_BIT_READER_H_
-#define MEDIA_BASE_BIT_READER_H_
-
-#include <sys/types.h>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// A class to read bit streams.
-class MEDIA_EXPORT BitReader {
- public:
- // Initialize the reader to start reading at |data|, |size| being size
- // of |data| in bytes.
- BitReader(const uint8* data, off_t size);
- ~BitReader();
-
- // Read |num_bits| next bits from stream and return in |*out|, first bit
- // from the stream starting at |num_bits| position in |*out|.
- // |num_bits| cannot be larger than the bits the type can hold.
- // Return false if the given number of bits cannot be read (not enough
- // bits in the stream), true otherwise. When return false, the stream will
- // enter a state where further ReadBits/SkipBits operations will always
- // return false unless |num_bits| is 0. The type |T| has to be a primitive
- // integer type.
- template<typename T> bool ReadBits(int num_bits, T *out) {
- DCHECK_LE(num_bits, static_cast<int>(sizeof(T) * 8));
- uint64 temp;
- bool ret = ReadBitsInternal(num_bits, &temp);
- *out = static_cast<T>(temp);
- return ret;
- }
-
- private:
- // Help function used by ReadBits to avoid inlining the bit reading logic.
- bool ReadBitsInternal(int num_bits, uint64* out);
-
- // Advance to the next byte, loading it into curr_byte_.
- // If the num_remaining_bits_in_curr_byte_ is 0 after this function returns,
- // the stream has reached the end.
- void UpdateCurrByte();
-
- // Pointer to the next unread (not in curr_byte_) byte in the stream.
- const uint8* data_;
-
- // Bytes left in the stream (without the curr_byte_).
- off_t bytes_left_;
-
- // Contents of the current byte; first unread bit starting at position
- // 8 - num_remaining_bits_in_curr_byte_ from MSB.
- uint8 curr_byte_;
-
- // Number of bits remaining in curr_byte_
- int num_remaining_bits_in_curr_byte_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(BitReader);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_BIT_READER_H_
diff --git a/src/media/base/bit_reader_unittest.cc b/src/media/base/bit_reader_unittest.cc
deleted file mode 100644
index 48e8c5e..0000000
--- a/src/media/base/bit_reader_unittest.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/bit_reader.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-TEST(BitReaderTest, NormalOperationTest) {
- uint8 value8;
- uint64 value64;
- // 0101 0101 1001 1001 repeats 4 times
- uint8 buffer[] = {0x55, 0x99, 0x55, 0x99, 0x55, 0x99, 0x55, 0x99};
- BitReader reader1(buffer, 6); // Initialize with 6 bytes only
-
- EXPECT_TRUE(reader1.ReadBits(1, &value8));
- EXPECT_EQ(value8, 0);
- EXPECT_TRUE(reader1.ReadBits(8, &value8));
- EXPECT_EQ(value8, 0xab); // 1010 1011
- EXPECT_TRUE(reader1.ReadBits(7, &value64));
- EXPECT_TRUE(reader1.ReadBits(32, &value64));
- EXPECT_EQ(value64, 0x55995599u);
- EXPECT_FALSE(reader1.ReadBits(1, &value8));
- value8 = 0xff;
- EXPECT_TRUE(reader1.ReadBits(0, &value8));
- EXPECT_EQ(value8, 0);
-
- BitReader reader2(buffer, 8);
- EXPECT_TRUE(reader2.ReadBits(64, &value64));
- EXPECT_EQ(value64, 0x5599559955995599ull);
- EXPECT_FALSE(reader2.ReadBits(1, &value8));
- EXPECT_TRUE(reader2.ReadBits(0, &value8));
-}
-
-TEST(BitReaderTest, ReadBeyondEndTest) {
- uint8 value8;
- uint8 buffer[] = {0x12};
- BitReader reader1(buffer, sizeof(buffer));
-
- EXPECT_TRUE(reader1.ReadBits(4, &value8));
- EXPECT_FALSE(reader1.ReadBits(5, &value8));
- EXPECT_FALSE(reader1.ReadBits(1, &value8));
- EXPECT_TRUE(reader1.ReadBits(0, &value8));
-}
-
-} // namespace media
diff --git a/src/media/base/bitstream_buffer.h b/src/media/base/bitstream_buffer.h
deleted file mode 100644
index b7ff3d7..0000000
--- a/src/media/base/bitstream_buffer.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_BITSTREAM_BUFFER_H_
-#define MEDIA_BASE_BITSTREAM_BUFFER_H_
-
-#include "base/basictypes.h"
-#include "base/shared_memory.h"
-
-namespace media {
-
-// Class for passing bitstream buffers around. Does not take ownership of the
-// data. This is the media-namespace equivalent of PP_VideoBitstreamBuffer_Dev.
-class BitstreamBuffer {
- public:
- BitstreamBuffer(int32 id, base::SharedMemoryHandle handle, size_t size)
- : id_(id),
- handle_(handle),
- size_(size) {
- }
-
- int32 id() const { return id_; }
- base::SharedMemoryHandle handle() const { return handle_; }
- size_t size() const { return size_; }
-
- private:
- int32 id_;
- base::SharedMemoryHandle handle_;
- size_t size_;
-
- // Allow compiler-generated copy & assign constructors.
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_BITSTREAM_BUFFER_H_
diff --git a/src/media/base/buffers.cc b/src/media/base/buffers.cc
deleted file mode 100644
index 63802c6..0000000
--- a/src/media/base/buffers.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/buffers.h"
-
-namespace media {
-
-Buffer::Buffer(base::TimeDelta timestamp, base::TimeDelta duration)
- : timestamp_(timestamp),
- duration_(duration) {
-}
-
-Buffer::~Buffer() {}
-
-bool Buffer::IsEndOfStream() const {
- return GetData() == NULL;
-}
-
-} // namespace media
diff --git a/src/media/base/buffers.h b/src/media/base/buffers.h
deleted file mode 100644
index d14f4db..0000000
--- a/src/media/base/buffers.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Defines a base class for representing timestamped media data. Every buffer
-// contains a timestamp in microseconds describing the relative position of
-// the buffer within the media stream, and the duration in microseconds for
-// the length of time the buffer will be rendered.
-//
-// Timestamps are derived directly from the encoded media file and are commonly
-// known as the presentation timestamp (PTS). Durations are a best-guess and
-// are usually derived from the sample/frame rate of the media file.
-//
-// Due to encoding and transmission errors, it is not guaranteed that timestamps
-// arrive in a monotonically increasing order nor that the next timestamp will
-// be equal to the previous timestamp plus the duration.
-//
-// In the ideal scenario for a 25fps movie, buffers are timestamped as followed:
-//
-// Buffer0 Buffer1 Buffer2 ... BufferN
-// Timestamp: 0us 40000us 80000us ... (N*40000)us
-// Duration*: 40000us 40000us 40000us ... 40000us
-//
-// *25fps = 0.04s per frame = 40000us per frame
-
-#ifndef MEDIA_BASE_BUFFERS_H_
-#define MEDIA_BASE_BUFFERS_H_
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Indicates an invalid or missing timestamp.
-MEDIA_EXPORT extern inline base::TimeDelta kNoTimestamp() {
- return base::TimeDelta::FromMicroseconds(kint64min);
-}
-
-// Represents an infinite stream duration.
-MEDIA_EXPORT extern inline base::TimeDelta kInfiniteDuration() {
- return base::TimeDelta::FromMicroseconds(kint64max);
-}
-
-class MEDIA_EXPORT Buffer : public base::RefCountedThreadSafe<Buffer> {
- public:
- // Returns a read only pointer to the buffer data.
- virtual const uint8* GetData() const = 0;
-
- // Returns the size of valid data in bytes.
- virtual int GetDataSize() const = 0;
-
- // If there's no data in this buffer, it represents end of stream.
- bool IsEndOfStream() const;
-
- base::TimeDelta GetTimestamp() const {
- return timestamp_;
- }
- void SetTimestamp(const base::TimeDelta& timestamp) {
- timestamp_ = timestamp;
- }
-
- base::TimeDelta GetDuration() const {
- return duration_;
- }
- void SetDuration(const base::TimeDelta& duration) {
- duration_ = duration;
- }
-
- protected:
- friend class base::RefCountedThreadSafe<Buffer>;
- Buffer(base::TimeDelta timestamp, base::TimeDelta duration);
- virtual ~Buffer();
-
- private:
- base::TimeDelta timestamp_;
- base::TimeDelta duration_;
-
- DISALLOW_COPY_AND_ASSIGN(Buffer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_BUFFERS_H_
diff --git a/src/media/base/buffers_unittest.cc b/src/media/base/buffers_unittest.cc
deleted file mode 100644
index a96b40b..0000000
--- a/src/media/base/buffers_unittest.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/string_util.h"
-#include "media/base/buffers.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-namespace {
-
-// Simple implementation of Buffer to test base class functionality.
-class TestBuffer : public Buffer {
- public:
- TestBuffer()
- : Buffer(base::TimeDelta(), base::TimeDelta()) {
- }
-
- // Sets |data_| and |size_| members for testing purposes. Does not take
- // ownership of |data|.
- TestBuffer(const uint8* data, int size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- data_(data),
- size_(size) {
- }
-
- // Buffer implementation.
- virtual const uint8* GetData() const OVERRIDE { return data_; }
- virtual int GetDataSize() const OVERRIDE { return size_; }
-
- protected:
- virtual ~TestBuffer() {}
-
- private:
- const uint8* data_;
- int size_;
-
- DISALLOW_COPY_AND_ASSIGN(TestBuffer);
-};
-
-} // namespace
-
-TEST(BufferTest, Timestamp) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimestampA = base::TimeDelta::FromMicroseconds(1337);
- const base::TimeDelta kTimestampB = base::TimeDelta::FromMicroseconds(1234);
-
- scoped_refptr<TestBuffer> buffer = new TestBuffer();
- EXPECT_TRUE(buffer->GetTimestamp() == kZero);
-
- buffer->SetTimestamp(kTimestampA);
- EXPECT_TRUE(buffer->GetTimestamp() == kTimestampA);
-
- buffer->SetTimestamp(kTimestampB);
- EXPECT_TRUE(buffer->GetTimestamp() == kTimestampB);
-}
-
-TEST(BufferTest, Duration) {
- const base::TimeDelta kZero;
- const base::TimeDelta kDurationA = base::TimeDelta::FromMicroseconds(1337);
- const base::TimeDelta kDurationB = base::TimeDelta::FromMicroseconds(1234);
-
- scoped_refptr<TestBuffer> buffer = new TestBuffer();
- EXPECT_TRUE(buffer->GetDuration() == kZero);
-
- buffer->SetDuration(kDurationA);
- EXPECT_TRUE(buffer->GetDuration() == kDurationA);
-
- buffer->SetDuration(kDurationB);
- EXPECT_TRUE(buffer->GetDuration() == kDurationB);
-}
-
-TEST(BufferTest, IsEndOfStream) {
- const uint8 kData[] = { 0x00, 0xFF };
- const int kDataSize = arraysize(kData);
-
- scoped_refptr<TestBuffer> buffer = new TestBuffer(NULL, 0);
- EXPECT_TRUE(buffer->IsEndOfStream());
-
- buffer = new TestBuffer(kData, kDataSize);
- EXPECT_FALSE(buffer->IsEndOfStream());
-}
-
-} // namespace media
diff --git a/src/media/base/byte_queue.cc b/src/media/base/byte_queue.cc
deleted file mode 100644
index e91bfb7..0000000
--- a/src/media/base/byte_queue.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/byte_queue.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-// Default starting size for the queue.
-enum { kDefaultQueueSize = 1024 };
-
-ByteQueue::ByteQueue()
- : buffer_(new uint8[kDefaultQueueSize]),
- size_(kDefaultQueueSize),
- offset_(0),
- used_(0) {
-}
-
-ByteQueue::~ByteQueue() {}
-
-void ByteQueue::Reset() {
- offset_ = 0;
- used_ = 0;
-}
-
-void ByteQueue::Push(const uint8* data, int size) {
- DCHECK(data);
- DCHECK_GT(size, 0);
-
- size_t size_needed = used_ + size;
-
- // Check to see if we need a bigger buffer.
- if (size_needed > size_) {
- size_t new_size = 2 * size_;
- while (size_needed > new_size && new_size > size_)
- new_size *= 2;
-
- // Sanity check to make sure we didn't overflow.
- CHECK_GT(new_size, size_);
-
- scoped_array<uint8> new_buffer(new uint8[new_size]);
-
- // Copy the data from the old buffer to the start of the new one.
- if (used_ > 0)
- memcpy(new_buffer.get(), front(), used_);
-
- buffer_.reset(new_buffer.release());
- size_ = new_size;
- offset_ = 0;
- } else if ((offset_ + used_ + size) > size_) {
- // The buffer is big enough, but we need to move the data in the queue.
- memmove(buffer_.get(), front(), used_);
- offset_ = 0;
- }
-
- memcpy(front() + used_, data, size);
- used_ += size;
-}
-
-void ByteQueue::Peek(const uint8** data, int* size) const {
- DCHECK(data);
- DCHECK(size);
- *data = front();
- *size = used_;
-}
-
-void ByteQueue::Pop(int count) {
- DCHECK_LE(count, used_);
-
- offset_ += count;
- used_ -= count;
-
- // Move the offset back to 0 if we have reached the end of the buffer.
- if (offset_ == size_) {
- DCHECK_EQ(used_, 0);
- offset_ = 0;
- }
-}
-
-uint8* ByteQueue::front() const { return buffer_.get() + offset_; }
-
-} // namespace media
diff --git a/src/media/base/byte_queue.h b/src/media/base/byte_queue.h
deleted file mode 100644
index 7619472..0000000
--- a/src/media/base/byte_queue.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_BYTE_QUEUE_H_
-#define MEDIA_BASE_BYTE_QUEUE_H_
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Represents a queue of bytes.
-// Data is added to the end of the queue via an Push() call and removed via
-// Pop(). The contents of the queue can be observed via the Peek() method.
-// This class manages the underlying storage of the queue and tries to minimize
-// the number of buffer copies when data is appended and removed.
-class MEDIA_EXPORT ByteQueue {
- public:
- ByteQueue();
- ~ByteQueue();
-
- // Reset the queue to the empty state.
- void Reset();
-
- // Appends new bytes onto the end of the queue.
- void Push(const uint8* data, int size);
-
- // Get a pointer to the front of the queue and the queue size.
- // These values are only valid until the next Push() or
- // Pop() call.
- void Peek(const uint8** data, int* size) const;
-
- // Remove |count| bytes from the front of the queue.
- void Pop(int count);
-
- private:
- // Returns a pointer to the front of the queue.
- uint8* front() const;
-
- scoped_array<uint8> buffer_;
-
- // Size of |buffer_|.
- size_t size_;
-
- // Offset from the start of |buffer_| that marks the front of the queue.
- size_t offset_;
-
- // Number of bytes stored in the queue.
- int used_;
-
- DISALLOW_COPY_AND_ASSIGN(ByteQueue);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_BYTE_QUEUE_H_
diff --git a/src/media/base/channel_layout.cc b/src/media/base/channel_layout.cc
deleted file mode 100644
index 8a442b3..0000000
--- a/src/media/base/channel_layout.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/channel_layout.h"
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-
-namespace media {
-
-static const int kLayoutToChannels[] = {
- 0, // CHANNEL_LAYOUT_NONE
- 0, // CHANNEL_LAYOUT_UNSUPPORTED
- 1, // CHANNEL_LAYOUT_MONO
- 2, // CHANNEL_LAYOUT_STEREO
- 3, // CHANNEL_LAYOUT_2_1
- 3, // CHANNEL_LAYOUT_SURROUND
- 4, // CHANNEL_LAYOUT_4POINT0
- 4, // CHANNEL_LAYOUT_2_2
- 4, // CHANNEL_LAYOUT_QUAD
- 5, // CHANNEL_LAYOUT_5POINT0
- 6, // CHANNEL_LAYOUT_5POINT1
- 5, // CHANNEL_LAYOUT_5POINT0_BACK
- 6, // CHANNEL_LAYOUT_5POINT1_BACK
- 7, // CHANNEL_LAYOUT_7POINT0
- 8, // CHANNEL_LAYOUT_7POINT1
- 8, // CHANNEL_LAYOUT_7POINT1_WIDE
- 2, // CHANNEL_LAYOUT_STEREO_DOWNMIX
- 3, // CHANNEL_LAYOUT_2POINT1
- 4, // CHANNEL_LAYOUT_3_1
- 5, // CHANNEL_LAYOUT_4_1
- 6, // CHANNEL_LAYOUT_6_0
- 6, // CHANNEL_LAYOUT_6_0_FRONT
- 6, // CHANNEL_LAYOUT_HEXAGONAL
- 7, // CHANNEL_LAYOUT_6_1
- 7, // CHANNEL_LAYOUT_6_1_BACK
- 7, // CHANNEL_LAYOUT_6_1_FRONT
- 7, // CHANNEL_LAYOUT_7_0_FRONT
- 8, // CHANNEL_LAYOUT_7_1_WIDE_BACK
- 8, // CHANNEL_LAYOUT_OCTAGONAL
-};
-
-// The channel orderings for each layout as specified by FFmpeg. Each value
-// represents the index of each channel in each layout. Values of -1 mean the
-// channel at that index is not used for that layout.For example, the left side
-// surround sound channel in FFmpeg's 5.1 layout is in the 5th position (because
-// the order is L, R, C, LFE, LS, RS), so
-// kChannelOrderings[CHANNEL_LAYOUT_5POINT1][SIDE_LEFT] = 4;
-static const int kChannelOrderings[CHANNEL_LAYOUT_MAX][CHANNELS_MAX] = {
- // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
-
- // CHANNEL_LAYOUT_NONE
- { -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_UNSUPPORTED
- { -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_MONO
- { -1 , -1 , 0 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_STEREO
- { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_2_1
- { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , 2 , -1 , -1 },
-
- // CHANNEL_LAYOUT_SURROUND
- { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_4POINT0
- { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , 3 , -1 , -1 },
-
- // CHANNEL_LAYOUT_2_2
- { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 2 , 3 },
-
- // CHANNEL_LAYOUT_QUAD
- { 0 , 1 , -1 , -1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_5POINT0
- { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , 3 , 4 },
-
- // CHANNEL_LAYOUT_5POINT1
- { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 , 4 , 5 },
-
- // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
-
- // CHANNEL_LAYOUT_5POINT0_BACK
- { 0 , 1 , 2 , -1 , 3 , 4 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_5POINT1_BACK
- { 0 , 1 , 2 , 3 , 4 , 5 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_7POINT0
- { 0 , 1 , 2 , -1 , 5 , 6 , -1 , -1 , -1 , 3 , 4 },
-
- // CHANNEL_LAYOUT_7POINT1
- { 0 , 1 , 2 , 3 , 6 , 7 , -1 , -1 , -1 , 4 , 5 },
-
- // CHANNEL_LAYOUT_7POINT1_WIDE
- { 0 , 1 , 2 , 3 , -1 , -1 , 6 , 7 , -1 , 4 , 5 },
-
- // CHANNEL_LAYOUT_STEREO_DOWNMIX
- { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_2POINT1
- { 0 , 1 , -1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_3_1
- { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_4_1
- { 0 , 1 , 2 , 4 , -1 , -1 , -1 , -1 , 3 , -1 , -1 },
-
- // CHANNEL_LAYOUT_6_0
- { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , 5 , 3 , 4 },
-
- // CHANNEL_LAYOUT_6_0_FRONT
- { 0 , 1 , -1 , -1 , -1 , -1 , 4 , 5 , -1 , 2 , 3 },
-
- // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
-
- // CHANNEL_LAYOUT_HEXAGONAL
- { 0 , 1 , 2 , -1 , 3 , 4 , -1 , -1 , 5 , -1 , -1 },
-
- // CHANNEL_LAYOUT_6_1
- { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , 6 , 4 , 5 },
-
- // CHANNEL_LAYOUT_6_1_BACK
- { 0 , 1 , 2 , 3 , 4 , 5 , -1 , -1 , 6 , -1 , -1 },
-
- // CHANNEL_LAYOUT_6_1_FRONT
- { 0 , 1 , -1 , 6 , -1 , -1 , 4 , 5 , -1 , 2 , 3 },
-
- // CHANNEL_LAYOUT_7_0_FRONT
- { 0 , 1 , 2 , -1 , -1 , -1 , 5 , 6 , -1 , 3 , 4 },
-
- // CHANNEL_LAYOUT_7_1_WIDE_BACK
- { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , -1 , -1 , -1 },
-
- // CHANNEL_LAYOUT_OCTAGONAL
- { 0 , 1 , 2 , -1 , 5 , 6 , -1 , -1 , 7 , 3 , 4 },
-
- // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
-};
-
-int ChannelLayoutToChannelCount(ChannelLayout layout) {
- DCHECK_LT(static_cast<size_t>(layout), arraysize(kLayoutToChannels));
- return kLayoutToChannels[layout];
-}
-
-int ChannelOrder(ChannelLayout layout, Channels channel) {
- DCHECK_LT(static_cast<size_t>(layout), arraysize(kChannelOrderings));
- DCHECK_LT(static_cast<size_t>(channel), arraysize(kChannelOrderings[0]));
- return kChannelOrderings[layout][channel];
-}
-
-// Converts a channel count into a channel layout.
-ChannelLayout GuessChannelLayout(int channels) {
- switch (channels) {
- case 1:
- return CHANNEL_LAYOUT_MONO;
- case 2:
- return CHANNEL_LAYOUT_STEREO;
- case 3:
- return CHANNEL_LAYOUT_SURROUND;
- case 4:
- return CHANNEL_LAYOUT_QUAD;
- case 5:
- return CHANNEL_LAYOUT_5_0;
- case 6:
- return CHANNEL_LAYOUT_5_1;
- case 7:
- return CHANNEL_LAYOUT_6_1;
- case 8:
- return CHANNEL_LAYOUT_7_1;
- default:
- DVLOG(1) << "Unsupported channel count: " << channels;
- }
- return CHANNEL_LAYOUT_UNSUPPORTED;
-}
-
-} // namespace media
diff --git a/src/media/base/channel_layout.h b/src/media/base/channel_layout.h
deleted file mode 100644
index 168cce0..0000000
--- a/src/media/base/channel_layout.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_CHANNEL_LAYOUT_H_
-#define MEDIA_BASE_CHANNEL_LAYOUT_H_
-
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Enumerates the various representations of the ordering of audio channels.
-// Logged to UMA, so never reuse a value, always add new/greater ones!
-enum ChannelLayout {
- CHANNEL_LAYOUT_NONE = 0,
- CHANNEL_LAYOUT_UNSUPPORTED = 1,
-
- // Front C
- CHANNEL_LAYOUT_MONO = 2,
-
- // Front L, Front R
- CHANNEL_LAYOUT_STEREO = 3,
-
- // Front L, Front R, Back C
- CHANNEL_LAYOUT_2_1 = 4,
-
- // Front L, Front R, Front C
- CHANNEL_LAYOUT_SURROUND = 5,
-
- // Front L, Front R, Front C, Back C
- CHANNEL_LAYOUT_4_0 = 6,
-
- // Front L, Front R, Side L, Side R
- CHANNEL_LAYOUT_2_2 = 7,
-
- // Front L, Front R, Back L, Back R
- CHANNEL_LAYOUT_QUAD = 8,
-
- // Front L, Front R, Front C, Side L, Side R
- CHANNEL_LAYOUT_5_0 = 9,
-
- // Front L, Front R, Front C, Side L, Side R, LFE
- CHANNEL_LAYOUT_5_1 = 10,
-
- // Front L, Front R, Front C, Back L, Back R
- CHANNEL_LAYOUT_5_0_BACK = 11,
-
- // Front L, Front R, Front C, Back L, Back R, LFE
- CHANNEL_LAYOUT_5_1_BACK = 12,
-
- // Front L, Front R, Front C, Side L, Side R, Back L, Back R
- CHANNEL_LAYOUT_7_0 = 13,
-
- // Front L, Front R, Front C, Side L, Side R, LFE, Back L, Back R
- CHANNEL_LAYOUT_7_1 = 14,
-
- // Front L, Front R, Front C, Side L, Side R, LFE, Front LofC, Front RofC
- CHANNEL_LAYOUT_7_1_WIDE = 15,
-
- // Stereo L, Stereo R
- CHANNEL_LAYOUT_STEREO_DOWNMIX = 16,
-
- // Stereo L, Stereo R, LFE
- CHANNEL_LAYOUT_2POINT1 = 17,
-
- // Stereo L, Stereo R, Front C, LFE
- CHANNEL_LAYOUT_3_1 = 18,
-
- // Stereo L, Stereo R, Front C, Rear C, LFE
- CHANNEL_LAYOUT_4_1 = 19,
-
- // Stereo L, Stereo R, Front C, Side L, Side R, Back C
- CHANNEL_LAYOUT_6_0 = 20,
-
- // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC
- CHANNEL_LAYOUT_6_0_FRONT = 21,
-
- // Stereo L, Stereo R, Side L, Side R, Front C, Rear C.
- CHANNEL_LAYOUT_HEXAGONAL = 22,
-
- // Stereo L, Stereo R, Side L, Side R, Front C, Rear Center, LFE
- CHANNEL_LAYOUT_6_1 = 23,
-
- // Stereo L, Stereo R, Back L, Back R, Front C, Rear Center, LFE
- CHANNEL_LAYOUT_6_1_BACK = 24,
-
- // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC, LFE
- CHANNEL_LAYOUT_6_1_FRONT = 25,
-
- // Front L, Front R, Front C, Side L, Side R, Front LofC, Front RofC
- CHANNEL_LAYOUT_7_0_FRONT = 26,
-
- // Front L, Front R, Front C, Back L, Back R, LFE, Front LofC, Front RofC
- CHANNEL_LAYOUT_7_1_WIDE_BACK = 27,
-
- // Front L, Front R, Front C, Side L, Side R, Rear C, Back L, Back R.
- CHANNEL_LAYOUT_OCTAGONAL = 28,
-
- // Total number of layouts.
- CHANNEL_LAYOUT_MAX // Must always be last!
-};
-
-enum Channels {
- LEFT = 0,
- RIGHT,
- CENTER,
- LFE,
- BACK_LEFT,
- BACK_RIGHT,
- LEFT_OF_CENTER,
- RIGHT_OF_CENTER,
- BACK_CENTER,
- SIDE_LEFT,
- SIDE_RIGHT,
- CHANNELS_MAX
-};
-
-// Returns the expected channel position in an interleaved stream. Values of -1
-// mean the channel at that index is not used for that layout. Values range
-// from 0 to CHANNELS_MAX - 1.
-MEDIA_EXPORT int ChannelOrder(ChannelLayout layout, Channels channel);
-
-// Returns the number of channels in a given ChannelLayout.
-MEDIA_EXPORT int ChannelLayoutToChannelCount(ChannelLayout layout);
-
-// Given the number of channels, return the best layout,
-// or return CHANNEL_LAYOUT_UNSUPPORTED if there is no good match.
-MEDIA_EXPORT ChannelLayout GuessChannelLayout(int channels);
-
-} // namespace media
-
-#endif // MEDIA_BASE_CHANNEL_LAYOUT_H_
diff --git a/src/media/base/channel_mixer.cc b/src/media/base/channel_mixer.cc
deleted file mode 100644
index fa4cbb6..0000000
--- a/src/media/base/channel_mixer.cc
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MSVC++ requires this to be set before any other includes to get M_SQRT1_2.
-#define _USE_MATH_DEFINES
-
-#include "media/base/channel_mixer.h"
-
-#include <algorithm>
-#include <cmath>
-
-#include "base/logging.h"
-#include "media/base/audio_bus.h"
-#include "media/base/vector_math.h"
-
-namespace media {
-
-// Default scale factor for mixing two channels together. We use a different
-// value for stereo -> mono and mono -> stereo mixes.
-static const float kEqualPowerScale = static_cast<float>(M_SQRT1_2);
-
-static int ValidateLayout(ChannelLayout layout) {
- CHECK_NE(layout, CHANNEL_LAYOUT_NONE);
- CHECK_NE(layout, CHANNEL_LAYOUT_MAX);
-
- // TODO(dalecurtis, crogers): We will eventually handle unsupported layouts by
- // simply copying the input channels to the output channels, similar to if the
- // user requests identical input and output layouts today.
- CHECK_NE(layout, CHANNEL_LAYOUT_UNSUPPORTED);
-
- // Verify there's at least one channel. Should always be true here by virtue
- // of not being one of the invalid layouts, but lets double check to be sure.
- int channel_count = ChannelLayoutToChannelCount(layout);
- DCHECK_GT(channel_count, 0);
-
- // If we have more than one channel, verify a symmetric layout for sanity.
- // The unit test will verify all possible layouts, so this can be a DCHECK.
- // Symmetry allows simplifying the matrix building code by allowing us to
- // assume that if one channel of a pair exists, the other will too.
- if (channel_count > 1) {
- DCHECK((ChannelOrder(layout, LEFT) >= 0 &&
- ChannelOrder(layout, RIGHT) >= 0) ||
- (ChannelOrder(layout, SIDE_LEFT) >= 0 &&
- ChannelOrder(layout, SIDE_RIGHT) >= 0) ||
- (ChannelOrder(layout, BACK_LEFT) >= 0 &&
- ChannelOrder(layout, BACK_RIGHT) >= 0) ||
- (ChannelOrder(layout, LEFT_OF_CENTER) >= 0 &&
- ChannelOrder(layout, RIGHT_OF_CENTER) >= 0))
- << "Non-symmetric channel layout encountered.";
- } else {
- DCHECK_EQ(layout, CHANNEL_LAYOUT_MONO);
- }
-
- return channel_count;
-}
-
-ChannelMixer::ChannelMixer(ChannelLayout input, ChannelLayout output)
- : input_layout_(input),
- output_layout_(output),
- remapping_(false) {
- // Stereo down mix should never be the output layout.
- CHECK_NE(output_layout_, CHANNEL_LAYOUT_STEREO_DOWNMIX);
-
- int input_channels = ValidateLayout(input_layout_);
- int output_channels = ValidateLayout(output_layout_);
-
- // Size out the initial matrix.
- matrix_.reserve(output_channels);
- for (int output_ch = 0; output_ch < output_channels; ++output_ch)
- matrix_.push_back(std::vector<float>(input_channels, 0));
-
- // Route matching channels and figure out which ones aren't accounted for.
- for (Channels ch = LEFT; ch < CHANNELS_MAX;
- ch = static_cast<Channels>(ch + 1)) {
- int input_ch_index = ChannelOrder(input_layout_, ch);
- int output_ch_index = ChannelOrder(output_layout_, ch);
-
- if (input_ch_index < 0)
- continue;
-
- if (output_ch_index < 0) {
- unaccounted_inputs_.push_back(ch);
- continue;
- }
-
- DCHECK_LT(static_cast<size_t>(output_ch_index), matrix_.size());
- DCHECK_LT(static_cast<size_t>(input_ch_index),
- matrix_[output_ch_index].size());
- matrix_[output_ch_index][input_ch_index] = 1;
- }
-
- // If all input channels are accounted for, there's nothing left to do.
- if (unaccounted_inputs_.empty()) {
- // Since all output channels map directly to inputs we can optimize.
- remapping_ = true;
- return;
- }
-
- // Mix front LR into center.
- if (IsUnaccounted(LEFT)) {
- // When down mixing to mono from stereo, we need to be careful of full scale
- // stereo mixes. Scaling by 1 / sqrt(2) here will likely lead to clipping
- // so we use 1 / 2 instead.
- float scale = (output == CHANNEL_LAYOUT_MONO && input_channels == 2) ?
- 0.5 : kEqualPowerScale;
- Mix(LEFT, CENTER, scale);
- Mix(RIGHT, CENTER, scale);
- }
-
- // Mix center into front LR.
- if (IsUnaccounted(CENTER)) {
- // When up mixing from mono, just do a copy to front LR.
- float scale = (input == CHANNEL_LAYOUT_MONO) ? 1 : kEqualPowerScale;
- MixWithoutAccounting(CENTER, LEFT, scale);
- Mix(CENTER, RIGHT, scale);
- }
-
- // Mix back LR into: side LR || back center || front LR || front center.
- if (IsUnaccounted(BACK_LEFT)) {
- if (HasOutputChannel(SIDE_LEFT)) {
- // If we have side LR, mix back LR into side LR, but instead if the input
- // doesn't have side LR (but output does) copy back LR to side LR.
- float scale = HasInputChannel(SIDE_LEFT) ? kEqualPowerScale : 1;
- Mix(BACK_LEFT, SIDE_LEFT, scale);
- Mix(BACK_RIGHT, SIDE_RIGHT, scale);
- } else if (HasOutputChannel(BACK_CENTER)) {
- // Mix back LR into back center.
- Mix(BACK_LEFT, BACK_CENTER, kEqualPowerScale);
- Mix(BACK_RIGHT, BACK_CENTER, kEqualPowerScale);
- } else if (output > CHANNEL_LAYOUT_MONO) {
- // Mix back LR into front LR.
- Mix(BACK_LEFT, LEFT, kEqualPowerScale);
- Mix(BACK_RIGHT, RIGHT, kEqualPowerScale);
- } else {
- // Mix back LR into front center.
- Mix(BACK_LEFT, CENTER, kEqualPowerScale);
- Mix(BACK_RIGHT, CENTER, kEqualPowerScale);
- }
- }
-
- // Mix side LR into: back LR || back center || front LR || front center.
- if (IsUnaccounted(SIDE_LEFT)) {
- if (HasOutputChannel(BACK_LEFT)) {
- // If we have back LR, mix side LR into back LR, but instead if the input
- // doesn't have back LR (but output does) copy side LR to back LR.
- float scale = HasInputChannel(BACK_LEFT) ? kEqualPowerScale : 1;
- Mix(SIDE_LEFT, BACK_LEFT, scale);
- Mix(SIDE_RIGHT, BACK_RIGHT, scale);
- } else if (HasOutputChannel(BACK_CENTER)) {
- // Mix side LR into back center.
- Mix(SIDE_LEFT, BACK_CENTER, kEqualPowerScale);
- Mix(SIDE_RIGHT, BACK_CENTER, kEqualPowerScale);
- } else if (output > CHANNEL_LAYOUT_MONO) {
- // Mix side LR into front LR.
- Mix(SIDE_LEFT, LEFT, kEqualPowerScale);
- Mix(SIDE_RIGHT, RIGHT, kEqualPowerScale);
- } else {
- // Mix side LR into front center.
- Mix(SIDE_LEFT, CENTER, kEqualPowerScale);
- Mix(SIDE_RIGHT, CENTER, kEqualPowerScale);
- }
- }
-
- // Mix back center into: back LR || side LR || front LR || front center.
- if (IsUnaccounted(BACK_CENTER)) {
- if (HasOutputChannel(BACK_LEFT)) {
- // Mix back center into back LR.
- MixWithoutAccounting(BACK_CENTER, BACK_LEFT, kEqualPowerScale);
- Mix(BACK_CENTER, BACK_RIGHT, kEqualPowerScale);
- } else if (HasOutputChannel(SIDE_LEFT)) {
- // Mix back center into side LR.
- MixWithoutAccounting(BACK_CENTER, SIDE_LEFT, kEqualPowerScale);
- Mix(BACK_CENTER, SIDE_RIGHT, kEqualPowerScale);
- } else if (output > CHANNEL_LAYOUT_MONO) {
- // Mix back center into front LR.
- // TODO(dalecurtis): Not sure about these values?
- MixWithoutAccounting(BACK_CENTER, LEFT, kEqualPowerScale);
- Mix(BACK_CENTER, RIGHT, kEqualPowerScale);
- } else {
- // Mix back center into front center.
- // TODO(dalecurtis): Not sure about these values?
- Mix(BACK_CENTER, CENTER, kEqualPowerScale);
- }
- }
-
- // Mix LR of center into: front center || front LR.
- if (IsUnaccounted(LEFT_OF_CENTER)) {
- if (HasOutputChannel(LEFT)) {
- // Mix LR of center into front LR.
- Mix(LEFT_OF_CENTER, LEFT, kEqualPowerScale);
- Mix(RIGHT_OF_CENTER, RIGHT, kEqualPowerScale);
- } else {
- // Mix LR of center into front center.
- Mix(LEFT_OF_CENTER, CENTER, kEqualPowerScale);
- Mix(RIGHT_OF_CENTER, CENTER, kEqualPowerScale);
- }
- }
-
- // Mix LFE into: front LR || front center.
- if (IsUnaccounted(LFE)) {
- if (!HasOutputChannel(CENTER)) {
- // Mix LFE into front LR.
- MixWithoutAccounting(LFE, LEFT, kEqualPowerScale);
- Mix(LFE, RIGHT, kEqualPowerScale);
- } else {
- // Mix LFE into front center.
- Mix(LFE, CENTER, kEqualPowerScale);
- }
- }
-
- // All channels should now be accounted for.
- DCHECK(unaccounted_inputs_.empty());
-
- // See if the output |matrix_| is simply a remapping matrix. If each input
- // channel maps to a single output channel we can simply remap. Doing this
- // programmatically is less fragile than logic checks on channel mappings.
- for (int output_ch = 0; output_ch < output_channels; ++output_ch) {
- int input_mappings = 0;
- for (int input_ch = 0; input_ch < input_channels; ++input_ch) {
- // We can only remap if each row contains a single scale of 1. I.e., each
- // output channel is mapped from a single unscaled input channel.
- if (matrix_[output_ch][input_ch] != 1 || ++input_mappings > 1)
- return;
- }
- }
-
- // If we've gotten here, |matrix_| is simply a remapping.
- remapping_ = true;
-}
-
-ChannelMixer::~ChannelMixer() {}
-
-void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) {
- CHECK_EQ(matrix_.size(), static_cast<size_t>(output->channels()));
- CHECK_EQ(matrix_[0].size(), static_cast<size_t>(input->channels()));
- CHECK_EQ(input->frames(), output->frames());
-
- // Zero initialize |output| so we're accumulating from zero.
- output->Zero();
-
- // If we're just remapping we can simply copy the correct input to output.
- if (remapping_) {
- for (int output_ch = 0; output_ch < output->channels(); ++output_ch) {
- for (int input_ch = 0; input_ch < input->channels(); ++input_ch) {
- float scale = matrix_[output_ch][input_ch];
- if (scale > 0) {
- DCHECK_EQ(scale, 1.0f);
- memcpy(output->channel(output_ch), input->channel(input_ch),
- sizeof(*output->channel(output_ch)) * output->frames());
- break;
- }
- }
- }
- return;
- }
-
- for (int output_ch = 0; output_ch < output->channels(); ++output_ch) {
- for (int input_ch = 0; input_ch < input->channels(); ++input_ch) {
- float scale = matrix_[output_ch][input_ch];
- // Scale should always be positive. Don't bother scaling by zero.
- DCHECK_GE(scale, 0);
- if (scale > 0) {
- vector_math::FMAC(input->channel(input_ch), scale, output->frames(),
- output->channel(output_ch));
- }
- }
- }
-}
-
-void ChannelMixer::AccountFor(Channels ch) {
- unaccounted_inputs_.erase(std::find(
- unaccounted_inputs_.begin(), unaccounted_inputs_.end(), ch));
-}
-
-bool ChannelMixer::IsUnaccounted(Channels ch) {
- return std::find(unaccounted_inputs_.begin(), unaccounted_inputs_.end(),
- ch) != unaccounted_inputs_.end();
-}
-
-bool ChannelMixer::HasInputChannel(Channels ch) {
- return ChannelOrder(input_layout_, ch) >= 0;
-}
-
-bool ChannelMixer::HasOutputChannel(Channels ch) {
- return ChannelOrder(output_layout_, ch) >= 0;
-}
-
-void ChannelMixer::Mix(Channels input_ch, Channels output_ch, float scale) {
- MixWithoutAccounting(input_ch, output_ch, scale);
- AccountFor(input_ch);
-}
-
-void ChannelMixer::MixWithoutAccounting(Channels input_ch, Channels output_ch,
- float scale) {
- int input_ch_index = ChannelOrder(input_layout_, input_ch);
- int output_ch_index = ChannelOrder(output_layout_, output_ch);
-
- DCHECK(IsUnaccounted(input_ch));
- DCHECK_GE(input_ch_index, 0);
- DCHECK_GE(output_ch_index, 0);
-
- DCHECK_EQ(matrix_[output_ch_index][input_ch_index], 0);
- matrix_[output_ch_index][input_ch_index] = scale;
-}
-
-} // namespace media
diff --git a/src/media/base/channel_mixer.h b/src/media/base/channel_mixer.h
deleted file mode 100644
index 0fdcc18..0000000
--- a/src/media/base/channel_mixer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_CHANNEL_MIXER_H_
-#define MEDIA_BASE_CHANNEL_MIXER_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioBus;
-
-// ChannelMixer is for converting audio between channel layouts. The conversion
-// matrix is built upon construction and used during each Transform() call. The
-// algorithm works by generating a conversion matrix mapping each output channel
-// to list of input channels. The transform renders all of the output channels,
-// with each output channel rendered according to a weighted sum of the relevant
-// input channels as defined in the matrix.
-class MEDIA_EXPORT ChannelMixer {
- public:
- ChannelMixer(ChannelLayout input, ChannelLayout output);
- ~ChannelMixer();
-
- // Transforms all channels from |input| into |output| channels.
- void Transform(const AudioBus* input, AudioBus* output);
-
- private:
- // Constructor helper methods for managing unaccounted input channels.
- void AccountFor(Channels ch);
- bool IsUnaccounted(Channels ch);
-
- // Helper methods for checking if |ch| exists in either |input_layout_| or
- // |output_layout_| respectively.
- bool HasInputChannel(Channels ch);
- bool HasOutputChannel(Channels ch);
-
- // Constructor helper methods for updating |matrix_| with the proper value for
- // mixing |input_ch| into |output_ch|. MixWithoutAccounting() does not remove
- // the channel from |unaccounted_inputs_|.
- void Mix(Channels input_ch, Channels output_ch, float scale);
- void MixWithoutAccounting(Channels input_ch, Channels output_ch, float scale);
-
- // Input and output channel layout provided during construction.
- ChannelLayout input_layout_;
- ChannelLayout output_layout_;
-
- // Helper variable for tracking which inputs are currently unaccounted, should
- // be empty after construction completes.
- std::vector<Channels> unaccounted_inputs_;
-
- // 2D matrix of output channels to input channels.
- std::vector< std::vector<float> > matrix_;
-
- // Optimization case for when we can simply remap the input channels to output
- // channels and don't need to do a multiply-accumulate loop over |matrix_|.
- bool remapping_;
-
- DISALLOW_COPY_AND_ASSIGN(ChannelMixer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_CHANNEL_MIXER_H_
diff --git a/src/media/base/channel_mixer_unittest.cc b/src/media/base/channel_mixer_unittest.cc
deleted file mode 100644
index a71f86b..0000000
--- a/src/media/base/channel_mixer_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MSVC++ requires this to be set before any other includes to get M_SQRT1_2.
-#define _USE_MATH_DEFINES
-
-#include <cmath>
-
-#include "base/stringprintf.h"
-#include "media/base/audio_bus.h"
-#include "media/base/channel_mixer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Number of frames to test with.
-enum { kFrames = 16 };
-
-// Test all possible layout conversions can be constructed and mixed.
-TEST(ChannelMixerTest, ConstructAllPossibleLayouts) {
- for (ChannelLayout input_layout = CHANNEL_LAYOUT_MONO;
- input_layout < CHANNEL_LAYOUT_MAX;
- input_layout = static_cast<ChannelLayout>(input_layout + 1)) {
- for (ChannelLayout output_layout = CHANNEL_LAYOUT_MONO;
- output_layout < CHANNEL_LAYOUT_STEREO_DOWNMIX;
- output_layout = static_cast<ChannelLayout>(output_layout + 1)) {
- SCOPED_TRACE(base::StringPrintf(
- "Input Layout: %d, Output Layout: %d", input_layout, output_layout));
- ChannelMixer mixer(input_layout, output_layout);
- scoped_ptr<AudioBus> input_bus = AudioBus::Create(
- ChannelLayoutToChannelCount(input_layout), kFrames);
- scoped_ptr<AudioBus> output_bus = AudioBus::Create(
- ChannelLayoutToChannelCount(output_layout), kFrames);
- for (int ch = 0; ch < input_bus->channels(); ++ch)
- std::fill(input_bus->channel(ch), input_bus->channel(ch) + kFrames, 1);
-
- mixer.Transform(input_bus.get(), output_bus.get());
- }
- }
-}
-
-struct ChannelMixerTestData {
- ChannelMixerTestData(ChannelLayout input_layout, ChannelLayout output_layout,
- float* channel_values, int num_channel_values,
- float scale)
- : input_layout(input_layout),
- output_layout(output_layout),
- channel_values(channel_values),
- num_channel_values(num_channel_values),
- scale(scale) {
- }
-
- std::string DebugString() const {
- return base::StringPrintf(
- "Input Layout: %d, Output Layout %d, Scale: %f", input_layout,
- output_layout, scale);
- }
-
- ChannelLayout input_layout;
- ChannelLayout output_layout;
- float* channel_values;
- int num_channel_values;
- float scale;
-};
-
-std::ostream& operator<<(std::ostream& os, const ChannelMixerTestData& data) {
- return os << data.DebugString();
-}
-
-class ChannelMixerTest : public testing::TestWithParam<ChannelMixerTestData> {};
-
-// Verify channels are mixed and scaled correctly. The test only works if all
-// output channels have the same value.
-TEST_P(ChannelMixerTest, Mixing) {
- ChannelLayout input_layout = GetParam().input_layout;
- ChannelLayout output_layout = GetParam().output_layout;
-
- ChannelMixer mixer(input_layout, output_layout);
- scoped_ptr<AudioBus> input_bus = AudioBus::Create(
- ChannelLayoutToChannelCount(input_layout), kFrames);
- scoped_ptr<AudioBus> output_bus = AudioBus::Create(
- ChannelLayoutToChannelCount(output_layout), kFrames);
-
- const float* channel_values = GetParam().channel_values;
- ASSERT_EQ(input_bus->channels(), GetParam().num_channel_values);
-
- float expected_value = 0;
- float scale = GetParam().scale;
- for (int ch = 0; ch < input_bus->channels(); ++ch) {
- std::fill(input_bus->channel(ch), input_bus->channel(ch) + kFrames,
- channel_values[ch]);
- expected_value += channel_values[ch] * scale;
- }
-
- mixer.Transform(input_bus.get(), output_bus.get());
-
- for (int ch = 0; ch < output_bus->channels(); ++ch) {
- for (int frame = 0; frame < output_bus->frames(); ++frame) {
- ASSERT_FLOAT_EQ(output_bus->channel(ch)[frame], expected_value);
- }
- }
-}
-
-static float kStereoToMonoValues[] = { 0.5f, 0.75f };
-static float kMonoToStereoValues[] = { 0.5f };
-// Zero the center channel since it will be mixed at scale 1 vs M_SQRT1_2.
-static float kFiveOneToMonoValues[] = { 0.1f, 0.2f, 0.0f, 0.4f, 0.5f, 0.6f };
-
-// Run through basic sanity tests for some common conversions.
-INSTANTIATE_TEST_CASE_P(ChannelMixerTest, ChannelMixerTest, testing::Values(
- ChannelMixerTestData(CHANNEL_LAYOUT_STEREO, CHANNEL_LAYOUT_MONO,
- kStereoToMonoValues, arraysize(kStereoToMonoValues),
- 0.5f),
- ChannelMixerTestData(CHANNEL_LAYOUT_MONO, CHANNEL_LAYOUT_STEREO,
- kMonoToStereoValues, arraysize(kMonoToStereoValues),
- 1.0f),
- ChannelMixerTestData(CHANNEL_LAYOUT_5_1, CHANNEL_LAYOUT_MONO,
- kFiveOneToMonoValues, arraysize(kFiveOneToMonoValues),
- static_cast<float>(M_SQRT1_2))
-));
-
-} // namespace media
diff --git a/src/media/base/clock.cc b/src/media/base/clock.cc
deleted file mode 100644
index 2432f91..0000000
--- a/src/media/base/clock.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/clock.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-Clock::Clock(TimeProvider* time_provider)
- : time_provider_(time_provider) {
- Reset();
-}
-
-Clock::~Clock() {}
-
-bool Clock::IsPlaying() const {
- return playing_;
-}
-
-base::TimeDelta Clock::Play() {
- DCHECK(!playing_);
- UpdateReferencePoints();
- playing_ = true;
- return media_time_;
-}
-
-base::TimeDelta Clock::Pause() {
- DCHECK(playing_);
- UpdateReferencePoints();
- playing_ = false;
- return media_time_;
-}
-
-void Clock::SetPlaybackRate(float playback_rate) {
- UpdateReferencePoints();
- playback_rate_ = playback_rate;
-}
-
-void Clock::SetTime(base::TimeDelta current_time, base::TimeDelta max_time) {
- DCHECK(current_time <= max_time);
- DCHECK(current_time != kNoTimestamp());
-
- UpdateReferencePoints(current_time);
- max_time_ = ClampToValidTimeRange(max_time);
- underflow_ = false;
-}
-
-base::TimeDelta Clock::Elapsed() {
- if (duration_ == kNoTimestamp())
- return base::TimeDelta();
-
- // The clock is not advancing, so return the last recorded time.
- if (!playing_ || underflow_)
- return media_time_;
-
- base::TimeDelta elapsed = EstimatedElapsedTime();
- if (max_time_ != kNoTimestamp() && elapsed > max_time_) {
- UpdateReferencePoints(max_time_);
- underflow_ = true;
- elapsed = max_time_;
- }
-
- return elapsed;
-}
-
-void Clock::SetMaxTime(base::TimeDelta max_time) {
- DCHECK(max_time != kNoTimestamp());
-
- UpdateReferencePoints();
- max_time_ = ClampToValidTimeRange(max_time);
-
- underflow_ = media_time_ > max_time_;
- if (underflow_)
- media_time_ = max_time_;
-}
-
-void Clock::SetDuration(base::TimeDelta duration) {
- DCHECK(duration > base::TimeDelta());
- duration_ = duration;
-
- media_time_ = ClampToValidTimeRange(media_time_);
- if (max_time_ != kNoTimestamp())
- max_time_ = ClampToValidTimeRange(max_time_);
-}
-
-base::TimeDelta Clock::ElapsedViaProvidedTime(const base::Time& time) const {
- // TODO(scherkus): floating point badness scaling time by playback rate.
- int64 now_us = (time - reference_).InMicroseconds();
- now_us = static_cast<int64>(now_us * playback_rate_);
- return media_time_ + base::TimeDelta::FromMicroseconds(now_us);
-}
-
-base::Time Clock::GetTimeFromProvider() const {
- if (time_provider_)
- return time_provider_();
- return base::Time();
-}
-
-base::TimeDelta Clock::ClampToValidTimeRange(base::TimeDelta time) const {
- if (duration_ == kNoTimestamp())
- return base::TimeDelta();
- return std::max(std::min(time, duration_), base::TimeDelta());
-}
-
-void Clock::EndOfStream() {
- Pause();
- SetTime(Duration(), Duration());
-}
-
-base::TimeDelta Clock::Duration() const {
- if (duration_ == kNoTimestamp())
- return base::TimeDelta();
- return duration_;
-}
-
-void Clock::UpdateReferencePoints() {
- UpdateReferencePoints(Elapsed());
-}
-
-void Clock::UpdateReferencePoints(base::TimeDelta current_time) {
- media_time_ = ClampToValidTimeRange(current_time);
- reference_ = GetTimeFromProvider();
-}
-
-base::TimeDelta Clock::EstimatedElapsedTime() {
- return ClampToValidTimeRange(
- ElapsedViaProvidedTime(GetTimeFromProvider()));
-}
-
-void Clock::Reset() {
- playing_ = false;
- playback_rate_ = 1.0f;
- max_time_ = kNoTimestamp();
- duration_ = kNoTimestamp();
- media_time_ = base::TimeDelta();
- reference_ = base::Time();
- underflow_ = false;
-}
-
-} // namespace media
diff --git a/src/media/base/clock.h b/src/media/base/clock.h
deleted file mode 100644
index 5b2a90c..0000000
--- a/src/media/base/clock.h
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_CLOCK_H_
-#define MEDIA_BASE_CLOCK_H_
-
-#include "base/basictypes.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// A clock represents a single source of time to allow audio and video streams
-// to synchronize with each other. Clock essentially tracks the media time with
-// respect to some other source of time, whether that may be the system clock or
-// updates via SetTime(). Clock uses linear interpolation to calculate the
-// current media time since the last time SetTime() was called.
-//
-// Clocks start off paused with a playback rate of 1.0f and a media time of 0.
-//
-// Clock is not thread-safe and must be externally locked.
-//
-// TODO(scherkus): Clock will some day be responsible for executing callbacks
-// given a media time. This will be used primarily by video renderers. For now
-// we'll keep using a poll-and-sleep solution.
-class MEDIA_EXPORT Clock {
- public:
- // Type for a static function pointer that acts as a time source.
- typedef base::Time(TimeProvider)();
-
- explicit Clock(TimeProvider* time_provider);
- ~Clock();
-
- // Returns true if the clock is running.
- bool IsPlaying() const;
-
- // Starts the clock and returns the current media time, which will increase
- // with respect to the current playback rate.
- base::TimeDelta Play();
-
- // Stops the clock and returns the current media time, which will remain
- // constant until Play() is called.
- base::TimeDelta Pause();
-
- // Sets a new playback rate. The rate at which the media time will increase
- // will now change.
- void SetPlaybackRate(float playback_rate);
-
- // Forcefully sets the media time to |current_time|. The second parameter is
- // the |max_time| that the clock should progress after a call to Play(). This
- // value is often the time of the end of the last frame buffered and decoded.
- //
- // These values are clamped to the duration of the video, which is initially
- // set to 0 (before SetDuration() is called).
- void SetTime(base::TimeDelta current_time, base::TimeDelta max_time);
-
- // Sets the |max_time| to be returned by a call to Elapsed().
- void SetMaxTime(base::TimeDelta max_time);
-
- // Returns the current elapsed media time. Returns 0 if SetDuration() has
- // never been called.
- base::TimeDelta Elapsed();
-
- // Sets the duration of the video. Clock expects the duration will be set
- // exactly once.
- void SetDuration(base::TimeDelta duration);
-
- // Resets clock to an uninitialized state.
- void Reset();
-
- // Notifies the clock that the end of stream has been reached. The clock state
- // is updated accordingly.
- void EndOfStream();
-
- // Returns the duration of the clock, or 0 if not set.
- base::TimeDelta Duration() const;
-
- private:
- // Updates the reference points based on the current calculated time.
- void UpdateReferencePoints();
-
- // Updates the reference points based on the given |current_time|.
- void UpdateReferencePoints(base::TimeDelta current_time);
-
- // Returns the time elapsed based on the current reference points, ignoring
- // the |max_time_| cap.
- base::TimeDelta EstimatedElapsedTime();
-
- // Returns the current media time treating the given time as the latest
- // value as returned by |time_provider_|.
- base::TimeDelta ElapsedViaProvidedTime(const base::Time& time) const;
-
- base::Time GetTimeFromProvider() const;
-
- base::TimeDelta ClampToValidTimeRange(base::TimeDelta time) const;
-
- // Function returning current time in base::Time units.
- TimeProvider* time_provider_;
-
- // Whether the clock is running.
- bool playing_;
-
- // Whether the clock is stalled because it has reached the |max_time_|
- // allowed.
- bool underflow_;
-
- // The system clock time when this clock last starting playing or had its
- // time set via SetTime().
- base::Time reference_;
-
- // Current accumulated amount of media time. The remaining portion must be
- // calculated by comparing the system time to the reference time.
- base::TimeDelta media_time_;
-
- // Current playback rate.
- float playback_rate_;
-
- // The maximum time that can be returned by calls to Elapsed().
- base::TimeDelta max_time_;
-
- // Duration of the media.
- base::TimeDelta duration_;
-
- DISALLOW_COPY_AND_ASSIGN(Clock);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_CLOCK_H_
diff --git a/src/media/base/clock_unittest.cc b/src/media/base/clock_unittest.cc
deleted file mode 100644
index 6773a5b..0000000
--- a/src/media/base/clock_unittest.cc
+++ /dev/null
@@ -1,256 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "base/test/mock_time_provider.h"
-#include "media/base/clock.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-using ::testing::InSequence;
-using ::testing::Return;
-using ::testing::StrictMock;
-
-namespace base {
-
-// Provide a stream output operator so we can use EXPECT_EQ(...) with TimeDelta.
-//
-// TODO(scherkus): move this into the testing package.
-static std::ostream& operator<<(std::ostream& stream, const TimeDelta& time) {
- return (stream << time.ToInternalValue());
-}
-
-} // namespace
-
-namespace media {
-
-static const int kDurationInSeconds = 120;
-
-class ClockTest : public ::testing::Test {
- public:
- ClockTest()
- : clock_(&base::MockTimeProvider::StaticNow) {
- SetDuration();
- EXPECT_CALL(mock_time_, Now())
- .WillRepeatedly(Return(base::Time::UnixEpoch()));
- }
-
- protected:
- void SetDuration() {
- const base::TimeDelta kDuration =
- base::TimeDelta::FromSeconds(kDurationInSeconds);
- clock_.SetDuration(kDuration);
- EXPECT_EQ(kDuration, clock_.Duration());
- }
-
- void AdvanceSystemTime(base::TimeDelta delta) {
- time_elapsed_ += delta;
- EXPECT_CALL(mock_time_, Now())
- .WillRepeatedly(Return(base::Time::UnixEpoch() + time_elapsed_));
- }
-
- Clock clock_;
- StrictMock<base::MockTimeProvider> mock_time_;
- base::TimeDelta time_elapsed_;
-};
-
-TEST_F(ClockTest, Created) {
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(0);
- EXPECT_EQ(kExpected, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Play_NormalSpeed) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(2);
-
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kTimeToAdvance);
- EXPECT_EQ(kTimeToAdvance, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Play_DoubleSpeed) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(5);
-
- clock_.SetPlaybackRate(2.0f);
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kTimeToAdvance);
- EXPECT_EQ(2 * kTimeToAdvance, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Play_HalfSpeed) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(4);
-
- clock_.SetPlaybackRate(0.5f);
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kTimeToAdvance);
- EXPECT_EQ(kTimeToAdvance / 2, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Play_ZeroSpeed) {
- // We'll play for 2 seconds at normal speed, 4 seconds at zero speed, and 8
- // seconds at normal speed.
- const base::TimeDelta kZero;
- const base::TimeDelta kPlayDuration1 = base::TimeDelta::FromSeconds(2);
- const base::TimeDelta kPlayDuration2 = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kPlayDuration3 = base::TimeDelta::FromSeconds(8);
- const base::TimeDelta kExpected = kPlayDuration1 + kPlayDuration3;
-
- EXPECT_EQ(kZero, clock_.Play());
-
- AdvanceSystemTime(kPlayDuration1);
- clock_.SetPlaybackRate(0.0f);
- AdvanceSystemTime(kPlayDuration2);
- clock_.SetPlaybackRate(1.0f);
- AdvanceSystemTime(kPlayDuration3);
-
- EXPECT_EQ(kExpected, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Play_MultiSpeed) {
- // We'll play for 2 seconds at half speed, 4 seconds at normal speed, and 8
- // seconds at double speed.
- const base::TimeDelta kZero;
- const base::TimeDelta kPlayDuration1 = base::TimeDelta::FromSeconds(2);
- const base::TimeDelta kPlayDuration2 = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kPlayDuration3 = base::TimeDelta::FromSeconds(8);
- const base::TimeDelta kExpected =
- kPlayDuration1 / 2 + kPlayDuration2 + 2 * kPlayDuration3;
-
- clock_.SetPlaybackRate(0.5f);
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kPlayDuration1);
-
- clock_.SetPlaybackRate(1.0f);
- AdvanceSystemTime(kPlayDuration2);
-
- clock_.SetPlaybackRate(2.0f);
- AdvanceSystemTime(kPlayDuration3);
- EXPECT_EQ(kExpected, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, Pause) {
- const base::TimeDelta kZero;
- const base::TimeDelta kPlayDuration = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kPauseDuration = base::TimeDelta::FromSeconds(20);
- const base::TimeDelta kExpectedFirstPause = kPlayDuration;
- const base::TimeDelta kExpectedSecondPause = 2 * kPlayDuration;
-
- // Play for 4 seconds.
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kPlayDuration);
-
- // Pause for 20 seconds.
- EXPECT_EQ(kExpectedFirstPause, clock_.Pause());
- EXPECT_EQ(kExpectedFirstPause, clock_.Elapsed());
- AdvanceSystemTime(kPauseDuration);
- EXPECT_EQ(kExpectedFirstPause, clock_.Elapsed());
-
- // Play again for 4 more seconds.
- EXPECT_EQ(kExpectedFirstPause, clock_.Play());
- AdvanceSystemTime(kPlayDuration);
- EXPECT_EQ(kExpectedSecondPause, clock_.Pause());
- EXPECT_EQ(kExpectedSecondPause, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, SetTime_Paused) {
- const base::TimeDelta kFirstTime = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kSecondTime = base::TimeDelta::FromSeconds(16);
-
- clock_.SetTime(kFirstTime, clock_.Duration());
- EXPECT_EQ(kFirstTime, clock_.Elapsed());
- clock_.SetTime(kSecondTime, clock_.Duration());
- EXPECT_EQ(kSecondTime, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, SetTime_Playing) {
- // We'll play for 4 seconds, then set the time to 12, then play for 4 more
- // seconds.
- const base::TimeDelta kZero;
- const base::TimeDelta kPlayDuration = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kUpdatedTime = base::TimeDelta::FromSeconds(12);
- const base::TimeDelta kExpected = kUpdatedTime + kPlayDuration;
-
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(kPlayDuration);
-
- clock_.SetTime(kUpdatedTime, clock_.Duration());
- AdvanceSystemTime(kPlayDuration);
- EXPECT_EQ(kExpected, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, CapAtMediaDuration_Paused) {
- const base::TimeDelta kDuration =
- base::TimeDelta::FromSeconds(kDurationInSeconds);
- const base::TimeDelta kTimeOverDuration =
- base::TimeDelta::FromSeconds(kDurationInSeconds + 4);
-
- // Elapsed time should always be capped at the duration of the media.
- clock_.SetTime(kTimeOverDuration, kTimeOverDuration);
- EXPECT_EQ(kDuration, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, CapAtMediaDuration_Playing) {
- const base::TimeDelta kZero;
- const base::TimeDelta kDuration =
- base::TimeDelta::FromSeconds(kDurationInSeconds);
- const base::TimeDelta kTimeOverDuration =
- base::TimeDelta::FromSeconds(kDurationInSeconds + 4);
-
- // Play for twice as long as the duration of the media.
- EXPECT_EQ(kZero, clock_.Play());
- AdvanceSystemTime(2 * kDuration);
- EXPECT_EQ(kDuration, clock_.Elapsed());
-
- // Manually set the time past the duration.
- clock_.SetTime(kTimeOverDuration, kTimeOverDuration);
- EXPECT_EQ(kDuration, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, SetMaxTime) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimeInterval = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kMaxTime = base::TimeDelta::FromSeconds(6);
-
- EXPECT_EQ(kZero, clock_.Play());
- clock_.SetMaxTime(kMaxTime);
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kTimeInterval, clock_.Elapsed());
-
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime, clock_.Elapsed());
-
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime, clock_.Elapsed());
-}
-
-TEST_F(ClockTest, SetMaxTime_MultipleTimes) {
- const base::TimeDelta kZero;
- const base::TimeDelta kTimeInterval = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kMaxTime1 = base::TimeDelta::FromSeconds(6);
- const base::TimeDelta kMaxTime2 = base::TimeDelta::FromSeconds(12);
-
- EXPECT_EQ(kZero, clock_.Play());
- clock_.SetMaxTime(clock_.Duration());
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kTimeInterval, clock_.Elapsed());
-
- clock_.SetMaxTime(kMaxTime1);
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime1, clock_.Elapsed());
-
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime1, clock_.Elapsed());
-
- clock_.SetMaxTime(kMaxTime2);
- EXPECT_EQ(kMaxTime1, clock_.Elapsed());
-
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime1 + kTimeInterval, clock_.Elapsed());
-
- AdvanceSystemTime(kTimeInterval);
- EXPECT_EQ(kMaxTime2, clock_.Elapsed());
-}
-
-} // namespace media
diff --git a/src/media/base/color_space.cc b/src/media/base/color_space.cc
deleted file mode 100644
index a37c7bb..0000000
--- a/src/media/base/color_space.cc
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "media/base/color_space.h"
-
-#include "starboard/memory.h"
-
-namespace gfx {
-
-ColorSpace::PrimaryID ColorSpace::PrimaryIDFromInt(int primary_id) {
- if (primary_id < 0 || primary_id > static_cast<int>(kPrimaryIdLast))
- return kPrimaryIdUnknown;
- if (primary_id > static_cast<int>(kPrimaryIdLastStandardValue) &&
- primary_id < 1000)
- return kPrimaryIdUnknown;
- return static_cast<PrimaryID>(primary_id);
-}
-
-ColorSpace::TransferID ColorSpace::TransferIDFromInt(int transfer_id) {
- if (transfer_id < 0 || transfer_id > static_cast<int>(kTransferIdLast))
- return kTransferIdUnknown;
- if (transfer_id > static_cast<int>(kTransferIdLastStandardValue) &&
- transfer_id < 1000)
- return kTransferIdUnknown;
- return static_cast<TransferID>(transfer_id);
-}
-
-ColorSpace::MatrixID ColorSpace::MatrixIDFromInt(int matrix_id) {
- if (matrix_id < 0 || matrix_id > static_cast<int>(kMatrixIdLast))
- return kMatrixIdUnknown;
- if (matrix_id > static_cast<int>(kMatrixIdLastStandardValue) &&
- matrix_id < 1000)
- return kMatrixIdUnknown;
- return static_cast<MatrixID>(matrix_id);
-}
-
-ColorSpace::ColorSpace()
- : primaries_(kPrimaryIdUnspecified),
- transfer_(kTransferIdUnspecified),
- matrix_(kMatrixIdUnspecified),
- range_(kRangeIdLimited) {
- SbMemorySet(custom_primary_matrix_, 0, sizeof(custom_primary_matrix_));
-}
-
-ColorSpace::ColorSpace(PrimaryID primaries,
- TransferID transfer,
- MatrixID matrix,
- RangeID range)
- : primaries_(primaries),
- transfer_(transfer),
- matrix_(matrix),
- range_(range) {
- SbMemorySet(custom_primary_matrix_, 0, sizeof(custom_primary_matrix_));
-}
-
-ColorSpace::ColorSpace(int primaries, int transfer, int matrix, RangeID range)
- : primaries_(PrimaryIDFromInt(primaries)),
- transfer_(TransferIDFromInt(transfer)),
- matrix_(MatrixIDFromInt(matrix)),
- range_(range) {
- SbMemorySet(custom_primary_matrix_, 0, sizeof(custom_primary_matrix_));
-}
-
-ColorSpace::ColorSpace(const ColorSpace& other)
- : primaries_(other.primaries_),
- transfer_(other.transfer_),
- matrix_(other.matrix_),
- range_(other.range_) {
- SbMemoryCopy(custom_primary_matrix_, other.custom_primary_matrix_,
- sizeof(custom_primary_matrix_));
-}
-
-ColorSpace::~ColorSpace() {}
-
-// Static
-ColorSpace ColorSpace::CreateXYZD50() {
- return ColorSpace(kPrimaryIdXyzD50, kTransferIdLinear, kMatrixIdRgb,
- kRangeIdFull);
-}
-
-// static
-ColorSpace ColorSpace::CreateJpeg() {
- return ColorSpace(kPrimaryIdBt709, kTransferIdIec6196621, kMatrixIdBt709,
- kRangeIdFull);
-}
-
-// static
-ColorSpace ColorSpace::CreateREC601() {
- return ColorSpace(kPrimaryIdSmpte170M, kTransferIdSmpte170M,
- kMatrixIdSmpte170M, kRangeIdLimited);
-}
-
-// static
-ColorSpace ColorSpace::CreateREC709() {
- return ColorSpace(kPrimaryIdBt709, kTransferIdBt709, kMatrixIdBt709,
- kRangeIdLimited);
-}
-
-bool ColorSpace::operator==(const ColorSpace& other) const {
- if (primaries_ != other.primaries_ || transfer_ != other.transfer_ ||
- matrix_ != other.matrix_ || range_ != other.range_)
- return false;
- if (primaries_ == kPrimaryIdCustom &&
- SbMemoryCompare(custom_primary_matrix_, other.custom_primary_matrix_,
- sizeof(custom_primary_matrix_)))
- return false;
- return true;
-}
-
-bool ColorSpace::operator!=(const ColorSpace& other) const {
- return !(*this == other);
-}
-
-bool ColorSpace::operator<(const ColorSpace& other) const {
- if (primaries_ < other.primaries_)
- return true;
- if (primaries_ > other.primaries_)
- return false;
- if (transfer_ < other.transfer_)
- return true;
- if (transfer_ > other.transfer_)
- return false;
- if (matrix_ < other.matrix_)
- return true;
- if (matrix_ > other.matrix_)
- return false;
- if (range_ < other.range_)
- return true;
- if (range_ > other.range_)
- return false;
- if (primaries_ == kPrimaryIdCustom) {
- int primary_result =
- SbMemoryCompare(custom_primary_matrix_, other.custom_primary_matrix_,
- sizeof(custom_primary_matrix_));
- if (primary_result < 0)
- return true;
- if (primary_result > 0)
- return false;
- }
- return false;
-}
-
-} // namespace gfx
diff --git a/src/media/base/color_space.h b/src/media/base/color_space.h
deleted file mode 100644
index b4086a0..0000000
--- a/src/media/base/color_space.h
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COBALT_MEDIA_BASE_COLOR_SPACE_H_
-#define COBALT_MEDIA_BASE_COLOR_SPACE_H_
-
-#include <stdint.h>
-
-#include "base/gtest_prod_util.h"
-#include "base/logging.h"
-#include "build/build_config.h"
-#include "media/base/gfx_export.h"
-
-namespace IPC {
-template <class P>
-struct ParamTraits;
-} // namespace IPC
-
-
-// This is a modified version of gfx::ColorSpace with Skia dependency removed.
-// It is tentatively put inside media2 to avoid introducing new code into
-// ui/gfx. It will be further simplified and merged into media in follow up
-// refactors.
-namespace gfx {
-
-// Used to represet a color space for the purpose of color conversion.
-// This is designed to be safe and compact enough to send over IPC
-// between any processes.
-class ColorSpace {
- public:
- enum PrimaryID {
- // The first 0-255 values should match the H264 specification (see Table E-3
- // Colour Primaries in https://www.itu.int/rec/T-REC-H.264/en).
- kPrimaryIdReserved0 = 0,
- kPrimaryIdBt709 = 1,
- kPrimaryIdUnspecified = 2,
- kPrimaryIdReserved = 3,
- kPrimaryIdBt470M = 4,
- kPrimaryIdBt470Bg = 5,
- kPrimaryIdSmpte170M = 6,
- kPrimaryIdSmpte240M = 7,
- kPrimaryIdFilm = 8,
- kPrimaryIdBt2020 = 9,
- kPrimaryIdSmpteSt4281 = 10,
- kPrimaryIdSmpteSt4312 = 11,
- kPrimaryIdSmpteSt4321 = 12,
-
- kPrimaryIdLastStandardValue = kPrimaryIdSmpteSt4321,
-
- // Chrome-specific values start at 1000.
- kPrimaryIdUnknown = 1000,
- kPrimaryIdXyzD50,
- kPrimaryIdCustom,
- kPrimaryIdLast = kPrimaryIdCustom
- };
-
- enum TransferID {
- // The first 0-255 values should match the H264 specification (see Table E-4
- // Transfer Characteristics in https://www.itu.int/rec/T-REC-H.264/en).
- kTransferIdReserved0 = 0,
- kTransferIdBt709 = 1,
- kTransferIdUnspecified = 2,
- kTransferIdReserved = 3,
- kTransferIdGamma22 = 4,
- kTransferIdGamma28 = 5,
- kTransferIdSmpte170M = 6,
- kTransferIdSmpte240M = 7,
- kTransferIdLinear = 8,
- kTransferIdLog = 9,
- kTransferIdLogSqrt = 10,
- kTransferIdIec6196624 = 11,
- kTransferIdBt1361Ecg = 12,
- kTransferIdIec6196621 = 13,
- kTransferId10BitBt2020 = 14,
- kTransferId12BitBt2020 = 15,
- kTransferIdSmpteSt2084 = 16,
- kTransferIdSmpteSt4281 = 17,
- kTransferIdAribStdB67 = 18, // AKA hybrid-log gamma, HLG.
-
- kTransferIdLastStandardValue = kTransferIdSmpteSt4281,
-
- // Chrome-specific values start at 1000.
- kTransferIdUnknown = 1000,
- kTransferIdGamma24,
-
- // This is an ad-hoc transfer function that decodes SMPTE 2084 content
- // into a 0-1 range more or less suitable for viewing on a non-hdr
- // display.
- kTransferIdSmpteSt2084NonHdr,
-
- // TODO(hubbe): Need to store an approximation of the gamma function(s).
- kTransferIdCustom,
- kTransferIdLast = kTransferIdCustom,
- };
-
- enum MatrixID {
- // The first 0-255 values should match the H264 specification (see Table E-5
- // Matrix Coefficients in https://www.itu.int/rec/T-REC-H.264/en).
- kMatrixIdRgb = 0,
- kMatrixIdBt709 = 1,
- kMatrixIdUnspecified = 2,
- kMatrixIdReserved = 3,
- kMatrixIdFcc = 4,
- kMatrixIdBt470Bg = 5,
- kMatrixIdSmpte170M = 6,
- kMatrixIdSmpte240M = 7,
- kMatrixIdYCgCo = 8,
- kMatrixIdBt2020NonconstantLuminance = 9,
- kMatrixIdBt2020ConstantLuminance = 10,
- kMatrixIdYDzDx = 11,
-
- kMatrixIdLastStandardValue = kMatrixIdYDzDx,
-
- // Chrome-specific values start at 1000
- kMatrixIdUnknown = 1000,
- kMatrixIdLast = kMatrixIdUnknown,
- };
-
- // This corresponds to the WebM Range enum which is part of WebM color data
- // (see http://www.webmproject.org/docs/container/#Range).
- // H.264 only uses a bool, which corresponds to the LIMITED/FULL values.
- // Chrome-specific values start at 1000.
- enum RangeID {
- // Range is not explicitly specified / unknown.
- kRangeIdUnspecified = 0,
-
- // Limited Rec. 709 color range with RGB values ranging from 16 to 235.
- kRangeIdLimited = 1,
-
- // Full RGB color range with RGB valees from 0 to 255.
- kRangeIdFull = 2,
-
- // Range is defined by TransferID/MatrixID.
- kRangeIdDerived = 3,
-
- kRangeIdLast = kRangeIdDerived
- };
-
- ColorSpace();
- ColorSpace(PrimaryID primaries,
- TransferID transfer,
- MatrixID matrix,
- RangeID full_range);
- ColorSpace(const ColorSpace& other);
- ColorSpace(int primaries, int transfer, int matrix, RangeID full_range);
- ~ColorSpace();
-
- typedef float CustomPrimaryMatrix[12];
-
- static PrimaryID PrimaryIDFromInt(int primary_id);
- static TransferID TransferIDFromInt(int transfer_id);
- static MatrixID MatrixIDFromInt(int matrix_id);
-
- static ColorSpace CreateXYZD50();
-
- // TODO: Remove these, and replace with more generic constructors.
- static ColorSpace CreateJpeg();
- static ColorSpace CreateREC601();
- static ColorSpace CreateREC709();
-
- bool operator==(const ColorSpace& other) const;
- bool operator!=(const ColorSpace& other) const;
- bool operator<(const ColorSpace& other) const;
-
- PrimaryID primaries() const { return primaries_; }
- TransferID transfer() const { return transfer_; }
- MatrixID matrix() const { return matrix_; }
- RangeID range() const { return range_; }
-
- const CustomPrimaryMatrix& custom_primary_matrix() const {
- DCHECK_EQ(primaries_, kPrimaryIdCustom);
- return custom_primary_matrix_;
- }
-
- private:
- PrimaryID primaries_;
- TransferID transfer_;
- MatrixID matrix_;
- RangeID range_;
-
- // Only used if primaries_ == kPrimaryIdCustom
- float custom_primary_matrix_[12];
-};
-
-} // namespace gfx
-
-#endif // COBALT_MEDIA_BASE_COLOR_SPACE_H_
diff --git a/src/media/base/data_buffer.cc b/src/media/base/data_buffer.cc
deleted file mode 100644
index 28ca491..0000000
--- a/src/media/base/data_buffer.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/data_buffer.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-DataBuffer::DataBuffer(scoped_array<uint8> buffer, int buffer_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- data_(buffer.Pass()),
- buffer_size_(buffer_size),
- data_size_(buffer_size) {
-}
-
-DataBuffer::DataBuffer(int buffer_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- buffer_size_(buffer_size),
- data_size_(0) {
- Initialize();
-}
-
-DataBuffer::DataBuffer(const uint8* data, int data_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- buffer_size_(data_size),
- data_size_(data_size) {
- Initialize();
- memcpy(data_.get(), data, data_size_);
-}
-
-DataBuffer::~DataBuffer() {}
-
-void DataBuffer::Initialize() {
- // Prevent arbitrary pointers.
- if (buffer_size_ <= 0) {
- buffer_size_ = data_size_ = 0;
- data_.reset();
- return;
- }
-
- data_.reset(new uint8[buffer_size_]);
-}
-
-const uint8* DataBuffer::GetData() const {
- return data_.get();
-}
-
-int DataBuffer::GetDataSize() const {
- return data_size_;
-}
-
-uint8* DataBuffer::GetWritableData() {
- return data_.get();
-}
-
-void DataBuffer::SetDataSize(int data_size) {
- DCHECK_LE(data_size, buffer_size_);
- data_size_ = data_size;
-}
-
-int DataBuffer::GetBufferSize() const {
- return buffer_size_;
-}
-
-} // namespace media
diff --git a/src/media/base/data_buffer.h b/src/media/base/data_buffer.h
deleted file mode 100644
index 96e9af5..0000000
--- a/src/media/base/data_buffer.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// A simple implementation of Buffer that takes ownership of the given data
-// pointer.
-//
-// DataBuffer assumes that memory was allocated with new uint8[].
-
-#ifndef MEDIA_BASE_DATA_BUFFER_H_
-#define MEDIA_BASE_DATA_BUFFER_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-class MEDIA_EXPORT DataBuffer : public Buffer {
- public:
- // Assumes valid data of size |buffer_size|.
- DataBuffer(scoped_array<uint8> buffer, int buffer_size);
-
- // Allocates buffer of size |buffer_size|. If |buffer_size| is 0, |data_| is
- // set to a NULL ptr.
- explicit DataBuffer(int buffer_size);
-
- // Allocates buffer of size |data_size|, copies [data,data+data_size) to
- // the allocated buffer and sets data size to |data_size|.
- DataBuffer(const uint8* data, int data_size);
-
- // Buffer implementation.
- virtual const uint8* GetData() const OVERRIDE;
- virtual int GetDataSize() const OVERRIDE;
-
- // Returns a read-write pointer to the buffer data.
- virtual uint8* GetWritableData();
-
- // Updates the size of valid data in bytes, which must be less than or equal
- // to GetBufferSize().
- virtual void SetDataSize(int data_size);
-
- // Returns the size of the underlying buffer.
- virtual int GetBufferSize() const;
-
- protected:
- virtual ~DataBuffer();
-
- private:
- // Constructor helper method for memory allocations.
- void Initialize();
-
- scoped_array<uint8> data_;
- int buffer_size_;
- int data_size_;
-
- DISALLOW_COPY_AND_ASSIGN(DataBuffer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DATA_BUFFER_H_
diff --git a/src/media/base/data_buffer_unittest.cc b/src/media/base/data_buffer_unittest.cc
deleted file mode 100644
index 71d8389..0000000
--- a/src/media/base/data_buffer_unittest.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/string_util.h"
-#include "media/base/data_buffer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-TEST(DataBufferTest, Constructors) {
- const uint8 kTestData[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
- const int kTestDataSize = arraysize(kTestData);
-
- scoped_refptr<DataBuffer> buffer(new DataBuffer(0));
- EXPECT_FALSE(buffer->GetData());
-
- scoped_refptr<DataBuffer> buffer2(new DataBuffer(kTestDataSize));
- EXPECT_EQ(0, buffer2->GetDataSize());
- EXPECT_EQ(kTestDataSize, buffer2->GetBufferSize());
-
- scoped_refptr<DataBuffer> buffer3(new DataBuffer(kTestData, kTestDataSize));
- EXPECT_EQ(kTestDataSize, buffer3->GetDataSize());
- EXPECT_EQ(kTestDataSize, buffer3->GetBufferSize());
- ASSERT_EQ(0, memcmp(buffer3->GetData(), kTestData, kTestDataSize));
- // Ensure we are copying the data, not just pointing to the original data.
- buffer3->GetWritableData()[0] = 0xFF;
- ASSERT_NE(0, memcmp(buffer3->GetData(), kTestData, kTestDataSize));
-}
-
-TEST(DataBufferTest, ReadingWriting) {
- const char kData[] = "hello";
- const int kDataSize = arraysize(kData);
- const char kNewData[] = "chromium";
- const int kNewDataSize = arraysize(kNewData);
-
- // Create a DataBuffer.
- scoped_refptr<DataBuffer> buffer(new DataBuffer(kDataSize));
- ASSERT_TRUE(buffer);
-
- uint8* data = buffer->GetWritableData();
- ASSERT_TRUE(data);
- ASSERT_EQ(kDataSize, buffer->GetBufferSize());
- memcpy(data, kData, kDataSize);
- buffer->SetDataSize(kDataSize);
- const uint8* read_only_data = buffer->GetData();
- ASSERT_EQ(data, read_only_data);
- ASSERT_EQ(0, memcmp(read_only_data, kData, kDataSize));
- EXPECT_FALSE(buffer->IsEndOfStream());
-
- scoped_refptr<DataBuffer> buffer2(new DataBuffer(kNewDataSize + 10));
- data = buffer2->GetWritableData();
- ASSERT_TRUE(data);
- ASSERT_EQ(kNewDataSize + 10, buffer2->GetBufferSize());
- memcpy(data, kNewData, kNewDataSize);
- buffer2->SetDataSize(kNewDataSize);
- read_only_data = buffer2->GetData();
- EXPECT_EQ(kNewDataSize, buffer2->GetDataSize());
- ASSERT_EQ(data, read_only_data);
- EXPECT_EQ(0, memcmp(read_only_data, kNewData, kNewDataSize));
-}
-
-} // namespace media
diff --git a/src/media/base/data_source.cc b/src/media/base/data_source.cc
deleted file mode 100644
index c25f9e7..0000000
--- a/src/media/base/data_source.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/data_source.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-// static
-const int DataSource::kReadError = -1;
-
-DataSourceHost::~DataSourceHost() {}
-
-DataSource::DataSource() : host_(NULL) {}
-
-DataSource::~DataSource() {}
-
-void DataSource::set_host(DataSourceHost* host) {
- DCHECK(host);
- DCHECK(!host_);
- host_ = host;
-}
-
-void DataSource::SetPlaybackRate(float playback_rate) {}
-
-DataSourceHost* DataSource::host() { return host_; }
-
-} // namespace media
diff --git a/src/media/base/data_source.h b/src/media/base/data_source.h
deleted file mode 100644
index 8de2213..0000000
--- a/src/media/base/data_source.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DATA_SOURCE_H_
-#define MEDIA_BASE_DATA_SOURCE_H_
-
-#include "base/callback.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class MEDIA_EXPORT DataSourceHost {
- public:
- // Set the total size of the media file.
- virtual void SetTotalBytes(int64 total_bytes) = 0;
-
- // Notify the host that byte range [start,end] has been buffered.
- // TODO(fischman): remove this method when demuxing is push-based instead of
- // pull-based. http://crbug.com/131444
- virtual void AddBufferedByteRange(int64 start, int64 end) = 0;
-
- // Notify the host that time range [start,end] has been buffered.
- virtual void AddBufferedTimeRange(base::TimeDelta start,
- base::TimeDelta end) = 0;
-
- protected:
- virtual ~DataSourceHost();
-};
-
-class MEDIA_EXPORT DataSource {
- public:
- typedef base::Callback<void(int64, int64)> StatusCallback;
- typedef base::Callback<void(int)> ReadCB;
- static const int kReadError;
-
- DataSource();
-
- virtual void set_host(DataSourceHost* host);
-
- // Reads |size| bytes from |position| into |data|. And when the read is done
- // or failed, |read_cb| is called with the number of bytes read or
- // kReadError in case of error.
- virtual void Read(int64 position, int size, uint8* data,
- const DataSource::ReadCB& read_cb) = 0;
-
- // Notifies the DataSource of a change in the current playback rate.
- virtual void SetPlaybackRate(float playback_rate);
-
- // Stops the DataSource. Once this is called all future Read() calls will
- // return an error.
- virtual void Stop() = 0;
-
- // Returns true and the file size, false if the file size could not be
- // retrieved.
- virtual bool GetSize(int64* size_out) = 0;
-
- // Returns true if we are performing streaming. In this case seeking is
- // not possible.
- virtual bool IsStreaming() = 0;
-
- // Notify the DataSource of the bitrate of the media.
- // Values of |bitrate| <= 0 are invalid and should be ignored.
- virtual void SetBitrate(int bitrate) = 0;
-
- protected:
- virtual ~DataSource();
-
- DataSourceHost* host();
-
- private:
- DataSourceHost* host_;
-
- DISALLOW_COPY_AND_ASSIGN(DataSource);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DATA_SOURCE_H_
diff --git a/src/media/base/decoder_buffer.cc b/src/media/base/decoder_buffer.cc
deleted file mode 100644
index 14c4151..0000000
--- a/src/media/base/decoder_buffer.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/decoder_buffer.h"
-
-#include "base/logging.h"
-#include "media/base/decrypt_config.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "base/debug/trace_event.h"
-#include "media/base/shell_buffer_factory.h"
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-#if !defined(OS_ANDROID)
-#include "base/memory/aligned_memory.h"
-#endif
-
-namespace media {
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-// static
-scoped_refptr<DecoderBuffer> DecoderBuffer::CreateEOSBuffer(
- base::TimeDelta timestamp) {
- scoped_refptr<DecoderBuffer> eos =
- scoped_refptr<DecoderBuffer>(new DecoderBuffer(NULL, 0, true));
- eos->SetTimestamp(timestamp);
- return eos;
-}
-
-void DecoderBuffer::ShrinkTo(int size) {
- CHECK_LE(size, GetAllocatedSize());
- size_ = size;
-}
-
-DecoderBuffer::DecoderBuffer(uint8* reusable_buffer,
- size_t size,
- bool is_keyframe)
- : Buffer(kNoTimestamp(), kInfiniteDuration()),
- buffer_(reusable_buffer),
- size_(size),
- allocated_size_(size),
- is_decrypted_(false),
- is_keyframe_(is_keyframe) {
- if (buffer_) {
- // Retain a reference to the buffer factory, to ensure that we do not
- // outlive it.
- buffer_factory_ = ShellBufferFactory::Instance();
- }
-}
-
-DecoderBuffer::~DecoderBuffer() {
- // recycle our buffer
- if (buffer_) {
- TRACE_EVENT1("media_stack", "DecoderBuffer::~DecoderBuffer()",
- "timestamp", GetTimestamp().InMicroseconds());
- DCHECK_NE(buffer_factory_, (ShellBufferFactory*)NULL);
- buffer_factory_->Reclaim(buffer_);
- }
-}
-
-const DecryptConfig* DecoderBuffer::GetDecryptConfig() const {
- DCHECK(!IsEndOfStream());
- return decrypt_config_.get();
-}
-
-void DecoderBuffer::SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config) {
- DCHECK(!IsEndOfStream());
- decrypt_config_ = decrypt_config.Pass();
-}
-
-void DecoderBuffer::SetBuffer(uint8* reusable_buffer) {
- buffer_ = reusable_buffer;
- if (buffer_) {
- // Retain a reference to the buffer factory, to ensure that we do not
- // outlive it.
- buffer_factory_ = ShellBufferFactory::Instance();
- }
-}
-
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-DecoderBuffer::DecoderBuffer(int buffer_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- buffer_size_(buffer_size) {
- Initialize();
-}
-
-DecoderBuffer::DecoderBuffer(const uint8* data, int buffer_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- buffer_size_(buffer_size) {
- // Prevent invalid allocations. Also used to create end of stream buffers.
- if (!data) {
- buffer_size_ = 0;
- data_ = NULL;
- return;
- }
-
- Initialize();
- memcpy(data_, data, buffer_size_);
-}
-
-DecoderBuffer::~DecoderBuffer() {
-#if !defined(OS_ANDROID)
- base::AlignedFree(data_);
-#else
- delete[] data_;
-#endif
-}
-
-void DecoderBuffer::Initialize() {
- DCHECK_GE(buffer_size_, 0);
-#if !defined(OS_ANDROID)
- data_ = reinterpret_cast<uint8*>(
- base::AlignedAlloc(buffer_size_ + kPaddingSize, kAlignmentSize));
- memset(data_ + buffer_size_, 0, kPaddingSize);
-#else
- data_ = new uint8[buffer_size_];
-#endif
-}
-
-scoped_refptr<DecoderBuffer> DecoderBuffer::CopyFrom(const uint8* data,
- int data_size) {
- DCHECK(data);
- return make_scoped_refptr(new DecoderBuffer(data, data_size));
-}
-
-scoped_refptr<DecoderBuffer> DecoderBuffer::CreateEOSBuffer() {
- return make_scoped_refptr(new DecoderBuffer(NULL, 0));
-}
-
-const uint8* DecoderBuffer::GetData() const {
- return data_;
-}
-
-int DecoderBuffer::GetDataSize() const {
- return buffer_size_;
-}
-
-uint8* DecoderBuffer::GetWritableData() {
- return data_;
-}
-
-const DecryptConfig* DecoderBuffer::GetDecryptConfig() const {
- return decrypt_config_.get();
-}
-
-void DecoderBuffer::SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config) {
- decrypt_config_ = decrypt_config.Pass();
-}
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-} // namespace media
diff --git a/src/media/base/decoder_buffer.h b/src/media/base/decoder_buffer.h
deleted file mode 100644
index 16f5cce..0000000
--- a/src/media/base/decoder_buffer.h
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// A specialized buffer for interfacing with audio / video decoders.
-//
-// Specifically ensures that data is aligned and padded as necessary by the
-// underlying decoding framework. On desktop platforms this means memory is
-// allocated using FFmpeg with particular alignment and padding requirements.
-//
-// Also includes decoder specific functionality for decryption.
-
-#ifndef MEDIA_BASE_DECODER_BUFFER_H_
-#define MEDIA_BASE_DECODER_BUFFER_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "build/build_config.h"
-#include "media/base/buffers.h"
-#include "media/base/decrypt_config.h"
-
-namespace media {
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-class DecryptConfig;
-class ShellBufferFactory;
-
-class MEDIA_EXPORT DecoderBuffer : public Buffer {
- public:
- // Create a DecoderBuffer indicating we've reached end of stream or an error.
- // GetData() and GetWritableData() return NULL and GetDataSize() returns 0.
- static scoped_refptr<DecoderBuffer> CreateEOSBuffer(
- base::TimeDelta timestamp);
-
- // Buffer implementation.
- const uint8* GetData() const OVERRIDE { return buffer_; }
- // Data size can be less than allocated size after ShrinkTo is called.
- int GetDataSize() const OVERRIDE { return static_cast<int>(size_); }
-
- int GetAllocatedSize() const { return static_cast<int>(allocated_size_); }
- // This is used by the data that we don't know the exact size before reading.
- void ShrinkTo(int size);
- bool IsKeyframe() const { return is_keyframe_; }
-
- // Returns a read-write pointer to the buffer data.
- virtual uint8* GetWritableData() { return buffer_; }
-
- // Returns a flag indicating whether or not the buffer has been decrypted
- // in-place. If so, a CDM should avoid decrypting it again after a seek.
- bool IsAlreadyDecrypted() { return is_decrypted_; }
- void SetAlreadyDecrypted(bool value) { is_decrypted_ = value; }
-
- virtual const DecryptConfig* GetDecryptConfig() const;
- virtual void SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config);
-
- protected:
- friend class ShellBufferFactory;
- // Should only be called by ShellBufferFactory, consumers should use
- // ShellBufferFactory::AllocateBuffer to make a DecoderBuffer.
- DecoderBuffer(uint8* reusable_buffer, size_t size, bool is_keyframe);
- // For deferred allocation create a shell buffer with buffer_ NULL but a
- // non-zero size. Then we use the SetBuffer() method below to actually
- // set the reusable buffer pointer when it becomes available
- void SetBuffer(uint8* reusable_buffer);
-
- virtual ~DecoderBuffer();
- uint8* buffer_;
- size_t size_;
- size_t allocated_size_;
- scoped_refptr<ShellBufferFactory> buffer_factory_;
- scoped_ptr<DecryptConfig> decrypt_config_;
- bool is_decrypted_;
- bool is_keyframe_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(DecoderBuffer);
-};
-
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-class MEDIA_EXPORT DecoderBuffer : public Buffer {
- public:
- enum {
- kPaddingSize = 16,
-#if defined(ARCH_CPU_ARM_FAMILY)
- kAlignmentSize = 16
-#else
- kAlignmentSize = 32
-#endif
- };
-
- // Allocates buffer of size |buffer_size| >= 0. Buffer will be padded and
- // aligned as necessary.
- explicit DecoderBuffer(int buffer_size);
-
- // Create a DecoderBuffer whose |data_| is copied from |data|. Buffer will be
- // padded and aligned as necessary. |data| must not be NULL and |size| >= 0.
- static scoped_refptr<DecoderBuffer> CopyFrom(const uint8* data, int size);
-
- // Create a DecoderBuffer indicating we've reached end of stream. GetData()
- // and GetWritableData() will return NULL and GetDataSize() will return 0.
- static scoped_refptr<DecoderBuffer> CreateEOSBuffer();
-
- // Buffer implementation.
- virtual const uint8* GetData() const OVERRIDE;
- virtual int GetDataSize() const OVERRIDE;
-
- // Returns a read-write pointer to the buffer data.
- virtual uint8* GetWritableData();
-
- virtual const DecryptConfig* GetDecryptConfig() const;
- virtual void SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config);
-
- protected:
- // Allocates a buffer of size |size| >= 0 and copies |data| into it. Buffer
- // will be padded and aligned as necessary. If |data| is NULL then |data_| is
- // set to NULL and |buffer_size_| to 0.
- DecoderBuffer(const uint8* data, int size);
- virtual ~DecoderBuffer();
-
- private:
- int buffer_size_;
- uint8* data_;
- scoped_ptr<DecryptConfig> decrypt_config_;
-
- // Constructor helper method for memory allocations.
- void Initialize();
-
- DISALLOW_COPY_AND_ASSIGN(DecoderBuffer);
-};
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECODER_BUFFER_H_
diff --git a/src/media/base/decoder_buffer_pool.cc b/src/media/base/decoder_buffer_pool.cc
deleted file mode 100644
index 0d52b2a..0000000
--- a/src/media/base/decoder_buffer_pool.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/decoder_buffer_pool.h"
-
-#include "base/logging.h"
-#include "media/base/shell_buffer_factory.h"
-
-namespace media {
-
-DecoderBufferPool::DecoderBufferPool(uint32 sample_size_in_bytes) {
- uint32 buffer_size = kMaxSamplesPerBuffer * sample_size_in_bytes;
- while (decoder_buffers_.size() < kBufferCount) {
- decoder_buffers_.push_back(AllocateFromShellBufferFactory(buffer_size));
- DCHECK(decoder_buffers_.back());
- }
-}
-
-scoped_refptr<DecoderBuffer> DecoderBufferPool::Allocate(size_t size_in_bytes) {
- for (DecoderBuffers::iterator iter = decoder_buffers_.begin();
- iter != decoder_buffers_.end(); ++iter) {
- if ((*iter)->HasOneRef()) {
- DCHECK_LE(size_in_bytes, (*iter)->GetAllocatedSize());
- if (size_in_bytes <= (*iter)->GetAllocatedSize()) {
- (*iter)->ShrinkTo(size_in_bytes);
- return *iter;
- }
- break;
- }
- }
- NOTREACHED();
- return AllocateFromShellBufferFactory(size_in_bytes);
-}
-
-scoped_refptr<DecoderBuffer> DecoderBufferPool::AllocateFromShellBufferFactory(
- size_t size_in_bytes) {
- return ShellBufferFactory::Instance()->AllocateBufferNow(size_in_bytes,
- false);
-}
-
-} // namespace media
diff --git a/src/media/base/decoder_buffer_pool.h b/src/media/base/decoder_buffer_pool.h
deleted file mode 100644
index 2b2636b..0000000
--- a/src/media/base/decoder_buffer_pool.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_DECODER_BUFFER_POOL_H_
-#define MEDIA_BASE_DECODER_BUFFER_POOL_H_
-
-#include <vector>
-
-#include "base/memory/ref_counted.h"
-#include "media/base/decoder_buffer.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-// This class is currently used by classes inherited from ShellRawAudioDecoder.
-// These classes may need to allocate DecoderBuffer from ShellBufferFactory
-// during playback. Our progressive demuxer will try to use up all free space
-// of ShellBufferFactory so the allocation made by ShellRawAudioDecoder may
-// fail. This class can pre-allocate DecoderBuffer on playback start to ensure
-// that the raw audio decoder can always have buffer to use.
-class DecoderBufferPool {
- public:
- static const uint32 kMaxAudioChannels = 8; // We support 7.1 at most.
- static const uint32 kMaxSamplesPerBuffer =
- mp4::AAC::kFramesPerAccessUnit * kMaxAudioChannels;
- static const size_t kBufferCount = 48;
-
- DecoderBufferPool(uint32 sample_size_in_bytes);
- scoped_refptr<DecoderBuffer> Allocate(size_t size_in_bytes);
-
- private:
- typedef std::vector<scoped_refptr<DecoderBuffer> > DecoderBuffers;
-
- scoped_refptr<DecoderBuffer> AllocateFromShellBufferFactory(
- size_t size_in_bytes);
-
- DecoderBuffers decoder_buffers_;
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECODER_BUFFER_POOL_H_
diff --git a/src/media/base/decoder_buffer_queue.cc b/src/media/base/decoder_buffer_queue.cc
deleted file mode 100644
index ad91c37..0000000
--- a/src/media/base/decoder_buffer_queue.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/decoder_buffer_queue.h"
-
-#include "base/logging.h"
-#include "media/base/decoder_buffer.h"
-
-namespace media {
-
-DecoderBufferQueue::DecoderBufferQueue()
- : earliest_valid_timestamp_(kNoTimestamp()) {
-}
-
-DecoderBufferQueue::~DecoderBufferQueue() {}
-
-void DecoderBufferQueue::Push(const scoped_refptr<DecoderBuffer>& buffer) {
- CHECK(!buffer->IsEndOfStream());
-
- queue_.push_back(buffer);
-
- // TODO(scherkus): FFmpeg returns some packets with no timestamp after
- // seeking. Fix and turn this into CHECK(). See http://crbug.com/162192
- if (buffer->GetTimestamp() == kNoTimestamp()) {
- DVLOG(1) << "Buffer has no timestamp";
- return;
- }
-
- if (earliest_valid_timestamp_ == kNoTimestamp()) {
- earliest_valid_timestamp_ = buffer->GetTimestamp();
- }
-
- if (buffer->GetTimestamp() < earliest_valid_timestamp_) {
- DVLOG(1)
- << "Out of order timestamps: "
- << buffer->GetTimestamp().InMicroseconds()
- << " vs. "
- << earliest_valid_timestamp_.InMicroseconds();
- return;
- }
-
- earliest_valid_timestamp_ = buffer->GetTimestamp();
- in_order_queue_.push_back(buffer);
-}
-
-scoped_refptr<DecoderBuffer> DecoderBufferQueue::Pop() {
- scoped_refptr<DecoderBuffer> buffer = queue_.front();
- queue_.pop_front();
-
- if (!in_order_queue_.empty() &&
- in_order_queue_.front() == buffer) {
- in_order_queue_.pop_front();
- }
-
- return buffer;
-}
-
-void DecoderBufferQueue::Clear() {
- queue_.clear();
- in_order_queue_.clear();
- earliest_valid_timestamp_ = kNoTimestamp();
-}
-
-bool DecoderBufferQueue::IsEmpty() {
- return queue_.empty();
-}
-
-base::TimeDelta DecoderBufferQueue::Duration() {
- if (in_order_queue_.size() < 2)
- return base::TimeDelta();
-
- base::TimeDelta start = in_order_queue_.front()->GetTimestamp();
- base::TimeDelta end = in_order_queue_.back()->GetTimestamp();
- return end - start;
-}
-
-} // namespace media
diff --git a/src/media/base/decoder_buffer_queue.h b/src/media/base/decoder_buffer_queue.h
deleted file mode 100644
index f75046c..0000000
--- a/src/media/base/decoder_buffer_queue.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DECODER_BUFFER_QUEUE_H_
-#define MEDIA_BASE_DECODER_BUFFER_QUEUE_H_
-
-#include <deque>
-
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class DecoderBuffer;
-
-// Maintains a queue of DecoderBuffers in increasing timestamp order.
-//
-// Individual buffer durations are ignored when calculating the duration of the
-// queue i.e., the queue must have at least 2 in-order buffers to calculate
-// duration.
-//
-// Not thread safe: access must be externally synchronized.
-class MEDIA_EXPORT DecoderBufferQueue {
- public:
- DecoderBufferQueue();
- ~DecoderBufferQueue();
-
- // Push |buffer| to the end of the queue. If |buffer| is queued out of order
- // it will be excluded from duration calculations.
- //
- // It is invalid to push an end-of-stream |buffer|.
- void Push(const scoped_refptr<DecoderBuffer>& buffer);
-
- // Pops a DecoderBuffer from the front of the queue.
- //
- // It is invalid to call Pop() on an empty queue.
- scoped_refptr<DecoderBuffer> Pop();
-
- // Removes all queued buffers.
- void Clear();
-
- // Returns true if this queue is empty.
- bool IsEmpty();
-
- // Returns the duration of encoded data stored in this queue as measured by
- // the timestamps of the earliest and latest buffers, ignoring out of order
- // buffers.
- //
- // Returns zero if the queue is empty.
- base::TimeDelta Duration();
-
- private:
- typedef std::deque<scoped_refptr<DecoderBuffer> > Queue;
- Queue queue_;
-
- // A subset of |queue_| that contains buffers that are in strictly
- // increasing timestamp order. Used to calculate Duration() while ignoring
- // out-of-order buffers.
- Queue in_order_queue_;
-
- base::TimeDelta earliest_valid_timestamp_;
-
- DISALLOW_COPY_AND_ASSIGN(DecoderBufferQueue);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECODER_BUFFER_QUEUE_H_
diff --git a/src/media/base/decoder_buffer_queue_unittest.cc b/src/media/base/decoder_buffer_queue_unittest.cc
deleted file mode 100644
index 02cd541..0000000
--- a/src/media/base/decoder_buffer_queue_unittest.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/decoder_buffer.h"
-#include "media/base/decoder_buffer_queue.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static base::TimeDelta ToTimeDelta(int seconds) {
- if (seconds < 0)
- return kNoTimestamp();
- return base::TimeDelta::FromSeconds(seconds);
-}
-
-// Helper to create buffers with specified timestamp in seconds.
-//
-// Negative numbers will be converted to kNoTimestamp();
-static scoped_refptr<DecoderBuffer> CreateBuffer(int timestamp) {
- scoped_refptr<DecoderBuffer> buffer = new DecoderBuffer(0);
- buffer->SetTimestamp(ToTimeDelta(timestamp));
- buffer->SetDuration(ToTimeDelta(0));
- return buffer;
-}
-
-TEST(DecoderBufferQueueTest, IsEmpty) {
- DecoderBufferQueue queue;
- EXPECT_TRUE(queue.IsEmpty());
-
- queue.Push(CreateBuffer(0));
- EXPECT_FALSE(queue.IsEmpty());
-}
-
-TEST(DecoderBufferQueueTest, Clear) {
- DecoderBufferQueue queue;
- queue.Push(CreateBuffer(0));
- queue.Push(CreateBuffer(1));
- EXPECT_FALSE(queue.IsEmpty());
- EXPECT_EQ(1, queue.Duration().InSeconds());
-
- queue.Clear();
- EXPECT_TRUE(queue.IsEmpty());
- EXPECT_EQ(0, queue.Duration().InSeconds());
-}
-
-TEST(DecoderBufferQueueTest, Duration) {
- DecoderBufferQueue queue;
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(0));
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(1));
- EXPECT_EQ(1, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(2));
- EXPECT_EQ(2, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(4));
- EXPECT_EQ(4, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(3, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(2, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-}
-
-TEST(DecoderBufferQueueTest, Duration_OutOfOrder) {
- DecoderBufferQueue queue;
- queue.Push(CreateBuffer(10));
- queue.Push(CreateBuffer(12));
- EXPECT_EQ(2, queue.Duration().InSeconds());
-
- // Out of order: duration shouldn't change.
- queue.Push(CreateBuffer(8));
- EXPECT_EQ(2, queue.Duration().InSeconds());
-
- // Removing first buffer should leave the second buffer as the only buffer
- // included in the duration calculation.
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- // Removing second buffer leaves the out-of-order buffer. It shouldn't be
- // included in duration calculations.
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- // Push a still-too-early buffer. It shouldn't be included in duration
- // calculations.
- queue.Push(CreateBuffer(11));
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- // Push a buffer that's after the earliest valid time. It's a singular valid
- // buffer so duration is still zero.
- queue.Push(CreateBuffer(14));
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- // Push a second valid buffer. We should now have a duration.
- queue.Push(CreateBuffer(17));
- EXPECT_EQ(3, queue.Duration().InSeconds());
-}
-
-TEST(DecoderBufferQueueTest, Duration_NoTimestamp) {
- // Buffers with no timestamp don't affect duration.
- DecoderBufferQueue queue;
- queue.Push(CreateBuffer(0));
- queue.Push(CreateBuffer(4));
- EXPECT_EQ(4, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(-1));
- EXPECT_EQ(4, queue.Duration().InSeconds());
-
- queue.Push(CreateBuffer(6));
- EXPECT_EQ(6, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(2, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-
- queue.Pop();
- EXPECT_EQ(0, queue.Duration().InSeconds());
-}
-
-} // namespace media
diff --git a/src/media/base/decoder_buffer_unittest.cc b/src/media/base/decoder_buffer_unittest.cc
deleted file mode 100644
index 65a14b6..0000000
--- a/src/media/base/decoder_buffer_unittest.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/string_util.h"
-#include "media/base/decoder_buffer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-TEST(DecoderBufferTest, Constructors) {
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(0));
- EXPECT_TRUE(buffer->GetData());
- EXPECT_EQ(0, buffer->GetDataSize());
- EXPECT_FALSE(buffer->IsEndOfStream());
-
- const int kTestSize = 10;
- scoped_refptr<DecoderBuffer> buffer3(new DecoderBuffer(kTestSize));
- ASSERT_TRUE(buffer3);
- EXPECT_EQ(kTestSize, buffer3->GetDataSize());
-}
-
-TEST(DecoderBufferTest, CreateEOSBuffer) {
- scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CreateEOSBuffer());
- EXPECT_TRUE(buffer->IsEndOfStream());
- EXPECT_FALSE(buffer->GetData());
- EXPECT_EQ(0, buffer->GetDataSize());
-}
-
-TEST(DecoderBufferTest, CopyFrom) {
- const uint8 kData[] = "hello";
- const int kDataSize = arraysize(kData);
- scoped_refptr<DecoderBuffer> buffer2(DecoderBuffer::CopyFrom(
- reinterpret_cast<const uint8*>(&kData), kDataSize));
- ASSERT_TRUE(buffer2);
- EXPECT_NE(kData, buffer2->GetData());
- EXPECT_EQ(buffer2->GetDataSize(), kDataSize);
- EXPECT_EQ(0, memcmp(buffer2->GetData(), kData, kDataSize));
- EXPECT_FALSE(buffer2->IsEndOfStream());
-}
-
-#if !defined(OS_ANDROID)
-TEST(DecoderBufferTest, PaddingAlignment) {
- const uint8 kData[] = "hello";
- const int kDataSize = arraysize(kData);
- scoped_refptr<DecoderBuffer> buffer2(DecoderBuffer::CopyFrom(
- reinterpret_cast<const uint8*>(&kData), kDataSize));
- ASSERT_TRUE(buffer2);
-
- // Padding data should always be zeroed.
- for(int i = 0; i < DecoderBuffer::kPaddingSize; i++)
- EXPECT_EQ((buffer2->GetData() + kDataSize)[i], 0);
-
- // If the data is padded correctly we should be able to read and write past
- // the end of the data by DecoderBuffer::kPaddingSize bytes without crashing
- // or Valgrind/ASAN throwing errors.
- const uint8 kFillChar = 0xFF;
- memset(
- buffer2->GetWritableData() + kDataSize, kFillChar,
- DecoderBuffer::kPaddingSize);
- for(int i = 0; i < DecoderBuffer::kPaddingSize; i++)
- EXPECT_EQ((buffer2->GetData() + kDataSize)[i], kFillChar);
-
- EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(
- buffer2->GetData()) & (DecoderBuffer::kAlignmentSize - 1));
-}
-#endif
-
-TEST(DecoderBufferTest, ReadingWriting) {
- const char kData[] = "hello";
- const int kDataSize = arraysize(kData);
-
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(kDataSize));
- ASSERT_TRUE(buffer);
-
- uint8* data = buffer->GetWritableData();
- ASSERT_TRUE(data);
- ASSERT_EQ(kDataSize, buffer->GetDataSize());
- memcpy(data, kData, kDataSize);
- const uint8* read_only_data = buffer->GetData();
- ASSERT_EQ(data, read_only_data);
- ASSERT_EQ(0, memcmp(read_only_data, kData, kDataSize));
- EXPECT_FALSE(buffer->IsEndOfStream());
-}
-
-TEST(DecoderBufferTest, GetDecryptConfig) {
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(0));
- EXPECT_FALSE(buffer->GetDecryptConfig());
-}
-
-} // namespace media
diff --git a/src/media/base/decrypt_config.cc b/src/media/base/decrypt_config.cc
deleted file mode 100644
index 0b730ab..0000000
--- a/src/media/base/decrypt_config.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/decrypt_config.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-DecryptConfig::DecryptConfig(const std::string& key_id,
- const std::string& iv,
- const std::vector<SubsampleEntry>& subsamples)
- : key_id_(key_id),
- iv_(iv),
- subsamples_(subsamples) {
- CHECK_GT(key_id.size(), 0u);
- CHECK(iv.size() == static_cast<size_t>(DecryptConfig::kDecryptionKeySize) ||
- iv.empty());
-}
-
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-DecryptConfig::DecryptConfig(const std::string& key_id,
- const std::string& iv,
- const int data_offset,
- const std::vector<SubsampleEntry>& subsamples)
- : key_id_(key_id),
- iv_(iv),
- data_offset_(data_offset),
- subsamples_(subsamples) {
- CHECK_GT(key_id.size(), 0u);
- CHECK(iv.size() == static_cast<size_t>(DecryptConfig::kDecryptionKeySize) ||
- iv.empty());
- CHECK_GE(data_offset, 0);
-}
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-DecryptConfig::~DecryptConfig() {}
-
-} // namespace media
diff --git a/src/media/base/decrypt_config.h b/src/media/base/decrypt_config.h
deleted file mode 100644
index 6dbc4e3..0000000
--- a/src/media/base/decrypt_config.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DECRYPT_CONFIG_H_
-#define MEDIA_BASE_DECRYPT_CONFIG_H_
-
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// The Common Encryption spec provides for subsample encryption, where portions
-// of a sample are set in cleartext. A SubsampleEntry specifies the number of
-// clear and encrypted bytes in each subsample. For decryption, all of the
-// encrypted bytes in a sample should be considered a single logical stream,
-// regardless of how they are divided into subsamples, and the clear bytes
-// should not be considered as part of decryption. This is logically equivalent
-// to concatenating all 'cypher_bytes' portions of subsamples, decrypting that
-// result, and then copying each byte from the decrypted block over the
-// position of the corresponding encrypted byte.
-struct SubsampleEntry {
- uint32 clear_bytes;
- uint32 cypher_bytes;
-};
-
-// Contains all information that a decryptor needs to decrypt a media sample.
-class MEDIA_EXPORT DecryptConfig {
- public:
- // Keys are always 128 bits.
- static const int kDecryptionKeySize = 16;
-
- // |key_id| is the ID that references the decryption key for this sample.
- // |iv| is the initialization vector defined by the encrypted format.
- // Currently |iv| must be 16 bytes as defined by WebM and ISO. Or must be
- // empty which signals an unencrypted frame.
- // |data_offset| is the amount of data that should be discarded from the
- // head of the sample buffer before applying subsample information. A
- // decrypted buffer will be shorter than an encrypted buffer by this amount.
- // |subsamples| defines the clear and encrypted portions of the sample as
- // described above. A decrypted buffer will be equal in size to the sum
- // of the subsample sizes.
- //
- // |data_offset| is applied before |subsamples|.
- DecryptConfig(const std::string& key_id,
- const std::string& iv,
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- const int data_offset,
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
- const std::vector<SubsampleEntry>& subsamples);
- ~DecryptConfig();
-
- const std::string& key_id() const { return key_id_; }
- const std::string& iv() const { return iv_; }
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- int data_offset() const { return data_offset_; }
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
- const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
-
- private:
- const std::string key_id_;
-
- // Initialization vector.
- const std::string iv_;
-
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- // TODO(fgalligan): Remove |data_offset_| if there is no plan to use it in
- // the future.
- // Amount of data to be discarded before applying subsample information.
- const int data_offset_;
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
-
- // Subsample information. May be empty for some formats, meaning entire frame
- // (less data ignored by data_offset_) is encrypted.
- const std::vector<SubsampleEntry> subsamples_;
-
- DISALLOW_COPY_AND_ASSIGN(DecryptConfig);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECRYPT_CONFIG_H_
diff --git a/src/media/base/decryptor.cc b/src/media/base/decryptor.cc
deleted file mode 100644
index e9b232d..0000000
--- a/src/media/base/decryptor.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/decryptor.h"
-
-namespace media {
-
-Decryptor::Decryptor() {}
-
-Decryptor::~Decryptor() {}
-
-} // namespace media
diff --git a/src/media/base/decryptor.h b/src/media/base/decryptor.h
deleted file mode 100644
index fd51c96..0000000
--- a/src/media/base/decryptor.h
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DECRYPTOR_H_
-#define MEDIA_BASE_DECRYPTOR_H_
-
-#include <list>
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioDecoderConfig;
-class Buffer;
-class DecoderBuffer;
-class VideoDecoderConfig;
-class VideoFrame;
-
-// Performs key operations and decrypts (and decodes) encrypted buffer.
-//
-// Key operations (GenerateKeyRequest(), AddKey() and CancelKeyRequest())
-// are called on the renderer thread. Therefore, these calls should be fast
-// and nonblocking; key events should be fired asynchronously.
-// All other methods are called on the (video/audio) decoder thread.
-// Decryptor implementations must be thread safe when methods are called
-// following the above model.
-// Depending on the implementation callbacks may be fired synchronously or
-// asynchronously.
-class MEDIA_EXPORT Decryptor {
- public:
- // Reported to UMA, so never reuse a value!
- // Must be kept in sync with WebKit::WebMediaPlayerClient::MediaKeyErrorCode
- // (enforced in webmediaplayer_impl.cc).
- enum KeyError {
- kUnknownError = 1,
- kClientError,
- kServiceError,
- kOutputError,
- kHardwareChangeError,
- kDomainError,
- kMaxKeyError // Must be last and greater than any legit value.
- };
-
- // TODO(xhwang): Replace kError with kDecryptError and kDecodeError.
- // TODO(xhwang): Replace kNeedMoreData with kNotEnoughData.
- enum Status {
- kSuccess, // Decryption successfully completed. Decrypted buffer ready.
- kNoKey, // No key is available to decrypt.
- kNeedMoreData, // Decoder needs more data to produce a frame.
- kError // Key is available but an error occurred during decryption.
- };
-
- // TODO(xhwang): Unify this with DemuxerStream::Type.
- enum StreamType {
- kAudio,
- kVideo
- };
-
- Decryptor();
- virtual ~Decryptor();
-
- // Generates a key request for the |key_system| with |type| and
- // |init_data| provided.
- // Returns true if generating key request succeeded, false otherwise.
- // Note: AddKey() and CancelKeyRequest() should only be called after
- // GenerateKeyRequest() returns true.
- virtual bool GenerateKeyRequest(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length) = 0;
-
- // Adds a |key| to the |key_system|. The |key| is not limited to a decryption
- // key. It can be any data that the key system accepts, such as a license.
- // If multiple calls of this function set different keys for the same
- // key ID, the older key will be replaced by the newer key.
- virtual void AddKey(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id) = 0;
-
- // Cancels the key request specified by |session_id|.
- virtual void CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) = 0;
-
- // Indicates that a key has been added to the Decryptor.
- typedef base::Callback<void()> KeyAddedCB;
-
- // Registers a KeyAddedCB which should be called when a key is added to the
- // decryptor. Only one KeyAddedCB can be registered for one |stream_type|.
- // If this function is called multiple times for the same |stream_type|, the
- // previously registered callback will be replaced. In other words,
- // registering a null callback cancels the originally registered callback.
- virtual void RegisterKeyAddedCB(StreamType stream_type,
- const KeyAddedCB& key_added_cb) = 0;
-
- // Indicates completion of a decryption operation.
- //
- // First parameter: The status of the decryption operation.
- // - Set to kSuccess if the encrypted buffer is successfully decrypted and
- // the decrypted buffer is ready to be read.
- // - Set to kNoKey if no decryption key is available to decrypt the encrypted
- // buffer. In this case the decrypted buffer must be NULL.
- // - Set to kError if unexpected error has occurred. In this case the
- // decrypted buffer must be NULL.
- // - This parameter should not be set to kNeedMoreData.
- // Second parameter: The decrypted buffer.
- typedef base::Callback<void(Status,
- const scoped_refptr<DecoderBuffer>&)> DecryptCB;
-
- // Decrypts the |encrypted| buffer. The decrypt status and decrypted buffer
- // are returned via the provided callback |decrypt_cb|. The |encrypted| buffer
- // must not be NULL.
- // Decrypt() should not be called until any previous DecryptCB of the same
- // |stream_type| has completed. Thus, only one DecryptCB may be pending at
- // a time for a given |stream_type|.
- virtual void Decrypt(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb) = 0;
-
- // Cancels the scheduled decryption operation for |stream_type| and fires the
- // pending DecryptCB immediately with kSuccess and NULL.
- // Decrypt() should not be called again before the pending DecryptCB for the
- // same |stream_type| is fired.
- virtual void CancelDecrypt(StreamType stream_type) = 0;
-
- // Indicates completion of audio/video decoder initialization.
- //
- // First Parameter: Indicates initialization success.
- // - Set to true if initialization was successful. False if an error occurred.
- typedef base::Callback<void(bool)> DecoderInitCB;
-
- // Initializes a decoder with the given |config|, executing the |init_cb|
- // upon completion.
- virtual void InitializeAudioDecoder(scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) = 0;
- virtual void InitializeVideoDecoder(scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) = 0;
-
- // Helper structure for managing multiple decoded audio buffers per input.
- // TODO(xhwang): Rename this to AudioFrames.
- typedef std::list<scoped_refptr<Buffer> > AudioBuffers;
-
- // Indicates completion of audio/video decrypt-and-decode operation.
- //
- // First parameter: The status of the decrypt-and-decode operation.
- // - Set to kSuccess if the encrypted buffer is successfully decrypted and
- // decoded. In this case, the decoded frame/buffers can be/contain:
- // 1) NULL, which means the operation has been aborted.
- // 2) End-of-stream (EOS) frame, which means that the decoder has hit EOS,
- // flushed all internal buffers and cannot produce more video frames.
- // 3) Decrypted and decoded video frame or audio buffer.
- // - Set to kNoKey if no decryption key is available to decrypt the encrypted
- // buffer. In this case the returned frame(s) must be NULL/empty.
- // - Set to kNeedMoreData if more data is needed to produce a video frame. In
- // this case the returned frame(s) must be NULL/empty.
- // - Set to kError if unexpected error has occurred. In this case the
- // returned frame(s) must be NULL/empty.
- // Second parameter: The decoded video frame or audio buffers.
- typedef base::Callback<void(Status, const AudioBuffers&)> AudioDecodeCB;
- typedef base::Callback<void(Status,
- const scoped_refptr<VideoFrame>&)> VideoDecodeCB;
-
- // Decrypts and decodes the |encrypted| buffer. The status and the decrypted
- // buffer are returned via the provided callback.
- // The |encrypted| buffer must not be NULL.
- // At end-of-stream, this method should be called repeatedly with
- // end-of-stream DecoderBuffer until no frame/buffer can be produced.
- // These methods can only be called after the corresponding decoder has
- // been successfully initialized.
- virtual void DecryptAndDecodeAudio(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb) = 0;
- virtual void DecryptAndDecodeVideo(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb) = 0;
-
- // Resets the decoder to an initialized clean state, cancels any scheduled
- // decrypt-and-decode operations, and fires any pending
- // AudioDecodeCB/VideoDecodeCB immediately with kError and NULL.
- // This method can only be called after the corresponding decoder has been
- // successfully initialized.
- virtual void ResetDecoder(StreamType stream_type) = 0;
-
- // Releases decoder resources, deinitializes the decoder, cancels any
- // scheduled initialization or decrypt-and-decode operations, and fires
- // any pending DecoderInitCB/AudioDecodeCB/VideoDecodeCB immediately.
- // DecoderInitCB should be fired with false. AudioDecodeCB/VideoDecodeCB
- // should be fired with kError.
- // This method can be called any time after Initialize{Audio|Video}Decoder()
- // has been called (with the correct stream type).
- // After this operation, the decoder is set to an uninitialized state.
- // The decoder can be reinitialized after it is uninitialized.
- virtual void DeinitializeDecoder(StreamType stream_type) = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Decryptor);
-};
-
-// Callback to notify that a decryptor is ready.
-typedef base::Callback<void(Decryptor*)> DecryptorReadyCB;
-
-// Callback to set/cancel a DecryptorReadyCB.
-// Calling this callback with a non-null callback registers decryptor ready
-// notification. When the decryptor is ready, notification will be sent
-// through the provided callback.
-// Calling this callback with a null callback cancels previously registered
-// decryptor ready notification. Any previously provided callback will be
-// fired immediately with NULL.
-typedef base::Callback<void(const DecryptorReadyCB&)> SetDecryptorReadyCB;
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECRYPTOR_H_
diff --git a/src/media/base/decryptor_client.h b/src/media/base/decryptor_client.h
deleted file mode 100644
index 14d09a9..0000000
--- a/src/media/base/decryptor_client.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DECRYPTOR_CLIENT_H_
-#define MEDIA_BASE_DECRYPTOR_CLIENT_H_
-
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "media/base/decryptor.h"
-
-namespace media {
-
-// Interface used by a decryptor to fire key events.
-// See: http://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/encrypted-media.html#event-summary
-class DecryptorClient {
- public:
- // Signals that a key has been added.
- virtual void KeyAdded(const std::string& key_system,
- const std::string& session_id) = 0;
-
- // Signals that a key error occurred. The |system_code| is key
- // system-dependent. For clear key system, the |system_code| is always zero.
- virtual void KeyError(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code) = 0;
-
- // Signals that a key message has been generated.
- virtual void KeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url) = 0;
-
- // Signals that a key is needed for decryption. |key_system| and |session_id|
- // can be empty if the key system has not been selected.
- // TODO(xhwang): Figure out if "type" is optional for NeedKey fired from the
- // decoder.
- virtual void NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_length) = 0;
-
- protected:
- virtual ~DecryptorClient() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DECRYPTOR_CLIENT_H_
diff --git a/src/media/base/demuxer.cc b/src/media/base/demuxer.cc
deleted file mode 100644
index 6cd4e29..0000000
--- a/src/media/base/demuxer.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/demuxer.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-DemuxerHost::~DemuxerHost() {}
-
-Demuxer::Demuxer() {}
-
-Demuxer::~Demuxer() {}
-
-void Demuxer::SetPlaybackRate(float playback_rate) {}
-
-void Demuxer::Seek(base::TimeDelta time, const PipelineStatusCB& status_cb) {
- DCHECK(!status_cb.is_null());
- status_cb.Run(PIPELINE_OK);
-}
-
-void Demuxer::Stop(const base::Closure& callback) {
- DCHECK(!callback.is_null());
- callback.Run();
-}
-
-void Demuxer::OnAudioRendererDisabled() {}
-
-} // namespace media
diff --git a/src/media/base/demuxer.h b/src/media/base/demuxer.h
deleted file mode 100644
index 34150af..0000000
--- a/src/media/base/demuxer.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DEMUXER_H_
-#define MEDIA_BASE_DEMUXER_H_
-
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/data_source.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/media_export.h"
-#include "media/base/pipeline_status.h"
-
-namespace media {
-
-class MEDIA_EXPORT DemuxerHost : public DataSourceHost {
- public:
- // Sets the duration of the media in microseconds.
- // Duration may be kInfiniteDuration() if the duration is not known.
- virtual void SetDuration(base::TimeDelta duration) = 0;
-
- // Stops execution of the pipeline due to a fatal error. Do not call this
- // method with PIPELINE_OK.
- virtual void OnDemuxerError(PipelineStatus error) = 0;
-
- protected:
- virtual ~DemuxerHost();
-};
-
-class MEDIA_EXPORT Demuxer : public base::RefCountedThreadSafe<Demuxer> {
- public:
- Demuxer();
-
- // Completes initialization of the demuxer.
- //
- // The demuxer does not own |host| as it is guaranteed to outlive the
- // lifetime of the demuxer. Don't delete it!
- virtual void Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) = 0;
-
- // The pipeline playback rate has been changed. Demuxers may implement this
- // method if they need to respond to this call.
- virtual void SetPlaybackRate(float playback_rate);
-
- // Carry out any actions required to seek to the given time, executing the
- // callback upon completion.
- virtual void Seek(base::TimeDelta time, const PipelineStatusCB& status_cb);
-
- // The pipeline is being stopped either as a result of an error or because
- // the client called Stop().
- virtual void Stop(const base::Closure& callback);
-
- // This method is called from the pipeline when the audio renderer
- // is disabled. Demuxers can ignore the notification if they do not
- // need to react to this event.
- //
- // TODO(acolwell): Change to generic DisableStream(DemuxerStream::Type).
- virtual void OnAudioRendererDisabled();
-
- // Returns the given stream type, or NULL if that type is not present.
- virtual scoped_refptr<DemuxerStream> GetStream(DemuxerStream::Type type) = 0;
-
- // Returns the starting time for the media file.
- virtual base::TimeDelta GetStartTime() const = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<Demuxer>;
- virtual ~Demuxer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Demuxer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DEMUXER_H_
diff --git a/src/media/base/demuxer_stream.cc b/src/media/base/demuxer_stream.cc
deleted file mode 100644
index daede65..0000000
--- a/src/media/base/demuxer_stream.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/demuxer_stream.h"
-
-namespace media {
-
-DemuxerStream::~DemuxerStream() {}
-
-} // namespace media
diff --git a/src/media/base/demuxer_stream.h b/src/media/base/demuxer_stream.h
deleted file mode 100644
index d9a16ea..0000000
--- a/src/media/base/demuxer_stream.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DEMUXER_STREAM_H_
-#define MEDIA_BASE_DEMUXER_STREAM_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/media_export.h"
-#include "media/base/ranges.h"
-
-namespace media {
-
-class AudioDecoderConfig;
-#if defined(__LB_SHELL__) || defined(COBALT)
-class Decryptor;
-#endif
-class DecoderBuffer;
-class VideoDecoderConfig;
-
-class MEDIA_EXPORT DemuxerStream
- : public base::RefCountedThreadSafe<DemuxerStream> {
- public:
- enum Type {
- UNKNOWN,
- AUDIO,
- VIDEO,
- NUM_TYPES, // Always keep this entry as the last one!
- };
-
- // Status returned in the Read() callback.
- // kOk : Indicates the second parameter is Non-NULL and contains media data
- // or the end of the stream.
- // kAborted : Indicates an aborted Read(). This can happen if the
- // DemuxerStream gets flushed and doesn't have any more data to
- // return. The second parameter MUST be NULL when this status is
- // returned.
- // kConfigChange : Indicates that the AudioDecoderConfig or
- // VideoDecoderConfig for the stream has changed.
- // The DemuxerStream expects an audio_decoder_config() or
- // video_decoder_config() call before Read() will start
- // returning DecoderBuffers again. The decoder will need this
- // new configuration to properly decode the buffers read
- // from this point forward. The second parameter MUST be NULL
- // when this status is returned.
- enum Status {
- kOk,
- kAborted,
- kConfigChanged,
- };
-
- // Request a buffer to returned via the provided callback.
- //
- // The first parameter indicates the status of the read.
- // The second parameter is non-NULL and contains media data
- // or the end of the stream if the first parameter is kOk. NULL otherwise.
- typedef base::Callback<void(Status,
- const scoped_refptr<DecoderBuffer>&)>ReadCB;
- virtual void Read(const ReadCB& read_cb) = 0;
-
- // Returns the audio decoder configuration. It is an error to call this method
- // if type() != AUDIO.
- virtual const AudioDecoderConfig& audio_decoder_config() = 0;
-
- // Returns the video decoder configuration. It is an error to call this method
- // if type() != VIDEO.
- virtual const VideoDecoderConfig& video_decoder_config() = 0;
-
- // Returns the type of stream.
- virtual Type type() = 0;
-
- virtual void EnableBitstreamConverter() = 0;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // Returns true if the content was encrypted at some point
- virtual bool StreamWasEncrypted() const = 0;
-
- virtual Decryptor* GetDecryptor() const { return NULL; }
-#endif
-
- protected:
- friend class base::RefCountedThreadSafe<DemuxerStream>;
- virtual ~DemuxerStream();
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_DEMUXER_STREAM_H_
diff --git a/src/media/base/djb2.cc b/src/media/base/djb2.cc
deleted file mode 100644
index 8d47ed2..0000000
--- a/src/media/base/djb2.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/djb2.h"
-
-uint32 DJB2Hash(const void* buf, size_t len, uint32 seed) {
- const uint8* src = reinterpret_cast<const uint8*>(buf);
- uint32 hash = seed;
- for (size_t i = 0; i < len; ++i) {
- hash = hash * 33 + src[i];
- }
- return hash;
-}
diff --git a/src/media/base/djb2.h b/src/media/base/djb2.h
deleted file mode 100644
index 598f9d1..0000000
--- a/src/media/base/djb2.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DJB2_H_
-#define MEDIA_BASE_DJB2_H_
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-// DJB2 is a hash algorithm with excellent distribution and speed
-// on many different sets.
-// It has marginally more collisions than FNV1, but makes up for it in
-// performance.
-// The return value is suitable for table lookups.
-// For small fixed sizes (ie a pixel), it has low overhead and inlines well.
-// For large data sets, it optimizes into assembly/simd and is appropriate
-// for realtime applications.
-// See Also:
-// http://www.cse.yorku.ca/~oz/hash.html
-
-static const uint32 kDJB2HashSeed = 5381u;
-
-// These functions perform DJB2 hash. The simplest call is DJB2Hash() to
-// generate the DJB2 hash of the given data:
-// uint32 hash = DJB2Hash(data1, length1, kDJB2HashSeed);
-//
-// You can also compute the DJB2 hash of data incrementally by making multiple
-// calls to DJB2Hash():
-// uint32 hash_value = kDJB2HashSeed; // Initial seed for DJB2.
-// for (size_t i = 0; i < copy_lines; ++i) {
-// hash_value = DJB2Hash(source, bytes_per_line, hash_value);
-// source += source_stride;
-// }
-
-// For the given buffer of data, compute the DJB2 hash of
-// the data. You can call this any number of times during the computation.
-MEDIA_EXPORT uint32 DJB2Hash(const void* buf, size_t len, uint32 seed);
-
-#endif // MEDIA_BASE_DJB2_H_
-
diff --git a/src/media/base/djb2_unittest.cc b/src/media/base/djb2_unittest.cc
deleted file mode 100644
index f7898aa..0000000
--- a/src/media/base/djb2_unittest.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2008 The Chromium 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 "media/base/djb2.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-uint8 kTestData[] = { 1, 2, 3 };
-
-TEST(DJB2HashTest, HashTest) {
- EXPECT_EQ(DJB2Hash(NULL, 0, 0u), 0u);
- EXPECT_EQ(DJB2Hash(kTestData, sizeof(kTestData), 5381u),
- ((5381u * 33u + 1u) * 33u + 2u) * 33u + 3u);
-}
diff --git a/src/media/base/endian_util.h b/src/media/base/endian_util.h
deleted file mode 100644
index b9d6c02..0000000
--- a/src/media/base/endian_util.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef MEDIA_BASE_ENDIAN_UTIL_H_
-#define MEDIA_BASE_ENDIAN_UTIL_H_
-
-#include "base/sys_byteorder.h"
-
-namespace media {
-namespace endian_util {
-
-// The following functions must be able to support storing to/loading from
-// non-aligned memory. Thus, casts like "*reinterpret_cast<uint16_t*>(p)"
-// should be avoided as these can cause crashes due to alignment on some
-// platforms.
-
-// Load 2 little-endian bytes at |p| and return as a host-endian uint16_t.
-inline uint16_t load_uint16_little_endian(const uint8_t* p) {
- uint16_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::ByteSwapToLE16(aligned_p);
-}
-
-// Load 4 little-endian bytes at |p| and return as a host-endian uint32_t.
-inline uint32_t load_uint32_little_endian(const uint8_t* p) {
- uint32_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::ByteSwapToLE32(aligned_p);
-}
-
-// Load 8 little-endian bytes at |p| and return as a host-endian uint64_t.
-inline uint64_t load_uint64_little_endian(const uint8_t* p) {
- uint64_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::ByteSwapToLE64(aligned_p);
-}
-
-// Load 2 big-endian bytes at |p| and return as a host-endian uint16_t.
-inline uint16_t load_uint16_big_endian(const uint8_t* p) {
- uint16_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::NetToHost16(aligned_p);
-}
-
-// Load 4 big-endian bytes at |p| and return as a host-endian uint32_t.
-inline uint32_t load_uint32_big_endian(const uint8_t* p) {
- uint32_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::NetToHost32(aligned_p);
-}
-
-// Load 8 big-endian bytes at |p| and return as a host-endian uint64_t.
-inline uint64_t load_uint64_big_endian(const uint8_t* p) {
- uint64_t aligned_p;
- memcpy(&aligned_p, p, sizeof(aligned_p));
- return base::NetToHost64(aligned_p);
-}
-
-// Load 2 big-endian bytes at |p| and return as an host-endian int16_t.
-inline int16_t load_int16_big_endian(const uint8_t* p) {
- return static_cast<int16_t>(load_uint16_big_endian(p));
-}
-
-// Load 4 big-endian bytes at |p| and return as an host-endian int32_t.
-inline int32_t load_int32_big_endian(const uint8_t* p) {
- return static_cast<int32_t>(load_uint32_big_endian(p));
-}
-
-// Load 8 big-endian bytes at |p| and return as an host-endian int64_t.
-inline int64_t load_int64_big_endian(const uint8_t* p) {
- return static_cast<int64_t>(load_uint64_big_endian(p));
-}
-
-// Load 2 little-endian bytes at |p| and return as a host-endian int16_t.
-inline int16_t load_int16_little_endian(const uint8_t* p) {
- return static_cast<int16_t>(load_uint16_little_endian(p));
-}
-
-// Load 4 little-endian bytes at |p| and return as a host-endian int32_t.
-inline int32_t load_int32_little_endian(const uint8_t* p) {
- return static_cast<int32_t>(load_uint32_little_endian(p));
-}
-
-// Load 8 little-endian bytes at |p| and return as a host-endian int64_t.
-inline int64_t load_int64_little_endian(const uint8_t* p) {
- return static_cast<int64_t>(load_uint64_little_endian(p));
-}
-
-// Store 2 host-endian bytes as big-endian at |p|.
-inline void store_uint16_big_endian(uint16_t d, uint8_t* p) {
- uint16_t big_d = base::HostToNet16(d);
- memcpy(p, &big_d, sizeof(big_d));
-}
-
-// Store 4 host-endian bytes as big-endian at |p|.
-inline void store_uint32_big_endian(uint32_t d, uint8_t* p) {
- uint32_t big_d = base::HostToNet32(d);
- memcpy(p, &big_d, sizeof(big_d));
-}
-
-// Store 8 host-endian bytes as big-endian at |p|.
-inline void store_uint64_big_endian(uint64_t d, uint8_t* p) {
- uint64_t big_d = base::HostToNet64(d);
- memcpy(p, &big_d, sizeof(big_d));
-}
-
-// Store 2 host-endian bytes as little-endian at |p|.
-inline void store_uint16_little_endian(uint16_t d, uint8_t* p) {
- uint16_t little_d = base::ByteSwapToLE16(d);
- memcpy(p, &little_d, sizeof(little_d));
-}
-
-// Store 4 host-endian bytes as little-endian at |p|.
-inline void store_uint32_little_endian(uint32_t d, uint8_t* p) {
- uint32_t little_d = base::ByteSwapToLE32(d);
- memcpy(p, &little_d, sizeof(little_d));
-}
-
-// Store 8 host-endian bytes as little-endian at |p|.
-inline void store_uint64_little_endian(uint64_t d, uint8_t* p) {
- uint64_t little_d = base::ByteSwapToLE64(d);
- memcpy(p, &little_d, sizeof(little_d));
-}
-
-} // namespace endian_util
-} // namespace media
-
-#endif // MEDIA_BASE_ENDIAN_UTIL_H_
diff --git a/src/media/base/fake_audio_render_callback.cc b/src/media/base/fake_audio_render_callback.cc
deleted file mode 100644
index af55910..0000000
--- a/src/media/base/fake_audio_render_callback.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MSVC++ requires this to be set before any other includes to get M_PI.
-#define _USE_MATH_DEFINES
-
-#include <cmath>
-
-#include "media/base/fake_audio_render_callback.h"
-
-namespace media {
-
-FakeAudioRenderCallback::FakeAudioRenderCallback(double step)
- : half_fill_(false),
- step_(step),
- last_audio_delay_milliseconds_(-1),
- volume_(1) {
- reset();
-}
-
-FakeAudioRenderCallback::~FakeAudioRenderCallback() {}
-
-int FakeAudioRenderCallback::Render(AudioBus* audio_bus,
- int audio_delay_milliseconds) {
- last_audio_delay_milliseconds_ = audio_delay_milliseconds;
- int number_of_frames = audio_bus->frames();
- if (half_fill_)
- number_of_frames /= 2;
-
- // Fill first channel with a sine wave.
- for (int i = 0; i < number_of_frames; ++i)
- audio_bus->channel(0)[i] = sin(2 * M_PI * (x_ + step_ * i));
- x_ += number_of_frames * step_;
-
- // Copy first channel into the rest of the channels.
- for (int i = 1; i < audio_bus->channels(); ++i)
- memcpy(audio_bus->channel(i), audio_bus->channel(0),
- number_of_frames * sizeof(*audio_bus->channel(i)));
-
- return number_of_frames;
-}
-
-double FakeAudioRenderCallback::ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) {
- Render(audio_bus, buffer_delay.InMilliseconds());
- return volume_;
-}
-
-} // namespace media
diff --git a/src/media/base/fake_audio_render_callback.h b/src/media/base/fake_audio_render_callback.h
deleted file mode 100644
index 5318c99..0000000
--- a/src/media/base/fake_audio_render_callback.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_
-#define MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_
-
-#include "media/base/audio_converter.h"
-#include "media/base/audio_renderer_sink.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-// Fake RenderCallback which will fill each request with a sine wave. Sine
-// state is kept across callbacks. State can be reset to default via reset().
-// Also provide an interface to AudioTransformInput.
-class FakeAudioRenderCallback
- : public AudioRendererSink::RenderCallback,
- public AudioConverter::InputCallback {
- public:
- // The function used to fulfill Render() is f(x) = sin(2 * PI * x * |step|),
- // where x = [|number_of_frames| * m, |number_of_frames| * (m + 1)] and m =
- // the number of Render() calls fulfilled thus far.
- explicit FakeAudioRenderCallback(double step);
- virtual ~FakeAudioRenderCallback();
-
- // Renders a sine wave into the provided audio data buffer. If |half_fill_|
- // is set, will only fill half the buffer.
- virtual int Render(AudioBus* audio_bus,
- int audio_delay_milliseconds) OVERRIDE;
- MOCK_METHOD0(OnRenderError, void());
-
- // AudioTransform::ProvideAudioTransformInput implementation.
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE;
-
- // Toggles only filling half the requested amount during Render().
- void set_half_fill(bool half_fill) { half_fill_ = half_fill; }
-
- // Reset the sine state to initial value.
- void reset() { x_ = 0; }
-
- // Returns the last |audio_delay_milliseconds| provided to Render() or -1 if
- // no Render() call occurred.
- int last_audio_delay_milliseconds() { return last_audio_delay_milliseconds_; }
-
- // Set volume information used by ProvideAudioTransformInput().
- void set_volume(double volume) { volume_ = volume; }
-
- private:
- bool half_fill_;
- double x_;
- double step_;
- int last_audio_delay_milliseconds_;
- double volume_;
-
- DISALLOW_COPY_AND_ASSIGN(FakeAudioRenderCallback);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_
diff --git a/src/media/base/filter_collection.cc b/src/media/base/filter_collection.cc
deleted file mode 100644
index f82a61b..0000000
--- a/src/media/base/filter_collection.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/filter_collection.h"
-
-#include "base/logging.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/demuxer.h"
-#include "media/base/video_decoder.h"
-#include "media/base/video_renderer.h"
-
-namespace media {
-
-FilterCollection::FilterCollection() {}
-
-FilterCollection::~FilterCollection() {}
-
-void FilterCollection::SetDemuxer(const scoped_refptr<Demuxer>& demuxer) {
- demuxer_ = demuxer;
-}
-
-const scoped_refptr<Demuxer>& FilterCollection::GetDemuxer() {
- return demuxer_;
-}
-
-void FilterCollection::AddAudioRenderer(AudioRenderer* audio_renderer) {
- audio_renderers_.push_back(audio_renderer);
-}
-
-void FilterCollection::AddVideoRenderer(VideoRenderer* video_renderer) {
- video_renderers_.push_back(video_renderer);
-}
-
-void FilterCollection::Clear() {
- audio_decoders_.clear();
- video_decoders_.clear();
- audio_renderers_.clear();
- video_renderers_.clear();
-}
-
-void FilterCollection::SelectAudioRenderer(scoped_refptr<AudioRenderer>* out) {
- if (audio_renderers_.empty()) {
- *out = NULL;
- return;
- }
- *out = audio_renderers_.front();
- audio_renderers_.pop_front();
-}
-
-void FilterCollection::SelectVideoRenderer(scoped_refptr<VideoRenderer>* out) {
- if (video_renderers_.empty()) {
- *out = NULL;
- return;
- }
- *out = video_renderers_.front();
- video_renderers_.pop_front();
-}
-
-FilterCollection::AudioDecoderList* FilterCollection::GetAudioDecoders() {
- return &audio_decoders_;
-}
-
-FilterCollection::VideoDecoderList* FilterCollection::GetVideoDecoders() {
- return &video_decoders_;
-}
-
-} // namespace media
diff --git a/src/media/base/filter_collection.h b/src/media/base/filter_collection.h
deleted file mode 100644
index e9f2be5..0000000
--- a/src/media/base/filter_collection.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_FILTER_COLLECTION_H_
-#define MEDIA_BASE_FILTER_COLLECTION_H_
-
-#include <list>
-
-#include "base/memory/ref_counted.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class AudioDecoder;
-class AudioRenderer;
-class Demuxer;
-class VideoDecoder;
-class VideoRenderer;
-
-// Represents a set of uninitialized demuxer and audio/video decoders and
-// renderers. Used to start a Pipeline object for media playback.
-//
-// TODO(scherkus): Replace FilterCollection with something sensible, see
-// http://crbug.com/110800
-class MEDIA_EXPORT FilterCollection {
- public:
- typedef std::list<scoped_refptr<AudioDecoder> > AudioDecoderList;
- typedef std::list<scoped_refptr<VideoDecoder> > VideoDecoderList;
-
- FilterCollection();
- ~FilterCollection();
-
- // Demuxer accessor methods.
- void SetDemuxer(const scoped_refptr<Demuxer>& demuxer);
- const scoped_refptr<Demuxer>& GetDemuxer();
-
- // Adds a filter to the collection.
- void AddAudioDecoder(AudioDecoder* audio_decoder);
- void AddAudioRenderer(AudioRenderer* audio_renderer);
- void AddVideoRenderer(VideoRenderer* video_renderer);
-
- // Remove remaining filters.
- void Clear();
-
- // Selects a filter of the specified type from the collection.
- // If the required filter cannot be found, NULL is returned.
- // If a filter is returned it is removed from the collection.
- // Filters are selected in FIFO order.
- void SelectAudioRenderer(scoped_refptr<AudioRenderer>* out);
- void SelectVideoRenderer(scoped_refptr<VideoRenderer>* out);
-
- AudioDecoderList* GetAudioDecoders();
- VideoDecoderList* GetVideoDecoders();
-
- private:
- scoped_refptr<Demuxer> demuxer_;
- AudioDecoderList audio_decoders_;
- VideoDecoderList video_decoders_;
- std::list<scoped_refptr<AudioRenderer> > audio_renderers_;
- std::list<scoped_refptr<VideoRenderer> > video_renderers_;
-
- DISALLOW_COPY_AND_ASSIGN(FilterCollection);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_FILTER_COLLECTION_H_
diff --git a/src/media/base/filter_collection_unittest.cc b/src/media/base/filter_collection_unittest.cc
deleted file mode 100644
index a8fdd70..0000000
--- a/src/media/base/filter_collection_unittest.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/filter_collection.h"
-#include "media/base/mock_filters.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class FilterCollectionTest : public ::testing::Test {
- public:
- FilterCollectionTest() {}
- virtual ~FilterCollectionTest() {}
-
- protected:
- FilterCollection collection_;
- MockFilterCollection mock_filters_;
-
- DISALLOW_COPY_AND_ASSIGN(FilterCollectionTest);
-};
-
-TEST_F(FilterCollectionTest, SelectXXXMethods) {
- scoped_refptr<AudioRenderer> audio_renderer;
-
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_FALSE(audio_renderer);
-
- // Add an audio decoder.
- collection_.AddAudioRenderer(mock_filters_.audio_renderer());
-
- // Verify that we can select the audio decoder.
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_TRUE(audio_renderer);
-
- // Verify that we can't select it again since only one has been added.
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_FALSE(audio_renderer);
-}
-
-TEST_F(FilterCollectionTest, MultipleFiltersOfSameType) {
- scoped_refptr<AudioRenderer> audio_renderer_a(new MockAudioRenderer());
- scoped_refptr<AudioRenderer> audio_renderer_b(new MockAudioRenderer());
-
- scoped_refptr<AudioRenderer> audio_renderer;
-
- collection_.AddAudioRenderer(audio_renderer_a.get());
- collection_.AddAudioRenderer(audio_renderer_b.get());
-
- // Verify that first SelectAudioRenderer() returns audio_renderer_a.
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_TRUE(audio_renderer);
- EXPECT_EQ(audio_renderer, audio_renderer_a);
-
- // Verify that second SelectAudioRenderer() returns audio_renderer_b.
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_TRUE(audio_renderer);
- EXPECT_EQ(audio_renderer, audio_renderer_b);
-
- // Verify that third SelectAudioRenderer() returns nothing.
- collection_.SelectAudioRenderer(&audio_renderer);
- EXPECT_FALSE(audio_renderer);
-}
-
-} // namespace media
diff --git a/src/media/base/gfx_export.h b/src/media/base/gfx_export.h
deleted file mode 100644
index 3106747..0000000
--- a/src/media/base/gfx_export.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COBALT_MEDIA_BASE_GFX_EXPORT_H_
-#define COBALT_MEDIA_BASE_GFX_EXPORT_H_
-
-#if defined(COMPONENT_BUILD)
-#if defined(WIN32)
-
-#if defined(GFX_IMPLEMENTATION)
-#define GFX_EXPORT __declspec(dllexport)
-#else
-#define GFX_EXPORT __declspec(dllimport)
-#endif // defined(GFX_IMPLEMENTATION)
-
-#else // defined(WIN32)
-#if defined(GFX_IMPLEMENTATION)
-#define GFX_EXPORT __attribute__((visibility("default")))
-#else
-#define GFX_EXPORT
-#endif
-#endif
-
-#else // defined(COMPONENT_BUILD)
-#define GFX_EXPORT
-#endif
-
-#endif // COBALT_MEDIA_BASE_GFX_EXPORT_H_
diff --git a/src/media/base/gmock_callback_support.h b/src/media/base/gmock_callback_support.h
deleted file mode 100644
index 22f4c10..0000000
--- a/src/media/base/gmock_callback_support.h
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_GMOCK_CALLBACK_SUPPORT_H_
-#define MEDIA_BASE_GMOCK_CALLBACK_SUPPORT_H_
-
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-// Matchers for base::Callback and base::Closure.
-
-MATCHER(IsNullCallback, "a null callback") {
- return (arg.is_null());
-}
-
-MATCHER(IsNotNullCallback, "a non-null callback") {
- return (!arg.is_null());
-}
-
-// The RunClosure<N>() action invokes Run() method on the N-th (0-based)
-// argument of the mock function.
-
-ACTION_TEMPLATE(RunClosure,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_0_VALUE_PARAMS()) {
- ::std::tr1::get<k>(args).Run();
-}
-
-// Various overloads for RunCallback<N>().
-//
-// The RunCallback<N>(p1, p2, ..., p_k) action invokes Run() method on the N-th
-// (0-based) argument of the mock function, with arguments p1, p2, ..., p_k.
-//
-// Notes:
-//
-// 1. The arguments are passed by value by default. If you need to
-// pass an argument by reference, wrap it inside ByRef(). For example,
-//
-// RunCallback<1>(5, string("Hello"), ByRef(foo))
-//
-// passes 5 and string("Hello") by value, and passes foo by reference.
-//
-// 2. If the callback takes an argument by reference but ByRef() is
-// not used, it will receive the reference to a copy of the value,
-// instead of the original value. For example, when the 0-th
-// argument of the callback takes a const string&, the action
-//
-// RunCallback<0>(string("Hello"))
-//
-// makes a copy of the temporary string("Hello") object and passes a
-// reference of the copy, instead of the original temporary object,
-// to the callback. This makes it easy for a user to define an
-// RunCallback action from temporary values and have it performed later.
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_0_VALUE_PARAMS()) {
- return ::std::tr1::get<k>(args).Run();
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_1_VALUE_PARAMS(p0)) {
- return ::std::tr1::get<k>(args).Run(p0);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_2_VALUE_PARAMS(p0, p1)) {
- return ::std::tr1::get<k>(args).Run(p0, p1);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_3_VALUE_PARAMS(p0, p1, p2)) {
- return ::std::tr1::get<k>(args).Run(p0, p1, p2);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
- return ::std::tr1::get<k>(args).Run(p0, p1, p2, p3);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
- return ::std::tr1::get<k>(args).Run(p0, p1, p2, p3, p4);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
- return ::std::tr1::get<k>(args).Run(p0, p1, p2, p3, p4, p5);
-}
-
-ACTION_TEMPLATE(RunCallback,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
- return ::std::tr1::get<k>(args).Run(p0, p1, p2, p3, p4, p5, p6);
-}
-
-} // namespace media
-
-#endif // MEDIA_BASE_GMOCK_CALLBACK_SUPPORT_H_
diff --git a/src/media/base/gmock_callback_support_unittest.cc b/src/media/base/gmock_callback_support_unittest.cc
deleted file mode 100644
index fb1beb9..0000000
--- a/src/media/base/gmock_callback_support_unittest.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/gmock_callback_support.h"
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::ByRef;
-using testing::MockFunction;
-
-namespace media {
-
-typedef base::Callback<void(const bool& src, bool* dst)> TestCallback;
-
-void SetBool(const bool& src, bool* dst) {
- *dst = src;
-}
-
-TEST(GmockCallbackSupportTest, IsNullCallback) {
- MockFunction<void(const TestCallback&)> check;
- EXPECT_CALL(check, Call(IsNullCallback()));
- check.Call(TestCallback());
-}
-
-TEST(GmockCallbackSupportTest, IsNotNullCallback) {
- MockFunction<void(const TestCallback&)> check;
- EXPECT_CALL(check, Call(IsNotNullCallback()));
- check.Call(base::Bind(&SetBool));
-}
-
-TEST(GmockCallbackSupportTest, RunClosure) {
- MockFunction<void(const base::Closure&)> check;
- bool dst = false;
- EXPECT_CALL(check, Call(IsNotNullCallback()))
- .WillOnce(RunClosure<0>());
- check.Call(base::Bind(&SetBool, true, &dst));
- EXPECT_TRUE(dst);
-}
-
-TEST(GmockCallbackSupportTest, RunCallback0) {
- MockFunction<void(const TestCallback&)> check;
- bool dst = false;
- EXPECT_CALL(check, Call(IsNotNullCallback()))
- .WillOnce(RunCallback<0>(true, &dst));
- check.Call(base::Bind(&SetBool));
- EXPECT_TRUE(dst);
-}
-
-TEST(GmockCallbackSupportTest, RunCallback1) {
- MockFunction<void(int, const TestCallback&)> check;
- bool dst = false;
- EXPECT_CALL(check, Call(0, IsNotNullCallback()))
- .WillOnce(RunCallback<1>(true, &dst));
- check.Call(0, base::Bind(&SetBool));
- EXPECT_TRUE(dst);
-}
-
-TEST(GmockCallbackSupportTest, RunCallbackPassByRef) {
- MockFunction<void(const TestCallback&)> check;
- bool dst = false;
- bool src = false;
- EXPECT_CALL(check, Call(IsNotNullCallback()))
- .WillOnce(RunCallback<0>(ByRef(src), &dst));
- src = true;
- check.Call(base::Bind(&SetBool));
- EXPECT_TRUE(dst);
-}
-
-TEST(GmockCallbackSupportTest, RunCallbackPassByValue) {
- MockFunction<void(const TestCallback&)> check;
- bool dst = false;
- bool src = true;
- EXPECT_CALL(check, Call(IsNotNullCallback()))
- .WillOnce(RunCallback<0>(src, &dst));
- src = false;
- check.Call(base::Bind(&SetBool));
- EXPECT_TRUE(dst);
-}
-
-} // namespace media
diff --git a/src/media/base/hdr_metadata.cc b/src/media/base/hdr_metadata.cc
deleted file mode 100644
index 98dcd57..0000000
--- a/src/media/base/hdr_metadata.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 The Chromium 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 "media/base/hdr_metadata.h"
-
-namespace media {
-
-MasteringMetadata::MasteringMetadata() {
- primary_r_chromaticity_x = 0;
- primary_r_chromaticity_y = 0;
- primary_g_chromaticity_x = 0;
- primary_g_chromaticity_y = 0;
- primary_b_chromaticity_x = 0;
- primary_b_chromaticity_y = 0;
- white_point_chromaticity_x = 0;
- white_point_chromaticity_y = 0;
- luminance_max = 0;
- luminance_min = 0;
-}
-
-HDRMetadata::HDRMetadata() {
- max_cll = 0;
- max_fall = 0;
-}
-
-} // namespace media
diff --git a/src/media/base/hdr_metadata.h b/src/media/base/hdr_metadata.h
deleted file mode 100644
index cc21972..0000000
--- a/src/media/base/hdr_metadata.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_HDR_METADATA_H_
-#define MEDIA_BASE_HDR_METADATA_H_
-
-#include "media/base/color_space.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// SMPTE ST 2086 mastering metadata.
-struct MEDIA_EXPORT MasteringMetadata {
- float primary_r_chromaticity_x;
- float primary_r_chromaticity_y;
- float primary_g_chromaticity_x;
- float primary_g_chromaticity_y;
- float primary_b_chromaticity_x;
- float primary_b_chromaticity_y;
- float white_point_chromaticity_x;
- float white_point_chromaticity_y;
- float luminance_max;
- float luminance_min;
-
- MasteringMetadata();
-
- bool operator==(const MasteringMetadata& rhs) const {
- return ((primary_r_chromaticity_x == rhs.primary_r_chromaticity_x) &&
- (primary_r_chromaticity_y == rhs.primary_r_chromaticity_y) &&
- (primary_g_chromaticity_x == rhs.primary_g_chromaticity_x) &&
- (primary_g_chromaticity_y == rhs.primary_g_chromaticity_y) &&
- (primary_b_chromaticity_x == rhs.primary_b_chromaticity_x) &&
- (primary_b_chromaticity_y == rhs.primary_b_chromaticity_y) &&
- (white_point_chromaticity_x == rhs.white_point_chromaticity_x) &&
- (white_point_chromaticity_y == rhs.white_point_chromaticity_y) &&
- (luminance_max == rhs.luminance_max) &&
- (luminance_min == rhs.luminance_min));
- }
-};
-
-// HDR metadata common for HDR10 and WebM/VP9-based HDR formats.
-struct MEDIA_EXPORT HDRMetadata {
- MasteringMetadata mastering_metadata;
- unsigned int max_cll;
- unsigned int max_fall;
-
- HDRMetadata();
-
- bool operator==(const HDRMetadata& rhs) const {
- return ((max_cll == rhs.max_cll) && (max_fall == rhs.max_fall) &&
- (mastering_metadata == rhs.mastering_metadata));
- }
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_HDR_METADATA_H_
diff --git a/src/media/base/icc_profile.cc b/src/media/base/icc_profile.cc
deleted file mode 100644
index 1e7b95d..0000000
--- a/src/media/base/icc_profile.cc
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2016 The Chromium 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 "ui/gfx/icc_profile.h"
-
-#include <list>
-
-#include "base/containers/mru_cache.h"
-#include "base/lazy_instance.h"
-#include "base/synchronization/lock.h"
-#include "ui/gfx/color_transform.h"
-
-namespace gfx {
-
-namespace {
-const size_t kMinProfileLength = 128;
-const size_t kMaxProfileLength = 4 * 1024 * 1024;
-
-// Allow keeping around a maximum of 8 cached ICC profiles. Beware that
-// we will do a linear search thorugh currently-cached ICC profiles,
-// when creating a new ICC profile.
-const size_t kMaxCachedICCProfiles = 8;
-
-struct Cache {
- Cache() : id_to_icc_profile_mru(kMaxCachedICCProfiles) {}
- ~Cache() {}
-
- // Start from-ICC-data IDs at the end of the hard-coded list.
- uint64_t next_unused_id = 5;
- base::MRUCache<uint64_t, ICCProfile> id_to_icc_profile_mru;
- base::Lock lock;
-};
-static base::LazyInstance<Cache> g_cache;
-
-} // namespace
-
-ICCProfile::ICCProfile() = default;
-ICCProfile::ICCProfile(ICCProfile&& other) = default;
-ICCProfile::ICCProfile(const ICCProfile& other) = default;
-ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
-ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
-ICCProfile::~ICCProfile() = default;
-
-bool ICCProfile::operator==(const ICCProfile& other) const {
- if (type_ != other.type_)
- return false;
- switch (type_) {
- case Type::INVALID:
- return true;
- case Type::FROM_COLOR_SPACE:
- return color_space_ == other.color_space_;
- case Type::FROM_DATA:
- return data_ == other.data_;
- }
- return false;
-}
-
-// static
-ICCProfile ICCProfile::FromData(const char* data, size_t size) {
- ICCProfile icc_profile;
- if (IsValidProfileLength(size)) {
- icc_profile.type_ = Type::FROM_DATA;
- icc_profile.data_.insert(icc_profile.data_.begin(), data, data + size);
- } else {
- return ICCProfile();
- }
-
- Cache& cache = g_cache.Get();
- base::AutoLock lock(cache.lock);
-
- // Linearly search the cached ICC profiles to find one with the same data.
- // If it exists, re-use its id and touch it in the cache.
- for (auto iter = cache.id_to_icc_profile_mru.begin();
- iter != cache.id_to_icc_profile_mru.end(); ++iter) {
- if (icc_profile.data_ == iter->second.data_) {
- icc_profile = iter->second;
- cache.id_to_icc_profile_mru.Get(icc_profile.id_);
- return icc_profile;
- }
- }
-
- // Create a new cached id and add it to the cache.
- icc_profile.id_ = cache.next_unused_id++;
- icc_profile.color_space_ =
- ColorSpace(ColorSpace::PrimaryID::CUSTOM, ColorSpace::TransferID::CUSTOM,
- ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL);
- icc_profile.color_space_.icc_profile_id_ = icc_profile.id_;
- cache.id_to_icc_profile_mru.Put(icc_profile.id_, icc_profile);
- return icc_profile;
-}
-
-#if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(USE_X11)
-// static
-ICCProfile ICCProfile::FromBestMonitor() {
- return ICCProfile();
-}
-#endif
-
-// static
-ICCProfile ICCProfile::FromColorSpace(const gfx::ColorSpace& color_space) {
- if (color_space == gfx::ColorSpace())
- return ICCProfile();
-
- // If |color_space| was created from an ICC profile, retrieve that exact
- // profile.
- if (color_space.icc_profile_id_) {
- Cache& cache = g_cache.Get();
- base::AutoLock lock(cache.lock);
-
- auto found = cache.id_to_icc_profile_mru.Get(color_space.icc_profile_id_);
- if (found != cache.id_to_icc_profile_mru.end()) {
- return found->second;
- }
- }
-
- // TODO(ccameron): Support constructing ICC profiles from arbitrary ColorSpace
- // objects.
- ICCProfile icc_profile;
- icc_profile.type_ = gfx::ICCProfile::Type::FROM_COLOR_SPACE;
- icc_profile.color_space_ = color_space;
- return icc_profile;
-}
-
-const std::vector<char>& ICCProfile::GetData() const {
- return data_;
-}
-
-ColorSpace ICCProfile::GetColorSpace() const {
- if (type_ == Type::INVALID)
- return gfx::ColorSpace();
- if (type_ == Type::FROM_COLOR_SPACE)
- return color_space_;
-
- ColorSpace color_space = color_space_;
-
- // Move this ICC profile to the most recently used end of the cache.
- {
- Cache& cache = g_cache.Get();
- base::AutoLock lock(cache.lock);
-
- auto found = cache.id_to_icc_profile_mru.Get(id_);
- if (found == cache.id_to_icc_profile_mru.end())
- cache.id_to_icc_profile_mru.Put(id_, *this);
- }
-
- ColorSpace unity_colorspace(
- ColorSpace::PrimaryID::CUSTOM, ColorSpace::TransferID::LINEAR,
- ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL);
- unity_colorspace.custom_primary_matrix_[0] = 1.0f;
- unity_colorspace.custom_primary_matrix_[1] = 0.0f;
- unity_colorspace.custom_primary_matrix_[2] = 0.0f;
- unity_colorspace.custom_primary_matrix_[3] = 0.0f;
-
- unity_colorspace.custom_primary_matrix_[4] = 0.0f;
- unity_colorspace.custom_primary_matrix_[5] = 1.0f;
- unity_colorspace.custom_primary_matrix_[6] = 0.0f;
- unity_colorspace.custom_primary_matrix_[7] = 0.0f;
-
- unity_colorspace.custom_primary_matrix_[8] = 0.0f;
- unity_colorspace.custom_primary_matrix_[9] = 0.0f;
- unity_colorspace.custom_primary_matrix_[10] = 1.0f;
- unity_colorspace.custom_primary_matrix_[11] = 0.0f;
-
- // This will look up and use the ICC profile.
- std::unique_ptr<ColorTransform> transform(ColorTransform::NewColorTransform(
- color_space, unity_colorspace, ColorTransform::Intent::INTENT_ABSOLUTE));
-
- ColorTransform::TriStim tmp[4];
- tmp[0].set_x(1.0f);
- tmp[1].set_y(1.0f);
- tmp[2].set_z(1.0f);
- transform->transform(tmp, arraysize(tmp));
-
- color_space.custom_primary_matrix_[0] = tmp[0].x() - tmp[3].x();
- color_space.custom_primary_matrix_[1] = tmp[1].x() - tmp[3].x();
- color_space.custom_primary_matrix_[2] = tmp[2].x() - tmp[3].x();
- color_space.custom_primary_matrix_[3] = tmp[3].x();
-
- color_space.custom_primary_matrix_[4] = tmp[0].y() - tmp[3].y();
- color_space.custom_primary_matrix_[5] = tmp[1].y() - tmp[3].y();
- color_space.custom_primary_matrix_[6] = tmp[2].y() - tmp[3].y();
- color_space.custom_primary_matrix_[7] = tmp[3].y();
-
- color_space.custom_primary_matrix_[8] = tmp[0].z() - tmp[3].z();
- color_space.custom_primary_matrix_[9] = tmp[1].z() - tmp[3].z();
- color_space.custom_primary_matrix_[10] = tmp[2].z() - tmp[3].z();
- color_space.custom_primary_matrix_[11] = tmp[3].z();
-
- return color_space;
-}
-
-// static
-bool ICCProfile::IsValidProfileLength(size_t length) {
- return length >= kMinProfileLength && length <= kMaxProfileLength;
-}
-
-} // namespace gfx
diff --git a/src/media/base/icc_profile.h b/src/media/base/icc_profile.h
deleted file mode 100644
index 25cf632..0000000
--- a/src/media/base/icc_profile.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_GFX_ICC_PROFILE_H_
-#define UI_GFX_ICC_PROFILE_H_
-
-#include <stdint.h>
-#include <vector>
-
-#include "base/gtest_prod_util.h"
-#include "media/base/color_space.h"
-
-#if defined(OS_MACOSX)
-#include <CoreGraphics/CGColorSpace.h>
-#endif
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-
-namespace mojo {
-template <typename, typename>
-struct StructTraits;
-}
-
-namespace gfx {
-
-namespace mojom {
-class ICCProfileDataView;
-}
-
-// Used to represent a full ICC profile, usually retrieved from a monitor. It
-// can be lossily compressed into a ColorSpace object. This structure should
-// only be sent from higher-privilege processes to lower-privilege processes,
-// as parsing this structure is not secure.
-class GFX_EXPORT ICCProfile {
- public:
- ICCProfile();
- ICCProfile(ICCProfile&& other);
- ICCProfile(const ICCProfile& other);
- ICCProfile& operator=(ICCProfile&& other);
- ICCProfile& operator=(const ICCProfile& other);
- ~ICCProfile();
- bool operator==(const ICCProfile& other) const;
-
- // Returns the color profile of the monitor that can best represent color.
- // This profile should be used for creating content that does not know on
- // which monitor it will be displayed.
- static ICCProfile FromBestMonitor();
-#if defined(OS_MACOSX)
- static ICCProfile FromCGColorSpace(CGColorSpaceRef cg_color_space);
-#endif
-
- // This will recover a ICCProfile from a compact ColorSpace representation.
- // Internally, this will make an effort to create an identical ICCProfile
- // to the one that created |color_space|, but this is not guaranteed.
- static ICCProfile FromColorSpace(const gfx::ColorSpace& color_space);
-
- // Create directly from profile data.
- static ICCProfile FromData(const char* icc_profile, size_t size);
-
- // This will perform a potentially-lossy conversion to a more compact color
- // space representation.
- ColorSpace GetColorSpace() const;
-
- const std::vector<char>& GetData() const;
-
-#if defined(OS_WIN)
- // This will read monitor ICC profiles from disk and cache the results for the
- // other functions to read. This should not be called on the UI or IO thread.
- static void UpdateCachedProfilesOnBackgroundThread();
- static bool CachedProfilesNeedUpdate();
-#endif
-
- enum class Type {
- // This is not a valid profile.
- INVALID,
- // This is from a gfx::ColorSpace. This ensures that GetColorSpace returns
- // the exact same object as was used to create this.
- FROM_COLOR_SPACE,
- // This was created from ICC profile data.
- FROM_DATA,
- LAST = FROM_DATA
- };
-
- private:
- static bool IsValidProfileLength(size_t length);
-
- Type type_ = Type::INVALID;
- gfx::ColorSpace color_space_;
- std::vector<char> data_;
-
- // This globally identifies this ICC profile. It is used to look up this ICC
- // profile from a ColorSpace object created from it.
- uint64_t id_ = 0;
-
- FRIEND_TEST_ALL_PREFIXES(SimpleColorSpace, BT709toSRGBICC);
- FRIEND_TEST_ALL_PREFIXES(SimpleColorSpace, GetColorSpace);
- friend int ::LLVMFuzzerTestOneInput(const uint8_t*, size_t);
- friend class ColorSpace;
- friend struct IPC::ParamTraits<gfx::ICCProfile>;
- friend struct IPC::ParamTraits<gfx::ICCProfile::Type>;
- friend struct mojo::StructTraits<gfx::mojom::ICCProfileDataView,
- gfx::ICCProfile>;
-};
-
-} // namespace gfx
-
-#endif // UI_GFX_ICC_PROFILE_H_
diff --git a/src/media/base/interleaved_sinc_resampler.cc b/src/media/base/interleaved_sinc_resampler.cc
deleted file mode 100644
index d4df9ff..0000000
--- a/src/media/base/interleaved_sinc_resampler.cc
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright (c) 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Input buffer layout, dividing the total buffer into regions (r0_ - r5_):
-//
-// |----------------|-----------------------------------------|----------------|
-//
-// kBlockSize + kKernelSize / 2
-// <--------------------------------------------------------->
-// r0_
-//
-// kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2
-// <---------------> <---------------> <---------------> <--------------->
-// r1_ r2_ r3_ r4_
-//
-// kBlockSize
-// <--------------------------------------->
-// r5_
-//
-// The algorithm:
-//
-// 1) Consume input frames into r0_ (r1_ is zero-initialized).
-// 2) Position kernel centered at start of r0_ (r2_) and generate output frames
-// until kernel is centered at start of r4_ or we've finished generating all
-// the output frames.
-// 3) Copy r3_ to r1_ and r4_ to r2_.
-// 4) Consume input frames into r5_ (zero-pad if we run out of input).
-// 5) Goto (2) until all of input is consumed.
-//
-// Note: we're glossing over how the sub-sample handling works with
-// |virtual_source_idx_|, etc.
-
-#include "media/base/interleaved_sinc_resampler.h"
-
-#include <algorithm>
-#include <cmath>
-
-#include "base/logging.h"
-
-namespace media {
-
-namespace {
-
-// The kernel size can be adjusted for quality (higher is better) at the
-// expense of performance. Must be a multiple of 32.
-const int kKernelSize = 32;
-
-// The number of destination frames generated per processing pass. Affects
-// how often and for how much InterleavedSincResampler calls back for input.
-// Must be greater than kKernelSize.
-const int kBlockSize = 512;
-
-// The kernel offset count is used for interpolation and is the number of
-// sub-sample kernel shifts. Can be adjusted for quality (higher is better)
-// at the expense of allocating more memory.
-const int kKernelOffsetCount = 32;
-const int kKernelStorageSize = kKernelSize * (kKernelOffsetCount + 1);
-
-// The size (in samples) of the internal buffer used by the resampler.
-const int kBufferSize = kBlockSize + kKernelSize;
-
-// The maximum numbers of buffer can be queued.
-const int kMaximumPendingBuffers = 8;
-
-} // namespace
-
-InterleavedSincResampler::InterleavedSincResampler(double io_sample_rate_ratio,
- int channel_count)
- : io_sample_rate_ratio_(io_sample_rate_ratio),
- virtual_source_idx_(0),
- buffer_primed_(false),
- channel_count_(channel_count),
- frame_size_in_bytes_(sizeof(float) * channel_count_),
- // Create buffers with a 16-byte alignment for possible optimizations.
- kernel_storage_(static_cast<float*>(
- base::AlignedAlloc(sizeof(float) * kKernelStorageSize, 16))),
- input_buffer_(static_cast<float*>(
- base::AlignedAlloc(frame_size_in_bytes_ * kBufferSize, 16))),
- offset_in_frames_(0),
- frames_resampled_(0),
- frames_queued_(0),
- // Setup various region pointers in the buffer (see diagram above).
- r0_(input_buffer_.get() + kKernelSize / 2 * channel_count_),
- r1_(input_buffer_.get()),
- r2_(r0_),
- r3_(r0_ + (kBlockSize - kKernelSize / 2) * channel_count_),
- r4_(r0_ + kBlockSize * channel_count_),
- r5_(r0_ + kKernelSize / 2 * channel_count_) {
- // Ensure kKernelSize is a multiple of 32 for easy SSE optimizations; causes
- // r0_ and r5_ (used for input) to always be 16-byte aligned by virtue of
- // input_buffer_ being 16-byte aligned.
- DCHECK_EQ(kKernelSize % 32, 0) << "kKernelSize must be a multiple of 32!";
- DCHECK_GT(kBlockSize, kKernelSize)
- << "kBlockSize must be greater than kKernelSize!";
- // Basic sanity checks to ensure buffer regions are laid out correctly:
- // r0_ and r2_ should always be the same position.
- DCHECK_EQ(r0_, r2_);
- // r1_ at the beginning of the buffer.
- DCHECK_EQ(r1_, input_buffer_.get());
- // r1_ left of r2_, r2_ left of r5_ and r1_, r2_ size correct.
- DCHECK_EQ(r2_ - r1_, r5_ - r2_);
- // r3_ left of r4_, r5_ left of r0_ and r3_ size correct.
- DCHECK_EQ(r4_ - r3_, r5_ - r0_);
- // r3_, r4_ size correct and r4_ at the end of the buffer.
- DCHECK_EQ(r4_ + (r4_ - r3_), r1_ + kBufferSize * channel_count_);
- // r5_ size correct and at the end of the buffer.
- DCHECK_EQ(r5_ + kBlockSize * channel_count_,
- r1_ + kBufferSize * channel_count_);
-
- memset(kernel_storage_.get(), 0,
- sizeof(*kernel_storage_.get()) * kKernelStorageSize);
- memset(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
-
- InitializeKernel();
-}
-
-void InterleavedSincResampler::InitializeKernel() {
- // Blackman window parameters.
- static const double kAlpha = 0.16;
- static const double kA0 = 0.5 * (1.0 - kAlpha);
- static const double kA1 = 0.5;
- static const double kA2 = 0.5 * kAlpha;
-
- // |sinc_scale_factor| is basically the normalized cutoff frequency of the
- // low-pass filter.
- double sinc_scale_factor =
- io_sample_rate_ratio_ > 1.0 ? 1.0 / io_sample_rate_ratio_ : 1.0;
-
- // The sinc function is an idealized brick-wall filter, but since we're
- // windowing it the transition from pass to stop does not happen right away.
- // So we should adjust the low pass filter cutoff slightly downward to avoid
- // some aliasing at the very high-end.
- // TODO(crogers): this value is empirical and to be more exact should vary
- // depending on kKernelSize.
- sinc_scale_factor *= 0.9;
-
- // Generates a set of windowed sinc() kernels.
- // We generate a range of sub-sample offsets from 0.0 to 1.0.
- for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) {
- double subsample_offset =
- static_cast<double>(offset_idx) / kKernelOffsetCount;
-
- for (int i = 0; i < kKernelSize; ++i) {
- // Compute the sinc with offset.
- double s =
- sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset);
- double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor;
-
- // Compute Blackman window, matching the offset of the sinc().
- double x = (i - subsample_offset) / kKernelSize;
- double window =
- kA0 - kA1 * cos(2.0 * M_PI * x) + kA2 * cos(4.0 * M_PI * x);
-
- // Window the sinc() function and store at the correct offset.
- kernel_storage_.get()[i + offset_idx * kKernelSize] = sinc * window;
- }
- }
-}
-
-void InterleavedSincResampler::QueueBuffer(
- const scoped_refptr<Buffer>& buffer) {
- DCHECK(buffer);
- DCHECK(CanQueueBuffer());
-
- if (!pending_buffers_.empty() && pending_buffers_.back()->IsEndOfStream()) {
- DCHECK(buffer->IsEndOfStream());
- return;
- }
-
- if (!buffer->IsEndOfStream()) {
- frames_queued_ += buffer->GetDataSize() / frame_size_in_bytes_;
- }
-
- pending_buffers_.push(buffer);
-}
-
-bool InterleavedSincResampler::Resample(float* destination, int frames) {
- if (!HasEnoughData(frames)) {
- return false;
- }
-
- int remaining_frames = frames;
-
- // Step (1) -- Prime the input buffer at the start of the input stream.
- if (!buffer_primed_) {
- Read(r0_, kBlockSize + kKernelSize / 2);
- buffer_primed_ = true;
- }
-
- // Step (2) -- Resample!
- while (remaining_frames) {
- while (virtual_source_idx_ < kBlockSize) {
- // |virtual_source_idx_| lies in between two kernel offsets so figure out
- // what they are.
- int source_idx = static_cast<int>(virtual_source_idx_);
- double subsample_remainder = virtual_source_idx_ - source_idx;
-
- double virtual_offset_idx = subsample_remainder * kKernelOffsetCount;
- int offset_idx = static_cast<int>(virtual_offset_idx);
-
- // We'll compute "convolutions" for the two kernels which straddle
- // |virtual_source_idx_|.
- float* k1 = kernel_storage_.get() + offset_idx * kKernelSize;
- float* k2 = k1 + kKernelSize;
-
- // Initialize input pointer based on quantized |virtual_source_idx_|.
- float* input_ptr = r1_ + source_idx * channel_count_;
-
- // Figure out how much to weight each kernel's "convolution".
- double kernel_interpolation_factor = virtual_offset_idx - offset_idx;
- for (int i = 0; i < channel_count_; ++i) {
- *destination++ =
- Convolve(input_ptr + i, k1, k2, kernel_interpolation_factor);
- }
-
- // Advance the virtual index.
- virtual_source_idx_ += io_sample_rate_ratio_;
-
- if (!--remaining_frames) {
- frames_resampled_ += frames;
- return true;
- }
- }
-
- // Wrap back around to the start.
- virtual_source_idx_ -= kBlockSize;
-
- // Step (3) Copy r3_ to r1_ and r4_ to r2_.
- // This wraps the last input frames back to the start of the buffer.
- memcpy(r1_, r3_, frame_size_in_bytes_ * (kKernelSize / 2));
- memcpy(r2_, r4_, frame_size_in_bytes_ * (kKernelSize / 2));
-
- // Step (4)
- // Refresh the buffer with more input.
- Read(r5_, kBlockSize);
- }
-
- NOTREACHED();
- return false;
-}
-
-void InterleavedSincResampler::Flush() {
- virtual_source_idx_ = 0;
- buffer_primed_ = false;
- memset(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
- while (!pending_buffers_.empty()) {
- pending_buffers_.pop();
- }
- offset_in_frames_ = 0;
- frames_resampled_ = 0;
- frames_queued_ = 0;
-}
-
-bool InterleavedSincResampler::CanQueueBuffer() const {
- if (pending_buffers_.empty()) {
- return true;
- }
- if (pending_buffers_.back()->IsEndOfStream()) {
- return false;
- }
- return pending_buffers_.size() < kMaximumPendingBuffers;
-}
-
-bool InterleavedSincResampler::ReachedEOS() const {
- if (pending_buffers_.empty() || !pending_buffers_.back()->IsEndOfStream()) {
- return false;
- }
- return frames_resampled_ * io_sample_rate_ratio_ >= frames_queued_;
-}
-
-bool InterleavedSincResampler::HasEnoughData(int frames_to_resample) const {
- // Always return true if EOS is seen, as in this case we will just fill 0.
- if (!pending_buffers_.empty() && pending_buffers_.back()->IsEndOfStream()) {
- return true;
- }
-
- // We have to decrease frames_queued_ down as the Read()s are always done in
- // blocks of kBlockSize or kBufferSize. We have to ensure that there is buffer
- // for an extra Read().
- return (frames_resampled_ + frames_to_resample) * io_sample_rate_ratio_ <
- frames_queued_ - kBufferSize;
-}
-
-void InterleavedSincResampler::Read(float* destination, int frames) {
- while (frames > 0 && !pending_buffers_.empty()) {
- scoped_refptr<Buffer> buffer = pending_buffers_.front();
- if (buffer->IsEndOfStream()) {
- // Zero fill the buffer after EOS has reached.
- memset(destination, 0, frame_size_in_bytes_ * frames);
- return;
- }
- // Copy the data over.
- int frames_in_buffer = buffer->GetDataSize() / frame_size_in_bytes_;
- int frames_to_copy = std::min(frames_in_buffer - offset_in_frames_, frames);
- const uint8* source = buffer->GetData();
- source += frame_size_in_bytes_ * offset_in_frames_;
- memcpy(destination, source, frame_size_in_bytes_ * frames_to_copy);
- offset_in_frames_ += frames_to_copy;
- // Pop the first buffer if all its content has been read.
- if (offset_in_frames_ == frames_in_buffer) {
- offset_in_frames_ = 0;
- pending_buffers_.pop();
- }
- frames -= frames_to_copy;
- destination += frames_to_copy * channel_count_;
- }
-
- // Read should always be satisfied as otherwise Resample should return false
- // to the caller directly.
- DCHECK_EQ(frames, 0);
-}
-
-float InterleavedSincResampler::Convolve(const float* input_ptr,
- const float* k1,
- const float* k2,
- double kernel_interpolation_factor) {
- float sum1 = 0;
- float sum2 = 0;
-
- // Generate a single output sample. Unrolling this loop hurt performance in
- // local testing.
- int n = kKernelSize;
- while (n--) {
- sum1 += *input_ptr * *k1++;
- sum2 += *input_ptr * *k2++;
- input_ptr += channel_count_;
- }
-
- // Linearly interpolate the two "convolutions".
- return (1.0 - kernel_interpolation_factor) * sum1 +
- kernel_interpolation_factor * sum2;
-}
-
-} // namespace media
diff --git a/src/media/base/interleaved_sinc_resampler.h b/src/media/base/interleaved_sinc_resampler.h
deleted file mode 100644
index edfdada..0000000
--- a/src/media/base/interleaved_sinc_resampler.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
-#define MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
-
-#include <queue>
-
-#include "base/memory/aligned_memory.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/buffers.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// InterleavedSincResampler is a high-quality interleaved multi-channel sample
-//-rate converter operating on samples in float. It uses the same algorithm as
-// SincResampler. Unlike SincResampler, it works in push mode instead of pull
-// mode.
-class MEDIA_EXPORT InterleavedSincResampler {
- public:
- // |io_sample_rate_ratio| is the ratio of input / output sample rates.
- // |channel_count| is the number of channels in the interleaved audio stream.
- InterleavedSincResampler(double io_sample_rate_ratio, int channel_count);
-
- // Append a buffer to the queue. The samples in the buffer has to be floats.
- void QueueBuffer(const scoped_refptr<Buffer>& buffer);
-
- // Resample |frames| of data from enqueued buffers. Return false if no sample
- // is read. Return true if all requested samples have been written into
- // |destination|. It will never do a partial read. After the stream reaches
- // the end, the function will fill the rest of buffer with 0.
- bool Resample(float* destination, int frames);
-
- // Flush all buffered data and reset internal indices.
- void Flush();
-
- // Return false if we shouldn't queue more buffers to the resampler.
- bool CanQueueBuffer() const;
-
- // Returning true when we start to return zero filled data because of EOS.
- bool ReachedEOS() const;
-
- private:
- void InitializeKernel();
- bool HasEnoughData(int frames_to_resample) const;
- void Read(float* destination, int frames);
-
- float Convolve(const float* input_ptr,
- const float* k1,
- const float* k2,
- double kernel_interpolation_factor);
-
- // The ratio of input / output sample rates.
- double io_sample_rate_ratio_;
-
- // An index on the source input buffer with sub-sample precision. It must be
- // double precision to avoid drift.
- double virtual_source_idx_;
-
- // The buffer is primed once at the very beginning of processing.
- bool buffer_primed_;
-
- // Number of audio channels.
- int channel_count_;
-
- // The size of bytes for an audio frame.
- const int frame_size_in_bytes_;
-
- // Contains kKernelOffsetCount kernels back-to-back, each of size kKernelSize.
- // The kernel offsets are sub-sample shifts of a windowed sinc shifted from
- // 0.0 to 1.0 sample.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> kernel_storage_;
-
- // Data from the source is copied into this buffer for each processing pass.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_buffer_;
-
- // A queue of buffers to be resampled.
- std::queue<scoped_refptr<Buffer> > pending_buffers_;
-
- // The current offset to read when reading from the first pending buffer.
- int offset_in_frames_;
-
- // The following two variables are used to calculate EOS and in HasEnoughData.
- int frames_resampled_;
- int frames_queued_;
-
- // Pointers to the various regions inside |input_buffer_|. See the diagram at
- // the top of the .cc file for more information.
- float* const r0_;
- float* const r1_;
- float* const r2_;
- float* const r3_;
- float* const r4_;
- float* const r5_;
-
- DISALLOW_COPY_AND_ASSIGN(InterleavedSincResampler);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
diff --git a/src/media/base/interleaved_sinc_resampler_unittest.cc b/src/media/base/interleaved_sinc_resampler_unittest.cc
deleted file mode 100644
index a8f2e15..0000000
--- a/src/media/base/interleaved_sinc_resampler_unittest.cc
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <math.h>
-
-#include <algorithm>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "media/base/interleaved_sinc_resampler.h"
-#include "media/base/sinc_resampler.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-namespace {
-
-// Used to compare if two samples are the same. Because the resampled result
-// from the SincResampler and the InterleavedSincResampler can be slightly
-// different as the first one may use the SSE Convolve function.
-const float kEpsilon = 0.0001f;
-
-bool AreSamplesSame(float sample1, float sample2) {
- return fabs(sample1 - sample2) < kEpsilon;
-}
-
-// Function to provide the audio data of a single channel indicated by
-// |channel_index| inside a multi channel audio stream to SincResampler.
-void ReadCB(const float* source,
- const int source_size,
- int channel_index,
- int channel_count,
- int* offset,
- float* destination,
- int samples) {
- int samples_to_copy = std::min(source_size - *offset, samples);
- samples -= samples_to_copy;
-
- while (samples_to_copy != 0) {
- *destination++ = source[*offset * channel_count + channel_index];
- --samples_to_copy;
- ++*offset;
- }
- if (samples != 0) {
- memset(destination, 0, sizeof(float) * samples);
- }
-}
-
-class TestBuffer : public Buffer {
- public:
- TestBuffer(const void* data, int data_size)
- : Buffer(base::TimeDelta(), base::TimeDelta()),
- data_(static_cast<const uint8*>(data)),
- data_size_(data_size) {}
-
- const uint8* GetData() const OVERRIDE { return data_; }
-
- int GetDataSize() const OVERRIDE { return data_size_; }
-
- private:
- const uint8* data_;
- int data_size_;
-};
-
-} // namespace
-
-TEST(InterleavedSincResamplerTest, InitialState) {
- InterleavedSincResampler interleaved_resampler(1, 1);
- float output[1];
-
- ASSERT_FALSE(interleaved_resampler.ReachedEOS());
- ASSERT_TRUE(interleaved_resampler.CanQueueBuffer());
- ASSERT_FALSE(interleaved_resampler.Resample(output, 1));
-}
-
-TEST(InterleavedSincResamplerTest, Read) {
- const int kInputFrames = 1024;
- const int kOutputFrames = kInputFrames * 2;
- float samples[kInputFrames] = {0.0};
- float output[kOutputFrames];
-
- InterleavedSincResampler interleaved_resampler(
- static_cast<double>(kInputFrames) / kOutputFrames, 1);
-
- interleaved_resampler.QueueBuffer(new TestBuffer(samples, sizeof(samples)));
- ASSERT_FALSE(interleaved_resampler.Resample(output, kOutputFrames + 1));
-
- while (interleaved_resampler.CanQueueBuffer()) {
- interleaved_resampler.QueueBuffer(new TestBuffer(samples, sizeof(samples)));
- }
-
- // There is really no guarantee that we can read more.
- ASSERT_TRUE(interleaved_resampler.Resample(output, 1));
-}
-
-TEST(InterleavedSincResamplerTest, ReachedEOS) {
- const int kInputFrames = 512 * 3 + 32;
- const int kOutputFrames = kInputFrames * 2;
- float input[kInputFrames] = {0.0};
-
- InterleavedSincResampler interleaved_resampler(
- static_cast<double>(kInputFrames) / kOutputFrames, 1);
-
- interleaved_resampler.QueueBuffer(new TestBuffer(input, sizeof(input)));
- interleaved_resampler.QueueBuffer(new TestBuffer(NULL, 0)); // EOS
-
- ASSERT_FALSE(interleaved_resampler.ReachedEOS());
-
- float output[kOutputFrames];
-
- ASSERT_TRUE(interleaved_resampler.Resample(output, kOutputFrames - 4));
- ASSERT_FALSE(interleaved_resampler.ReachedEOS());
-
- ASSERT_TRUE(interleaved_resampler.Resample(output, 4));
- ASSERT_TRUE(interleaved_resampler.ReachedEOS());
-}
-
-// As InterleavedSincResampler is just the interleaved version of SincResampler,
-// the following unit tests just try to verify that the results of using
-// InterleavedSincResampler are the same as using SincResampler on individual
-// channel.
-TEST(InterleavedSincResamplerTest, ResampleSingleChannel) {
- const int kInputFrames = 1719;
- // Read twice of the frames out to ensure that we saturate the input frames.
- const int kOutputFrames = kInputFrames * 2;
- const double kResampleRatio = 44100. / 48000.;
- float input[kInputFrames];
-
- // Filled the samples
- for (int i = 0; i < kInputFrames; ++i) {
- input[i] = i / static_cast<float>(kInputFrames);
- }
-
- int offset = 0;
- SincResampler sinc_resampler(
- kResampleRatio, base::Bind(ReadCB, input, kInputFrames, 0, 1, &offset));
- InterleavedSincResampler interleaved_resampler(kResampleRatio, 1);
-
- interleaved_resampler.QueueBuffer(new TestBuffer(input, sizeof(input)));
- interleaved_resampler.QueueBuffer(new TestBuffer(NULL, 0)); // EOS
-
- float non_interleaved_output[kOutputFrames];
- float interleaved_output[kOutputFrames];
-
- sinc_resampler.Resample(non_interleaved_output, kOutputFrames);
- ASSERT_TRUE(
- interleaved_resampler.Resample(interleaved_output, kOutputFrames));
-
- for (int i = 0; i < kOutputFrames; ++i) {
- ASSERT_TRUE(
- AreSamplesSame(non_interleaved_output[i], interleaved_output[i]));
- }
-}
-
-TEST(InterleavedSincResamplerTest, ResampleMultipleChannels) {
- const int kChannelCount = 3;
- const int kInputFrames = 8737;
- // Read twice of the frames out to ensure that we saturate the input frames.
- const int kOutputFrames = kInputFrames * 2;
- const double kResampleRatio = 44100. / 48000.;
- float input[kInputFrames * kChannelCount];
-
- // Filled the buffer with different samples per frame on different channels.
- for (int i = 0; i < kInputFrames * kChannelCount; ++i) {
- input[i] = i / static_cast<float>(kInputFrames * kChannelCount);
- }
-
- float non_interleaved_outputs[kChannelCount][kInputFrames * 2];
-
- for (int i = 0; i < kChannelCount; ++i) {
- int offset = 0;
- SincResampler sinc_resampler(
- kResampleRatio,
- base::Bind(ReadCB, input, kInputFrames, i, kChannelCount, &offset));
- sinc_resampler.Resample(non_interleaved_outputs[i], kOutputFrames);
- }
-
- InterleavedSincResampler interleaved_resampler(kResampleRatio, kChannelCount);
- interleaved_resampler.QueueBuffer(new TestBuffer(input, sizeof(input)));
- interleaved_resampler.QueueBuffer(new TestBuffer(NULL, 0)); // EOS
-
- float interleaved_output[kOutputFrames * kChannelCount];
-
- ASSERT_TRUE(
- interleaved_resampler.Resample(interleaved_output, kOutputFrames));
-
- for (int i = 0; i < kOutputFrames; ++i) {
- for (int channel = 0; channel < kChannelCount; ++channel) {
- ASSERT_TRUE(
- AreSamplesSame(non_interleaved_outputs[channel][i],
- interleaved_output[i * kChannelCount + channel]));
- }
- }
-}
-
-TEST(InterleavedSincResamplerTest, Benchmark) {
- const int kChannelCount = 8;
- const int kInputFrames = 44100;
- const int kNumberOfIterations = 100;
- const int kOutputFrames = kInputFrames * 2;
- const double kResampleRatio = 44100. / 48000.;
- std::vector<float> input(kInputFrames * kChannelCount);
-
- // Filled the buffer with different samples per frame on different channels.
- for (int i = 0; i < kInputFrames * kChannelCount; ++i) {
- input[i] = i / static_cast<float>(kInputFrames * kChannelCount);
- }
-
- InterleavedSincResampler interleaved_resampler(kResampleRatio, kChannelCount);
-
- base::TimeTicks start = base::TimeTicks::HighResNow();
- std::vector<float> interleaved_output(kOutputFrames * kChannelCount);
- int total_output_frames = 0;
-
- for (int i = 0; i < kNumberOfIterations; ++i) {
- interleaved_resampler.QueueBuffer(
- new TestBuffer(&input[0], sizeof(float) * input.size()));
- if (interleaved_resampler.Resample(&interleaved_output[0], kOutputFrames)) {
- total_output_frames += kOutputFrames;
- }
- }
-
- double total_time_c_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(
- "Benchmarking InterleavedSincResampler in %d channels for %d "
- "iterations took %.4gms.\n:\n",
- kChannelCount, kNumberOfIterations, total_time_c_ms);
-
- start = base::TimeTicks::HighResNow();
-
- int offset = 0;
- SincResampler sinc_resampler(
- kResampleRatio,
- base::Bind(ReadCB, &input[0], kInputFrames, 0, 1, &offset));
-
- while (total_output_frames > 0) {
- sinc_resampler.Resample(&interleaved_output[0], kOutputFrames);
- total_output_frames -= kOutputFrames;
- // Set offset to 0 so we'll never reach EOS to enforce a sample by sample
- // copy for every frame as previously we have to convert interleaved stream
- // to non-interleaved stream to use MultiChannelResampler and then convert
- // the result stream back to interleaved.
- offset = 0;
- }
-
- total_time_c_ms = (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(
- "Benchmarking SincResampler with one channel for %d iterations took "
- "%.4gms.\n:\n",
- kNumberOfIterations, total_time_c_ms);
-}
-
-} // namespace media
diff --git a/src/media/base/limits.h b/src/media/base/limits.h
deleted file mode 100644
index 3eff5f4..0000000
--- a/src/media/base/limits.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Contains limit definition constants for the media subsystem.
-
-#ifndef MEDIA_BASE_LIMITS_H_
-#define MEDIA_BASE_LIMITS_H_
-
-#include "base/basictypes.h"
-
-namespace media {
-
-namespace limits {
-
-enum {
- // Maximum possible dimension (width or height) for any video.
- kMaxDimension = (1 << 15) - 1, // 32767
-
- // Maximum possible canvas size (width multiplied by height) for any video.
- kMaxCanvas = (1 << (14 * 2)), // 16384 x 16384
-
- // Total number of video frames which are populating in the pipeline.
- kMaxVideoFrames = 4,
-
- // The following limits are used by AudioParameters::IsValid().
- //
- // A few notes on sample rates of common formats:
- // - AAC files are limited to 96 kHz.
- // - MP3 files are limited to 48 kHz.
- // - Vorbis used to be limited to 96 KHz, but no longer has that
- // restriction.
- // - Most PC audio hardware is limited to 192 KHz.
- kMaxSampleRate = 192000,
- kMinSampleRate = 3000,
- kMaxChannels = 32,
- kMaxBitsPerSample = 64,
- kMaxSamplesPerPacket = kMaxSampleRate,
- kMaxPacketSizeInBytes =
- (kMaxBitsPerSample / 8) * kMaxChannels * kMaxSamplesPerPacket,
-
- // This limit is used by ParamTraits<VideoCaptureParams>.
- kMaxFramesPerSecond = 1000,
-};
-
-} // namespace limits
-
-} // namespace media
-
-#endif // MEDIA_BASE_LIMITS_H_
diff --git a/src/media/base/media.h b/src/media/base/media.h
deleted file mode 100644
index 277c740..0000000
--- a/src/media/base/media.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Contains code that should be used for initializing, or querying the state
-// of the media library as a whole.
-
-#ifndef MEDIA_BASE_MEDIA_H_
-#define MEDIA_BASE_MEDIA_H_
-
-#include "media/base/media_export.h"
-
-class FilePath;
-
-namespace media {
-
-// Attempts to initialize the media library (loading DLLs, DSOs, etc.).
-//
-// If |module_dir| is the emptry string, then the system default library paths
-// are searched for the dynamic libraries. If a |module_dir| is provided, then
-// only the specified |module_dir| will be searched for the dynamic libraries.
-//
-// If multiple initializations are attempted with different |module_dir|s
-// specified then the first one to succeed remains effective for the lifetime
-// of the process.
-//
-// Returns true if everything was successfully initialized, false otherwise.
-MEDIA_EXPORT bool InitializeMediaLibrary(const FilePath& module_dir);
-
-// Helper function for unit tests to avoid boiler plate code everywhere. This
-// function will crash if it fails to load the media library. This ensures tests
-// fail if the media library is not available.
-MEDIA_EXPORT void InitializeMediaLibraryForTesting();
-
-// Use this if you need to check whether the media library is initialized
-// for the this process, without actually trying to initialize it.
-MEDIA_EXPORT bool IsMediaLibraryInitialized();
-
-} // namespace media
-
-#endif // MEDIA_BASE_MEDIA_H_
diff --git a/src/media/base/media_export.h b/src/media/base/media_export.h
deleted file mode 100644
index 44ccef7..0000000
--- a/src/media/base/media_export.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MEDIA_EXPORT_H_
-#define MEDIA_BASE_MEDIA_EXPORT_H_
-
-// Define MEDIA_EXPORT so that functionality implemented by the Media module
-// can be exported to consumers.
-
-#if defined(COMPONENT_BUILD)
-#if defined(_MSC_VER)
-
-#if defined(MEDIA_IMPLEMENTATION)
-#define MEDIA_EXPORT __declspec(dllexport)
-#else
-#define MEDIA_EXPORT __declspec(dllimport)
-#endif // defined(MEDIA_IMPLEMENTATION)
-
-#else // defined(WIN32)
-#if defined(MEDIA_IMPLEMENTATION)
-#define MEDIA_EXPORT __attribute__((visibility("default")))
-#else
-#define MEDIA_EXPORT
-#endif
-#endif
-
-#else // defined(COMPONENT_BUILD)
-#define MEDIA_EXPORT
-#endif
-
-#endif // MEDIA_BASE_MEDIA_EXPORT_H_
diff --git a/src/media/base/media_log.cc b/src/media/base/media_log.cc
deleted file mode 100644
index eb8ebbb..0000000
--- a/src/media/base/media_log.cc
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/media_log.h"
-
-#include <string>
-
-#include "base/atomic_sequence_num.h"
-#include "base/logging.h"
-#include "base/values.h"
-
-namespace media {
-
-// A count of all MediaLogs created in the current process. Used to generate
-// unique IDs.
-static base::StaticAtomicSequenceNumber g_media_log_count;
-
-const char* MediaLog::EventTypeToString(MediaLogEvent::Type type) {
- switch (type) {
- case MediaLogEvent::WEBMEDIAPLAYER_CREATED:
- return "WEBMEDIAPLAYER_CREATED";
- case MediaLogEvent::WEBMEDIAPLAYER_DESTROYED:
- return "WEBMEDIAPLAYER_DESTROYED";
- case MediaLogEvent::PIPELINE_CREATED:
- return "PIPELINE_CREATED";
- case MediaLogEvent::PIPELINE_DESTROYED:
- return "PIPELINE_DESTROYED";
- case MediaLogEvent::LOAD:
- return "LOAD";
- case MediaLogEvent::SEEK:
- return "SEEK";
- case MediaLogEvent::PLAY:
- return "PLAY";
- case MediaLogEvent::PAUSE:
- return "PAUSE";
- case MediaLogEvent::PIPELINE_STATE_CHANGED:
- return "PIPELINE_STATE_CHANGED";
- case MediaLogEvent::PIPELINE_ERROR:
- return "PIPELINE_ERROR";
- case MediaLogEvent::VIDEO_SIZE_SET:
- return "VIDEO_SIZE_SET";
- case MediaLogEvent::DURATION_SET:
- return "DURATION_SET";
- case MediaLogEvent::TOTAL_BYTES_SET:
- return "TOTAL_BYTES_SET";
- case MediaLogEvent::NETWORK_ACTIVITY_SET:
- return "NETWORK_ACTIVITY_SET";
- case MediaLogEvent::AUDIO_ENDED:
- return "AUDIO_ENDED";
- case MediaLogEvent::VIDEO_ENDED:
- return "VIDEO_ENDED";
- case MediaLogEvent::AUDIO_RENDERER_DISABLED:
- return "AUDIO_RENDERER_DISABLED";
- case MediaLogEvent::BUFFERED_EXTENTS_CHANGED:
- return "BUFFERED_EXTENTS_CHANGED";
- case MediaLogEvent::MEDIA_SOURCE_ERROR:
- return "MEDIA_SOURCE_ERROR";
- }
- NOTREACHED();
- return NULL;
-}
-
-const char* MediaLog::PipelineStatusToString(PipelineStatus status) {
- switch (status) {
- case PIPELINE_OK:
- return "pipeline: ok";
- case PIPELINE_ERROR_URL_NOT_FOUND:
- return "pipeline: url not found";
- case PIPELINE_ERROR_NETWORK:
- return "pipeline: network error";
- case PIPELINE_ERROR_DECODE:
- return "pipeline: decode error";
- case PIPELINE_ERROR_DECRYPT:
- return "pipeline: decrypt error";
- case PIPELINE_ERROR_ABORT:
- return "pipeline: abort";
- case PIPELINE_ERROR_INITIALIZATION_FAILED:
- return "pipeline: initialization failed";
- case PIPELINE_ERROR_COULD_NOT_RENDER:
- return "pipeline: could not render";
- case PIPELINE_ERROR_READ:
- return "pipeline: read error";
- case PIPELINE_ERROR_OPERATION_PENDING:
- return "pipeline: operation pending";
- case PIPELINE_ERROR_INVALID_STATE:
- return "pipeline: invalid state";
- case DEMUXER_ERROR_COULD_NOT_OPEN:
- return "demuxer: could not open";
- case DEMUXER_ERROR_COULD_NOT_PARSE:
- return "dumuxer: could not parse";
- case DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
- return "demuxer: no supported streams";
- case DECODER_ERROR_NOT_SUPPORTED:
- return "decoder: not supported";
- case PIPELINE_STATUS_MAX:
- NOTREACHED();
- }
- NOTREACHED();
- return NULL;
-}
-
-LogHelper::LogHelper(const LogCB& log_cb) : log_cb_(log_cb) {}
-
-LogHelper::~LogHelper() {
- if (log_cb_.is_null())
- return;
- log_cb_.Run(stream_.str());
-}
-
-MediaLog::MediaLog() : id_(g_media_log_count.GetNext()) {}
-
-MediaLog::~MediaLog() {}
-
-void MediaLog::AddEvent(scoped_ptr<MediaLogEvent> event) {}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateEvent(MediaLogEvent::Type type) {
- scoped_ptr<MediaLogEvent> event(new MediaLogEvent);
- event->id = id_;
- event->type = type;
- event->time = base::Time::Now();
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateBooleanEvent(
- MediaLogEvent::Type type, const char* property, bool value) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(type));
- event->params.SetBoolean(property, value);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateStringEvent(
- MediaLogEvent::Type type, const char* property, const std::string& value) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(type));
- event->params.SetString(property, value);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateTimeEvent(
- MediaLogEvent::Type type, const char* property, base::TimeDelta value) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(type));
- event->params.SetDouble(property, value.InSecondsF());
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateLoadEvent(const std::string& url) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::LOAD));
- event->params.SetString("url", url);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateSeekEvent(float seconds) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::SEEK));
- event->params.SetDouble("seek_target", seconds);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreatePipelineStateChangedEvent(
- const std::string& state) {
- scoped_ptr<MediaLogEvent> event(
- CreateEvent(MediaLogEvent::PIPELINE_STATE_CHANGED));
- event->params.SetString("pipeline_state", state);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreatePipelineErrorEvent(
- PipelineStatus error) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::PIPELINE_ERROR));
- event->params.SetString("pipeline_error", PipelineStatusToString(error));
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateVideoSizeSetEvent(
- size_t width, size_t height) {
- scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::VIDEO_SIZE_SET));
- event->params.SetInteger("width", width);
- event->params.SetInteger("height", height);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateBufferedExtentsChangedEvent(
- size_t start, size_t current, size_t end) {
- scoped_ptr<MediaLogEvent> event(
- CreateEvent(MediaLogEvent::BUFFERED_EXTENTS_CHANGED));
- event->params.SetInteger("buffer_start", start);
- event->params.SetInteger("buffer_current", current);
- event->params.SetInteger("buffer_end", end);
- return event.Pass();
-}
-
-scoped_ptr<MediaLogEvent> MediaLog::CreateMediaSourceErrorEvent(
- const std::string& error) {
- scoped_ptr<MediaLogEvent> event(
- CreateEvent(MediaLogEvent::MEDIA_SOURCE_ERROR));
- event->params.SetString("error", error);
- return event.Pass();
-}
-
-} //namespace media
diff --git a/src/media/base/media_log.h b/src/media/base/media_log.h
deleted file mode 100644
index a3ec7ed..0000000
--- a/src/media/base/media_log.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MEDIA_LOG_H_
-#define MEDIA_BASE_MEDIA_LOG_H_
-
-#include <sstream>
-#include <string>
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log_event.h"
-#include "media/base/pipeline_status.h"
-
-namespace media {
-
-// Indicates a string should be added to the log.
-// First parameter - The string to add to the log.
-typedef base::Callback<void(const std::string&)> LogCB;
-
-// Helper class to make it easier to use log_cb like DVLOG().
-class LogHelper {
- public:
- LogHelper(const LogCB& Log_cb);
- ~LogHelper();
-
- std::ostream& stream() { return stream_; }
-
- private:
- LogCB log_cb_;
- std::stringstream stream_;
-};
-
-#define MEDIA_LOG(log_cb) LogHelper(log_cb).stream()
-
-class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe<MediaLog> {
- public:
- // Convert various enums to strings.
- static const char* EventTypeToString(MediaLogEvent::Type type);
- static const char* PipelineStatusToString(PipelineStatus);
-
- MediaLog();
-
- // Add an event to this log. Overriden by inheritors to actually do something
- // with it.
- virtual void AddEvent(scoped_ptr<MediaLogEvent> event);
-
- // Helper methods to create events and their parameters.
- scoped_ptr<MediaLogEvent> CreateEvent(MediaLogEvent::Type type);
- scoped_ptr<MediaLogEvent> CreateBooleanEvent(
- MediaLogEvent::Type type, const char* property, bool value);
- scoped_ptr<MediaLogEvent> CreateStringEvent(
- MediaLogEvent::Type type, const char* property, const std::string& value);
- scoped_ptr<MediaLogEvent> CreateTimeEvent(
- MediaLogEvent::Type type, const char* property, base::TimeDelta value);
- scoped_ptr<MediaLogEvent> CreateLoadEvent(const std::string& url);
- scoped_ptr<MediaLogEvent> CreateSeekEvent(float seconds);
- scoped_ptr<MediaLogEvent> CreatePipelineStateChangedEvent(
- const std::string& state);
- scoped_ptr<MediaLogEvent> CreatePipelineErrorEvent(PipelineStatus error);
- scoped_ptr<MediaLogEvent> CreateVideoSizeSetEvent(
- size_t width, size_t height);
- scoped_ptr<MediaLogEvent> CreateBufferedExtentsChangedEvent(
- size_t start, size_t current, size_t end);
- scoped_ptr<MediaLogEvent> CreateMediaSourceErrorEvent(
- const std::string& error);
-
- protected:
- friend class base::RefCountedThreadSafe<MediaLog>;
- virtual ~MediaLog();
-
- private:
- // A unique (to this process) id for this MediaLog.
- int32 id_;
-
- DISALLOW_COPY_AND_ASSIGN(MediaLog);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MEDIA_LOG_H_
diff --git a/src/media/base/media_log_event.h b/src/media/base/media_log_event.h
deleted file mode 100644
index 9b0f6e1..0000000
--- a/src/media/base/media_log_event.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MEDIA_LOG_EVENT_H_
-#define MEDIA_BASE_MEDIA_LOG_EVENT_H_
-
-#include "base/time.h"
-#include "base/values.h"
-
-namespace media {
-
-struct MediaLogEvent {
- enum Type {
- // A WebMediaPlayer is being created or destroyed.
- // params: none.
- WEBMEDIAPLAYER_CREATED,
- WEBMEDIAPLAYER_DESTROYED,
-
- // A Pipeline is being created or destroyed.
- // params: none.
- PIPELINE_CREATED,
- PIPELINE_DESTROYED,
-
- // A media player is loading a resource.
- // params: "url": <URL of the resource>.
- LOAD,
-
- // A media player has started seeking.
- // params: "seek_target": <number of seconds to which to seek>.
- SEEK,
-
- // A media player has been told to play or pause.
- // params: none.
- PLAY,
- PAUSE,
-
- // The state of Pipeline has changed.
- // params: "pipeline_state": <string name of the state>.
- PIPELINE_STATE_CHANGED,
-
- // An error has occurred in the pipeline.
- // params: "pipeline_error": <string name of the error>.
- PIPELINE_ERROR,
-
- // The size of the video has been determined.
- // params: "width": <integral width of the video>.
- // "height": <integral height of the video>.
- VIDEO_SIZE_SET,
-
- // A property of the pipeline has been set by a filter.
- // These take a single parameter based upon the name of the event and of
- // the appropriate type. e.g. DURATION_SET: "duration" of type TimeDelta.
- DURATION_SET,
- TOTAL_BYTES_SET,
- NETWORK_ACTIVITY_SET,
-
- // Audio/Video stream playback has ended.
- AUDIO_ENDED,
- VIDEO_ENDED,
-
- // The audio renderer has been disabled.
- // params: none.
- AUDIO_RENDERER_DISABLED,
-
- // The extents of the sliding buffer have changed.
- // params: "buffer_start": <first buffered byte>.
- // "buffer_current": <current offset>.
- // "buffer_end": <last buffered byte>.
- BUFFERED_EXTENTS_CHANGED,
-
- // Errors reported by Media Source Extensions code.
- MEDIA_SOURCE_ERROR,
- // params: "error": Error string describing the error detected.
- };
-
- int32 id;
- Type type;
- base::DictionaryValue params;
- base::Time time;
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MEDIA_LOG_EVENT_H_
diff --git a/src/media/base/media_posix.cc b/src/media/base/media_posix.cc
deleted file mode 100644
index a6bebaa..0000000
--- a/src/media/base/media_posix.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/media.h"
-
-#include <string>
-
-#include "base/file_path.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "base/stringize_macros.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-
-#if !defined(USE_SYSTEM_FFMPEG)
-#include "third_party/ffmpeg/ffmpeg_stubs.h"
-
-using third_party_ffmpeg::kNumStubModules;
-using third_party_ffmpeg::kModuleFfmpegsumo;
-using third_party_ffmpeg::InitializeStubs;
-using third_party_ffmpeg::StubPathMap;
-#endif // !defined(USE_SYSTEM_FFMPEG)
-
-namespace media {
-
-// Handy to prevent shooting ourselves in the foot with macro wizardry.
-#if !defined(LIBAVCODEC_VERSION_MAJOR) || \
- !defined(LIBAVFORMAT_VERSION_MAJOR) || \
- !defined(LIBAVUTIL_VERSION_MAJOR)
-#error FFmpeg headers not included!
-#endif
-
-#define AVCODEC_VERSION STRINGIZE(LIBAVCODEC_VERSION_MAJOR)
-#define AVFORMAT_VERSION STRINGIZE(LIBAVFORMAT_VERSION_MAJOR)
-#define AVUTIL_VERSION STRINGIZE(LIBAVUTIL_VERSION_MAJOR)
-
-#if defined(OS_MACOSX)
-// TODO(evan): should be using .so like ffmepgsumo here.
-#define DSO_NAME(MODULE, VERSION) ("lib" MODULE "." VERSION ".dylib")
-static const FilePath::CharType kSumoLib[] =
- FILE_PATH_LITERAL("ffmpegsumo.so");
-#elif defined(OS_POSIX)
-#define DSO_NAME(MODULE, VERSION) ("lib" MODULE ".so." VERSION)
-static const FilePath::CharType kSumoLib[] =
- FILE_PATH_LITERAL("libffmpegsumo.so");
-#else
-#error "Do not know how to construct DSO name for this OS."
-#endif
-
-// Use a global to indicate whether the library has been initialized or not. We
-// rely on function level static initialization in InitializeMediaLibrary() to
-// guarantee this is only set once in a thread safe manner.
-static bool g_media_library_is_initialized = false;
-
-static bool InitializeMediaLibraryInternal(const FilePath& module_dir) {
- DCHECK(!g_media_library_is_initialized);
-
-#if defined(USE_SYSTEM_FFMPEG)
- // No initialization is necessary when using system ffmpeg,
- // we just link directly with system ffmpeg libraries.
- g_media_library_is_initialized = true;
-#else
- StubPathMap paths;
-
- // First try to initialize with Chrome's sumo library.
- DCHECK_EQ(kNumStubModules, 1);
- paths[kModuleFfmpegsumo].push_back(module_dir.Append(kSumoLib).value());
-
- // If that fails, see if any system libraries are available.
- paths[kModuleFfmpegsumo].push_back(module_dir.Append(
- FILE_PATH_LITERAL(DSO_NAME("avutil", AVUTIL_VERSION))).value());
- paths[kModuleFfmpegsumo].push_back(module_dir.Append(
- FILE_PATH_LITERAL(DSO_NAME("avcodec", AVCODEC_VERSION))).value());
- paths[kModuleFfmpegsumo].push_back(module_dir.Append(
- FILE_PATH_LITERAL(DSO_NAME("avformat", AVFORMAT_VERSION))).value());
-
- g_media_library_is_initialized = InitializeStubs(paths);
-#endif // !defined(USE_SYSTEM_FFMPEG)
- return g_media_library_is_initialized;
-}
-
-bool InitializeMediaLibrary(const FilePath& base_path) {
- static const bool kMediaLibraryInitialized =
- InitializeMediaLibraryInternal(base_path);
- DCHECK_EQ(kMediaLibraryInitialized, g_media_library_is_initialized);
- return kMediaLibraryInitialized;
-}
-
-void InitializeMediaLibraryForTesting() {
- FilePath file_path;
- CHECK(PathService::Get(base::DIR_EXE, &file_path));
- CHECK(InitializeMediaLibrary(file_path));
-}
-
-bool IsMediaLibraryInitialized() {
- return g_media_library_is_initialized;
-}
-
-} // namespace media
diff --git a/src/media/base/media_stub.cc b/src/media/base/media_stub.cc
deleted file mode 100644
index a42bbf9..0000000
--- a/src/media/base/media_stub.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2012 The Chromium 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 "media/base/media.h"
-
-#include "base/logging.h"
-
-// This file is intended for platforms that don't need to load any media
-// libraries (e.g., Android and iOS).
-namespace media {
-
-bool InitializeMediaLibrary(const FilePath& module_dir) {
- return true;
-}
-
-void InitializeMediaLibraryForTesting() {
-}
-
-bool IsMediaLibraryInitialized() {
- return true;
-}
-
-} // namespace media
diff --git a/src/media/base/media_switches.cc b/src/media/base/media_switches.cc
deleted file mode 100644
index 6c020df..0000000
--- a/src/media/base/media_switches.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/media_switches.h"
-
-namespace switches {
-
-// Allow users to specify a custom buffer size for debugging purpose.
-const char kAudioBufferSize[] = "audio-buffer-size";
-
-#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_SOLARIS)
-// The Alsa device to use when opening an audio stream.
-const char kAlsaOutputDevice[] = "alsa-output-device";
-// The Alsa device to use when opening an audio input stream.
-const char kAlsaInputDevice[] = "alsa-input-device";
-#endif
-
-#if defined(USE_CRAS)
-// Use CRAS, the ChromeOS audio server.
-const char kUseCras[] = "use-cras";
-#endif
-
-#if defined(USE_PULSEAUDIO)
-// Use PulseAudio on platforms that support it.
-const char kUsePulseAudio[] = "use-pulseaudio";
-#endif
-
-#if defined(OS_WIN)
-// Use exclusive mode audio streaming for Windows Vista and higher.
-// Leads to lower latencies for audio streams which uses the
-// AudioParameters::AUDIO_PCM_LOW_LATENCY audio path.
-// See http://msdn.microsoft.com/en-us/library/windows/desktop/dd370844(v=vs.85).aspx
-// for details.
-const char kEnableExclusiveAudio[] = "enable-exclusive-audio";
-#endif
-
-// Disable automatic fallback from low latency to high latency path.
-const char kDisableAudioFallback[] = "disable-audio-fallback";
-
-// Disable AudioOutputResampler for automatic audio resampling and rebuffering.
-const char kDisableAudioOutputResampler[] = "disable-audio-output-resampler";
-
-// Controls renderer side mixing and low latency audio path for media elements.
-#if defined(OS_WIN) || defined(OS_MACOSX)
-const char kDisableRendererSideMixing[] = "disable-renderer-side-mixing";
-#else
-const char kEnableRendererSideMixing[] = "enable-renderer-side-mixing";
-#endif
-
-// Enable browser-side audio mixer.
-const char kEnableAudioMixer[] = "enable-audio-mixer";
-
-// Enable live audio input with getUserMedia() and the Web Audio API.
-const char kEnableWebAudioInput[] = "enable-webaudio-input";
-
-// Set number of threads to use for video decoding.
-const char kVideoThreads[] = "video-threads";
-
-// Enables support for encrypted media. Current implementation is
-// incomplete and this flag is used for development and testing.
-const char kEnableEncryptedMedia[] = "enable-encrypted-media";
-
-// Enables Opus playback in media elements.
-const char kEnableOpusPlayback[] = "enable-opus-playback";
-
-} // namespace switches
diff --git a/src/media/base/media_switches.h b/src/media/base/media_switches.h
deleted file mode 100644
index 4005efb..0000000
--- a/src/media/base/media_switches.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Defines all the "media" command-line switches.
-
-#ifndef MEDIA_BASE_MEDIA_SWITCHES_H_
-#define MEDIA_BASE_MEDIA_SWITCHES_H_
-
-#include "build/build_config.h"
-#include "media/base/media_export.h"
-
-namespace switches {
-
-#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_SOLARIS)
-extern const char kAlsaOutputDevice[];
-extern const char kAlsaInputDevice[];
-#endif
-
-MEDIA_EXPORT extern const char kAudioBufferSize[];
-
-#if defined(USE_CRAS)
-MEDIA_EXPORT extern const char kUseCras[];
-#endif
-
-#if defined(USE_PULSEAUDIO)
-MEDIA_EXPORT extern const char kUsePulseAudio[];
-#endif
-
-#if defined(OS_WIN)
-MEDIA_EXPORT extern const char kEnableExclusiveAudio[];
-#endif
-
-MEDIA_EXPORT extern const char kDisableAudioFallback[];
-
-MEDIA_EXPORT extern const char kDisableAudioOutputResampler[];
-
-#if defined(OS_WIN) || defined(OS_MACOSX)
-MEDIA_EXPORT extern const char kDisableRendererSideMixing[];
-#else
-MEDIA_EXPORT extern const char kEnableRendererSideMixing[];
-#endif
-
-MEDIA_EXPORT extern const char kEnableAudioMixer[];
-
-MEDIA_EXPORT extern const char kEnableWebAudioInput[];
-
-MEDIA_EXPORT extern const char kVideoThreads[];
-
-MEDIA_EXPORT extern const char kEnableEncryptedMedia[];
-
-MEDIA_EXPORT extern const char kEnableOpusPlayback[];
-
-} // namespace switches
-
-#endif // MEDIA_BASE_MEDIA_SWITCHES_H_
diff --git a/src/media/base/media_win.cc b/src/media/base/media_win.cc
deleted file mode 100644
index 616d370..0000000
--- a/src/media/base/media_win.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/media.h"
-
-#include <windows.h>
-#if defined(_WIN32_WINNT_WIN8)
-// The Windows 8 SDK defines FACILITY_VISUALCPP in winerror.h.
-#undef FACILITY_VISUALCPP
-#endif
-#include <delayimp.h>
-
-#include "base/file_path.h"
-#include "base/logging.h"
-#include "base/native_library.h"
-#include "base/path_service.h"
-
-#pragma comment(lib, "delayimp.lib")
-
-namespace media {
-
-// FFmpeg library name.
-static const char* kFFmpegDLL = "ffmpegsumo.dll";
-
-// Use a global to indicate whether the library has been initialized or not. We
-// rely on function level static initialization in InitializeMediaLibrary() to
-// guarantee this is only set once in a thread safe manner.
-static bool g_media_library_is_initialized = false;
-
-static bool InitializeMediaLibraryInternal(const FilePath& base_path) {
- DCHECK(!g_media_library_is_initialized);
-
- // LoadLibraryEx(..., LOAD_WITH_ALTERED_SEARCH_PATH) cannot handle
- // relative path.
- if (!base_path.IsAbsolute())
- return false;
-
- // Use alternate DLL search path so we don't load dependencies from the
- // system path. Refer to http://crbug.com/35857
- HMODULE lib = ::LoadLibraryEx(
- base_path.AppendASCII(kFFmpegDLL).value().c_str(), NULL,
- LOAD_WITH_ALTERED_SEARCH_PATH);
-
- // Check that we loaded the library successfully.
- g_media_library_is_initialized = (lib != NULL);
- return g_media_library_is_initialized;
-}
-
-bool InitializeMediaLibrary(const FilePath& base_path) {
- static const bool kMediaLibraryInitialized =
- InitializeMediaLibraryInternal(base_path);
- DCHECK_EQ(kMediaLibraryInitialized, g_media_library_is_initialized);
- return kMediaLibraryInitialized;
-}
-
-void InitializeMediaLibraryForTesting() {
- FilePath file_path;
- CHECK(PathService::Get(base::DIR_EXE, &file_path));
- CHECK(InitializeMediaLibrary(file_path));
-}
-
-bool IsMediaLibraryInitialized() {
- return g_media_library_is_initialized;
-}
-
-} // namespace media
diff --git a/src/media/base/message_loop_factory.cc b/src/media/base/message_loop_factory.cc
deleted file mode 100644
index 38f4473..0000000
--- a/src/media/base/message_loop_factory.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/message_loop_factory.h"
-
-#include "base/threading/thread.h"
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#endif // defined(OS_STARBOARD)
-
-namespace media {
-
-MessageLoopFactory::MessageLoopFactory() {}
-
-MessageLoopFactory::~MessageLoopFactory() {
- for (ThreadList::reverse_iterator it = threads_.rbegin();
- it != threads_.rend(); ++it) {
- base::Thread* thread = it->second;
- thread->Stop();
- delete thread;
- }
- threads_.clear();
-}
-
-scoped_refptr<base::MessageLoopProxy> MessageLoopFactory::GetMessageLoop(
- Type type) {
- return GetThread(type)->message_loop_proxy();
-}
-
-base::Thread* MessageLoopFactory::GetThread(Type type) {
- base::AutoLock auto_lock(lock_);
- for (ThreadList::iterator it = threads_.begin(); it != threads_.end(); ++it) {
- if (it->first == type)
- return it->second;
- }
-
- const char* name = NULL;
- switch (type) {
- case kPipeline:
- name = "MediaPipeline";
- break;
- }
-
- base::Thread* thread = new base::Thread(name);
- base::Thread::Options options;
-
- if (type == kPipeline) {
-#if defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE)
- options.stack_size = SB_MEDIA_THREAD_STACK_SIZE;
-#endif // defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE)
- }
-
- CHECK(thread->StartWithOptions(options))
- << "Failed to start thread: " << name;
- threads_.push_back(std::make_pair(type, thread));
- return thread;
-}
-
-} // namespace media
diff --git a/src/media/base/message_loop_factory.h b/src/media/base/message_loop_factory.h
deleted file mode 100644
index f399b48..0000000
--- a/src/media/base/message_loop_factory.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MESSAGE_LOOP_FACTORY_H_
-#define MEDIA_BASE_MESSAGE_LOOP_FACTORY_H_
-
-#include <list>
-#include <string>
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop_proxy.h"
-#include "base/synchronization/lock.h"
-#include "media/base/media_export.h"
-
-class MessageLoop;
-
-namespace base {
-class Thread;
-}
-
-namespace media {
-
-// Factory object that manages named MessageLoops.
-//
-// TODO(scherkus): replace this with something simpler http://crbug.com/116873
-class MEDIA_EXPORT MessageLoopFactory {
- public:
- enum Type {
- kPipeline
- };
-
- MessageLoopFactory();
-
- // Get the message loop proxy associated with |type|. A new MessageLoopProxy
- // is created if the factory doesn't have one associated with |type|.
- scoped_refptr<base::MessageLoopProxy> GetMessageLoop(Type type);
-
- private:
- // Only allow scoped_ptr<> to delete factory.
- friend class scoped_ptr<MessageLoopFactory>;
- ~MessageLoopFactory();
-
- // Returns the thread associated with |type| creating a new thread if needed.
- base::Thread* GetThread(Type type);
-
- // Lock used to serialize access for the following data members.
- base::Lock lock_;
-
- // List of pairs of created threads and their types. We use a list to ensure
- // threads are stopped & deleted in reverse order of creation.
- typedef std::list<std::pair<Type, base::Thread*> > ThreadList;
- ThreadList threads_;
-
- DISALLOW_COPY_AND_ASSIGN(MessageLoopFactory);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MESSAGE_LOOP_FACTORY_H_
diff --git a/src/media/base/mock_audio_renderer_sink.cc b/src/media/base/mock_audio_renderer_sink.cc
deleted file mode 100644
index b21eb19..0000000
--- a/src/media/base/mock_audio_renderer_sink.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/mock_audio_renderer_sink.h"
-
-namespace media {
-
-MockAudioRendererSink::MockAudioRendererSink() {}
-MockAudioRendererSink::~MockAudioRendererSink() {}
-
-void MockAudioRendererSink::Initialize(const AudioParameters& params,
- RenderCallback* renderer) {
- callback_ = renderer;
-}
-
-} // namespace media
diff --git a/src/media/base/mock_audio_renderer_sink.h b/src/media/base/mock_audio_renderer_sink.h
deleted file mode 100644
index 7f59bf6..0000000
--- a/src/media/base/mock_audio_renderer_sink.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MOCK_AUDIO_RENDERER_SINK_H_
-#define MEDIA_BASE_MOCK_AUDIO_RENDERER_SINK_H_
-
-#include "media/audio/audio_parameters.h"
-#include "media/base/audio_renderer_sink.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class MockAudioRendererSink : public AudioRendererSink {
- public:
- MockAudioRendererSink();
-
- MOCK_METHOD0(Start, void());
- MOCK_METHOD0(Stop, void());
- MOCK_METHOD1(Pause, void(bool flush));
- MOCK_METHOD0(Play, void());
- MOCK_METHOD1(SetVolume, bool(double volume));
-#if defined(__LB_SHELL__) || defined(COBALT)
- MOCK_METHOD1(ResumeAfterUnderflow, void(bool));
-#endif
-
- virtual void Initialize(const AudioParameters& params,
- RenderCallback* renderer) OVERRIDE;
- AudioRendererSink::RenderCallback* callback() { return callback_; }
-
- protected:
- virtual ~MockAudioRendererSink();
-
- private:
- RenderCallback* callback_;
-
- DISALLOW_COPY_AND_ASSIGN(MockAudioRendererSink);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MOCK_AUDIO_RENDERER_SINK_H_
diff --git a/src/media/base/mock_data_source_host.cc b/src/media/base/mock_data_source_host.cc
deleted file mode 100644
index eff0b78..0000000
--- a/src/media/base/mock_data_source_host.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/mock_data_source_host.h"
-
-namespace media {
-
-MockDataSourceHost::MockDataSourceHost() {}
-
-MockDataSourceHost::~MockDataSourceHost() {}
-
-} // namespace media
diff --git a/src/media/base/mock_data_source_host.h b/src/media/base/mock_data_source_host.h
deleted file mode 100644
index 914d055..0000000
--- a/src/media/base/mock_data_source_host.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-#ifndef MEDIA_BASE_MOCK_DATA_SOURCE_HOST_H_
-#define MEDIA_BASE_MOCK_DATA_SOURCE_HOST_H_
-
-#include <string>
-
-#include "media/base/data_source.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class MockDataSourceHost : public DataSourceHost {
- public:
- MockDataSourceHost();
- virtual ~MockDataSourceHost();
-
- // DataSourceHost implementation.
- MOCK_METHOD1(SetTotalBytes, void(int64 total_bytes));
- MOCK_METHOD2(AddBufferedByteRange, void(int64 start, int64 end));
- MOCK_METHOD2(AddBufferedTimeRange, void(base::TimeDelta start,
- base::TimeDelta end));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDataSourceHost);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MOCK_DATA_SOURCE_HOST_H_
diff --git a/src/media/base/mock_demuxer_host.cc b/src/media/base/mock_demuxer_host.cc
deleted file mode 100644
index 100787f..0000000
--- a/src/media/base/mock_demuxer_host.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/mock_demuxer_host.h"
-
-namespace media {
-
-MockDemuxerHost::MockDemuxerHost() {}
-
-MockDemuxerHost::~MockDemuxerHost() {}
-
-} // namespace media
diff --git a/src/media/base/mock_demuxer_host.h b/src/media/base/mock_demuxer_host.h
deleted file mode 100644
index 597c132..0000000
--- a/src/media/base/mock_demuxer_host.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-#ifndef MEDIA_BASE_MOCK_DEMUXER_HOST_H_
-#define MEDIA_BASE_MOCK_DEMUXER_HOST_H_
-
-#include <string>
-
-#include "media/base/demuxer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class MockDemuxerHost : public DemuxerHost {
- public:
- MockDemuxerHost();
- virtual ~MockDemuxerHost();
-
- // DataSourceHost implementation.
- MOCK_METHOD1(SetTotalBytes, void(int64 total_bytes));
- MOCK_METHOD2(AddBufferedByteRange, void(int64 start, int64 end));
- MOCK_METHOD2(AddBufferedTimeRange, void(base::TimeDelta start,
- base::TimeDelta end));
-
- // DemuxerHost implementation.
- MOCK_METHOD1(OnDemuxerError, void(PipelineStatus error));
- MOCK_METHOD1(SetDuration, void(base::TimeDelta duration));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDemuxerHost);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MOCK_DEMUXER_HOST_H_
diff --git a/src/media/base/mock_filters.cc b/src/media/base/mock_filters.cc
deleted file mode 100644
index c197ee2..0000000
--- a/src/media/base/mock_filters.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/mock_filters.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-
-using ::testing::_;
-using ::testing::Invoke;
-using ::testing::NotNull;
-using ::testing::Return;
-
-namespace media {
-
-MockDemuxer::MockDemuxer() {}
-
-MockDemuxer::~MockDemuxer() {}
-
-MockDemuxerStream::MockDemuxerStream() {}
-
-MockDemuxerStream::~MockDemuxerStream() {}
-
-MockVideoDecoder::MockVideoDecoder() {
- EXPECT_CALL(*this, HasAlpha()).WillRepeatedly(Return(false));
-}
-
-MockVideoDecoder::~MockVideoDecoder() {}
-
-MockAudioDecoder::MockAudioDecoder() {}
-
-MockAudioDecoder::~MockAudioDecoder() {}
-
-MockVideoRenderer::MockVideoRenderer() {}
-
-MockVideoRenderer::~MockVideoRenderer() {}
-
-MockAudioRenderer::MockAudioRenderer() {}
-
-MockAudioRenderer::~MockAudioRenderer() {}
-
-MockDecryptor::MockDecryptor() {}
-
-MockDecryptor::~MockDecryptor() {}
-
-void MockDecryptor::InitializeAudioDecoder(
- scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- InitializeAudioDecoderMock(*config, init_cb);
-}
-
-void MockDecryptor::InitializeVideoDecoder(
- scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- InitializeVideoDecoderMock(*config, init_cb);
-}
-
-MockDecryptorClient::MockDecryptorClient() {}
-
-MockDecryptorClient::~MockDecryptorClient() {}
-
-void MockDecryptorClient::NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_length) {
- NeedKeyMock(key_system, session_id, type, init_data.get(), init_data_length);
-}
-
-MockFilterCollection::MockFilterCollection()
- : demuxer_(new MockDemuxer()),
- video_decoder_(new MockVideoDecoder()),
- audio_decoder_(new MockAudioDecoder()),
- video_renderer_(new MockVideoRenderer()),
- audio_renderer_(new MockAudioRenderer()) {
-}
-
-MockFilterCollection::~MockFilterCollection() {}
-
-scoped_ptr<FilterCollection> MockFilterCollection::Create() {
- scoped_ptr<FilterCollection> collection(new FilterCollection());
- collection->SetDemuxer(demuxer_);
- collection->GetVideoDecoders()->push_back(video_decoder_);
- collection->GetAudioDecoders()->push_back(audio_decoder_);
- collection->AddVideoRenderer(video_renderer_);
- collection->AddAudioRenderer(audio_renderer_);
- return collection.Pass();
-}
-
-MockStatisticsCB::MockStatisticsCB() {}
-
-MockStatisticsCB::~MockStatisticsCB() {}
-
-} // namespace media
diff --git a/src/media/base/mock_filters.h b/src/media/base/mock_filters.h
deleted file mode 100644
index 6faf508..0000000
--- a/src/media/base/mock_filters.h
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// A new breed of mock media filters, this time using gmock! Feel free to add
-// actions if you need interesting side-effects.
-//
-// Don't forget you can use StrictMock<> and NiceMock<> if you want the mock
-// filters to fail the test or do nothing when an unexpected method is called.
-// http://code.google.com/p/googlemock/wiki/CookBook#Nice_Mocks_and_Strict_Mocks
-
-#ifndef MEDIA_BASE_MOCK_FILTERS_H_
-#define MEDIA_BASE_MOCK_FILTERS_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decryptor.h"
-#include "media/base/decryptor_client.h"
-#include "media/base/demuxer.h"
-#include "media/base/filter_collection.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/video_decoder.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_renderer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-// Use this template to test for object destruction by setting expectations on
-// the method OnDestroy().
-//
-// TODO(scherkus): not sure about the naming... perhaps contribute this back
-// to gmock itself!
-template<class MockClass>
-class Destroyable : public MockClass {
- public:
- Destroyable() {}
-
- MOCK_METHOD0(OnDestroy, void());
-
- protected:
- virtual ~Destroyable() {
- OnDestroy();
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Destroyable);
-};
-
-class MockDemuxer : public Demuxer {
- public:
- MockDemuxer();
-
- // Demuxer implementation.
- MOCK_METHOD2(Initialize, void(DemuxerHost* host, const PipelineStatusCB& cb));
- MOCK_METHOD1(SetPlaybackRate, void(float playback_rate));
- MOCK_METHOD2(Seek, void(base::TimeDelta time, const PipelineStatusCB& cb));
- MOCK_METHOD1(Stop, void(const base::Closure& callback));
- MOCK_METHOD0(OnAudioRendererDisabled, void());
- MOCK_METHOD1(GetStream, scoped_refptr<DemuxerStream>(DemuxerStream::Type));
- MOCK_CONST_METHOD0(GetStartTime, base::TimeDelta());
-
- protected:
- virtual ~MockDemuxer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDemuxer);
-};
-
-class MockDemuxerStream : public DemuxerStream {
- public:
- MockDemuxerStream();
-
- // DemuxerStream implementation.
- MOCK_METHOD0(type, Type());
- MOCK_METHOD1(Read, void(const ReadCB& read_cb));
- MOCK_METHOD0(audio_decoder_config, const AudioDecoderConfig&());
- MOCK_METHOD0(video_decoder_config, const VideoDecoderConfig&());
- MOCK_METHOD0(EnableBitstreamConverter, void());
-#if defined(__LB_SHELL__) || defined(COBALT)
- MOCK_CONST_METHOD0(StreamWasEncrypted, bool());
-#endif
-
- protected:
- virtual ~MockDemuxerStream();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDemuxerStream);
-};
-
-class MockVideoDecoder : public VideoDecoder {
- public:
- MockVideoDecoder();
-
- // VideoDecoder implementation.
- MOCK_METHOD3(Initialize, void(const scoped_refptr<DemuxerStream>&,
- const PipelineStatusCB&,
- const StatisticsCB&));
- MOCK_METHOD1(Read, void(const ReadCB&));
- MOCK_METHOD1(Reset, void(const base::Closure&));
- MOCK_METHOD1(Stop, void(const base::Closure&));
- MOCK_CONST_METHOD0(HasAlpha, bool());
-
- protected:
- virtual ~MockVideoDecoder();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder);
-};
-
-class MockAudioDecoder : public AudioDecoder {
- public:
- MockAudioDecoder();
-
- // AudioDecoder implementation.
- MOCK_METHOD3(Initialize, void(const scoped_refptr<DemuxerStream>&,
- const PipelineStatusCB&,
- const StatisticsCB&));
- MOCK_METHOD1(Read, void(const ReadCB&));
- MOCK_METHOD0(bits_per_channel, int(void));
- MOCK_METHOD0(channel_layout, ChannelLayout(void));
- MOCK_METHOD0(samples_per_second, int(void));
- MOCK_METHOD1(Reset, void(const base::Closure&));
-
- protected:
- virtual ~MockAudioDecoder();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioDecoder);
-};
-
-class MockVideoRenderer : public VideoRenderer {
- public:
- MockVideoRenderer();
-
- // VideoRenderer implementation.
- MOCK_METHOD10(Initialize, void(const scoped_refptr<DemuxerStream>& stream,
- const VideoDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const TimeCB& time_cb,
- const NaturalSizeChangedCB& size_changed_cb,
- const base::Closure& ended_cb,
- const PipelineStatusCB& error_cb,
- const TimeDeltaCB& get_time_cb,
- const TimeDeltaCB& get_duration_cb));
- MOCK_METHOD1(Play, void(const base::Closure& callback));
- MOCK_METHOD1(Pause, void(const base::Closure& callback));
- MOCK_METHOD1(Flush, void(const base::Closure& callback));
- MOCK_METHOD2(Preroll, void(base::TimeDelta time, const PipelineStatusCB& cb));
- MOCK_METHOD1(Stop, void(const base::Closure& callback));
- MOCK_METHOD1(SetPlaybackRate, void(float playback_rate));
-
- protected:
- virtual ~MockVideoRenderer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockVideoRenderer);
-};
-
-class MockAudioRenderer : public AudioRenderer {
- public:
- MockAudioRenderer();
-
- // AudioRenderer implementation.
- MOCK_METHOD9(Initialize, void(const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb));
- MOCK_METHOD1(Play, void(const base::Closure& callback));
- MOCK_METHOD1(Pause, void(const base::Closure& callback));
- MOCK_METHOD1(Flush, void(const base::Closure& callback));
- MOCK_METHOD1(Stop, void(const base::Closure& callback));
- MOCK_METHOD1(SetPlaybackRate, void(float playback_rate));
- MOCK_METHOD2(Preroll, void(base::TimeDelta time, const PipelineStatusCB& cb));
- MOCK_METHOD1(SetVolume, void(float volume));
- MOCK_METHOD1(ResumeAfterUnderflow, void(bool buffer_more_audio));
-
- protected:
- virtual ~MockAudioRenderer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockAudioRenderer);
-};
-
-class MockDecryptor : public Decryptor {
- public:
- MockDecryptor();
- virtual ~MockDecryptor();
-
- MOCK_METHOD4(GenerateKeyRequest, bool(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length));
- MOCK_METHOD6(AddKey, void(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id));
- MOCK_METHOD2(CancelKeyRequest, void(const std::string& key_system,
- const std::string& session_id));
- MOCK_METHOD2(RegisterKeyAddedCB, void(StreamType stream_type,
- const KeyAddedCB& key_added_cb));
- MOCK_METHOD3(Decrypt, void(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb));
- MOCK_METHOD1(CancelDecrypt, void(StreamType stream_type));
- // TODO(xhwang): The following two methods are workarounds of the issue that
- // move-only parameters are not supported in mocked methods. Remove when the
- // issue is fixed: http://code.google.com/p/googletest/issues/detail?id=395
- MOCK_METHOD2(InitializeAudioDecoderMock,
- void(const AudioDecoderConfig& config,
- const DecoderInitCB& init_cb));
- MOCK_METHOD2(InitializeVideoDecoderMock,
- void(const VideoDecoderConfig& config,
- const DecoderInitCB& init_cb));
- MOCK_METHOD2(DecryptAndDecodeAudio,
- void(const scoped_refptr<media::DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb));
- MOCK_METHOD2(DecryptAndDecodeVideo,
- void(const scoped_refptr<media::DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb));
- MOCK_METHOD1(ResetDecoder, void(StreamType stream_type));
- MOCK_METHOD1(DeinitializeDecoder, void(StreamType stream_type));
-
- virtual void InitializeAudioDecoder(scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
- virtual void InitializeVideoDecoder(scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDecryptor);
-};
-
-class MockDecryptorClient : public DecryptorClient {
- public:
- MockDecryptorClient();
- virtual ~MockDecryptorClient();
-
- MOCK_METHOD2(KeyAdded, void(const std::string&, const std::string&));
- MOCK_METHOD4(KeyError, void(const std::string&, const std::string&,
- Decryptor::KeyError, int));
- MOCK_METHOD4(KeyMessage, void(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url));
- // TODO(xhwang): This is a workaround of the issue that move-only parameters
- // are not supported in mocked methods. Remove this when the issue is fixed
- // (http://code.google.com/p/googletest/issues/detail?id=395) or when we use
- // std::string instead of scoped_array<uint8> (http://crbug.com/130689).
- MOCK_METHOD5(NeedKeyMock, void(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- const uint8* init_data,
- int init_data_length));
- virtual void NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_length) OVERRIDE;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockDecryptorClient);
-};
-
-// FilterFactory that returns canned instances of mock filters. You can set
-// expectations on the filters and then pass the collection into a pipeline.
-class MockFilterCollection {
- public:
- MockFilterCollection();
- virtual ~MockFilterCollection();
-
- // Mock accessors.
- MockDemuxer* demuxer() const { return demuxer_; }
- MockVideoDecoder* video_decoder() const { return video_decoder_; }
- MockAudioDecoder* audio_decoder() const { return audio_decoder_; }
- MockVideoRenderer* video_renderer() const { return video_renderer_; }
- MockAudioRenderer* audio_renderer() const { return audio_renderer_; }
-
- // Creates the FilterCollection containing the mocks.
- scoped_ptr<FilterCollection> Create();
-
- private:
- scoped_refptr<MockDemuxer> demuxer_;
- scoped_refptr<MockVideoDecoder> video_decoder_;
- scoped_refptr<MockAudioDecoder> audio_decoder_;
- scoped_refptr<MockVideoRenderer> video_renderer_;
- scoped_refptr<MockAudioRenderer> audio_renderer_;
-
- DISALLOW_COPY_AND_ASSIGN(MockFilterCollection);
-};
-
-// Helper mock statistics callback.
-class MockStatisticsCB {
- public:
- MockStatisticsCB();
- ~MockStatisticsCB();
-
- MOCK_METHOD1(OnStatistics, void(const media::PipelineStatistics& statistics));
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MOCK_FILTERS_H_
diff --git a/src/media/base/mock_shell_data_source_reader.h b/src/media/base/mock_shell_data_source_reader.h
deleted file mode 100644
index 49ae468..0000000
--- a/src/media/base/mock_shell_data_source_reader.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
-#define MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
-
-#include "media/base/shell_data_source_reader.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class MockShellDataSourceReader : public ShellDataSourceReader {
- public:
- MockShellDataSourceReader() {}
-
- // ShellDataSourceReader implementation
- MOCK_METHOD1(SetDataSource, void(scoped_refptr<DataSource>));
- MOCK_METHOD1(SetErrorCallback, void(base::Closure));
- MOCK_METHOD3(BlockingRead, int(int64, int, uint8*));
- MOCK_METHOD0(FileSize, int64());
- MOCK_METHOD0(AbortPendingReadIfAny, void());
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
diff --git a/src/media/base/multi_channel_resampler.cc b/src/media/base/multi_channel_resampler.cc
deleted file mode 100644
index a5cbf3e..0000000
--- a/src/media/base/multi_channel_resampler.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/multi_channel_resampler.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/logging.h"
-#include "media/base/audio_bus.h"
-
-namespace media {
-
-MultiChannelResampler::MultiChannelResampler(int channels,
- double io_sample_rate_ratio,
- const ReadCB& read_cb)
- : last_frame_count_(0),
- read_cb_(read_cb),
- output_frames_ready_(0) {
- // Allocate each channel's resampler.
- resamplers_.reserve(channels);
- for (int i = 0; i < channels; ++i) {
- resamplers_.push_back(new SincResampler(io_sample_rate_ratio, base::Bind(
- &MultiChannelResampler::ProvideInput, base::Unretained(this), i)));
- }
-}
-
-MultiChannelResampler::~MultiChannelResampler() {}
-
-void MultiChannelResampler::Resample(AudioBus* audio_bus, int frames) {
- DCHECK_EQ(static_cast<size_t>(audio_bus->channels()), resamplers_.size());
-
- // We need to ensure that SincResampler only calls ProvideInput once for each
- // channel. To ensure this, we chunk the number of requested frames into
- // SincResampler::ChunkSize() sized chunks. SincResampler guarantees it will
- // only call ProvideInput() once when we resample this way.
- output_frames_ready_ = 0;
- int chunk_size = resamplers_[0]->ChunkSize();
- while (output_frames_ready_ < frames) {
- int frames_this_time = std::min(frames - output_frames_ready_, chunk_size);
-
- // Resample each channel.
- for (size_t i = 0; i < resamplers_.size(); ++i) {
- DCHECK_EQ(chunk_size, resamplers_[i]->ChunkSize());
-
- // Depending on the sample-rate scale factor, and the internal buffering
- // used in a SincResampler kernel, this call to Resample() will only
- // sometimes call ProvideInput(). However, if it calls ProvideInput() for
- // the first channel, then it will call it for the remaining channels,
- // since they all buffer in the same way and are processing the same
- // number of frames.
- resamplers_[i]->Resample(
- audio_bus->channel(i) + output_frames_ready_, frames_this_time);
- }
-
- output_frames_ready_ += frames_this_time;
- }
-}
-
-void MultiChannelResampler::ProvideInput(int channel, float* destination,
- int frames) {
- // Get the data from the multi-channel provider when the first channel asks
- // for it. For subsequent channels, we can just dish out the channel data
- // from that (stored in |resampler_audio_bus_|).
- if (channel == 0) {
- // Allocate staging arrays on the first request and if the frame size or
- // |destination| changes (should only happen once).
- if (!resampler_audio_bus_.get() ||
- resampler_audio_bus_->frames() != frames ||
- wrapped_resampler_audio_bus_->channel(0) != destination) {
- resampler_audio_bus_ = AudioBus::Create(resamplers_.size(), frames);
-
- // Create a channel vector based on |resampler_audio_bus_| but using
- // |destination| directly for the first channel and then wrap it in a new
- // AudioBus so we can avoid an extra memcpy later.
- resampler_audio_data_.clear();
- resampler_audio_data_.reserve(resampler_audio_bus_->channels());
- resampler_audio_data_.push_back(destination);
- for (int i = 1; i < resampler_audio_bus_->channels(); ++i)
- resampler_audio_data_.push_back(resampler_audio_bus_->channel(i));
- wrapped_resampler_audio_bus_ = AudioBus::WrapVector(
- frames, resampler_audio_data_);
- }
-
- last_frame_count_ = frames;
- read_cb_.Run(output_frames_ready_, wrapped_resampler_audio_bus_.get());
- } else {
- // All channels must ask for the same amount. This should always be the
- // case, but let's just make sure.
- DCHECK_EQ(frames, last_frame_count_);
-
- // Copy the channel data from what we received from |read_cb_|.
- memcpy(destination, resampler_audio_bus_->channel(channel),
- sizeof(*resampler_audio_bus_->channel(channel)) * frames);
- }
-}
-
-void MultiChannelResampler::Flush() {
- last_frame_count_ = 0;
- for (size_t i = 0; i < resamplers_.size(); ++i)
- resamplers_[i]->Flush();
-}
-
-} // namespace media
diff --git a/src/media/base/multi_channel_resampler.h b/src/media/base/multi_channel_resampler.h
deleted file mode 100644
index 6dd565b..0000000
--- a/src/media/base/multi_channel_resampler.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_MULTI_CHANNEL_RESAMPLER_H_
-#define MEDIA_BASE_MULTI_CHANNEL_RESAMPLER_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/scoped_vector.h"
-#include "media/base/sinc_resampler.h"
-
-namespace media {
-class AudioBus;
-
-// MultiChannelResampler is a multi channel wrapper for SincResampler; allowing
-// high quality sample rate conversion of multiple channels at once.
-class MEDIA_EXPORT MultiChannelResampler {
- public:
- // Callback type for providing more data into the resampler. Expects AudioBus
- // to be completely filled with data upon return; zero padded if not enough
- // frames are available to satisfy the request. |frame_delay| is the number
- // of output frames already processed and can be used to estimate delay.
- typedef base::Callback<void(int frame_delay, AudioBus* audio_bus)> ReadCB;
-
- // Constructs a MultiChannelResampler with the specified |read_cb|, which is
- // used to acquire audio data for resampling. |io_sample_rate_ratio| is the
- // ratio of input / output sample rates.
- MultiChannelResampler(int channels, double io_sample_rate_ratio,
- const ReadCB& read_cb);
- virtual ~MultiChannelResampler();
-
- // Resamples |frames| of data from |read_cb_| into AudioBus.
- void Resample(AudioBus* audio_bus, int frames);
-
- // Flush all buffered data and reset internal indices.
- void Flush();
-
- private:
- // SincResampler::ReadCB implementation. ProvideInput() will be called for
- // each channel (in channel order) as SincResampler needs more data.
- void ProvideInput(int channel, float* destination, int frames);
-
- // Sanity check to ensure that ProvideInput() retrieves the same number of
- // frames for every channel.
- int last_frame_count_;
-
- // Source of data for resampling.
- ReadCB read_cb_;
-
- // Each channel has its own high quality resampler.
- ScopedVector<SincResampler> resamplers_;
-
- // Buffers for audio data going into SincResampler from ReadCB.
- scoped_ptr<AudioBus> resampler_audio_bus_;
- scoped_ptr<AudioBus> wrapped_resampler_audio_bus_;
- std::vector<float*> resampler_audio_data_;
-
- // The number of output frames that have successfully been processed during
- // the current Resample() call.
- int output_frames_ready_;
-
- DISALLOW_COPY_AND_ASSIGN(MultiChannelResampler);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_MULTI_CHANNEL_RESAMPLER_H_
diff --git a/src/media/base/multi_channel_resampler_unittest.cc b/src/media/base/multi_channel_resampler_unittest.cc
deleted file mode 100644
index ad67550..0000000
--- a/src/media/base/multi_channel_resampler_unittest.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <cmath>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/audio_bus.h"
-#include "media/base/multi_channel_resampler.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Just test a basic resampling case. The SincResampler unit test will take
-// care of accuracy testing; we just need to check that multichannel works as
-// expected within some tolerance.
-static const float kScaleFactor = 192000.0f / 44100.0f;
-
-// Simulate large and small sample requests used by the different audio paths.
-static const int kHighLatencySize = 8192;
-// Low latency buffers show a larger error than high latency ones. Which makes
-// sense since each error represents a larger portion of the total request.
-static const int kLowLatencySize = 128;
-
-// Test fill value.
-static const float kFillValue = 0.1f;
-
-// Chosen arbitrarily based on what each resampler reported during testing.
-static const double kLowLatencyMaxRMSError = 0.0036;
-static const double kLowLatencyMaxError = 0.04;
-static const double kHighLatencyMaxRMSError = 0.0036;
-static const double kHighLatencyMaxError = 0.04;
-
-class MultiChannelResamplerTest
- : public testing::TestWithParam<int> {
- public:
- MultiChannelResamplerTest()
- : last_frame_delay_(-1) {
- }
- virtual ~MultiChannelResamplerTest() {}
-
- void InitializeAudioData(int channels, int frames) {
- frames_ = frames;
- audio_bus_ = AudioBus::Create(channels, frames);
- }
-
- // MultiChannelResampler::MultiChannelAudioSourceProvider implementation, just
- // fills the provided audio_data with |kFillValue|.
- virtual void ProvideInput(int frame_delay, AudioBus* audio_bus) {
- EXPECT_GT(frame_delay, last_frame_delay_);
- last_frame_delay_ = frame_delay;
-
- float fill_value = fill_junk_values_ ? (1 / kFillValue) : kFillValue;
- EXPECT_EQ(audio_bus->channels(), audio_bus_->channels());
- for (int i = 0; i < audio_bus->channels(); ++i)
- for (int j = 0; j < audio_bus->frames(); ++j)
- audio_bus->channel(i)[j] = fill_value;
- }
-
- void MultiChannelTest(int channels, int frames, double expected_max_rms_error,
- double expected_max_error) {
- InitializeAudioData(channels, frames);
- MultiChannelResampler resampler(channels, kScaleFactor, base::Bind(
- &MultiChannelResamplerTest::ProvideInput, base::Unretained(this)));
-
- // First prime the resampler with some junk data, so we can verify Flush().
- fill_junk_values_ = true;
- resampler.Resample(audio_bus_.get(), 1);
- resampler.Flush();
- fill_junk_values_ = false;
-
- // The last frame delay should be strictly less than the total frame count.
- EXPECT_LT(last_frame_delay_, audio_bus_->frames());
- last_frame_delay_ = -1;
-
- // If Flush() didn't work, the rest of the tests will fail.
- resampler.Resample(audio_bus_.get(), frames);
- TestValues(expected_max_rms_error, expected_max_error);
- }
-
- void HighLatencyTest(int channels) {
- MultiChannelTest(channels, kHighLatencySize, kHighLatencyMaxRMSError,
- kHighLatencyMaxError);
- }
-
- void LowLatencyTest(int channels) {
- MultiChannelTest(channels, kLowLatencySize, kLowLatencyMaxRMSError,
- kLowLatencyMaxError);
- }
-
- void TestValues(double expected_max_rms_error, double expected_max_error ) {
- // Calculate Root-Mean-Square-Error for the resampling.
- double max_error = 0.0;
- double sum_of_squares = 0.0;
- for (int i = 0; i < audio_bus_->channels(); ++i) {
- for (int j = 0; j < frames_; ++j) {
- // Ensure all values are accounted for.
- ASSERT_NE(audio_bus_->channel(i)[j], 0);
-
- double error = fabs(audio_bus_->channel(i)[j] - kFillValue);
- max_error = std::max(max_error, error);
- sum_of_squares += error * error;
- }
- }
-
- double rms_error = sqrt(
- sum_of_squares / (frames_ * audio_bus_->channels()));
-
- EXPECT_LE(rms_error, expected_max_rms_error);
- EXPECT_LE(max_error, expected_max_error);
- }
-
- protected:
- int frames_;
- bool fill_junk_values_;
- scoped_ptr<AudioBus> audio_bus_;
- int last_frame_delay_;
-
- DISALLOW_COPY_AND_ASSIGN(MultiChannelResamplerTest);
-};
-
-TEST_P(MultiChannelResamplerTest, HighLatency) {
- HighLatencyTest(GetParam());
-}
-
-TEST_P(MultiChannelResamplerTest, LowLatency) {
- LowLatencyTest(GetParam());
-}
-
-// Test common channel layouts: mono, stereo, 5.1, 7.1.
-INSTANTIATE_TEST_CASE_P(
- MultiChannelResamplerTest, MultiChannelResamplerTest,
- testing::Values(1, 2, 6, 8));
-
-} // namespace media
diff --git a/src/media/base/pipeline.h b/src/media/base/pipeline.h
deleted file mode 100644
index d3d752a..0000000
--- a/src/media/base/pipeline.h
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef MEDIA_BASE_PIPELINE_H_
-#define MEDIA_BASE_PIPELINE_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/message_loop_proxy.h"
-#include "base/time.h"
-#include "media/base/decryptor.h"
-#include "media/base/filter_collection.h"
-#include "media/base/media_export.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/ranges.h"
-#include "ui/gfx/rect.h"
-#include "ui/gfx/size.h"
-
-#if defined(OS_STARBOARD)
-#if SB_HAS(PLAYER)
-
-#define COBALT_USE_SBPLAYER_PIPELINE
-
-#endif // SB_HAS(PLAYER)
-#endif // defined(OS_STARBOARD)
-
-#if defined(COBALT_USE_SBPLAYER_PIPELINE)
-#include "starboard/window.h"
-typedef SbWindow PipelineWindow;
-#else // defined(COBALT_USE_SBPLAYER_PIPELINE)
-typedef void* PipelineWindow;
-#endif // defined(COBALT_USE_SBPLAYER_PIPELINE)
-
-namespace media {
-
-class MediaLog;
-
-// Pipeline contains the common interface for media pipelines. It provides
-// functions to perform asynchronous initialization, pausing, seeking and
-// playing.
-class MEDIA_EXPORT Pipeline : public base::RefCountedThreadSafe<Pipeline> {
- public:
- // Return true if the punch through box should be rendered. Return false if
- // no punch through box should be rendered.
- typedef base::Callback<bool(const gfx::Rect&)> SetBoundsCB;
-
- // Buffering states the pipeline transitions between during playback.
- // kHaveMetadata:
- // Indicates that the following things are known:
- // content duration, natural size, start time, and whether the content has
- // audio and/or video in supported formats.
- // kPrerollCompleted:
- // All renderers have buffered enough data to satisfy preroll and are ready
- // to start playback.
- enum BufferingState {
- kHaveMetadata,
- kPrerollCompleted,
- };
-
- typedef base::Callback<void(BufferingState)> BufferingStateCB;
-
- static scoped_refptr<Pipeline> Create(
- PipelineWindow window,
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- MediaLog* media_log);
-
- virtual ~Pipeline() {}
-
- virtual void Suspend() {}
- virtual void Resume() {}
-
- // Build a pipeline to using the given filter collection to construct a filter
- // chain, executing |seek_cb| when the initial seek/preroll has completed.
- //
- // |filter_collection| must be a complete collection containing a demuxer,
- // audio/video decoders, and audio/video renderers. Failing to do so will
- // result in a crash.
- //
- // The following permanent callbacks will be executed as follows up until
- // Stop() has completed:
- // |decryptor_ready_cb| can be used if Pipeline needs to be notified when
- // the Decryptor is ready.
- // |ended_cb| will be executed whenever the media reaches the end.
- // |error_cb| will be executed whenever an error occurs but hasn't
- // been reported already through another callback.
- // |buffering_state_cb| Optional callback that will be executed whenever the
- // pipeline's buffering state changes.
- // |duration_change_cb| optional callback that will be executed whenever the
- // presentation duration changes.
- // It is an error to call this method after the pipeline has already started.
- virtual void Start(scoped_ptr<FilterCollection> filter_collection,
- const SetDecryptorReadyCB& decryptor_ready_cb,
- const PipelineStatusCB& ended_cb,
- const PipelineStatusCB& error_cb,
- const PipelineStatusCB& seek_cb,
- const BufferingStateCB& buffering_state_cb,
- const base::Closure& duration_change_cb) = 0;
-
- // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline
- // teardown has completed.
- //
- // Stop() must complete before destroying the pipeline. It it permissible to
- // call Stop() at any point during the lifetime of the pipeline.
- virtual void Stop(const base::Closure& stop_cb) = 0;
-
- // Attempt to seek to the position specified by time. |seek_cb| will be
- // executed when the all filters in the pipeline have processed the seek.
- //
- // Clients are expected to call GetMediaTime() to check whether the seek
- // succeeded.
- //
- // It is an error to call this method if the pipeline has not started.
- virtual void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) = 0;
-
- // Returns true if the media has audio.
- virtual bool HasAudio() const = 0;
-
- // Returns true if the media has video.
- virtual bool HasVideo() const = 0;
-
- // Gets the current playback rate of the pipeline. When the pipeline is
- // started, the playback rate will be 0.0f. A rate of 1.0f indicates
- // that the pipeline is rendering the media at the standard rate. Valid
- // values for playback rate are >= 0.0f.
- virtual float GetPlaybackRate() const = 0;
-
- // Attempt to adjust the playback rate. Setting a playback rate of 0.0f pauses
- // all rendering of the media. A rate of 1.0f indicates a normal playback
- // rate. Values for the playback rate must be greater than or equal to 0.0f.
- virtual void SetPlaybackRate(float playback_rate) = 0;
-
- // Gets the current volume setting being used by the audio renderer. When
- // the pipeline is started, this value will be 1.0f. Valid values range
- // from 0.0f to 1.0f.
- virtual float GetVolume() const = 0;
-
- // Attempt to set the volume of the audio renderer. Valid values for volume
- // range from 0.0f (muted) to 1.0f (full volume). This value affects all
- // channels proportionately for multi-channel audio streams.
- virtual void SetVolume(float volume) = 0;
-
- // Returns the current media playback time, which progresses from 0 until
- // GetMediaDuration().
- virtual base::TimeDelta GetMediaTime() const = 0;
-
- // Get approximate time ranges of buffered media.
- virtual Ranges<base::TimeDelta> GetBufferedTimeRanges() = 0;
-
- // Get the duration of the media in microseconds. If the duration has not
- // been determined yet, then returns 0.
- virtual base::TimeDelta GetMediaDuration() const = 0;
-
- // Get the total size of the media file. If the size has not yet been
- // determined or can not be determined, this value is 0.
- virtual int64 GetTotalBytes() const = 0;
-
- // Gets the natural size of the video output in pixel units. If there is no
- // video or the video has not been rendered yet, the width and height will
- // be 0.
- virtual void GetNaturalVideoSize(gfx::Size* out_size) const = 0;
-
- // Return true if loading progress has been made since the last time this
- // method was called.
- virtual bool DidLoadingProgress() const = 0;
-
- // Gets the current pipeline statistics.
- virtual PipelineStatistics GetStatistics() const = 0;
-
- // Get the SetBoundsCB used to set the bounds of the video frame.
- virtual SetBoundsCB GetSetBoundsCB() { return SetBoundsCB(); }
-
- // Updates the player's preference for decode-to-texture versus punch through.
- virtual void SetDecodeToTextureOutputMode(bool /*enabled*/) {}
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_PIPELINE_H_
diff --git a/src/media/base/pipeline_impl.cc b/src/media/base/pipeline_impl.cc
deleted file mode 100644
index aff759d..0000000
--- a/src/media/base/pipeline_impl.cc
+++ /dev/null
@@ -1,1005 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/pipeline_impl.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/compiler_specific.h"
-#include "base/message_loop.h"
-#include "base/metrics/histogram.h"
-#include "base/stl_util.h"
-#include "base/string_number_conversions.h"
-#include "base/string_util.h"
-#include "base/synchronization/condition_variable.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/clock.h"
-#include "media/base/filter_collection.h"
-#include "media/base/media_log.h"
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/shell_media_platform.h"
-#include "media/base/shell_media_statistics.h"
-#include "media/base/shell_video_frame_provider.h"
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/video_decoder.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_renderer.h"
-
-using base::TimeDelta;
-
-namespace media {
-
-PipelineStatusNotification::PipelineStatusNotification()
- : cv_(&lock_), status_(PIPELINE_OK), notified_(false) {
-}
-
-PipelineStatusNotification::~PipelineStatusNotification() {
- DCHECK(notified_);
-}
-
-PipelineStatusCB PipelineStatusNotification::Callback() {
- return base::Bind(&PipelineStatusNotification::Notify,
- base::Unretained(this));
-}
-
-void PipelineStatusNotification::Notify(media::PipelineStatus status) {
- base::AutoLock auto_lock(lock_);
- DCHECK(!notified_);
- notified_ = true;
- status_ = status;
- cv_.Signal();
-}
-
-void PipelineStatusNotification::Wait() {
- base::AutoLock auto_lock(lock_);
- while (!notified_)
- cv_.Wait();
-}
-
-media::PipelineStatus PipelineStatusNotification::status() {
- base::AutoLock auto_lock(lock_);
- DCHECK(notified_);
- return status_;
-}
-
-scoped_refptr<Pipeline> Pipeline::Create(
- PipelineWindow window,
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- MediaLog* media_log) {
- UNREFERENCED_PARAMETER(window);
- return new PipelineImpl(message_loop, media_log);
-}
-
-PipelineImpl::PipelineImpl(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- MediaLog* media_log)
- : message_loop_(message_loop),
- media_log_(media_log),
- running_(false),
- did_loading_progress_(false),
- total_bytes_(0),
- natural_size_(0, 0),
- volume_(1.0f),
- playback_rate_(0.0f),
- clock_(new Clock(&base::Time::Now)),
- waiting_for_clock_update_(false),
- status_(PIPELINE_OK),
- has_audio_(false),
- has_video_(false),
- state_(kCreated),
- audio_ended_(false),
- video_ended_(false),
- audio_disabled_(false),
- creation_time_(base::Time::Now()) {
- media_log_->AddEvent(
- media_log_->CreatePipelineStateChangedEvent(GetStateString(kCreated)));
- media_log_->AddEvent(
- media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED));
-}
-
-PipelineImpl::~PipelineImpl() {
- // TODO(scherkus): Reenable after figuring out why this is firing, see
- // http://crbug.com/148405
-#if 0
- DCHECK(thread_checker_.CalledOnValidThread())
- << "PipelineImpl must be destroyed on same thread that created it";
-#endif
- DCHECK(!running_) << "Stop() must complete before destroying object";
- DCHECK(stop_cb_.is_null());
- DCHECK(seek_cb_.is_null());
-
- media_log_->AddEvent(
- media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED));
-}
-
-void PipelineImpl::Suspend() {
- // PipelineImpl::Suspend() is only called during quitting. It is blocking
- // and may take a long time.
- base::WaitableEvent waiter(false, false);
- DLOG(INFO) << "Trying to stop media pipeline.";
- Stop(base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)));
- waiter.Wait();
- DLOG(INFO) << "Media pipeline suspended.";
-}
-
-void PipelineImpl::Resume() {
- // PipelineImpl doesn't support Resume().
- NOTREACHED();
-}
-
-void PipelineImpl::Start(scoped_ptr<FilterCollection> collection,
- const SetDecryptorReadyCB& decryptor_ready_cb,
- const PipelineStatusCB& ended_cb,
- const PipelineStatusCB& error_cb,
- const PipelineStatusCB& seek_cb,
- const BufferingStateCB& buffering_state_cb,
- const base::Closure& duration_change_cb) {
- DCHECK_EQ(collection->GetAudioDecoders()->size(), 1);
- DCHECK_EQ(collection->GetVideoDecoders()->size(), 1);
-
- base::AutoLock auto_lock(lock_);
- CHECK(!running_) << "Media pipeline is already running";
- DCHECK(!buffering_state_cb.is_null());
-
- running_ = true;
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::StartTask, this,
- base::Passed(&collection), ended_cb, error_cb,
- seek_cb, buffering_state_cb, duration_change_cb));
-}
-
-void PipelineImpl::Stop(const base::Closure& stop_cb) {
- base::AutoLock auto_lock(lock_);
- message_loop_->PostTask(FROM_HERE,
- base::Bind(&PipelineImpl::StopTask, this, stop_cb));
-}
-
-void PipelineImpl::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
- base::AutoLock auto_lock(lock_);
- if (running_) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::SeekTask, this, time, seek_cb));
- } else {
- // This callback will be silently ignored if there is a reload as the
- // PipelineImpl will be killed in that case. This is acceptable since the
- // app
- // needn't rely on this callback.
- message_loop_->PostTask(
- FROM_HERE, base::Bind(seek_cb, PIPELINE_ERROR_INVALID_STATE));
- }
-}
-
-bool PipelineImpl::IsRunning() const {
- base::AutoLock auto_lock(lock_);
- return running_;
-}
-
-bool PipelineImpl::HasAudio() const {
- base::AutoLock auto_lock(lock_);
- return has_audio_;
-}
-
-bool PipelineImpl::HasVideo() const {
- base::AutoLock auto_lock(lock_);
- return has_video_;
-}
-
-float PipelineImpl::GetPlaybackRate() const {
- base::AutoLock auto_lock(lock_);
- return playback_rate_;
-}
-
-void PipelineImpl::SetPlaybackRate(float playback_rate) {
- if (playback_rate < 0.0f)
- return;
-
- base::AutoLock auto_lock(lock_);
- playback_rate_ = playback_rate;
- if (running_) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::PlaybackRateChangedTask, this,
- playback_rate));
- }
-}
-
-float PipelineImpl::GetVolume() const {
- base::AutoLock auto_lock(lock_);
- return volume_;
-}
-
-void PipelineImpl::SetVolume(float volume) {
- if (volume < 0.0f || volume > 1.0f)
- return;
-
- base::AutoLock auto_lock(lock_);
- volume_ = volume;
- if (running_) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::VolumeChangedTask, this, volume));
- }
-}
-
-TimeDelta PipelineImpl::GetMediaTime() const {
- base::AutoLock auto_lock(lock_);
- return clock_->Elapsed();
-}
-
-Ranges<TimeDelta> PipelineImpl::GetBufferedTimeRanges() {
- base::AutoLock auto_lock(lock_);
- Ranges<TimeDelta> time_ranges;
- for (size_t i = 0; i < buffered_time_ranges_.size(); ++i) {
- time_ranges.Add(buffered_time_ranges_.start(i),
- buffered_time_ranges_.end(i));
- }
- if (clock_->Duration() == TimeDelta() || total_bytes_ == 0)
- return time_ranges;
- for (size_t i = 0; i < buffered_byte_ranges_.size(); ++i) {
- TimeDelta start = TimeForByteOffset_Locked(buffered_byte_ranges_.start(i));
- TimeDelta end = TimeForByteOffset_Locked(buffered_byte_ranges_.end(i));
- // Cap approximated buffered time at the length of the video.
- end = std::min(end, clock_->Duration());
- time_ranges.Add(start, end);
- }
-
- return time_ranges;
-}
-
-TimeDelta PipelineImpl::GetMediaDuration() const {
- base::AutoLock auto_lock(lock_);
- return clock_->Duration();
-}
-
-int64 PipelineImpl::GetTotalBytes() const {
- base::AutoLock auto_lock(lock_);
- return total_bytes_;
-}
-
-void PipelineImpl::GetNaturalVideoSize(gfx::Size* out_size) const {
- CHECK(out_size);
- base::AutoLock auto_lock(lock_);
- *out_size = natural_size_;
-}
-
-bool PipelineImpl::DidLoadingProgress() const {
- base::AutoLock auto_lock(lock_);
- bool ret = did_loading_progress_;
- did_loading_progress_ = false;
- return ret;
-}
-
-PipelineStatistics PipelineImpl::GetStatistics() const {
- base::AutoLock auto_lock(lock_);
- return statistics_;
-}
-
-void PipelineImpl::SetClockForTesting(Clock* clock) {
- clock_.reset(clock);
-}
-
-void PipelineImpl::SetErrorForTesting(PipelineStatus status) {
- SetError(status);
-}
-
-void PipelineImpl::SetState(State next_state) {
- if (state_ != kStarted && next_state == kStarted &&
- !creation_time_.is_null()) {
- UMA_HISTOGRAM_TIMES(
- "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_);
- creation_time_ = base::Time();
- }
-
- DVLOG(2) << GetStateString(state_) << " -> " << GetStateString(next_state);
-
- state_ = next_state;
- media_log_->AddEvent(
- media_log_->CreatePipelineStateChangedEvent(GetStateString(next_state)));
-}
-
-#define RETURN_STRING(state) case state: return #state;
-
-const char* PipelineImpl::GetStateString(State state) {
- switch (state) {
- RETURN_STRING(kCreated);
- RETURN_STRING(kInitDemuxer);
- RETURN_STRING(kInitAudioRenderer);
- RETURN_STRING(kInitVideoRenderer);
- RETURN_STRING(kInitPrerolling);
- RETURN_STRING(kSeeking);
- RETURN_STRING(kStarting);
- RETURN_STRING(kStarted);
- RETURN_STRING(kStopping);
- RETURN_STRING(kStopped);
- }
- NOTREACHED();
- return "INVALID";
-}
-
-#undef RETURN_STRING
-
-PipelineImpl::State PipelineImpl::GetNextState() const {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(stop_cb_.is_null())
- << "State transitions don't happen when stopping";
- DCHECK_EQ(status_, PIPELINE_OK)
- << "State transitions don't happen when there's an error: " << status_;
-
- switch (state_) {
- case kCreated:
- return kInitDemuxer;
-
- case kInitDemuxer:
- if (demuxer_->GetStream(DemuxerStream::AUDIO))
- return kInitAudioRenderer;
- if (demuxer_->GetStream(DemuxerStream::VIDEO))
- return kInitVideoRenderer;
- return kInitPrerolling;
-
- case kInitAudioRenderer:
- if (demuxer_->GetStream(DemuxerStream::VIDEO))
- return kInitVideoRenderer;
- return kInitPrerolling;
-
- case kInitVideoRenderer:
- return kInitPrerolling;
-
- case kInitPrerolling:
- return kStarting;
-
- case kSeeking:
- return kStarting;
-
- case kStarting:
- return kStarted;
-
- case kStarted:
- case kStopping:
- case kStopped:
- break;
- }
- NOTREACHED() << "State has no transition: " << state_;
- return state_;
-}
-
-void PipelineImpl::OnDemuxerError(PipelineStatus error) {
- SetError(error);
-}
-
-void PipelineImpl::SetError(PipelineStatus error) {
- DCHECK(IsRunning());
- DCHECK_NE(PIPELINE_OK, error);
- VLOG(1) << "Media pipeline error: " << error;
-
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::ErrorChangedTask, this, error));
-
- media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error));
-}
-
-void PipelineImpl::OnAudioDisabled() {
- DCHECK(IsRunning());
- message_loop_->PostTask(FROM_HERE,
- base::Bind(&PipelineImpl::AudioDisabledTask, this));
- media_log_->AddEvent(
- media_log_->CreateEvent(MediaLogEvent::AUDIO_RENDERER_DISABLED));
-}
-
-void PipelineImpl::OnAudioTimeUpdate(TimeDelta time, TimeDelta max_time) {
- DCHECK_LE(time.InMicroseconds(), max_time.InMicroseconds());
- DCHECK(IsRunning());
- base::AutoLock auto_lock(lock_);
-
- if (!has_audio_)
- return;
- if (waiting_for_clock_update_ && time < clock_->Elapsed())
- return;
-
- // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
- // http://crbug.com/137973
- if (state_ == kSeeking)
- return;
-
- clock_->SetTime(time, max_time);
- StartClockIfWaitingForTimeUpdate_Locked();
-}
-
-void PipelineImpl::OnVideoTimeUpdate(TimeDelta max_time) {
- DCHECK(IsRunning());
- base::AutoLock auto_lock(lock_);
-
- if (has_audio_)
- return;
-
- // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
- // http://crbug.com/137973
- if (state_ == kSeeking)
- return;
-
- DCHECK(!waiting_for_clock_update_);
- clock_->SetMaxTime(max_time);
-}
-
-void PipelineImpl::SetDuration(TimeDelta duration) {
- media_log_->AddEvent(
- media_log_->CreateTimeEvent(
- MediaLogEvent::DURATION_SET, "duration", duration));
- UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
-
- base::AutoLock auto_lock(lock_);
- clock_->SetDuration(duration);
- if (!duration_change_cb_.is_null())
- duration_change_cb_.Run();
-}
-
-void PipelineImpl::SetTotalBytes(int64 total_bytes) {
- DCHECK(IsRunning());
- media_log_->AddEvent(
- media_log_->CreateStringEvent(
- MediaLogEvent::TOTAL_BYTES_SET, "total_bytes",
- base::Int64ToString(total_bytes)));
- int64 total_mbytes = total_bytes >> 20;
- if (total_mbytes > kint32max)
- total_mbytes = kint32max;
- UMA_HISTOGRAM_CUSTOM_COUNTS(
- "Media.TotalMBytes", static_cast<int32>(total_mbytes), 1, kint32max, 50);
-
- base::AutoLock auto_lock(lock_);
- total_bytes_ = total_bytes;
-}
-
-TimeDelta PipelineImpl::TimeForByteOffset_Locked(int64 byte_offset) const {
- lock_.AssertAcquired();
- TimeDelta time_offset = byte_offset * clock_->Duration() / total_bytes_;
- // Since the byte->time calculation is approximate, fudge the beginning &
- // ending areas to look better.
- TimeDelta epsilon = clock_->Duration() / 100;
- if (time_offset < epsilon)
- return TimeDelta();
- if (time_offset + epsilon > clock_->Duration())
- return clock_->Duration();
- return time_offset;
-}
-
-void PipelineImpl::OnStateTransition(PipelineStatus status) {
- // Force post to process state transitions after current execution frame.
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::StateTransitionTask, this, status));
-}
-
-void PipelineImpl::StateTransitionTask(PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // No-op any state transitions if we're stopping.
- if (state_ == kStopping || state_ == kStopped)
- return;
-
- // Preserve existing abnormal status, otherwise update based on the result of
- // the previous operation.
- status_ = (status_ != PIPELINE_OK ? status_ : status);
-
- if (status_ != PIPELINE_OK) {
- ErrorChangedTask(status_);
- return;
- }
-
- // Guard against accidentally clearing |pending_callbacks_| for states that
- // use it as well as states that should not be using it.
- //
- // TODO(scherkus): Make every state transition use |pending_callbacks_|.
- DCHECK_EQ(pending_callbacks_.get() != NULL,
- (state_ == kInitPrerolling || state_ == kStarting ||
- state_ == kSeeking));
- pending_callbacks_.reset();
-
- PipelineStatusCB done_cb = base::Bind(&PipelineImpl::OnStateTransition, this);
-
- // Switch states, performing any entrance actions for the new state as well.
- SetState(GetNextState());
- switch (state_) {
- case kInitDemuxer:
- return InitializeDemuxer(done_cb);
-
- case kInitAudioRenderer:
- return InitializeAudioRenderer(done_cb);
-
- case kInitVideoRenderer:
- return InitializeVideoRenderer(done_cb);
-
- case kInitPrerolling:
- filter_collection_.reset();
- {
- base::AutoLock l(lock_);
- // We do not want to start the clock running. We only want to set the
- // base media time so our timestamp calculations will be correct.
- clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime());
-
- // TODO(scherkus): |has_audio_| should be true no matter what --
- // otherwise people with muted/disabled sound cards will make our
- // default controls look as if every video doesn't contain an audio
- // track.
- has_audio_ = audio_renderer_ != NULL && !audio_disabled_;
- has_video_ = video_renderer_ != NULL;
- }
- if (!audio_renderer_ && !video_renderer_) {
- done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
- return;
- }
-
- buffering_state_cb_.Run(kHaveMetadata);
-
- return DoInitialPreroll(done_cb);
-
- case kStarting:
- return DoPlay(done_cb);
-
- case kStarted:
-#if defined(__LB_SHELL__) || defined(COBALT)
- ShellMediaStatistics::Instance().OnPlaybackBegin();
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- {
- base::AutoLock l(lock_);
- // We use audio stream to update the clock. So if there is such a
- // stream, we pause the clock until we receive a valid timestamp.
- waiting_for_clock_update_ = true;
- if (!has_audio_) {
- clock_->SetMaxTime(clock_->Duration());
- StartClockIfWaitingForTimeUpdate_Locked();
- }
- }
-
- DCHECK(!seek_cb_.is_null());
- DCHECK_EQ(status_, PIPELINE_OK);
-
- // Fire canplaythrough immediately after playback begins because of
- // crbug.com/106480.
- // TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
- buffering_state_cb_.Run(kPrerollCompleted);
- return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
-
- case kStopping:
- case kStopped:
- case kCreated:
- case kSeeking:
- NOTREACHED() << "State has no transition: " << state_;
- return;
- }
-}
-
-void PipelineImpl::DoInitialPreroll(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!pending_callbacks_.get());
- SerialRunner::Queue bound_fns;
-
- base::TimeDelta seek_timestamp = demuxer_->GetStartTime();
-
- // Preroll renderers.
- if (audio_renderer_) {
- bound_fns.Push(base::Bind(
- &AudioRenderer::Preroll, audio_renderer_, seek_timestamp));
- }
-
- if (video_renderer_) {
- bound_fns.Push(base::Bind(
- &VideoRenderer::Preroll, video_renderer_, seek_timestamp));
- }
-
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
-}
-
-void PipelineImpl::DoSeek(base::TimeDelta seek_timestamp,
- const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!pending_callbacks_.get());
- SerialRunner::Queue bound_fns;
-
- // Pause.
- if (audio_renderer_)
- bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_));
- if (video_renderer_)
- bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_));
-
- // Flush.
- if (audio_renderer_)
- bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_));
- if (video_renderer_)
- bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_));
-
- // Seek demuxer.
- bound_fns.Push(base::Bind(
- &Demuxer::Seek, demuxer_, seek_timestamp));
-
- // Preroll renderers.
- if (audio_renderer_) {
- bound_fns.Push(base::Bind(
- &AudioRenderer::Preroll, audio_renderer_, seek_timestamp));
- }
-
- if (video_renderer_) {
- bound_fns.Push(base::Bind(
- &VideoRenderer::Preroll, video_renderer_, seek_timestamp));
- }
-
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
-}
-
-void PipelineImpl::DoPlay(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!pending_callbacks_.get());
- SerialRunner::Queue bound_fns;
-
- PlaybackRateChangedTask(GetPlaybackRate());
- VolumeChangedTask(GetVolume());
-
- if (audio_renderer_)
- bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_));
-
- if (video_renderer_)
- bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_));
-
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
-}
-
-void PipelineImpl::DoStop(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!pending_callbacks_.get());
- SerialRunner::Queue bound_fns;
-
- if (demuxer_)
- bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_));
-
- if (audio_renderer_)
- bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_));
-
- if (video_renderer_)
- bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_));
-
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
-}
-
-void PipelineImpl::OnStopCompleted(PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kStopping);
- {
- base::AutoLock l(lock_);
- running_ = false;
- }
-
- SetState(kStopped);
- pending_callbacks_.reset();
- filter_collection_.reset();
- audio_renderer_ = NULL;
- video_renderer_ = NULL;
- demuxer_ = NULL;
-
- // If we stop during initialization/seeking we want to run |seek_cb_|
- // followed by |stop_cb_| so we don't leave outstanding callbacks around.
- if (!seek_cb_.is_null()) {
- base::ResetAndReturn(&seek_cb_).Run(status_);
- error_cb_.Reset();
- }
- if (!stop_cb_.is_null()) {
- base::ResetAndReturn(&stop_cb_).Run();
- error_cb_.Reset();
- }
- if (!error_cb_.is_null()) {
- DCHECK_NE(status_, PIPELINE_OK);
- base::ResetAndReturn(&error_cb_).Run(status_);
- }
-}
-
-void PipelineImpl::AddBufferedByteRange(int64 start, int64 end) {
- DCHECK(IsRunning());
- base::AutoLock auto_lock(lock_);
- buffered_byte_ranges_.Add(start, end);
- did_loading_progress_ = true;
-}
-
-void PipelineImpl::AddBufferedTimeRange(base::TimeDelta start,
- base::TimeDelta end) {
- DCHECK(IsRunning());
- base::AutoLock auto_lock(lock_);
- buffered_time_ranges_.Add(start, end);
- did_loading_progress_ = true;
-}
-
-void PipelineImpl::OnNaturalVideoSizeChanged(const gfx::Size& size) {
- DCHECK(IsRunning());
- media_log_->AddEvent(media_log_->CreateVideoSizeSetEvent(
- size.width(), size.height()));
-
- base::AutoLock auto_lock(lock_);
- natural_size_ = size;
-}
-
-void PipelineImpl::OnAudioRendererEnded() {
- // Force post to process ended messages after current execution frame.
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::DoAudioRendererEnded, this));
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::AUDIO_ENDED));
-}
-
-void PipelineImpl::OnVideoRendererEnded() {
- // Force post to process ended messages after current execution frame.
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::DoVideoRendererEnded, this));
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED));
-}
-
-// Called from any thread.
-void PipelineImpl::OnUpdateStatistics(const PipelineStatistics& stats) {
- base::AutoLock auto_lock(lock_);
- statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
- statistics_.video_bytes_decoded += stats.video_bytes_decoded;
- statistics_.video_frames_decoded += stats.video_frames_decoded;
- statistics_.video_frames_dropped += stats.video_frames_dropped;
-}
-
-void PipelineImpl::StartTask(scoped_ptr<FilterCollection> filter_collection,
- const PipelineStatusCB& ended_cb,
- const PipelineStatusCB& error_cb,
- const PipelineStatusCB& seek_cb,
- const BufferingStateCB& buffering_state_cb,
- const base::Closure& duration_change_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK_EQ(kCreated, state_)
- << "Media pipeline cannot be started more than once";
-
- filter_collection_ = filter_collection.Pass();
- ended_cb_ = ended_cb;
- error_cb_ = error_cb;
- seek_cb_ = seek_cb;
- buffering_state_cb_ = buffering_state_cb;
- duration_change_cb_ = duration_change_cb;
-
- StateTransitionTask(PIPELINE_OK);
-}
-
-void PipelineImpl::StopTask(const base::Closure& stop_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(stop_cb_.is_null());
-
- if (state_ == kStopped) {
- stop_cb.Run();
- return;
- }
-
- stop_cb_ = stop_cb;
-
- // We may already be stopping due to a runtime error.
- if (state_ == kStopping) {
- return;
- }
-
- SetState(kStopping);
- pending_callbacks_.reset();
- DoStop(base::Bind(&PipelineImpl::OnStopCompleted, this));
-}
-
-void PipelineImpl::ErrorChangedTask(PipelineStatus error) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
-
- if (state_ == kStopping || state_ == kStopped)
- return;
-
- SetState(kStopping);
- pending_callbacks_.reset();
- status_ = error;
-
- DoStop(base::Bind(&PipelineImpl::OnStopCompleted, this));
-}
-
-void PipelineImpl::PlaybackRateChangedTask(float playback_rate) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Playback rate changes are only carried out while playing.
- if (state_ != kStarting && state_ != kStarted)
- return;
-
- {
- base::AutoLock auto_lock(lock_);
- clock_->SetPlaybackRate(playback_rate);
- }
-
- if (demuxer_)
- demuxer_->SetPlaybackRate(playback_rate);
- if (audio_renderer_)
- audio_renderer_->SetPlaybackRate(playback_rate_);
- if (video_renderer_)
- video_renderer_->SetPlaybackRate(playback_rate_);
-}
-
-void PipelineImpl::VolumeChangedTask(float volume) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Volume changes are only carried out while playing.
- if (state_ != kStarting && state_ != kStarted)
- return;
-
- if (audio_renderer_)
- audio_renderer_->SetVolume(volume);
-}
-
-void PipelineImpl::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(stop_cb_.is_null());
-
- // Suppress seeking if we're not fully started.
- if (state_ != kStarted) {
- DCHECK(state_ == kStopping || state_ == kStopped)
- << "Receive extra seek in unexpected state: " << state_;
-
- // TODO(scherkus): should we run the callback? I'm tempted to say the API
- // will only execute the first Seek() request.
- DVLOG(1) << "Media pipeline has not started, ignoring seek to "
- << time.InMicroseconds() << " (current state: " << state_ << ")";
- return;
- }
-
- DCHECK(seek_cb_.is_null());
-
- SetState(kSeeking);
- base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime());
- seek_cb_ = seek_cb;
- audio_ended_ = false;
- video_ended_ = false;
-
- // Kick off seeking!
- {
- base::AutoLock auto_lock(lock_);
- if (clock_->IsPlaying())
- clock_->Pause();
- waiting_for_clock_update_ = false;
- clock_->SetTime(seek_timestamp, seek_timestamp);
- }
- DoSeek(seek_timestamp, base::Bind(&PipelineImpl::OnStateTransition, this));
-}
-
-void PipelineImpl::DoAudioRendererEnded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ != kStarted)
- return;
-
- DCHECK(!audio_ended_);
- audio_ended_ = true;
-
- // Start clock since there is no more audio to trigger clock updates.
- if (!audio_disabled_) {
- base::AutoLock auto_lock(lock_);
- clock_->SetMaxTime(clock_->Duration());
- StartClockIfWaitingForTimeUpdate_Locked();
- }
-
- RunEndedCallbackIfNeeded();
-}
-
-void PipelineImpl::DoVideoRendererEnded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ != kStarted)
- return;
-
- DCHECK(!video_ended_);
- video_ended_ = true;
-
- RunEndedCallbackIfNeeded();
-}
-
-void PipelineImpl::RunEndedCallbackIfNeeded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (audio_renderer_ && !audio_ended_ && !audio_disabled_)
- return;
-
- if (video_renderer_ && !video_ended_)
- return;
-
- {
- base::AutoLock auto_lock(lock_);
- clock_->EndOfStream();
- }
-
- DLOG(INFO) << "video playback completed successfully! :)";
-
- // TODO(scherkus): Change |ended_cb_| into a Closure.
- DCHECK_EQ(status_, PIPELINE_OK);
- ended_cb_.Run(status_);
-}
-
-void PipelineImpl::AudioDisabledTask() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- base::AutoLock auto_lock(lock_);
- has_audio_ = false;
- audio_disabled_ = true;
-
- // Notify our demuxer that we're no longer rendering audio.
- demuxer_->OnAudioRendererDisabled();
-
- // Start clock since there is no more audio to trigger clock updates.
- clock_->SetMaxTime(clock_->Duration());
- StartClockIfWaitingForTimeUpdate_Locked();
-}
-
-void PipelineImpl::InitializeDemuxer(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- demuxer_ = filter_collection_->GetDemuxer();
- demuxer_->Initialize(this, done_cb);
-}
-
-void PipelineImpl::InitializeAudioRenderer(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- DCHECK(stream);
-
- filter_collection_->SelectAudioRenderer(&audio_renderer_);
- audio_renderer_->Initialize(
- stream, *filter_collection_->GetAudioDecoders(), done_cb,
- base::Bind(&PipelineImpl::OnUpdateStatistics, this),
- base::Bind(&PipelineImpl::OnAudioUnderflow, this),
- base::Bind(&PipelineImpl::OnAudioTimeUpdate, this),
- base::Bind(&PipelineImpl::OnAudioRendererEnded, this),
- base::Bind(&PipelineImpl::OnAudioDisabled, this),
- base::Bind(&PipelineImpl::SetError, this));
- filter_collection_->GetAudioDecoders()->clear();
-}
-
-void PipelineImpl::InitializeVideoRenderer(const PipelineStatusCB& done_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- DCHECK(stream);
-
- {
- // Get an initial natural size so we have something when we signal
- // the kHaveMetadata buffering state.
- base::AutoLock l(lock_);
- natural_size_ = stream->video_decoder_config().natural_size();
- }
-
- filter_collection_->SelectVideoRenderer(&video_renderer_);
- video_renderer_->Initialize(
- stream, *filter_collection_->GetVideoDecoders(), done_cb,
- base::Bind(&PipelineImpl::OnUpdateStatistics, this),
- base::Bind(&PipelineImpl::OnVideoTimeUpdate, this),
- base::Bind(&PipelineImpl::OnNaturalVideoSizeChanged, this),
- base::Bind(&PipelineImpl::OnVideoRendererEnded, this),
- base::Bind(&PipelineImpl::SetError, this),
- base::Bind(&PipelineImpl::GetMediaTime, this),
- base::Bind(&PipelineImpl::GetMediaDuration, this));
- filter_collection_->GetVideoDecoders()->clear();
-}
-
-void PipelineImpl::OnAudioUnderflow() {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE,
- base::Bind(&PipelineImpl::OnAudioUnderflow, this));
- return;
- }
-
- if (state_ != kStarted)
- return;
-
- if (audio_renderer_)
- audio_renderer_->ResumeAfterUnderflow(true);
-}
-
-void PipelineImpl::StartClockIfWaitingForTimeUpdate_Locked() {
- lock_.AssertAcquired();
- if (!waiting_for_clock_update_)
- return;
-
- waiting_for_clock_update_ = false;
- clock_->Play();
-}
-
-} // namespace media
diff --git a/src/media/base/pipeline_impl.h b/src/media/base/pipeline_impl.h
deleted file mode 100644
index 8b1983f..0000000
--- a/src/media/base/pipeline_impl.h
+++ /dev/null
@@ -1,466 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_PIPELINE_IMPL_H_
-#define MEDIA_BASE_PIPELINE_IMPL_H_
-
-#include "base/callback.h"
-#include "base/gtest_prod_util.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_checker.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/demuxer.h"
-#include "media/base/media_export.h"
-#include "media/base/pipeline.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/ranges.h"
-#include "media/base/serial_runner.h"
-
-class MessageLoop;
-
-namespace base {
-class MessageLoopProxy;
-class TimeDelta;
-}
-
-namespace media {
-
-class Clock;
-class FilterCollection;
-class MediaLog;
-class VideoRenderer;
-
-// Adapter for using asynchronous Pipeline methods in code that wants to run
-// synchronously. To use, construct an instance of this class and pass the
-// |Callback()| to the Pipeline method requiring a callback. Then Wait() for
-// the callback to get fired and call status() to see what the callback's
-// argument was. This object is for one-time use; call |Callback()| exactly
-// once.
-class MEDIA_EXPORT PipelineStatusNotification {
- public:
- PipelineStatusNotification();
- ~PipelineStatusNotification();
-
- // See class-level comment for usage.
- PipelineStatusCB Callback();
- void Wait();
- PipelineStatus status();
-
- private:
- void Notify(media::PipelineStatus status);
-
- base::Lock lock_;
- base::ConditionVariable cv_;
- media::PipelineStatus status_;
- bool notified_;
-
- DISALLOW_COPY_AND_ASSIGN(PipelineStatusNotification);
-};
-
-// Pipeline runs the media pipeline. Filters are created and called on the
-// message loop injected into this object. Pipeline works like a state
-// machine to perform asynchronous initialization, pausing, seeking and playing.
-//
-// Here's a state diagram that describes the lifetime of this object.
-//
-// [ *Created ] [ Any State ]
-// | Start() | Stop() / SetError()
-// V V
-// [ InitXXX (for each filter) ] [ Stopping ]
-// | |
-// V V
-// [ InitPreroll ] [ Stopped ]
-// |
-// V
-// [ Starting ] <-- [ Seeking ]
-// | ^
-// V |
-// [ Started ] ----------'
-// Seek()
-//
-// Initialization is a series of state transitions from "Created" through each
-// filter initialization state. When all filter initialization states have
-// completed, we are implicitly in a "Paused" state. At that point we simulate
-// a Seek() to the beginning of the media to give filters a chance to preroll.
-// From then on the normal Seek() transitions are carried out and we start
-// playing the media.
-//
-// If any error ever happens, this object will transition to the "Error" state
-// from any state. If Stop() is ever called, this object will transition to
-// "Stopped" state.
-class MEDIA_EXPORT PipelineImpl : public Pipeline, public DemuxerHost {
- public:
- // Constructs a media pipeline that will execute on |message_loop|.
- PipelineImpl(const scoped_refptr<base::MessageLoopProxy>& message_loop,
- MediaLog* media_log);
-
- void Suspend() OVERRIDE;
- void Resume() OVERRIDE;
-
- // Build a pipeline to using the given filter collection to construct a filter
- // chain, executing |seek_cb| when the initial seek/preroll has completed.
- //
- // |filter_collection| must be a complete collection containing a demuxer,
- // audio/video decoders, and audio/video renderers. Failing to do so will
- // result in a crash.
- //
- // The following permanent callbacks will be executed as follows up until
- // Stop() has completed:
- // |decryptor_ready_cb| can be used if Pipeline needs to be notified when
- // the Decryptor is ready.
- // |ended_cb| will be executed whenever the media reaches the end.
- // |error_cb| will be executed whenever an error occurs but hasn't
- // been reported already through another callback.
- // |buffering_state_cb| Optional callback that will be executed whenever the
- // pipeline's buffering state changes.
- // |duration_change_cb| optional callback that will be executed whenever the
- // presentation duration changes.
- // It is an error to call this method after the pipeline has already started.
- void Start(scoped_ptr<FilterCollection> filter_collection,
- const SetDecryptorReadyCB& decryptor_ready_cb,
- const PipelineStatusCB& ended_cb,
- const PipelineStatusCB& error_cb,
- const PipelineStatusCB& seek_cb,
- const BufferingStateCB& buffering_state_cb,
- const base::Closure& duration_change_cb) OVERRIDE;
-
- // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline
- // teardown has completed.
- //
- // Stop() must complete before destroying the pipeline. It it permissible to
- // call Stop() at any point during the lifetime of the pipeline.
- void Stop(const base::Closure& stop_cb) OVERRIDE;
-
- // Attempt to seek to the position specified by time. |seek_cb| will be
- // executed when the all filters in the pipeline have processed the seek.
- //
- // Clients are expected to call GetMediaTime() to check whether the seek
- // succeeded.
- //
- // It is an error to call this method if the pipeline has not started.
- void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) OVERRIDE;
-
- // Returns true if the pipeline has been started via Start(). If IsRunning()
- // returns true, it is expected that Stop() will be called before destroying
- // the pipeline.
- bool IsRunning() const;
-
- // Returns true if the media has audio.
- bool HasAudio() const OVERRIDE;
-
- // Returns true if the media has video.
- bool HasVideo() const OVERRIDE;
-
- // Gets the current playback rate of the pipeline. When the pipeline is
- // started, the playback rate will be 0.0f. A rate of 1.0f indicates
- // that the pipeline is rendering the media at the standard rate. Valid
- // values for playback rate are >= 0.0f.
- float GetPlaybackRate() const OVERRIDE;
-
- // Attempt to adjust the playback rate. Setting a playback rate of 0.0f pauses
- // all rendering of the media. A rate of 1.0f indicates a normal playback
- // rate. Values for the playback rate must be greater than or equal to 0.0f.
- //
- // TODO(scherkus): What about maximum rate? Does HTML5 specify a max?
- void SetPlaybackRate(float playback_rate) OVERRIDE;
-
- // Gets the current volume setting being used by the audio renderer. When
- // the pipeline is started, this value will be 1.0f. Valid values range
- // from 0.0f to 1.0f.
- float GetVolume() const OVERRIDE;
-
- // Attempt to set the volume of the audio renderer. Valid values for volume
- // range from 0.0f (muted) to 1.0f (full volume). This value affects all
- // channels proportionately for multi-channel audio streams.
- void SetVolume(float volume) OVERRIDE;
-
- // Returns the current media playback time, which progresses from 0 until
- // GetMediaDuration().
- base::TimeDelta GetMediaTime() const OVERRIDE;
-
- // Get approximate time ranges of buffered media.
- Ranges<base::TimeDelta> GetBufferedTimeRanges() OVERRIDE;
-
- // Get the duration of the media in microseconds. If the duration has not
- // been determined yet, then returns 0.
- base::TimeDelta GetMediaDuration() const OVERRIDE;
-
- // Get the total size of the media file. If the size has not yet been
- // determined or can not be determined, this value is 0.
- int64 GetTotalBytes() const OVERRIDE;
-
- // Gets the natural size of the video output in pixel units. If there is no
- // video or the video has not been rendered yet, the width and height will
- // be 0.
- void GetNaturalVideoSize(gfx::Size* out_size) const OVERRIDE;
-
- // Return true if loading progress has been made since the last time this
- // method was called.
- bool DidLoadingProgress() const OVERRIDE;
-
- // Gets the current pipeline statistics.
- PipelineStatistics GetStatistics() const OVERRIDE;
-
- void SetClockForTesting(Clock* clock);
- void SetErrorForTesting(PipelineStatus status);
-
- private:
- FRIEND_TEST_ALL_PREFIXES(PipelineTest, GetBufferedTimeRanges);
- FRIEND_TEST_ALL_PREFIXES(PipelineTest, DisableAudioRenderer);
- FRIEND_TEST_ALL_PREFIXES(PipelineTest, DisableAudioRendererDuringInit);
- FRIEND_TEST_ALL_PREFIXES(PipelineTest, EndedCallback);
- FRIEND_TEST_ALL_PREFIXES(PipelineTest, AudioStreamShorterThanVideo);
- friend class MediaLog;
-
- virtual ~PipelineImpl();
-
- // PipelineImpl states, as described above.
- enum State {
- kCreated,
- kInitDemuxer,
- kInitAudioRenderer,
- kInitVideoRenderer,
- kInitPrerolling,
- kSeeking,
- kStarting,
- kStarted,
- kStopping,
- kStopped,
- };
-
- // Updates |state_|. All state transitions should use this call.
- void SetState(State next_state);
-
- static const char* GetStateString(State state);
- State GetNextState() const;
-
- // Helper method that runs & resets |seek_cb_| and resets |seek_timestamp_|
- // and |seek_pending_|.
- void FinishSeek();
-
- // DataSourceHost (by way of DemuxerHost) implementation.
- virtual void SetTotalBytes(int64 total_bytes) OVERRIDE;
- virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE;
- virtual void AddBufferedTimeRange(base::TimeDelta start,
- base::TimeDelta end) OVERRIDE;
-
- // DemuxerHost implementaion.
- virtual void SetDuration(base::TimeDelta duration) OVERRIDE;
- virtual void OnDemuxerError(PipelineStatus error) OVERRIDE;
-
- // Initiates teardown sequence in response to a runtime error.
- //
- // Safe to call from any thread.
- void SetError(PipelineStatus error);
-
- // Callback executed when the natural size of the video has changed.
- void OnNaturalVideoSizeChanged(const gfx::Size& size);
-
- // Callbacks executed when a renderer has ended.
- void OnAudioRendererEnded();
- void OnVideoRendererEnded();
-
- // Callback executed by filters to update statistics.
- void OnUpdateStatistics(const PipelineStatistics& stats);
-
- // Callback executed by audio renderer when it has been disabled.
- void OnAudioDisabled();
-
- // Callback executed by audio renderer to update clock time.
- void OnAudioTimeUpdate(base::TimeDelta time, base::TimeDelta max_time);
-
- // Callback executed by video renderer to update clock time.
- void OnVideoTimeUpdate(base::TimeDelta max_time);
-
- // The following "task" methods correspond to the public methods, but these
- // methods are run as the result of posting a task to the PipelineInternal's
- // message loop.
- void StartTask(scoped_ptr<FilterCollection> filter_collection,
- const PipelineStatusCB& ended_cb,
- const PipelineStatusCB& error_cb,
- const PipelineStatusCB& seek_cb,
- const BufferingStateCB& buffering_state_cb,
- const base::Closure& duration_change_cb);
-
- // Stops and destroys all filters, placing the pipeline in the kStopped state.
- void StopTask(const base::Closure& stop_cb);
-
- // Carries out stopping and destroying all filters, placing the pipeline in
- // the kStopped state.
- void ErrorChangedTask(PipelineStatus error);
-
- // Carries out notifying filters that the playback rate has changed.
- void PlaybackRateChangedTask(float playback_rate);
-
- // Carries out notifying filters that the volume has changed.
- void VolumeChangedTask(float volume);
-
- // Carries out notifying filters that we are seeking to a new timestamp.
- void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_cb);
-
- // Handles audio/video ended logic and running |ended_cb_|.
- void DoAudioRendererEnded();
- void DoVideoRendererEnded();
- void RunEndedCallbackIfNeeded();
-
- // Carries out disabling the audio renderer.
- void AudioDisabledTask();
-
- // Kicks off initialization for each media object, executing |done_cb| with
- // the result when completed.
- void InitializeDemuxer(const PipelineStatusCB& done_cb);
- void InitializeAudioRenderer(const PipelineStatusCB& done_cb);
- void InitializeVideoRenderer(const PipelineStatusCB& done_cb);
-
- // Kicks off destroying filters. Called by StopTask() and ErrorChangedTask().
- // When we start to tear down the pipeline, we will consider two cases:
- // 1. when pipeline has not been initialized, we will transit to stopping
- // state first.
- // 2. when pipeline has been initialized, we will first transit to pausing
- // => flushing => stopping => stopped state.
- // This will remove the race condition during stop between filters.
- void TearDownPipeline();
-
- // Compute the time corresponding to a byte offset.
- base::TimeDelta TimeForByteOffset_Locked(int64 byte_offset) const;
-
- void OnStateTransition(PipelineStatus status);
- void StateTransitionTask(PipelineStatus status);
-
- // Initiates an asynchronous preroll call sequence executing |done_cb|
- // with the final status when completed.
- void DoInitialPreroll(const PipelineStatusCB& done_cb);
-
- // Initiates an asynchronous pause-flush-seek-preroll call sequence
- // executing |done_cb| with the final status when completed.
- //
- // TODO(scherkus): Prerolling should be separate from seeking so we can report
- // finer grained ready states (HAVE_CURRENT_DATA vs. HAVE_FUTURE_DATA)
- // indepentent from seeking.
- void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb);
-
- // Updates playback rate and volume and initiates an asynchronous play call
- // sequence executing |done_cb| with the final status when completed.
- void DoPlay(const PipelineStatusCB& done_cb);
-
- // Initiates an asynchronous pause-flush-stop call sequence executing
- // |done_cb| when completed.
- void DoStop(const PipelineStatusCB& done_cb);
- void OnStopCompleted(PipelineStatus status);
-
- void OnAudioUnderflow();
-
- void StartClockIfWaitingForTimeUpdate_Locked();
-
- // Message loop used to execute pipeline tasks.
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // MediaLog to which to log events.
- scoped_refptr<MediaLog> media_log_;
-
- // Lock used to serialize access for the following data members.
- mutable base::Lock lock_;
-
- // Whether or not the pipeline is running.
- bool running_;
-
- // Amount of available buffered data. Set by filters.
- Ranges<int64> buffered_byte_ranges_;
- Ranges<base::TimeDelta> buffered_time_ranges_;
-
- // True when AddBufferedByteRange() has been called more recently than
- // DidLoadingProgress().
- mutable bool did_loading_progress_;
-
- // Total size of the media. Set by filters.
- int64 total_bytes_;
-
- // Video's natural width and height. Set by filters.
- gfx::Size natural_size_;
-
- // Current volume level (from 0.0f to 1.0f). This value is set immediately
- // via SetVolume() and a task is dispatched on the message loop to notify the
- // filters.
- float volume_;
-
- // Current playback rate (>= 0.0f). This value is set immediately via
- // SetPlaybackRate() and a task is dispatched on the message loop to notify
- // the filters.
- float playback_rate_;
-
- // Reference clock. Keeps track of current playback time. Uses system
- // clock and linear interpolation, but can have its time manually set
- // by filters.
- scoped_ptr<Clock> clock_;
-
- // If this value is set to true, then |clock_| is paused and we are waiting
- // for an update of the clock greater than or equal to the elapsed time to
- // start the clock.
- bool waiting_for_clock_update_;
-
- // Status of the pipeline. Initialized to PIPELINE_OK which indicates that
- // the pipeline is operating correctly. Any other value indicates that the
- // pipeline is stopped or is stopping. Clients can call the Stop() method to
- // reset the pipeline state, and restore this to PIPELINE_OK.
- PipelineStatus status_;
-
- // Whether the media contains rendered audio and video streams.
- // TODO(fischman,scherkus): replace these with checks for
- // {audio,video}_decoder_ once extraction of {Audio,Video}Decoder from the
- // Filter heirarchy is done.
- bool has_audio_;
- bool has_video_;
-
- // The following data members are only accessed by tasks posted to
- // |message_loop_|.
-
- // Member that tracks the current state.
- State state_;
-
- // Whether we've received the audio/video ended events.
- bool audio_ended_;
- bool video_ended_;
-
- // Set to true in DisableAudioRendererTask().
- bool audio_disabled_;
-
- scoped_ptr<FilterCollection> filter_collection_;
-
- // Temporary callback used for Start() and Seek().
- PipelineStatusCB seek_cb_;
-
- // Temporary callback used for Stop().
- base::Closure stop_cb_;
-
- // Permanent callbacks passed in via Start().
- PipelineStatusCB ended_cb_;
- PipelineStatusCB error_cb_;
- BufferingStateCB buffering_state_cb_;
- base::Closure duration_change_cb_;
-
- // Renderer references used for setting the volume, playback rate, and
- // determining when playback has finished.
- scoped_refptr<AudioRenderer> audio_renderer_;
- scoped_refptr<VideoRenderer> video_renderer_;
-
- // Demuxer reference used for setting the preload value.
- scoped_refptr<Demuxer> demuxer_;
-
- PipelineStatistics statistics_;
-
- // Time of pipeline creation; is non-zero only until the pipeline first
- // reaches "kStarted", at which point it is used & zeroed out.
- base::Time creation_time_;
-
- scoped_ptr<SerialRunner> pending_callbacks_;
-
- base::ThreadChecker thread_checker_;
-
- DISALLOW_COPY_AND_ASSIGN(PipelineImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_PIPELINE_IMPL_H_
diff --git a/src/media/base/pipeline_impl_unittest.cc b/src/media/base/pipeline_impl_unittest.cc
deleted file mode 100644
index 59e743b..0000000
--- a/src/media/base/pipeline_impl_unittest.cc
+++ /dev/null
@@ -1,1215 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <vector>
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/stl_util.h"
-#include "base/threading/simple_thread.h"
-#include "media/base/clock.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/media_log.h"
-#include "media/base/mock_filters.h"
-#include "media/base/pipeline_impl.h"
-#include "media/base/test_helpers.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/size.h"
-
-using ::testing::_;
-using ::testing::DeleteArg;
-using ::testing::DoAll;
-// TODO(scherkus): Remove InSequence after refactoring PipelineImpl.
-using ::testing::InSequence;
-using ::testing::Invoke;
-using ::testing::InvokeWithoutArgs;
-using ::testing::Mock;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-using ::testing::WithArg;
-
-namespace media {
-
-// Demuxer properties.
-static const int kTotalBytes = 1024;
-
-ACTION_P(SetDemuxerProperties, duration) {
- arg0->SetTotalBytes(kTotalBytes);
- arg0->SetDuration(duration);
-}
-
-ACTION_P2(Stop, pipeline, stop_cb) {
- pipeline->Stop(stop_cb);
-}
-
-ACTION_P2(SetError, pipeline, status) {
- pipeline->SetErrorForTesting(status);
-}
-
-// Used for setting expectations on pipeline callbacks. Using a StrictMock
-// also lets us test for missing callbacks.
-class CallbackHelper {
- public:
- CallbackHelper() {}
- virtual ~CallbackHelper() {}
-
- MOCK_METHOD1(OnStart, void(PipelineStatus));
- MOCK_METHOD1(OnSeek, void(PipelineStatus));
- MOCK_METHOD0(OnStop, void());
- MOCK_METHOD1(OnEnded, void(PipelineStatus));
- MOCK_METHOD1(OnError, void(PipelineStatus));
- MOCK_METHOD1(OnBufferingState, void(PipelineImpl::BufferingState));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
-};
-
-// TODO(scherkus): even though some filters are initialized on separate
-// threads these test aren't flaky... why? It's because filters' Initialize()
-// is executed on |message_loop_| and the mock filters instantly call
-// InitializationComplete(), which keeps the pipeline humming along. If
-// either filters don't call InitializationComplete() immediately or filter
-// initialization is moved to a separate thread this test will become flaky.
-class PipelineTest : public ::testing::Test {
- public:
- PipelineTest()
- : pipeline_(new PipelineImpl(message_loop_.message_loop_proxy(),
- new MediaLog())) {
- mocks_.reset(new MockFilterCollection());
-
- // InitializeDemuxer() adds overriding expectations for expected non-NULL
- // streams.
- DemuxerStream* null_pointer = NULL;
- EXPECT_CALL(*mocks_->demuxer(), GetStream(_))
- .WillRepeatedly(Return(null_pointer));
-
- EXPECT_CALL(*mocks_->demuxer(), GetStartTime())
- .WillRepeatedly(Return(base::TimeDelta()));
- }
-
- virtual ~PipelineTest() {
- // Shutdown sequence.
- if (pipeline_->IsRunning()) {
- EXPECT_CALL(*mocks_->demuxer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- if (audio_stream_)
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- if (video_stream_)
- EXPECT_CALL(*mocks_->video_renderer(), Stop(_))
- .WillOnce(RunClosure<0>());
- }
-
- // Expect a stop callback if we were started.
- EXPECT_CALL(callbacks_, OnStop());
- pipeline_->Stop(base::Bind(&CallbackHelper::OnStop,
- base::Unretained(&callbacks_)));
- message_loop_.RunUntilIdle();
-
- pipeline_ = NULL;
- mocks_.reset();
- }
-
- protected:
- // Sets up expectations to allow the demuxer to initialize.
- typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector;
- void InitializeDemuxer(MockDemuxerStreamVector* streams,
- const base::TimeDelta& duration) {
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _))
- .WillOnce(DoAll(SetDemuxerProperties(duration),
- RunCallback<1>(PIPELINE_OK)));
-
- // Configure the demuxer to return the streams.
- for (size_t i = 0; i < streams->size(); ++i) {
- scoped_refptr<DemuxerStream> stream((*streams)[i]);
- EXPECT_CALL(*mocks_->demuxer(), GetStream(stream->type()))
- .WillRepeatedly(Return(stream));
- }
- }
-
- void InitializeDemuxer(MockDemuxerStreamVector* streams) {
- // Initialize with a default non-zero duration.
- InitializeDemuxer(streams, base::TimeDelta::FromSeconds(10));
- }
-
- StrictMock<MockDemuxerStream>* CreateStream(DemuxerStream::Type type) {
- StrictMock<MockDemuxerStream>* stream =
- new StrictMock<MockDemuxerStream>();
- EXPECT_CALL(*stream, type())
- .WillRepeatedly(Return(type));
- return stream;
- }
-
- // Sets up expectations to allow the video renderer to initialize.
- void InitializeVideoRenderer(const scoped_refptr<DemuxerStream>& stream) {
- EXPECT_CALL(*mocks_->video_renderer(),
- Initialize(stream, _, _, _, _, _, _, _, _, _))
- .WillOnce(RunCallback<2>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f));
-
- // Startup sequence.
- EXPECT_CALL(*mocks_->video_renderer(),
- Preroll(mocks_->demuxer()->GetStartTime(), _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->video_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
- }
-
- // Sets up expectations to allow the audio renderer to initialize.
- void InitializeAudioRenderer(const scoped_refptr<DemuxerStream>& stream,
- bool disable_after_init_cb) {
- if (disable_after_init_cb) {
- EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(stream, _, _, _, _, _, _, _, _))
- .WillOnce(DoAll(RunCallback<2>(PIPELINE_OK),
- WithArg<7>(RunClosure<0>()))); // |disabled_cb|.
- } else {
- EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(stream, _, _, _, _, _, _, _, _))
- .WillOnce(DoAll(SaveArg<5>(&audio_time_cb_),
- RunCallback<2>(PIPELINE_OK)));
- }
- }
-
- // Sets up expectations on the callback and initializes the pipeline. Called
- // after tests have set expectations any filters they wish to use.
- void InitializePipeline(PipelineStatus start_status) {
- EXPECT_CALL(callbacks_, OnStart(start_status));
-
- if (start_status == PIPELINE_OK) {
- EXPECT_CALL(callbacks_, OnBufferingState(PipelineImpl::kHaveMetadata));
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f));
-
- if (audio_stream_) {
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
-
- // Startup sequence.
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(base::TimeDelta(), _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
- }
- EXPECT_CALL(callbacks_,
- OnBufferingState(PipelineImpl::kPrerollCompleted));
- }
-
- pipeline_->Start(
- mocks_->Create().Pass(), SetDecryptorReadyCB(),
- base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnBufferingState,
- base::Unretained(&callbacks_)),
- base::Closure());
- message_loop_.RunUntilIdle();
- }
-
- void CreateAudioStream() {
- audio_stream_ = CreateStream(DemuxerStream::AUDIO);
- }
-
- void CreateVideoStream() {
- video_stream_ = CreateStream(DemuxerStream::VIDEO);
- EXPECT_CALL(*video_stream_, video_decoder_config())
- .WillRepeatedly(ReturnRef(video_decoder_config_));
- }
-
- MockDemuxerStream* audio_stream() {
- return audio_stream_;
- }
-
- MockDemuxerStream* video_stream() {
- return video_stream_;
- }
-
- void ExpectSeek(const base::TimeDelta& seek_time) {
- // Every filter should receive a call to Seek().
- EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(_));
-
- if (audio_stream_) {
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(_));
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(_));
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
- }
-
- if (video_stream_) {
- EXPECT_CALL(*mocks_->video_renderer(), Pause(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Flush(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Preroll(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(_));
- EXPECT_CALL(*mocks_->video_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
- }
-
- EXPECT_CALL(callbacks_, OnBufferingState(PipelineImpl::kPrerollCompleted));
-
- // We expect a successful seek callback.
- EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
- }
-
- void DoSeek(const base::TimeDelta& seek_time) {
- pipeline_->Seek(seek_time,
- base::Bind(&CallbackHelper::OnSeek,
- base::Unretained(&callbacks_)));
-
- // We expect the time to be updated only after the seek has completed.
- EXPECT_NE(seek_time, pipeline_->GetMediaTime());
- message_loop_.RunUntilIdle();
- EXPECT_EQ(seek_time, pipeline_->GetMediaTime());
- }
-
- // Fixture members.
- StrictMock<CallbackHelper> callbacks_;
- MessageLoop message_loop_;
- scoped_refptr<PipelineImpl> pipeline_;
- scoped_ptr<media::MockFilterCollection> mocks_;
- scoped_refptr<StrictMock<MockDemuxerStream> > audio_stream_;
- scoped_refptr<StrictMock<MockDemuxerStream> > video_stream_;
- AudioRenderer::TimeCB audio_time_cb_;
- VideoDecoderConfig video_decoder_config_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(PipelineTest);
-};
-
-// Test that playback controls methods no-op when the pipeline hasn't been
-// started.
-TEST_F(PipelineTest, NotStarted) {
- const base::TimeDelta kZero;
-
- EXPECT_FALSE(pipeline_->IsRunning());
- EXPECT_FALSE(pipeline_->HasAudio());
- EXPECT_FALSE(pipeline_->HasVideo());
-
- // Setting should still work.
- EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
- pipeline_->SetPlaybackRate(-1.0f);
- EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
- pipeline_->SetPlaybackRate(1.0f);
- EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate());
-
- // Setting should still work.
- EXPECT_EQ(1.0f, pipeline_->GetVolume());
- pipeline_->SetVolume(-1.0f);
- EXPECT_EQ(1.0f, pipeline_->GetVolume());
- pipeline_->SetVolume(0.0f);
- EXPECT_EQ(0.0f, pipeline_->GetVolume());
-
- EXPECT_TRUE(kZero == pipeline_->GetMediaTime());
- EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());
- EXPECT_TRUE(kZero == pipeline_->GetMediaDuration());
-
- EXPECT_EQ(0, pipeline_->GetTotalBytes());
-
- // Should always get set to zero.
- gfx::Size size(1, 1);
- pipeline_->GetNaturalVideoSize(&size);
- EXPECT_EQ(0, size.width());
- EXPECT_EQ(0, size.height());
-}
-
-TEST_F(PipelineTest, NeverInitializes) {
- // Don't execute the callback passed into Initialize().
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _));
-
- // This test hangs during initialization by never calling
- // InitializationComplete(). StrictMock<> will ensure that the callback is
- // never executed.
- pipeline_->Start(
- mocks_->Create().Pass(), SetDecryptorReadyCB(),
- base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnBufferingState,
- base::Unretained(&callbacks_)),
- base::Closure());
- message_loop_.RunUntilIdle();
-
-
- // Because our callback will get executed when the test tears down, we'll
- // verify that nothing has been called, then set our expectation for the call
- // made during tear down.
- Mock::VerifyAndClear(&callbacks_);
- EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK));
-}
-
-TEST_F(PipelineTest, URLNotFound) {
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _))
- .WillOnce(RunCallback<1>(PIPELINE_ERROR_URL_NOT_FOUND));
- EXPECT_CALL(*mocks_->demuxer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND);
-}
-
-TEST_F(PipelineTest, NoStreams) {
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->demuxer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- InitializePipeline(PIPELINE_ERROR_COULD_NOT_RENDER);
-}
-
-TEST_F(PipelineTest, AudioStream) {
- CreateAudioStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_TRUE(pipeline_->HasAudio());
- EXPECT_FALSE(pipeline_->HasVideo());
-}
-
-TEST_F(PipelineTest, VideoStream) {
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_FALSE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-}
-
-TEST_F(PipelineTest, AudioVideoStream) {
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_TRUE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-}
-
-TEST_F(PipelineTest, Seek) {
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000));
- InitializeAudioRenderer(audio_stream(), false);
- InitializeVideoRenderer(video_stream());
-
- // Initialize then seek!
- InitializePipeline(PIPELINE_OK);
-
- // Every filter should receive a call to Seek().
- base::TimeDelta expected = base::TimeDelta::FromSeconds(2000);
- ExpectSeek(expected);
- DoSeek(expected);
-}
-
-TEST_F(PipelineTest, SetVolume) {
- CreateAudioStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
-
- // The audio renderer should receive a call to SetVolume().
- float expected = 0.5f;
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(expected));
-
- // Initialize then set volume!
- InitializePipeline(PIPELINE_OK);
- pipeline_->SetVolume(expected);
-}
-
-TEST_F(PipelineTest, Properties) {
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(video_stream());
-
- const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
- InitializeDemuxer(&streams, kDuration);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_EQ(kDuration.ToInternalValue(),
- pipeline_->GetMediaDuration().ToInternalValue());
- EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes());
- EXPECT_FALSE(pipeline_->DidLoadingProgress());
-}
-
-TEST_F(PipelineTest, GetBufferedTimeRanges) {
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(video_stream());
-
- const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
- InitializeDemuxer(&streams, kDuration);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
-
- EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());
-
- EXPECT_FALSE(pipeline_->DidLoadingProgress());
- pipeline_->AddBufferedByteRange(0, kTotalBytes / 8);
- EXPECT_TRUE(pipeline_->DidLoadingProgress());
- EXPECT_FALSE(pipeline_->DidLoadingProgress());
- EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
- EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
- EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
- pipeline_->AddBufferedTimeRange(base::TimeDelta(), kDuration / 8);
- EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
- EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
-
- base::TimeDelta kSeekTime = kDuration / 2;
- ExpectSeek(kSeekTime);
- DoSeek(kSeekTime);
-
- EXPECT_TRUE(pipeline_->DidLoadingProgress());
- EXPECT_FALSE(pipeline_->DidLoadingProgress());
- pipeline_->AddBufferedByteRange(kTotalBytes / 2,
- kTotalBytes / 2 + kTotalBytes / 8);
- EXPECT_TRUE(pipeline_->DidLoadingProgress());
- EXPECT_FALSE(pipeline_->DidLoadingProgress());
- EXPECT_EQ(2u, pipeline_->GetBufferedTimeRanges().size());
- EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
- EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
- EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(1));
- EXPECT_EQ(kDuration / 2 + kDuration / 8,
- pipeline_->GetBufferedTimeRanges().end(1));
-
- pipeline_->AddBufferedTimeRange(kDuration / 4, 3 * kDuration / 8);
- EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
- EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
- EXPECT_EQ(kDuration / 4, pipeline_->GetBufferedTimeRanges().start(1));
- EXPECT_EQ(3* kDuration / 8, pipeline_->GetBufferedTimeRanges().end(1));
- EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(2));
- EXPECT_EQ(kDuration / 2 + kDuration / 8,
- pipeline_->GetBufferedTimeRanges().end(2));
-}
-
-TEST_F(PipelineTest, DisableAudioRenderer) {
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_TRUE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-
- EXPECT_CALL(*mocks_->demuxer(), OnAudioRendererDisabled());
- pipeline_->OnAudioDisabled();
-
- // Verify that ended event is fired when video ends.
- EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
- pipeline_->OnVideoRendererEnded();
-}
-
-TEST_F(PipelineTest, DisableAudioRendererDuringInit) {
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), true);
- InitializeVideoRenderer(video_stream());
-
- EXPECT_CALL(*mocks_->demuxer(),
- OnAudioRendererDisabled());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_FALSE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-
- // Verify that ended event is fired when video ends.
- EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
- pipeline_->OnVideoRendererEnded();
-}
-
-TEST_F(PipelineTest, EndedCallback) {
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializeVideoRenderer(video_stream());
- InitializePipeline(PIPELINE_OK);
-
- // The ended callback shouldn't run until both renderers have ended.
- pipeline_->OnAudioRendererEnded();
- message_loop_.RunUntilIdle();
-
- EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
- pipeline_->OnVideoRendererEnded();
- message_loop_.RunUntilIdle();
-}
-
-// Static function & time variable used to simulate changes in wallclock time.
-static int64 g_static_clock_time;
-static base::Time StaticClockFunction() {
- return base::Time::FromInternalValue(g_static_clock_time);
-}
-
-TEST_F(PipelineTest, AudioStreamShorterThanVideo) {
- base::TimeDelta duration = base::TimeDelta::FromSeconds(10);
-
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
-
- // Replace the clock so we can simulate wallclock time advancing w/o using
- // Sleep().
- pipeline_->SetClockForTesting(new Clock(&StaticClockFunction));
-
- InitializeDemuxer(&streams, duration);
- InitializeAudioRenderer(audio_stream(), false);
- InitializeVideoRenderer(video_stream());
- InitializePipeline(PIPELINE_OK);
-
- EXPECT_EQ(0, pipeline_->GetMediaTime().ToInternalValue());
-
- float playback_rate = 1.0f;
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate));
- EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(playback_rate));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate));
- pipeline_->SetPlaybackRate(playback_rate);
- message_loop_.RunUntilIdle();
-
- InSequence s;
-
- // Verify that the clock doesn't advance since it hasn't been started by
- // a time update from the audio stream.
- int64 start_time = pipeline_->GetMediaTime().ToInternalValue();
- g_static_clock_time +=
- base::TimeDelta::FromMilliseconds(100).ToInternalValue();
- EXPECT_EQ(pipeline_->GetMediaTime().ToInternalValue(), start_time);
-
- // Signal end of audio stream.
- pipeline_->OnAudioRendererEnded();
- message_loop_.RunUntilIdle();
-
- // Verify that the clock advances.
- start_time = pipeline_->GetMediaTime().ToInternalValue();
- g_static_clock_time +=
- base::TimeDelta::FromMilliseconds(100).ToInternalValue();
- EXPECT_GT(pipeline_->GetMediaTime().ToInternalValue(), start_time);
-
- // Signal end of video stream and make sure OnEnded() callback occurs.
- EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
- pipeline_->OnVideoRendererEnded();
-}
-
-TEST_F(PipelineTest, ErrorDuringSeek) {
- CreateAudioStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializePipeline(PIPELINE_OK);
-
- float playback_rate = 1.0f;
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate));
- pipeline_->SetPlaybackRate(playback_rate);
- message_loop_.RunUntilIdle();
-
- base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
-
- // Preroll() isn't called as the demuxer errors out first.
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
- EXPECT_CALL(*mocks_->demuxer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek,
- base::Unretained(&callbacks_)));
- EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
- message_loop_.RunUntilIdle();
-}
-
-// Invoked function OnError. This asserts that the pipeline does not enqueue
-// non-teardown related tasks while tearing down.
-static void TestNoCallsAfterError(PipelineImpl* pipeline,
- MessageLoop* message_loop,
- PipelineStatus /* status */) {
- CHECK(pipeline);
- CHECK(message_loop);
-
- // When we get to this stage, the message loop should be empty.
- message_loop->AssertIdle();
-
- // Make calls on pipeline after error has occurred.
- pipeline->SetPlaybackRate(0.5f);
- pipeline->SetVolume(0.5f);
-
- // No additional tasks should be queued as a result of these calls.
- message_loop->AssertIdle();
-}
-
-TEST_F(PipelineTest, NoMessageDuringTearDownFromError) {
- CreateAudioStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializePipeline(PIPELINE_OK);
-
- // Trigger additional requests on the pipeline during tear down from error.
- base::Callback<void(PipelineStatus)> cb = base::Bind(
- &TestNoCallsAfterError, pipeline_, &message_loop_);
- ON_CALL(callbacks_, OnError(_))
- .WillByDefault(Invoke(&cb, &base::Callback<void(PipelineStatus)>::Run));
-
- base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
-
- // Seek() isn't called as the demuxer errors out first.
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
- EXPECT_CALL(*mocks_->demuxer(), Stop(_))
- .WillOnce(RunClosure<0>());
-
- pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek,
- base::Unretained(&callbacks_)));
- EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
- message_loop_.RunUntilIdle();
-}
-
-TEST_F(PipelineTest, StartTimeIsZero) {
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(video_stream());
-
- const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
- InitializeDemuxer(&streams, kDuration);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_FALSE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-
- EXPECT_EQ(base::TimeDelta(), pipeline_->GetMediaTime());
-}
-
-TEST_F(PipelineTest, StartTimeIsNonZero) {
- const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
-
- EXPECT_CALL(*mocks_->demuxer(), GetStartTime())
- .WillRepeatedly(Return(kStartTime));
-
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(video_stream());
-
- InitializeDemuxer(&streams, kDuration);
- InitializeVideoRenderer(video_stream());
-
- InitializePipeline(PIPELINE_OK);
- EXPECT_FALSE(pipeline_->HasAudio());
- EXPECT_TRUE(pipeline_->HasVideo());
-
- EXPECT_EQ(kStartTime, pipeline_->GetMediaTime());
-}
-
-static void RunTimeCB(const AudioRenderer::TimeCB& time_cb,
- int time_in_ms,
- int max_time_in_ms) {
- time_cb.Run(base::TimeDelta::FromMilliseconds(time_in_ms),
- base::TimeDelta::FromMilliseconds(max_time_in_ms));
-}
-
-TEST_F(PipelineTest, AudioTimeUpdateDuringSeek) {
- CreateAudioStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
-
- InitializeDemuxer(&streams);
- InitializeAudioRenderer(audio_stream(), false);
- InitializePipeline(PIPELINE_OK);
-
- float playback_rate = 1.0f;
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate));
- pipeline_->SetPlaybackRate(playback_rate);
- message_loop_.RunUntilIdle();
-
- // Provide an initial time update so that the pipeline transitions out of the
- // "waiting for time update" state.
- audio_time_cb_.Run(base::TimeDelta::FromMilliseconds(100),
- base::TimeDelta::FromMilliseconds(500));
-
- base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
-
- // Arrange to trigger a time update while the demuxer is in the middle of
- // seeking. This update should be ignored by the pipeline and the clock should
- // not get updated.
- base::Closure closure = base::Bind(&RunTimeCB, audio_time_cb_, 300, 700);
- EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
- .WillOnce(DoAll(InvokeWithoutArgs(&closure, &base::Closure::Run),
- RunCallback<1>(PIPELINE_OK)));
-
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(seek_time, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(_));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(_));
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(_));
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
-
- EXPECT_CALL(callbacks_, OnBufferingState(PipelineImpl::kPrerollCompleted));
- EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
- DoSeek(seek_time);
-
- EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);
-
- // Now that the seek is complete, verify that time updates advance the current
- // time.
- base::TimeDelta new_time = seek_time + base::TimeDelta::FromMilliseconds(100);
- audio_time_cb_.Run(new_time, new_time);
-
- EXPECT_EQ(pipeline_->GetMediaTime(), new_time);
-}
-
-class FlexibleCallbackRunner : public base::DelegateSimpleThread::Delegate {
- public:
- FlexibleCallbackRunner(base::TimeDelta delay, PipelineStatus status,
- const PipelineStatusCB& status_cb)
- : delay_(delay),
- status_(status),
- status_cb_(status_cb) {
- if (delay_ < base::TimeDelta()) {
- status_cb_.Run(status_);
- return;
- }
- }
- virtual void Run() {
- if (delay_ < base::TimeDelta()) return;
- base::PlatformThread::Sleep(delay_);
- status_cb_.Run(status_);
- }
-
- private:
- base::TimeDelta delay_;
- PipelineStatus status_;
- PipelineStatusCB status_cb_;
-};
-
-void TestPipelineStatusNotification(base::TimeDelta delay) {
- PipelineStatusNotification note;
- // Arbitrary error value we expect to fish out of the notification after the
- // callback is fired.
- const PipelineStatus expected_error = PIPELINE_ERROR_URL_NOT_FOUND;
- FlexibleCallbackRunner runner(delay, expected_error, note.Callback());
- base::DelegateSimpleThread thread(&runner, "FlexibleCallbackRunner");
- thread.Start();
- note.Wait();
- EXPECT_EQ(note.status(), expected_error);
- thread.Join();
-}
-
-// Test that in-line callback (same thread, no yield) works correctly.
-TEST(PipelineStatusNotificationTest, InlineCallback) {
- TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(-1));
-}
-
-// Test that different-thread, no-delay callback works correctly.
-TEST(PipelineStatusNotificationTest, ImmediateCallback) {
- TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(0));
-}
-
-// Test that different-thread, some-delay callback (the expected common case)
-// works correctly.
-TEST(PipelineStatusNotificationTest, DelayedCallback) {
- TestPipelineStatusNotification(base::TimeDelta::FromMilliseconds(20));
-}
-
-class PipelineTeardownTest : public PipelineTest {
- public:
- enum TeardownState {
- kInitDemuxer,
- kInitAudioRenderer,
- kInitVideoRenderer,
- kPausing,
- kFlushing,
- kSeeking,
- kPrerolling,
- kStarting,
- kPlaying,
- };
-
- enum StopOrError {
- kStop,
- kError,
- };
-
- PipelineTeardownTest() {}
- virtual ~PipelineTeardownTest() {}
-
- void RunTest(TeardownState state, StopOrError stop_or_error) {
- switch (state) {
- case kInitDemuxer:
- case kInitAudioRenderer:
- case kInitVideoRenderer:
- DoInitialize(state, stop_or_error);
- break;
-
- case kPausing:
- case kFlushing:
- case kSeeking:
- case kPrerolling:
- case kStarting:
- DoInitialize(state, stop_or_error);
- DoSeek(state, stop_or_error);
- break;
-
- case kPlaying:
- DoInitialize(state, stop_or_error);
- DoStopOrError(stop_or_error);
- break;
- }
- }
-
- private:
- // TODO(scherkus): We do radically different things whether teardown is
- // invoked via stop vs error. The teardown path should be the same,
- // see http://crbug.com/110228
- void DoInitialize(TeardownState state, StopOrError stop_or_error) {
- PipelineStatus expected_status =
- SetInitializeExpectations(state, stop_or_error);
-
- EXPECT_CALL(callbacks_, OnStart(expected_status));
- pipeline_->Start(
- mocks_->Create().Pass(), SetDecryptorReadyCB(),
- base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)),
- base::Bind(&CallbackHelper::OnBufferingState,
- base::Unretained(&callbacks_)),
- base::Closure());
- message_loop_.RunUntilIdle();
- }
-
- PipelineStatus SetInitializeExpectations(TeardownState state,
- StopOrError stop_or_error) {
- PipelineStatus status = PIPELINE_OK;
- base::Closure stop_cb = base::Bind(
- &CallbackHelper::OnStop, base::Unretained(&callbacks_));
-
- if (state == kInitDemuxer) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb),
- RunCallback<1>(PIPELINE_OK)));
- EXPECT_CALL(callbacks_, OnStop());
- } else {
- status = DEMUXER_ERROR_COULD_NOT_OPEN;
- EXPECT_CALL(*mocks_->demuxer(), Initialize(_, _))
- .WillOnce(RunCallback<1>(status));
- }
-
- EXPECT_CALL(*mocks_->demuxer(), Stop(_)).WillOnce(RunClosure<0>());
- return status;
- }
-
- CreateAudioStream();
- CreateVideoStream();
- MockDemuxerStreamVector streams;
- streams.push_back(audio_stream());
- streams.push_back(video_stream());
- InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000));
-
- if (state == kInitAudioRenderer) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb),
- RunCallback<2>(PIPELINE_OK)));
- EXPECT_CALL(callbacks_, OnStop());
- } else {
- status = PIPELINE_ERROR_INITIALIZATION_FAILED;
- EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _))
- .WillOnce(RunCallback<2>(status));
- }
-
- EXPECT_CALL(*mocks_->demuxer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- return status;
- }
-
- EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _))
- .WillOnce(RunCallback<2>(PIPELINE_OK));
-
- if (state == kInitVideoRenderer) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->video_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _, _))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb),
- RunCallback<2>(PIPELINE_OK)));
- EXPECT_CALL(callbacks_, OnStop());
- } else {
- status = PIPELINE_ERROR_INITIALIZATION_FAILED;
- EXPECT_CALL(*mocks_->video_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _, _))
- .WillOnce(RunCallback<2>(status));
- }
-
- EXPECT_CALL(*mocks_->demuxer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- return status;
- }
-
- EXPECT_CALL(*mocks_->video_renderer(),
- Initialize(_, _, _, _, _, _, _, _, _, _))
- .WillOnce(RunCallback<2>(PIPELINE_OK));
-
- EXPECT_CALL(callbacks_, OnBufferingState(PipelineImpl::kHaveMetadata));
-
- // If we get here it's a successful initialization.
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(base::TimeDelta(), _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->video_renderer(), Preroll(base::TimeDelta(), _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
-
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
-
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Play(_))
- .WillOnce(RunClosure<0>());
-
- if (status == PIPELINE_OK)
- EXPECT_CALL(callbacks_,
- OnBufferingState(PipelineImpl::kPrerollCompleted));
-
- return status;
- }
-
- void DoSeek(TeardownState state, StopOrError stop_or_error) {
- InSequence s;
- PipelineStatus status = SetSeekExpectations(state, stop_or_error);
-
- EXPECT_CALL(*mocks_->demuxer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(callbacks_, OnSeek(status));
-
- if (status == PIPELINE_OK) {
- EXPECT_CALL(callbacks_, OnStop());
- }
-
- pipeline_->Seek(base::TimeDelta::FromSeconds(10), base::Bind(
- &CallbackHelper::OnSeek, base::Unretained(&callbacks_)));
- message_loop_.RunUntilIdle();
- }
-
- PipelineStatus SetSeekExpectations(TeardownState state,
- StopOrError stop_or_error) {
- PipelineStatus status = PIPELINE_OK;
- base::Closure stop_cb = base::Bind(
- &CallbackHelper::OnStop, base::Unretained(&callbacks_));
-
- if (state == kPausing) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb), RunClosure<0>()));
- } else {
- status = PIPELINE_ERROR_READ;
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_))
- .WillOnce(DoAll(SetError(pipeline_, status), RunClosure<0>()));
- }
-
- return status;
- }
-
- EXPECT_CALL(*mocks_->audio_renderer(), Pause(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Pause(_)).WillOnce(RunClosure<0>());
-
- if (state == kFlushing) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb), RunClosure<0>()));
- } else {
- status = PIPELINE_ERROR_READ;
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_))
- .WillOnce(DoAll(SetError(pipeline_, status), RunClosure<0>()));
- }
-
- return status;
- }
-
- EXPECT_CALL(*mocks_->audio_renderer(), Flush(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Flush(_)).WillOnce(RunClosure<0>());
-
- if (state == kSeeking) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->demuxer(), Seek(_, _))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb),
- RunCallback<1>(PIPELINE_OK)));
- } else {
- status = PIPELINE_ERROR_READ;
- EXPECT_CALL(*mocks_->demuxer(), Seek(_, _))
- .WillOnce(RunCallback<1>(status));
- }
-
- return status;
- }
-
- EXPECT_CALL(*mocks_->demuxer(), Seek(_, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
-
- if (state == kPrerolling) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(_, _))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb),
- RunCallback<1>(PIPELINE_OK)));
- } else {
- status = PIPELINE_ERROR_READ;
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(_, _))
- .WillOnce(RunCallback<1>(status));
- }
-
- return status;
- }
-
- EXPECT_CALL(*mocks_->audio_renderer(), Preroll(_, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*mocks_->video_renderer(), Preroll(_, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
-
- // Playback rate and volume are updated prior to starting.
- EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f));
- EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
-
- if (state == kStarting) {
- if (stop_or_error == kStop) {
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(DoAll(Stop(pipeline_, stop_cb), RunClosure<0>()));
- } else {
- status = PIPELINE_ERROR_READ;
- EXPECT_CALL(*mocks_->audio_renderer(), Play(_))
- .WillOnce(DoAll(SetError(pipeline_, status), RunClosure<0>()));
- }
- return status;
- }
-
- NOTREACHED() << "State not supported: " << state;
- return status;
- }
-
- void DoStopOrError(StopOrError stop_or_error) {
- InSequence s;
-
- EXPECT_CALL(*mocks_->demuxer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->audio_renderer(), Stop(_)).WillOnce(RunClosure<0>());
- EXPECT_CALL(*mocks_->video_renderer(), Stop(_)).WillOnce(RunClosure<0>());
-
- if (stop_or_error == kStop) {
- EXPECT_CALL(callbacks_, OnStop());
- pipeline_->Stop(base::Bind(
- &CallbackHelper::OnStop, base::Unretained(&callbacks_)));
- } else {
- EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ));
- pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ);
- }
-
- message_loop_.RunUntilIdle();
- }
-
- DISALLOW_COPY_AND_ASSIGN(PipelineTeardownTest);
-};
-
-#define INSTANTIATE_TEARDOWN_TEST(stop_or_error, state) \
- TEST_F(PipelineTeardownTest, stop_or_error##_##state) { \
- RunTest(k##state, k##stop_or_error); \
- }
-
-INSTANTIATE_TEARDOWN_TEST(Stop, InitDemuxer);
-INSTANTIATE_TEARDOWN_TEST(Stop, InitAudioRenderer);
-INSTANTIATE_TEARDOWN_TEST(Stop, InitVideoRenderer);
-INSTANTIATE_TEARDOWN_TEST(Stop, Pausing);
-INSTANTIATE_TEARDOWN_TEST(Stop, Flushing);
-INSTANTIATE_TEARDOWN_TEST(Stop, Seeking);
-INSTANTIATE_TEARDOWN_TEST(Stop, Prerolling);
-INSTANTIATE_TEARDOWN_TEST(Stop, Starting);
-INSTANTIATE_TEARDOWN_TEST(Stop, Playing);
-
-INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer);
-INSTANTIATE_TEARDOWN_TEST(Error, InitAudioRenderer);
-INSTANTIATE_TEARDOWN_TEST(Error, InitVideoRenderer);
-INSTANTIATE_TEARDOWN_TEST(Error, Pausing);
-INSTANTIATE_TEARDOWN_TEST(Error, Flushing);
-INSTANTIATE_TEARDOWN_TEST(Error, Seeking);
-INSTANTIATE_TEARDOWN_TEST(Error, Prerolling);
-INSTANTIATE_TEARDOWN_TEST(Error, Starting);
-INSTANTIATE_TEARDOWN_TEST(Error, Playing);
-
-} // namespace media
diff --git a/src/media/base/pipeline_status.cc b/src/media/base/pipeline_status.cc
deleted file mode 100644
index 6c08383..0000000
--- a/src/media/base/pipeline_status.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/pipeline_status.h"
-
-#include "base/bind.h"
-#include "base/metrics/histogram.h"
-
-namespace media {
-
-static void ReportAndRun(const std::string& name,
- const PipelineStatusCB& cb,
- PipelineStatus status) {
- UMA_HISTOGRAM_ENUMERATION(name, status, PIPELINE_STATUS_MAX);
- cb.Run(status);
-}
-
-PipelineStatusCB CreateUMAReportingPipelineCB(const std::string& name,
- const PipelineStatusCB& cb) {
- return base::Bind(&ReportAndRun, name, cb);
-}
-
-} // namespace media
diff --git a/src/media/base/pipeline_status.h b/src/media/base/pipeline_status.h
deleted file mode 100644
index c208d01..0000000
--- a/src/media/base/pipeline_status.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_PIPELINE_STATUS_H_
-#define MEDIA_BASE_PIPELINE_STATUS_H_
-
-#include "base/callback.h"
-
-#include <string>
-
-namespace media {
-
-// Status states for pipeline. All codes except PIPELINE_OK indicate errors.
-// Logged to UMA, so never reuse a value, always add new/greater ones!
-// TODO(vrk/scherkus): Trim the unused status codes. (crbug.com/126070)
-enum PipelineStatus {
- PIPELINE_OK = 0,
- PIPELINE_ERROR_URL_NOT_FOUND = 1,
- PIPELINE_ERROR_NETWORK = 2,
- PIPELINE_ERROR_DECODE = 3,
- PIPELINE_ERROR_DECRYPT = 4,
- PIPELINE_ERROR_ABORT = 5,
- PIPELINE_ERROR_INITIALIZATION_FAILED = 6,
- PIPELINE_ERROR_COULD_NOT_RENDER = 8,
- PIPELINE_ERROR_READ = 9,
- PIPELINE_ERROR_OPERATION_PENDING = 10,
- PIPELINE_ERROR_INVALID_STATE = 11,
- // Demuxer related errors.
- DEMUXER_ERROR_COULD_NOT_OPEN = 12,
- DEMUXER_ERROR_COULD_NOT_PARSE = 13,
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS = 14,
- // Decoder related errors.
- DECODER_ERROR_NOT_SUPPORTED = 15,
- PIPELINE_STATUS_MAX, // Must be greater than all other values logged.
-};
-
-typedef base::Callback<void(PipelineStatus)> PipelineStatusCB;
-
-// Wrap & return a callback around |cb| which reports its argument to UMA under
-// the requested |name|.
-PipelineStatusCB CreateUMAReportingPipelineCB(const std::string& name,
- const PipelineStatusCB& cb);
-
-// TODO(scherkus): this should be moved alongside host interface definitions.
-struct PipelineStatistics {
- PipelineStatistics()
- : audio_bytes_decoded(0),
- video_bytes_decoded(0),
- video_frames_decoded(0),
- video_frames_dropped(0) {
- }
-
- uint32 audio_bytes_decoded; // Should be uint64?
- uint32 video_bytes_decoded; // Should be uint64?
- uint32 video_frames_decoded;
- uint32 video_frames_dropped;
-};
-
-// Used for updating pipeline statistics.
-typedef base::Callback<void(const PipelineStatistics&)> StatisticsCB;
-
-} // namespace media
-
-#endif // MEDIA_BASE_PIPELINE_STATUS_H_
diff --git a/src/media/base/ranges.cc b/src/media/base/ranges.cc
deleted file mode 100644
index b7b2b55..0000000
--- a/src/media/base/ranges.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/ranges.h"
-
-namespace media {
-
-template<>
-void Ranges<base::TimeDelta>::DCheckLT(const base::TimeDelta& lhs,
- const base::TimeDelta& rhs) const {
- DCHECK(lhs < rhs) << lhs.ToInternalValue() << " < " << rhs.ToInternalValue();
-}
-
-} // namespace media
diff --git a/src/media/base/ranges.h b/src/media/base/ranges.h
deleted file mode 100644
index 5a1df66..0000000
--- a/src/media/base/ranges.h
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_RANGES_H_
-#define MEDIA_BASE_RANGES_H_
-
-#include <algorithm>
-#include <ostream>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Ranges allows holding an ordered list of ranges of [start,end) intervals.
-// The canonical example use-case is holding the list of ranges of buffered
-// bytes or times in a <video> tag.
-template<class T> // Endpoint type; typically a base::TimeDelta or an int64.
-class Ranges {
- public:
- // Allow copy & assign.
-
- // Add (start,end) to this object, coallescing overlaps as appropriate.
- // Returns the number of stored ranges, post coallescing.
- size_t Add(T start, T end);
-
- // Return the number of disjoint ranges.
- size_t size() const;
-
- // Return the "i"'th range's start & end (0-based).
- T start(int i) const;
- T end(int i) const;
-
- // Clear all ranges.
- void clear();
-
- // Computes the intersection between this range and |other|.
- Ranges<T> IntersectionWith(const Ranges<T>& other) const;
-
- private:
- // Wrapper around DCHECK_LT allowing comparisons of operator<<'able T's.
- void DCheckLT(const T& lhs, const T& rhs) const;
-
- // Disjoint, in increasing order of start.
- std::vector<std::pair<T, T> > ranges_;
-};
-
-//////////////////////////////////////////////////////////////////////
-// EVERYTHING BELOW HERE IS IMPLEMENTATION DETAIL!!
-//////////////////////////////////////////////////////////////////////
-
-template<class T>
-size_t Ranges<T>::Add(T start, T end) {
- if (start == end) // Nothing to be done with empty ranges.
- return ranges_.size();
-
- DCheckLT(start, end);
- size_t i;
- // Walk along the array of ranges until |start| is no longer larger than the
- // current interval's end.
- for (i = 0; i < ranges_.size() && ranges_[i].second < start; ++i) {
- // Empty body
- }
-
- // Now we know |start| belongs in the i'th slot.
- // If i is the end of the range, append new range and done.
- if (i == ranges_.size()) {
- ranges_.push_back(std::make_pair(start, end));
- return ranges_.size();
- }
-
- // If |end| is less than i->first, then [start,end) is a new (non-overlapping)
- // i'th entry pushing everyone else back, and done.
- if (end < ranges_[i].first) {
- ranges_.insert(ranges_.begin() + i, std::make_pair(start, end));
- return ranges_.size();
- }
-
- // Easy cases done. Getting here means there is overlap between [start,end)
- // and the existing ranges.
-
- // Now: start <= i->second && i->first <= end
- if (start < ranges_[i].first)
- ranges_[i].first = start;
- if (ranges_[i].second < end)
- ranges_[i].second = end;
-
- // Now: [start,end) is contained in the i'th range, and we'd be done, except
- // for the fact that the newly-extended i'th range might now overlap
- // subsequent ranges. Merge until discontinuities appear. Note that there's
- // no need to test/merge previous ranges, since needing that would mean the
- // original loop went too far.
- while ((i + 1) < ranges_.size() &&
- ranges_[i + 1].first <= ranges_[i].second) {
- ranges_[i].second = std::max(ranges_[i].second, ranges_[i + 1].second);
- ranges_.erase(ranges_.begin() + i + 1);
- }
-
- return ranges_.size();
-}
-
-template<>
-void Ranges<base::TimeDelta>::DCheckLT(const base::TimeDelta& lhs,
- const base::TimeDelta& rhs) const;
-
-template<class T>
-void Ranges<T>::DCheckLT(const T& lhs, const T& rhs) const {
- DCHECK_LT(lhs, rhs);
-}
-
-template<class T>
-size_t Ranges<T>::size() const {
- return ranges_.size();
-}
-
-template<class T>
-T Ranges<T>::start(int i) const {
- return ranges_[static_cast<size_t>(i)].first;
-}
-
-template<class T>
-T Ranges<T>::end(int i) const {
- return ranges_[static_cast<size_t>(i)].second;
-}
-
-template<class T>
-void Ranges<T>::clear() {
- ranges_.clear();
-}
-
-template<class T>
-Ranges<T> Ranges<T>::IntersectionWith(const Ranges<T>& other) const {
- Ranges<T> result;
-
- size_t i = 0;
- size_t j = 0;
-
- while (i < size() && j < other.size()) {
- T max_start = std::max(start(i), other.start(j));
- T min_end = std::min(end(i), other.end(j));
-
- // Add an intersection range to the result if the ranges overlap.
- if (max_start < min_end)
- result.Add(max_start, min_end);
-
- if (end(i) < other.end(j))
- ++i;
- else
- ++j;
- }
-
- return result;
-}
-
-} // namespace media
-
-#endif // MEDIA_BASE_RANGES_H_
diff --git a/src/media/base/ranges_unittest.cc b/src/media/base/ranges_unittest.cc
deleted file mode 100644
index 967d138..0000000
--- a/src/media/base/ranges_unittest.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <sstream>
-
-#include "media/base/ranges.h"
-
-#include "base/string_piece.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Human-readable output operator, for debugging/testability.
-template<class T>
-std::ostream& operator<<(std::ostream& os, const Ranges<T>& r) {
- os << "{ ";
- for(size_t i = 0; i < r.size(); ++i)
- os << "[" << r.start(i) << "," << r.end(i) << ") ";
- os << "}";
- return os;
-}
-
-// Helper method for asserting stringified form of |r| matches expectation.
-template<class T>
-static void ExpectRanges(const Ranges<T>& r,
- const base::StringPiece& expected_string) {
- std::stringstream ss;
- ss << r;
- ASSERT_EQ(ss.str(), expected_string);
-}
-
-#define ASSERT_RANGES(ranges, expectation) \
- ASSERT_NO_FATAL_FAILURE(ExpectRanges(ranges, expectation));
-
-TEST(RangesTest, SimpleTests) {
- Ranges<int> r;
- ASSERT_EQ(r.size(), 0u) << r;
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.size(), 1u) << r;
- ASSERT_RANGES(r, "{ [0,1) }");
- ASSERT_EQ(r.Add(2, 3), 2u) << r;
- ASSERT_RANGES(r, "{ [0,1) [2,3) }");
- ASSERT_EQ(r.Add(1, 2), 1u) << r;
- ASSERT_RANGES(r, "{ [0,3) }");
- ASSERT_EQ(r.Add(1, 4), 1u) << r;
- ASSERT_RANGES(r, "{ [0,4) }");
- ASSERT_EQ(r.Add(7, 9), 2u) << r;
- ASSERT_EQ(r.Add(5, 6), 3u) << r;
- ASSERT_RANGES(r, "{ [0,4) [5,6) [7,9) }");
- ASSERT_EQ(r.Add(6, 7), 2u) << r;
- ASSERT_RANGES(r, "{ [0,4) [5,9) }");
-}
-
-TEST(RangesTest, ExtendRange) {
- Ranges<double> r;
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(0.5, 1.5), 1u) << r;
- ASSERT_RANGES(r, "{ [0,1.5) }");
-
- r.clear();
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(-0.5, 0.5), 1u) << r;
- ASSERT_RANGES(r, "{ [-0.5,1) }");
-
- r.clear();
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(2, 3), 2u) << r;
- ASSERT_EQ(r.Add(4, 5), 3u) << r;
- ASSERT_EQ(r.Add(0.5, 1.5), 3u) << r;
- ASSERT_RANGES(r, "{ [0,1.5) [2,3) [4,5) }");
-
- r.clear();
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(2, 3), 2u) << r;
- ASSERT_EQ(r.Add(4, 5), 3u) << r;
- ASSERT_EQ(r.Add(1.5, 2.5), 3u) << r;
- ASSERT_RANGES(r, "{ [0,1) [1.5,3) [4,5) }");
-}
-
-TEST(RangesTest, CoalesceRanges) {
- Ranges<double> r;
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(2, 3), 2u) << r;
- ASSERT_EQ(r.Add(4, 5), 3u) << r;
- ASSERT_EQ(r.Add(0.5, 2.5), 2u) << r;
- ASSERT_RANGES(r, "{ [0,3) [4,5) }");
-
- r.clear();
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(2, 3), 2u) << r;
- ASSERT_EQ(r.Add(4, 5), 3u) << r;
- ASSERT_EQ(r.Add(0.5, 4.5), 1u) << r;
- ASSERT_RANGES(r, "{ [0,5) }");
-
- r.clear();
- ASSERT_EQ(r.Add(0, 1), 1u) << r;
- ASSERT_EQ(r.Add(1, 2), 1u) << r;
- ASSERT_RANGES(r, "{ [0,2) }");
-}
-
-TEST(RangesTest, IntersectionWith) {
- Ranges<int> a;
- Ranges<int> b;
-
- ASSERT_EQ(a.Add(0, 1), 1u) << a;
- ASSERT_EQ(a.Add(4, 7), 2u) << a;
- ASSERT_EQ(a.Add(10, 12), 3u) << a;
-
- // Test intersections with an empty range.
- ASSERT_RANGES(a, "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b, "{ }");
- ASSERT_RANGES(a.IntersectionWith(b), "{ }");
- ASSERT_RANGES(b.IntersectionWith(a), "{ }");
-
- // Test intersections with a completely overlaping range.
- ASSERT_EQ(b.Add(-1, 13), 1u) << b;
- ASSERT_RANGES(a, "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b, "{ [-1,13) }");
- ASSERT_RANGES(a.IntersectionWith(b), "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b.IntersectionWith(a), "{ [0,1) [4,7) [10,12) }");
-
- // Test intersections with a disjoint ranges.
- b.clear();
- ASSERT_EQ(b.Add(1, 4), 1u) << b;
- ASSERT_EQ(b.Add(8, 9), 2u) << b;
- ASSERT_RANGES(a, "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b, "{ [1,4) [8,9) }");
- ASSERT_RANGES(a.IntersectionWith(b), "{ }");
- ASSERT_RANGES(b.IntersectionWith(a), "{ }");
-
- // Test intersections with partially overlapping ranges.
- b.clear();
- ASSERT_EQ(b.Add(0, 3), 1u) << b;
- ASSERT_EQ(b.Add(5, 11), 2u) << b;
- ASSERT_RANGES(a, "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b, "{ [0,3) [5,11) }");
- ASSERT_RANGES(a.IntersectionWith(b), "{ [0,1) [5,7) [10,11) }");
- ASSERT_RANGES(b.IntersectionWith(a), "{ [0,1) [5,7) [10,11) }");
-
- // Test intersection with a range that starts at the beginning of the
- // first range and ends at the end of the last range.
- b.clear();
- ASSERT_EQ(b.Add(0, 12), 1u) << b;
- ASSERT_RANGES(a, "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b, "{ [0,12) }");
- ASSERT_RANGES(a.IntersectionWith(b), "{ [0,1) [4,7) [10,12) }");
- ASSERT_RANGES(b.IntersectionWith(a), "{ [0,1) [4,7) [10,12) }");
-}
-
-} // namespace media
diff --git a/src/media/base/run_all_unittests.cc b/src/media/base/run_all_unittests.cc
deleted file mode 100644
index 1942124..0000000
--- a/src/media/base/run_all_unittests.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/test/main_hook.h"
-#include "base/test/test_suite.h"
-#include "media/base/media.h"
-
-class TestSuiteNoAtExit : public base::TestSuite {
- public:
- TestSuiteNoAtExit(int argc, char** argv) : TestSuite(argc, argv) {}
- virtual ~TestSuiteNoAtExit() {}
- protected:
- virtual void Initialize();
-};
-
-void TestSuiteNoAtExit::Initialize() {
- // Run TestSuite::Initialize first so that logging is initialized.
- base::TestSuite::Initialize();
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- // Run this here instead of main() to ensure an AtExitManager is already
- // present.
- media::InitializeMediaLibraryForTesting();
-#endif
-}
-
-int main(int argc, char** argv) {
- MainHook hook(main, argc, argv);
- return TestSuiteNoAtExit(argc, argv).Run();
-}
diff --git a/src/media/base/sample_format.cc b/src/media/base/sample_format.cc
deleted file mode 100644
index f827d69..0000000
--- a/src/media/base/sample_format.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2013 The Chromium 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 "media/base/sample_format.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-int SampleFormatToBytesPerChannel(SampleFormat sample_format) {
- switch (sample_format) {
- case kUnknownSampleFormat:
- return 0;
- case kSampleFormatU8:
- return 1;
- case kSampleFormatS16:
- case kSampleFormatPlanarS16:
- return 2;
- case kSampleFormatS32:
- case kSampleFormatF32:
- case kSampleFormatPlanarF32:
- return 4;
- }
-
- NOTREACHED() << "Invalid sample format provided: " << sample_format;
- return 0;
-}
-
-const char* SampleFormatToString(SampleFormat sample_format) {
- switch(sample_format) {
- case kUnknownSampleFormat:
- return "Unknown sample format";
- case kSampleFormatU8:
- return "Unsigned 8-bit with bias of 128";
- case kSampleFormatS16:
- return "Signed 16-bit";
- case kSampleFormatS32:
- return "Signed 32-bit";
- case kSampleFormatF32:
- return "Float 32-bit";
- case kSampleFormatPlanarS16:
- return "Signed 16-bit planar";
- case kSampleFormatPlanarF32:
- return "Float 32-bit planar";
- }
- NOTREACHED() << "Invalid sample format provided: " << sample_format;
- return "";
-}
-
-} // namespace media
-
diff --git a/src/media/base/sample_format.h b/src/media/base/sample_format.h
deleted file mode 100644
index 806bc8e..0000000
--- a/src/media/base/sample_format.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SAMPLE_FORMAT_H
-#define MEDIA_BASE_SAMPLE_FORMAT_H
-
-#include "media/base/media_export.h"
-
-namespace media {
-
-enum SampleFormat {
- // These values are histogrammed over time; do not change their ordinal
- // values. When deleting a sample format replace it with a dummy value; when
- // adding a sample format, do so at the bottom before kSampleFormatMax, and
- // update the value of kSampleFormatMax.
- kUnknownSampleFormat = 0,
- kSampleFormatU8, // Unsigned 8-bit w/ bias of 128.
- kSampleFormatS16, // Signed 16-bit.
- kSampleFormatS32, // Signed 32-bit.
- kSampleFormatF32, // Float 32-bit.
- kSampleFormatPlanarS16, // Signed 16-bit planar.
- kSampleFormatPlanarF32, // Float 32-bit planar.
-
- // Must always be equal to largest value ever logged.
- kSampleFormatMax = kSampleFormatPlanarF32,
-};
-
-// Returns the number of bytes used per channel for the specified
-// |sample_format|.
-MEDIA_EXPORT int SampleFormatToBytesPerChannel(SampleFormat sample_format);
-
-// Returns the name of the sample format as a string
-MEDIA_EXPORT const char* SampleFormatToString(SampleFormat sample_format);
-
-} // namespace media
-
-#endif // MEDIA_BASE_SAMPLE_FORMAT_H
-
diff --git a/src/media/base/seekable_buffer.cc b/src/media/base/seekable_buffer.cc
deleted file mode 100644
index 48c0858..0000000
--- a/src/media/base/seekable_buffer.cc
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/seekable_buffer.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "media/base/data_buffer.h"
-
-namespace media {
-
-SeekableBuffer::SeekableBuffer(int backward_capacity,
- int forward_capacity)
- : current_buffer_offset_(0),
- backward_capacity_(backward_capacity),
- backward_bytes_(0),
- forward_capacity_(forward_capacity),
- forward_bytes_(0),
- current_time_(kNoTimestamp()) {
- current_buffer_ = buffers_.begin();
-}
-
-SeekableBuffer::~SeekableBuffer() {
-}
-
-void SeekableBuffer::Clear() {
- buffers_.clear();
- current_buffer_ = buffers_.begin();
- current_buffer_offset_ = 0;
- backward_bytes_ = 0;
- forward_bytes_ = 0;
- current_time_ = kNoTimestamp();
-}
-
-int SeekableBuffer::Read(uint8* data, int size) {
- DCHECK(data);
- return InternalRead(data, size, true, 0);
-}
-
-int SeekableBuffer::Peek(uint8* data, int size, int forward_offset) {
- DCHECK(data);
- return InternalRead(data, size, false, forward_offset);
-}
-
-bool SeekableBuffer::GetCurrentChunk(const uint8** data, int* size) const {
- BufferQueue::iterator current_buffer = current_buffer_;
- int current_buffer_offset = current_buffer_offset_;
- // Advance position if we are in the end of the current buffer.
- while (current_buffer != buffers_.end() &&
- current_buffer_offset >= (*current_buffer)->GetDataSize()) {
- ++current_buffer;
- current_buffer_offset = 0;
- }
- if (current_buffer == buffers_.end())
- return false;
- *data = (*current_buffer)->GetData() + current_buffer_offset;
- *size = (*current_buffer)->GetDataSize() - current_buffer_offset;
- return true;
-}
-
-bool SeekableBuffer::Append(Buffer* buffer_in) {
- if (buffers_.empty() && buffer_in->GetTimestamp() != kNoTimestamp()) {
- current_time_ = buffer_in->GetTimestamp();
- }
-
- // Since the forward capacity is only used to check the criteria for buffer
- // full, we always append data to the buffer.
- buffers_.push_back(scoped_refptr<Buffer>(buffer_in));
-
- // After we have written the first buffer, update |current_buffer_| to point
- // to it.
- if (current_buffer_ == buffers_.end()) {
- DCHECK_EQ(0, forward_bytes_);
- current_buffer_ = buffers_.begin();
- }
-
- // Update the |forward_bytes_| counter since we have more bytes.
- forward_bytes_ += buffer_in->GetDataSize();
-
- // Advise the user to stop append if the amount of forward bytes exceeds
- // the forward capacity. A false return value means the user should stop
- // appending more data to this buffer.
- if (forward_bytes_ >= forward_capacity_)
- return false;
- return true;
-}
-
-bool SeekableBuffer::Append(const uint8* data, int size) {
- if (size > 0) {
- DataBuffer* data_buffer = new DataBuffer(data, size);
- return Append(data_buffer);
- } else {
- // Return true if we have forward capacity.
- return forward_bytes_ < forward_capacity_;
- }
-}
-
-bool SeekableBuffer::Seek(int32 offset) {
- if (offset > 0)
- return SeekForward(offset);
- else if (offset < 0)
- return SeekBackward(-offset);
- return true;
-}
-
-bool SeekableBuffer::SeekForward(int size) {
- // Perform seeking forward only if we have enough bytes in the queue.
- if (size > forward_bytes_)
- return false;
-
- // Do a read of |size| bytes.
- int taken = InternalRead(NULL, size, true, 0);
- DCHECK_EQ(taken, size);
- return true;
-}
-
-bool SeekableBuffer::SeekBackward(int size) {
- if (size > backward_bytes_)
- return false;
- // Record the number of bytes taken.
- int taken = 0;
- // Loop until we taken enough bytes and rewind by the desired |size|.
- while (taken < size) {
- // |current_buffer_| can never be invalid when we are in this loop. It can
- // only be invalid before any data is appended. The invalid case should be
- // handled by checks before we enter this loop.
- DCHECK(current_buffer_ != buffers_.end());
-
- // We try to consume at most |size| bytes in the backward direction. We also
- // have to account for the offset we are in the current buffer, so take the
- // minimum between the two to determine the amount of bytes to take from the
- // current buffer.
- int consumed = std::min(size - taken, current_buffer_offset_);
-
- // Decreases the offset in the current buffer since we are rewinding.
- current_buffer_offset_ -= consumed;
-
- // Increase the amount of bytes taken in the backward direction. This
- // determines when to stop the loop.
- taken += consumed;
-
- // Forward bytes increases and backward bytes decreases by the amount
- // consumed in the current buffer.
- forward_bytes_ += consumed;
- backward_bytes_ -= consumed;
- DCHECK_GE(backward_bytes_, 0);
-
- // The current buffer pointed by current iterator has been consumed. Move
- // the iterator backward so it points to the previous buffer.
- if (current_buffer_offset_ == 0) {
- if (current_buffer_ == buffers_.begin())
- break;
- // Move the iterator backward.
- --current_buffer_;
- // Set the offset into the current buffer to be the buffer size as we
- // are preparing for rewind for next iteration.
- current_buffer_offset_ = (*current_buffer_)->GetDataSize();
- }
- }
-
- UpdateCurrentTime(current_buffer_, current_buffer_offset_);
-
- DCHECK_EQ(taken, size);
- return true;
-}
-
-void SeekableBuffer::EvictBackwardBuffers() {
- // Advances the iterator until we hit the current pointer.
- while (backward_bytes_ > backward_capacity_) {
- BufferQueue::iterator i = buffers_.begin();
- if (i == current_buffer_)
- break;
- scoped_refptr<Buffer> buffer = *i;
- backward_bytes_ -= buffer->GetDataSize();
- DCHECK_GE(backward_bytes_, 0);
-
- buffers_.erase(i);
- }
-}
-
-int SeekableBuffer::InternalRead(uint8* data, int size,
- bool advance_position,
- int forward_offset) {
- // Counts how many bytes are actually read from the buffer queue.
- int taken = 0;
-
- BufferQueue::iterator current_buffer = current_buffer_;
- int current_buffer_offset = current_buffer_offset_;
-
- int bytes_to_skip = forward_offset;
- while (taken < size) {
- // |current_buffer| is valid since the first time this buffer is appended
- // with data.
- if (current_buffer == buffers_.end())
- break;
-
- scoped_refptr<Buffer> buffer = *current_buffer;
-
- int remaining_bytes_in_buffer =
- buffer->GetDataSize() - current_buffer_offset;
-
- if (bytes_to_skip == 0) {
- // Find the right amount to copy from the current buffer referenced by
- // |buffer|. We shall copy no more than |size| bytes in total and each
- // single step copied no more than the current buffer size.
- int copied = std::min(size - taken, remaining_bytes_in_buffer);
-
- // |data| is NULL if we are seeking forward, so there's no need to copy.
- if (data)
- memcpy(data + taken, buffer->GetData() + current_buffer_offset, copied);
-
- // Increase total number of bytes copied, which regulates when to end this
- // loop.
- taken += copied;
-
- // We have read |copied| bytes from the current buffer. Advances the
- // offset.
- current_buffer_offset += copied;
- } else {
- int skipped = std::min(remaining_bytes_in_buffer, bytes_to_skip);
- current_buffer_offset += skipped;
- bytes_to_skip -= skipped;
- }
-
- // The buffer has been consumed.
- if (current_buffer_offset == buffer->GetDataSize()) {
- if (advance_position) {
- // Next buffer may not have timestamp, so we need to update current
- // timestamp before switching to the next buffer.
- UpdateCurrentTime(current_buffer, current_buffer_offset);
- }
-
- BufferQueue::iterator next = current_buffer;
- ++next;
- // If we are at the last buffer, don't advance.
- if (next == buffers_.end())
- break;
-
- // Advances the iterator.
- current_buffer = next;
- current_buffer_offset = 0;
- }
- }
-
- if (advance_position) {
- // We have less forward bytes and more backward bytes. Updates these
- // counters by |taken|.
- forward_bytes_ -= taken;
- backward_bytes_ += taken;
- DCHECK_GE(forward_bytes_, 0);
- DCHECK(current_buffer_ != buffers_.end() || forward_bytes_ == 0);
-
- current_buffer_ = current_buffer;
- current_buffer_offset_ = current_buffer_offset;
-
- UpdateCurrentTime(current_buffer_, current_buffer_offset_);
- EvictBackwardBuffers();
- }
-
- return taken;
-}
-
-void SeekableBuffer::UpdateCurrentTime(BufferQueue::iterator buffer,
- int offset) {
- // Garbage values are unavoidable, so this check will remain.
- if (buffer != buffers_.end() && (*buffer)->GetTimestamp() != kNoTimestamp()) {
- int64 time_offset = ((*buffer)->GetDuration().InMicroseconds() *
- offset) / (*buffer)->GetDataSize();
-
- current_time_ = (*buffer)->GetTimestamp() +
- base::TimeDelta::FromMicroseconds(time_offset);
- }
-}
-
-} // namespace media
diff --git a/src/media/base/seekable_buffer.h b/src/media/base/seekable_buffer.h
deleted file mode 100644
index 0a3ff72..0000000
--- a/src/media/base/seekable_buffer.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// SeekableBuffer to support backward and forward seeking in a buffer for
-// reading a media data source.
-//
-// In order to support backward and forward seeking, this class buffers data in
-// both backward and forward directions, the current read position can be reset
-// to anywhere in the buffered data.
-//
-// The amount of data buffered is regulated by two variables at construction,
-// |backward_capacity| and |forward_capacity|.
-//
-// In the case of reading and seeking forward, the current read position
-// advances and there will be more data in the backward direction. If backward
-// bytes exceeds |backward_capacity|, the exceeding bytes are evicted and thus
-// backward_bytes() will always be less than or equal to |backward_capacity|.
-// The eviction will be caused by Read() and Seek() in the forward direction and
-// is done internally when the mentioned criteria is fulfilled.
-//
-// In the case of appending data to the buffer, there is an advisory limit of
-// how many bytes can be kept in the forward direction, regulated by
-// |forward_capacity|. The append operation (by calling Append()) that caused
-// forward bytes to exceed |forward_capacity| will have a return value that
-// advises a halt of append operation, further append operations are allowed but
-// are not advised. Since this class is used as a backend buffer for caching
-// media files downloaded from network we cannot afford losing data, we can
-// only advise a halt of further writing to this buffer.
-// This class is not inherently thread-safe. Concurrent access must be
-// externally serialized.
-
-#ifndef MEDIA_BASE_SEEKABLE_BUFFER_H_
-#define MEDIA_BASE_SEEKABLE_BUFFER_H_
-
-#include <list>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-class MEDIA_EXPORT SeekableBuffer {
- public:
- // Constructs an instance with |forward_capacity| and |backward_capacity|.
- // The values are in bytes.
- SeekableBuffer(int backward_capacity, int forward_capacity);
-
- ~SeekableBuffer();
-
- // Clears the buffer queue.
- void Clear();
-
- // Reads a maximum of |size| bytes into |data| from the current read
- // position. Returns the number of bytes read.
- // The current read position will advance by the amount of bytes read. If
- // reading caused backward_bytes() to exceed backward_capacity(), an eviction
- // of the backward buffer will be done internally.
- int Read(uint8* data, int size);
-
- // Copies up to |size| bytes from current position to |data|. Returns
- // number of bytes copied. Doesn't advance current position. Optionally
- // starts at a |forward_offset| from current position.
- int Peek(uint8* data, int size) { return Peek(data, size, 0); }
- int Peek(uint8* data, int size, int forward_offset);
-
- // Returns pointer to the current chunk of data that is being consumed.
- // If there is no data left in the buffer false is returned, otherwise
- // true is returned and |data| and |size| are updated. The returned
- // |data| value becomes invalid when Read(), Append() or Seek()
- // are called.
- bool GetCurrentChunk(const uint8** data, int* size) const;
-
- // Appends |buffer_in| to this buffer. Returns false if forward_bytes() is
- // greater than or equals to forward_capacity(), true otherwise. The data
- // is added to the buffer in any case.
- bool Append(Buffer* buffer_in);
-
- // Appends |size| bytes of |data| to the buffer. Result is the same
- // as for Append(Buffer*).
- bool Append(const uint8* data, int size);
-
- // Moves the read position by |offset| bytes. If |offset| is positive, the
- // current read position is moved forward. If negative, the current read
- // position is moved backward. A zero |offset| value will keep the current
- // read position stationary.
- // If |offset| exceeds bytes buffered in either direction, reported by
- // forward_bytes() when seeking forward and backward_bytes() when seeking
- // backward, the seek operation will fail and return value will be false.
- // If the seek operation fails, the current read position will not be updated.
- // If a forward seeking caused backward_bytes() to exceed backward_capacity(),
- // this method call will cause an eviction of the backward buffer.
- bool Seek(int32 offset);
-
- // Returns the number of bytes buffered beyond the current read position.
- int forward_bytes() const { return forward_bytes_; }
-
- // Returns the number of bytes buffered that precedes the current read
- // position.
- int backward_bytes() const { return backward_bytes_; }
-
- // Sets the forward_capacity to |new_forward_capacity| bytes.
- void set_forward_capacity(int new_forward_capacity) {
- forward_capacity_ = new_forward_capacity;
- }
-
- // Sets the backward_capacity to |new_backward_capacity| bytes.
- void set_backward_capacity(int new_backward_capacity) {
- backward_capacity_ = new_backward_capacity;
- }
-
- // Returns the maximum number of bytes that should be kept in the forward
- // direction.
- int forward_capacity() const { return forward_capacity_; }
-
- // Returns the maximum number of bytes that should be kept in the backward
- // direction.
- int backward_capacity() const { return backward_capacity_; }
-
- // Returns the current timestamp, taking into account current offset. The
- // value calculated based on the timestamp of the current buffer. If
- // timestamp for the current buffer is set to 0 or the data was added with
- // Append(const uint*, int), then returns value that corresponds to the
- // last position in a buffer that had timestamp set.
- // kNoTimestamp() is returned if no buffers we read from had timestamp set.
- base::TimeDelta current_time() const { return current_time_; }
-
- private:
- // Definition of the buffer queue.
- typedef std::list<scoped_refptr<Buffer> > BufferQueue;
-
- // A helper method to evict buffers in the backward direction until backward
- // bytes is within the backward capacity.
- void EvictBackwardBuffers();
-
- // An internal method shared by Read() and SeekForward() that actually does
- // reading. It reads a maximum of |size| bytes into |data|. Returns the number
- // of bytes read. The current read position will be moved forward by the
- // number of bytes read. If |data| is NULL, only the current read position
- // will advance but no data will be copied.
- int InternalRead(
- uint8* data, int size, bool advance_position, int forward_offset);
-
- // A helper method that moves the current read position forward by |size|
- // bytes.
- // If the return value is true, the operation completed successfully.
- // If the return value is false, |size| is greater than forward_bytes() and
- // the seek operation failed. The current read position is not updated.
- bool SeekForward(int size);
-
- // A helper method that moves the current read position backward by |size|
- // bytes.
- // If the return value is true, the operation completed successfully.
- // If the return value is false, |size| is greater than backward_bytes() and
- // the seek operation failed. The current read position is not updated.
- bool SeekBackward(int size);
-
- // Updates |current_time_| with the time that corresponds to the
- // specified position in the buffer.
- void UpdateCurrentTime(BufferQueue::iterator buffer, int offset);
-
- BufferQueue::iterator current_buffer_;
- BufferQueue buffers_;
- int current_buffer_offset_;
-
- int backward_capacity_;
- int backward_bytes_;
-
- int forward_capacity_;
- int forward_bytes_;
-
- // Keeps track of the most recent time we've seen in case the |buffers_| is
- // empty when our owner asks what time it is.
- base::TimeDelta current_time_;
-
- DISALLOW_COPY_AND_ASSIGN(SeekableBuffer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SEEKABLE_BUFFER_H_
diff --git a/src/media/base/seekable_buffer_unittest.cc b/src/media/base/seekable_buffer_unittest.cc
deleted file mode 100644
index 38d36a6..0000000
--- a/src/media/base/seekable_buffer_unittest.cc
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/time.h"
-#include "media/base/data_buffer.h"
-#include "media/base/seekable_buffer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class SeekableBufferTest : public testing::Test {
- public:
- SeekableBufferTest() : buffer_(kBufferSize, kBufferSize) {
- }
-
- protected:
- static const int kDataSize = 409600;
- static const int kBufferSize = 4096;
- static const int kWriteSize = 512;
-
- virtual void SetUp() {
- // Setup seed.
- int seed = static_cast<int32>(base::Time::Now().ToInternalValue());
- srand(seed);
- VLOG(1) << "Random seed: " << seed;
-
- // Creates a test data.
- for (int i = 0; i < kDataSize; i++)
- data_[i] = static_cast<char>(rand());
- }
-
- int GetRandomInt(int maximum) {
- return rand() % maximum + 1;
- }
-
- SeekableBuffer buffer_;
- uint8 data_[kDataSize];
- uint8 write_buffer_[kDataSize];
-};
-
-TEST_F(SeekableBufferTest, RandomReadWrite) {
- int write_position = 0;
- int read_position = 0;
- while (read_position < kDataSize) {
- // Write a random amount of data.
- int write_size = GetRandomInt(kBufferSize);
- write_size = std::min(write_size, kDataSize - write_position);
- bool should_append = buffer_.Append(data_ + write_position, write_size);
- write_position += write_size;
- EXPECT_GE(write_position, read_position);
- EXPECT_EQ(write_position - read_position, buffer_.forward_bytes());
- EXPECT_EQ(should_append, buffer_.forward_bytes() < kBufferSize)
- << "Incorrect buffer full reported";
-
- // Peek a random amount of data.
- int copy_size = GetRandomInt(kBufferSize);
- int bytes_copied = buffer_.Peek(write_buffer_, copy_size);
- EXPECT_GE(copy_size, bytes_copied);
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, bytes_copied));
-
- // Read a random amount of data.
- int read_size = GetRandomInt(kBufferSize);
- int bytes_read = buffer_.Read(write_buffer_, read_size);
- EXPECT_GE(read_size, bytes_read);
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, bytes_read));
- read_position += bytes_read;
- EXPECT_GE(write_position, read_position);
- EXPECT_EQ(write_position - read_position, buffer_.forward_bytes());
- }
-}
-
-TEST_F(SeekableBufferTest, ReadWriteSeek) {
- const int kReadSize = kWriteSize / 4;
-
- for (int i = 0; i < 10; ++i) {
- // Write until buffer is full.
- for (int j = 0; j < kBufferSize; j += kWriteSize) {
- bool should_append = buffer_.Append(data_ + j, kWriteSize);
- EXPECT_EQ(j < kBufferSize - kWriteSize, should_append)
- << "Incorrect buffer full reported";
- EXPECT_EQ(j + kWriteSize, buffer_.forward_bytes());
- }
-
- // Simulate a read and seek pattern. Each loop reads 4 times, each time
- // reading a quarter of |kWriteSize|.
- int read_position = 0;
- int forward_bytes = kBufferSize;
- for (int j = 0; j < kBufferSize; j += kWriteSize) {
- // Read.
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- forward_bytes -= kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
- read_position += kReadSize;
-
- // Seek forward.
- EXPECT_TRUE(buffer_.Seek(2 * kReadSize));
- forward_bytes -= 2 * kReadSize;
- read_position += 2 * kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
-
- // Copy.
- EXPECT_EQ(kReadSize, buffer_.Peek(write_buffer_, kReadSize));
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
-
- // Read.
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- forward_bytes -= kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
- read_position += kReadSize;
-
- // Seek backward.
- EXPECT_TRUE(buffer_.Seek(-3 * static_cast<int32>(kReadSize)));
- forward_bytes += 3 * kReadSize;
- read_position -= 3 * kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
-
- // Copy.
- EXPECT_EQ(kReadSize, buffer_.Peek(write_buffer_, kReadSize));
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
-
- // Read.
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- forward_bytes -= kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
- read_position += kReadSize;
-
- // Copy.
- EXPECT_EQ(kReadSize, buffer_.Peek(write_buffer_, kReadSize));
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
-
- // Read.
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- forward_bytes -= kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, kReadSize));
- read_position += kReadSize;
-
- // Seek forward.
- EXPECT_TRUE(buffer_.Seek(kReadSize));
- forward_bytes -= kReadSize;
- read_position += kReadSize;
- EXPECT_EQ(forward_bytes, buffer_.forward_bytes());
- }
- }
-}
-
-TEST_F(SeekableBufferTest, BufferFull) {
- const int kMaxWriteSize = 2 * kBufferSize;
-
- // Write and expect the buffer to be not full.
- for (int i = 0; i < kBufferSize - kWriteSize; i += kWriteSize) {
- EXPECT_TRUE(buffer_.Append(data_ + i, kWriteSize));
- EXPECT_EQ(i + kWriteSize, buffer_.forward_bytes());
- }
-
- // Write until we have kMaxWriteSize bytes in the buffer. Buffer is full in
- // these writes.
- for (int i = buffer_.forward_bytes(); i < kMaxWriteSize; i += kWriteSize) {
- EXPECT_FALSE(buffer_.Append(data_ + i, kWriteSize));
- EXPECT_EQ(i + kWriteSize, buffer_.forward_bytes());
- }
-
- // Read until the buffer is empty.
- int read_position = 0;
- while (buffer_.forward_bytes()) {
- // Read a random amount of data.
- int read_size = GetRandomInt(kBufferSize);
- int forward_bytes = buffer_.forward_bytes();
- int bytes_read = buffer_.Read(write_buffer_, read_size);
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, bytes_read));
- if (read_size > forward_bytes)
- EXPECT_EQ(forward_bytes, bytes_read);
- else
- EXPECT_EQ(read_size, bytes_read);
- read_position += bytes_read;
- EXPECT_GE(kMaxWriteSize, read_position);
- EXPECT_EQ(kMaxWriteSize - read_position, buffer_.forward_bytes());
- }
-
- // Expects we have no bytes left.
- EXPECT_EQ(0, buffer_.forward_bytes());
- EXPECT_EQ(0, buffer_.Read(write_buffer_, 1));
-}
-
-TEST_F(SeekableBufferTest, SeekBackward) {
- EXPECT_EQ(0, buffer_.forward_bytes());
- EXPECT_EQ(0, buffer_.backward_bytes());
- EXPECT_FALSE(buffer_.Seek(1));
- EXPECT_FALSE(buffer_.Seek(-1));
-
- const int kReadSize = 256;
-
- // Write into buffer until it's full.
- for (int i = 0; i < kBufferSize; i += kWriteSize) {
- // Write a random amount of data.
- buffer_.Append(data_ + i, kWriteSize);
- }
-
- // Read until buffer is empty.
- for (int i = 0; i < kBufferSize; i += kReadSize) {
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + i, kReadSize));
- }
-
- // Seek backward.
- EXPECT_TRUE(buffer_.Seek(-static_cast<int32>(kBufferSize)));
- EXPECT_FALSE(buffer_.Seek(-1));
-
- // Read again.
- for (int i = 0; i < kBufferSize; i += kReadSize) {
- EXPECT_EQ(kReadSize, buffer_.Read(write_buffer_, kReadSize));
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + i, kReadSize));
- }
-}
-
-TEST_F(SeekableBufferTest, GetCurrentChunk) {
- const int kSeekSize = kWriteSize / 3;
-
- scoped_refptr<DataBuffer> buffer(new DataBuffer(data_, kWriteSize));
-
- const uint8* data;
- int size;
- EXPECT_FALSE(buffer_.GetCurrentChunk(&data, &size));
-
- buffer_.Append(buffer.get());
- EXPECT_TRUE(buffer_.GetCurrentChunk(&data, &size));
- EXPECT_EQ(data, buffer->GetData());
- EXPECT_EQ(size, buffer->GetDataSize());
-
- buffer_.Seek(kSeekSize);
- EXPECT_TRUE(buffer_.GetCurrentChunk(&data, &size));
- EXPECT_EQ(data, buffer->GetData() + kSeekSize);
- EXPECT_EQ(size, buffer->GetDataSize() - kSeekSize);
-}
-
-TEST_F(SeekableBufferTest, SeekForward) {
- int write_position = 0;
- int read_position = 0;
- while (read_position < kDataSize) {
- for (int i = 0; i < 10 && write_position < kDataSize; ++i) {
- // Write a random amount of data.
- int write_size = GetRandomInt(kBufferSize);
- write_size = std::min(write_size, kDataSize - write_position);
-
- bool should_append = buffer_.Append(data_ + write_position, write_size);
- write_position += write_size;
- EXPECT_GE(write_position, read_position);
- EXPECT_EQ(write_position - read_position, buffer_.forward_bytes());
- EXPECT_EQ(should_append, buffer_.forward_bytes() < kBufferSize)
- << "Incorrect buffer full status reported";
- }
-
- // Read a random amount of data.
- int seek_size = GetRandomInt(kBufferSize);
- if (buffer_.Seek(seek_size))
- read_position += seek_size;
- EXPECT_GE(write_position, read_position);
- EXPECT_EQ(write_position - read_position, buffer_.forward_bytes());
-
- // Read a random amount of data.
- int read_size = GetRandomInt(kBufferSize);
- int bytes_read = buffer_.Read(write_buffer_, read_size);
- EXPECT_GE(read_size, bytes_read);
- EXPECT_EQ(0, memcmp(write_buffer_, data_ + read_position, bytes_read));
- read_position += bytes_read;
- EXPECT_GE(write_position, read_position);
- EXPECT_EQ(write_position - read_position, buffer_.forward_bytes());
- }
-}
-
-TEST_F(SeekableBufferTest, AllMethods) {
- EXPECT_EQ(0, buffer_.Read(write_buffer_, 0));
- EXPECT_EQ(0, buffer_.Read(write_buffer_, 1));
- EXPECT_TRUE(buffer_.Seek(0));
- EXPECT_FALSE(buffer_.Seek(-1));
- EXPECT_FALSE(buffer_.Seek(1));
- EXPECT_EQ(0, buffer_.forward_bytes());
- EXPECT_EQ(0, buffer_.backward_bytes());
-}
-
-TEST_F(SeekableBufferTest, GetTime) {
- const int64 kNoTS = kNoTimestamp().ToInternalValue();
- const struct {
- int64 first_time_useconds;
- int64 duration_useconds;
- int consume_bytes;
- int64 expected_time;
- } tests[] = {
- { kNoTS, 1000000, 0, kNoTS },
- { kNoTS, 4000000, 0, kNoTS },
- { kNoTS, 8000000, 0, kNoTS },
- { kNoTS, 1000000, kWriteSize / 2, kNoTS },
- { kNoTS, 4000000, kWriteSize / 2, kNoTS },
- { kNoTS, 8000000, kWriteSize / 2, kNoTS },
- { kNoTS, 1000000, kWriteSize, kNoTS },
- { kNoTS, 4000000, kWriteSize, kNoTS },
- { kNoTS, 8000000, kWriteSize, kNoTS },
- { 0, 1000000, 0, 0 },
- { 0, 4000000, 0, 0 },
- { 0, 8000000, 0, 0 },
- { 0, 1000000, kWriteSize / 2, 500000 },
- { 0, 4000000, kWriteSize / 2, 2000000 },
- { 0, 8000000, kWriteSize / 2, 4000000 },
- { 0, 1000000, kWriteSize, 1000000 },
- { 0, 4000000, kWriteSize, 4000000 },
- { 0, 8000000, kWriteSize, 8000000 },
- { 5, 1000000, 0, 5 },
- { 5, 4000000, 0, 5 },
- { 5, 8000000, 0, 5 },
- { 5, 1000000, kWriteSize / 2, 500005 },
- { 5, 4000000, kWriteSize / 2, 2000005 },
- { 5, 8000000, kWriteSize / 2, 4000005 },
- { 5, 1000000, kWriteSize, 1000005 },
- { 5, 4000000, kWriteSize, 4000005 },
- { 5, 8000000, kWriteSize, 8000005 },
- };
-
- // current_time() must initially return kNoTimestamp().
- EXPECT_EQ(kNoTimestamp().ToInternalValue(),
- buffer_.current_time().ToInternalValue());
-
- scoped_refptr<DataBuffer> buffer(new DataBuffer(data_, kWriteSize));
-
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- buffer->SetTimestamp(base::TimeDelta::FromMicroseconds(
- tests[i].first_time_useconds));
- buffer->SetDuration(base::TimeDelta::FromMicroseconds(
- tests[i].duration_useconds));
- buffer_.Append(buffer.get());
- EXPECT_TRUE(buffer_.Seek(tests[i].consume_bytes));
-
- int64 actual = buffer_.current_time().ToInternalValue();
-
- EXPECT_EQ(tests[i].expected_time, actual) << "With test = { start:"
- << tests[i].first_time_useconds << ", duration:"
- << tests[i].duration_useconds << ", consumed:"
- << tests[i].consume_bytes << " }\n";
-
- buffer_.Clear();
- }
-}
-
-} // namespace media
diff --git a/src/media/base/serial_runner.cc b/src/media/base/serial_runner.cc
deleted file mode 100644
index 9d6c6ed..0000000
--- a/src/media/base/serial_runner.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/serial_runner.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "base/message_loop_proxy.h"
-
-namespace media {
-
-// Converts a bound function accepting a Closure into a bound function
-// accepting a PipelineStatusCB. Since closures have no way of reporting a
-// status |status_cb| is executed with PIPELINE_OK.
-static void RunBoundClosure(
- const SerialRunner::BoundClosure& bound_closure,
- const PipelineStatusCB& status_cb) {
- bound_closure.Run(base::Bind(status_cb, PIPELINE_OK));
-}
-
-// Runs |status_cb| with |last_status| on |message_loop|.
-static void RunOnMessageLoop(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const PipelineStatusCB& status_cb,
- PipelineStatus last_status) {
- // Force post to permit cancellation of a series in the scenario where all
- // bound functions run on the same thread.
- message_loop->PostTask(FROM_HERE, base::Bind(status_cb, last_status));
-}
-
-SerialRunner::Queue::Queue() {}
-SerialRunner::Queue::~Queue() {}
-
-void SerialRunner::Queue::Push(
- const BoundClosure& bound_closure) {
- bound_fns_.push(base::Bind(&RunBoundClosure, bound_closure));
-}
-
-void SerialRunner::Queue::Push(
- const BoundPipelineStatusCB& bound_status_cb) {
- bound_fns_.push(bound_status_cb);
-}
-
-SerialRunner::BoundPipelineStatusCB SerialRunner::Queue::Pop() {
- BoundPipelineStatusCB bound_fn = bound_fns_.front();
- bound_fns_.pop();
- return bound_fn;
-}
-
-bool SerialRunner::Queue::empty() {
- return bound_fns_.empty();
-}
-
-SerialRunner::SerialRunner(
- const Queue& bound_fns, const PipelineStatusCB& done_cb)
- : weak_this_(this),
- message_loop_(base::MessageLoopProxy::current()),
- bound_fns_(bound_fns),
- done_cb_(done_cb) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &SerialRunner::RunNextInSeries, weak_this_.GetWeakPtr(),
- PIPELINE_OK));
-}
-
-SerialRunner::~SerialRunner() {}
-
-scoped_ptr<SerialRunner> SerialRunner::Run(
- const Queue& bound_fns, const PipelineStatusCB& done_cb) {
- scoped_ptr<SerialRunner> callback_series(
- new SerialRunner(bound_fns, done_cb));
- return callback_series.Pass();
-}
-
-void SerialRunner::RunNextInSeries(PipelineStatus last_status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!done_cb_.is_null());
-
- if (bound_fns_.empty() || last_status != PIPELINE_OK) {
- base::ResetAndReturn(&done_cb_).Run(last_status);
- return;
- }
-
- BoundPipelineStatusCB bound_fn = bound_fns_.Pop();
- bound_fn.Run(base::Bind(&RunOnMessageLoop, message_loop_, base::Bind(
- &SerialRunner::RunNextInSeries, weak_this_.GetWeakPtr())));
-}
-
-} // namespace media
diff --git a/src/media/base/serial_runner.h b/src/media/base/serial_runner.h
deleted file mode 100644
index 16fa6f3..0000000
--- a/src/media/base/serial_runner.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SERIAL_RUNNER_H_
-#define MEDIA_BASE_SERIAL_RUNNER_H_
-
-#include <queue>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "media/base/pipeline_status.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-// Runs a series of bound functions accepting Closures or PipelineStatusCB.
-// SerialRunner doesn't use regular Closure/PipelineStatusCBs as it late binds
-// the completion callback as the series progresses.
-class SerialRunner {
- public:
- typedef base::Callback<void(const base::Closure&)> BoundClosure;
- typedef base::Callback<void(const PipelineStatusCB&)> BoundPipelineStatusCB;
-
- // Serial queue of bound functions to run.
- class Queue {
- public:
- Queue();
- ~Queue();
-
- void Push(const BoundClosure& bound_fn);
- void Push(const BoundPipelineStatusCB& bound_fn);
-
- private:
- friend class SerialRunner;
-
- BoundPipelineStatusCB Pop();
- bool empty();
-
- std::queue<BoundPipelineStatusCB> bound_fns_;
- };
-
- // Executes the bound functions in series, executing |done_cb| when finished.
- //
- // All bound functions are executed on the thread that Run() is called on,
- // including |done_cb|.
- //
- // Deleting the object will prevent execution of any unstarted bound
- // functions, including |done_cb|.
- static scoped_ptr<SerialRunner> Run(
- const Queue& bound_fns, const PipelineStatusCB& done_cb);
-
- private:
- friend class scoped_ptr<SerialRunner>;
-
- SerialRunner(const Queue& bound_fns, const PipelineStatusCB& done_cb);
- ~SerialRunner();
-
- void RunNextInSeries(PipelineStatus last_status);
-
- base::WeakPtrFactory<SerialRunner> weak_this_;
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- Queue bound_fns_;
- PipelineStatusCB done_cb_;
-
- DISALLOW_COPY_AND_ASSIGN(SerialRunner);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SERIAL_RUNNER_H_
diff --git a/src/media/base/shell_audio_bus.cc b/src/media/base/shell_audio_bus.cc
deleted file mode 100644
index 4b5ad5e..0000000
--- a/src/media/base/shell_audio_bus.cc
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_audio_bus.h"
-
-#include <algorithm>
-#include <limits>
-
-namespace media {
-
-namespace {
-
-typedef ShellAudioBus::StorageType StorageType;
-typedef ShellAudioBus::SampleType SampleType;
-
-const float kFloat32ToInt16Factor = 32768.f;
-
-inline void ConvertSample(ShellAudioBus::SampleType src_type,
- const uint8* src_ptr,
- ShellAudioBus::SampleType dest_type,
- uint8* dest_ptr) {
- if (src_type == dest_type) {
- memcpy(dest_ptr, src_ptr,
- src_type == ShellAudioBus::kInt16 ? sizeof(int16) : sizeof(float));
- } else if (src_type == ShellAudioBus::kFloat32) {
- float sample_in_float = *reinterpret_cast<const float*>(src_ptr);
- int32 sample_in_int32 =
- static_cast<int32>(sample_in_float * kFloat32ToInt16Factor);
- sample_in_int32 =
- std::max<int32>(sample_in_int32, std::numeric_limits<int16>::min());
- sample_in_int32 =
- std::min<int32>(sample_in_int32, std::numeric_limits<int16>::max());
- *reinterpret_cast<int16*>(dest_ptr) = static_cast<int16>(sample_in_int32);
- } else {
- int16 sample = *reinterpret_cast<const int16*>(src_ptr);
- *reinterpret_cast<float*>(dest_ptr) =
- static_cast<float>(sample) / kFloat32ToInt16Factor;
- }
-}
-
-} // namespace
-
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames,
- SampleType sample_type, StorageType storage_type)
- : channels_(channels),
- frames_(frames),
- sample_type_(sample_type),
- storage_type_(storage_type) {
- DCHECK_GT(channels_, 0);
-
- if (storage_type_ == kInterleaved) {
- data_.reset(static_cast<uint8*>(base::AlignedAlloc(
- GetSampleSizeInBytes() * frames * channels, kChannelAlignmentInBytes)));
- channel_data_.push_back(data_.get());
- } else {
- DCHECK_EQ(storage_type_, kPlanar);
- size_t aligned_per_channel_size_in_bytes =
- (GetSampleSizeInBytes() * frames + kChannelAlignmentInBytes - 1) /
- kChannelAlignmentInBytes * kChannelAlignmentInBytes;
- data_.reset(static_cast<uint8*>(
- base::AlignedAlloc(aligned_per_channel_size_in_bytes * channels,
- kChannelAlignmentInBytes)));
- channel_data_.reserve(channels);
- for (size_t i = 0; i < channels_; ++i) {
- channel_data_.push_back(data_.get() +
- aligned_per_channel_size_in_bytes * i);
- }
- }
-}
-
-ShellAudioBus::ShellAudioBus(size_t frames, const std::vector<float*>& samples)
- : channels_(samples.size()),
- frames_(frames),
- sample_type_(kFloat32),
- storage_type_(kPlanar) {
- DCHECK_GT(channels_, 0);
-
- channel_data_.reserve(samples.size());
- for (size_t i = 0; i < samples.size(); ++i) {
- channel_data_.push_back(reinterpret_cast<uint8*>(samples[i]));
- }
-}
-
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames, float* samples)
- : channels_(channels),
- frames_(frames),
- sample_type_(kFloat32),
- storage_type_(kInterleaved) {
- DCHECK_GT(channels_, 0);
-
- channel_data_.push_back(reinterpret_cast<uint8*>(samples));
-}
-
-ShellAudioBus::ShellAudioBus(size_t frames, const std::vector<int16*>& samples)
- : channels_(samples.size()),
- frames_(frames),
- sample_type_(kInt16),
- storage_type_(kPlanar) {
- DCHECK_GT(channels_, 0);
-
- channel_data_.reserve(samples.size());
- for (size_t i = 0; i < samples.size(); ++i) {
- channel_data_.push_back(reinterpret_cast<uint8*>(samples[i]));
- }
-}
-
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames, int16* samples)
- : channels_(channels),
- frames_(frames),
- sample_type_(kInt16),
- storage_type_(kInterleaved) {
- DCHECK_GT(channels_, 0);
-
- channel_data_.push_back(reinterpret_cast<uint8*>(samples));
-}
-
-size_t ShellAudioBus::GetSampleSizeInBytes() const {
- if (sample_type_ == kInt16) {
- return sizeof(int16);
- }
- DCHECK_EQ(sample_type_, kFloat32);
- return sizeof(float);
-}
-
-const uint8* ShellAudioBus::interleaved_data() const {
- DCHECK_EQ(storage_type_, kInterleaved);
- return channel_data_[0];
-}
-
-const uint8* ShellAudioBus::planar_data(size_t channel) const {
- DCHECK_LT(channel, channels_);
- DCHECK_EQ(storage_type_, kPlanar);
- return channel_data_[channel];
-}
-
-void ShellAudioBus::ZeroFrames(size_t start_frame, size_t end_frame) {
- DCHECK_LE(start_frame, end_frame);
- DCHECK_LE(end_frame, frames_);
- end_frame = std::min(end_frame, frames_);
- start_frame = std::min(start_frame, end_frame);
- if (start_frame >= end_frame) {
- return;
- }
- if (storage_type_ == kInterleaved) {
- memset(GetSamplePtr(0, start_frame), 0,
- GetSampleSizeInBytes() * (end_frame - start_frame) * channels_);
- } else {
- for (size_t channel = 0; channel < channels_; ++channel) {
- memset(GetSamplePtr(channel, start_frame), 0,
- GetSampleSizeInBytes() * (end_frame - start_frame));
- }
- }
-}
-
-void ShellAudioBus::Assign(const ShellAudioBus& source) {
- DCHECK_EQ(channels_, source.channels_);
- if (channels_ != source.channels_) {
- ZeroAllFrames();
- return;
- }
-
- if (sample_type_ == source.sample_type_ &&
- storage_type_ == source.storage_type_) {
- size_t frames = std::min(frames_, source.frames_);
- if (storage_type_ == kInterleaved) {
- memcpy(GetSamplePtr(0, 0), source.GetSamplePtr(0, 0),
- GetSampleSizeInBytes() * frames * channels_);
- } else {
- for (size_t channel = 0; channel < channels_; ++channel) {
- memcpy(GetSamplePtr(channel, 0), source.GetSamplePtr(channel, 0),
- GetSampleSizeInBytes() * frames);
- }
- }
- return;
- }
-
- size_t frames = std::min(frames_, source.frames_);
- for (size_t channel = 0; channel < channels_; ++channel) {
- for (size_t frame = 0; frame < frames; ++frame) {
- ConvertSample(source.sample_type_, source.GetSamplePtr(channel, frame),
- sample_type_, GetSamplePtr(channel, frame));
- }
- }
-}
-
-void ShellAudioBus::Assign(const ShellAudioBus& source,
- const std::vector<float>& matrix) {
- DCHECK_EQ(channels() * source.channels(), matrix.size());
- DCHECK_EQ(sample_type_, kFloat32);
- DCHECK_EQ(source.sample_type_, kFloat32);
- if (channels() * source.channels() != matrix.size() ||
- sample_type_ != kFloat32 || source.sample_type_ != kFloat32) {
- ZeroAllFrames();
- return;
- }
-
- size_t frames = std::min(frames_, source.frames_);
- for (size_t dest_channel = 0; dest_channel < channels_; ++dest_channel) {
- for (size_t frame = 0; frame < frames; ++frame) {
- float mixed_sample = 0.f;
- for (size_t src_channel = 0; src_channel < source.channels_;
- ++src_channel) {
- mixed_sample += source.GetFloat32Sample(src_channel, frame) *
- matrix[dest_channel * source.channels_ + src_channel];
- }
- SetFloat32Sample(dest_channel, frame, mixed_sample);
- }
- }
-}
-
-template <typename SourceSampleType,
- typename DestSampleType,
- StorageType SourceStorageType,
- StorageType DestStorageType>
-void ShellAudioBus::MixForTypes(const ShellAudioBus& source) {
- const size_t frames = std::min(frames_, source.frames_);
-
- for (size_t channel = 0; channel < channels_; ++channel) {
- for (size_t frame = 0; frame < frames; ++frame) {
- *reinterpret_cast<DestSampleType*>(
- GetSamplePtrForType<DestSampleType, DestStorageType>(channel,
- frame)) +=
- source.GetSampleForType<SourceSampleType, SourceStorageType>(channel,
- frame);
- }
- }
-}
-
-void ShellAudioBus::Mix(const ShellAudioBus& source) {
- DCHECK_EQ(channels_, source.channels_);
-
- if (channels_ != source.channels_) {
- ZeroAllFrames();
- return;
- }
-
- // Profiling has identified this area of code as hot, so instead of calling
- // GetSamplePtr, which branches each time it is called, we branch once
- // before we loop and inline the branch of the function we want.
- if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
- source.storage_type_ == kInterleaved && storage_type_ == kInterleaved) {
- MixForTypes<int16, int16, kInterleaved, kInterleaved>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
- source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
- MixForTypes<int16, int16, kInterleaved, kPlanar>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
- source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
- MixForTypes<int16, int16, kPlanar, kInterleaved>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
- source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
- MixForTypes<int16, int16, kPlanar, kPlanar>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
- source.storage_type_ == kInterleaved &&
- storage_type_ == kInterleaved) {
- MixForTypes<int16, float, kInterleaved, kInterleaved>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
- source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
- MixForTypes<int16, float, kInterleaved, kPlanar>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
- source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
- MixForTypes<int16, float, kPlanar, kInterleaved>(source);
- } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
- source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
- MixForTypes<int16, float, kPlanar, kPlanar>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
- source.storage_type_ == kInterleaved &&
- storage_type_ == kInterleaved) {
- MixForTypes<float, int16, kInterleaved, kInterleaved>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
- source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
- MixForTypes<float, int16, kInterleaved, kPlanar>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
- source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
- MixForTypes<float, int16, kPlanar, kInterleaved>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
- source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
- MixForTypes<float, int16, kPlanar, kPlanar>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
- source.storage_type_ == kInterleaved &&
- storage_type_ == kInterleaved) {
- MixForTypes<float, float, kInterleaved, kInterleaved>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
- source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
- MixForTypes<float, float, kInterleaved, kPlanar>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
- source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
- MixForTypes<float, float, kPlanar, kInterleaved>(source);
- } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
- source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
- MixForTypes<float, float, kPlanar, kPlanar>(source);
- } else {
- NOTREACHED();
- }
-}
-
-void ShellAudioBus::Mix(const ShellAudioBus& source,
- const std::vector<float>& matrix) {
- DCHECK_EQ(channels() * source.channels(), matrix.size());
- DCHECK_EQ(sample_type_, kFloat32);
- DCHECK_EQ(source.sample_type_, kFloat32);
- if (channels() * source.channels() != matrix.size() ||
- sample_type_ != kFloat32 || source.sample_type_ != kFloat32) {
- ZeroAllFrames();
- return;
- }
-
- size_t frames = std::min(frames_, source.frames_);
- for (size_t dest_channel = 0; dest_channel < channels_; ++dest_channel) {
- for (size_t frame = 0; frame < frames; ++frame) {
- float mixed_sample = 0.f;
- for (size_t src_channel = 0; src_channel < source.channels_;
- ++src_channel) {
- mixed_sample += source.GetFloat32Sample(src_channel, frame) *
- matrix[dest_channel * source.channels_ + src_channel];
- }
- mixed_sample += GetFloat32Sample(dest_channel, frame);
- SetFloat32Sample(dest_channel, frame, mixed_sample);
- }
- }
-}
-
-uint8* ShellAudioBus::GetSamplePtr(size_t channel, size_t frame) {
- DCHECK_LT(channel, channels_);
- DCHECK_LT(frame, frames_);
-
- if (storage_type_ == kInterleaved) {
- return channel_data_[0] +
- GetSampleSizeInBytes() * (channels_ * frame + channel);
- } else {
- return channel_data_[channel] + GetSampleSizeInBytes() * frame;
- }
-}
-
-const uint8* ShellAudioBus::GetSamplePtr(size_t channel, size_t frame) const {
- DCHECK_LT(channel, channels_);
- DCHECK_LT(frame, frames_);
-
- if (storage_type_ == kInterleaved) {
- return channel_data_[0] +
- GetSampleSizeInBytes() * (channels_ * frame + channel);
- } else {
- return channel_data_[channel] + GetSampleSizeInBytes() * frame;
- }
-}
-
-} // namespace media
diff --git a/src/media/base/shell_audio_bus.h b/src/media/base/shell_audio_bus.h
deleted file mode 100644
index 5beaf2c..0000000
--- a/src/media/base/shell_audio_bus.h
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_AUDIO_BUS_H_
-#define MEDIA_BASE_SHELL_AUDIO_BUS_H_
-
-#include <vector>
-
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// This swiss army knife class encapsulates audio data in multiple channels, in
-// different storage types and with different sample sizes. It also provides
-// operation to convert, mix between different types of audio data. It should
-// be used whenever such audio data is stored or passed around.
-// In this class, "sample" is one audio wave form data at a certain time from a
-// certain channel, while "frame" refers to all samples at the same time from
-// all channels. For example, for a 48000KHz stereo audio with samples in
-// float, its sample size in bytes is 4 but its frame size in bytes is 8. One
-// second of such audio contains 48000 frames (96000 samples).
-// Note: This class doesn't do endianness conversions. It assumes that all data
-// is in the correct endianness.
-class MEDIA_EXPORT ShellAudioBus {
- public:
- // Guaranteed alignment of each channel's data; use 64-byte alignment so it
- // satisfies all our current platforms. Note that this is only used for
- // buffers that are allocated and owned by the ShellAudioBus. We don't
- // enforce alignment for the buffers passed in and extra caution should be
- // taken if they are used as hardware buffer.
- static const size_t kChannelAlignmentInBytes = 64;
-
- enum SampleType { kInt16, kFloat32 };
-
- enum StorageType { kInterleaved, kPlanar };
-
- ShellAudioBus(size_t channels, size_t frames, SampleType sample_type,
- StorageType storage_type);
- ShellAudioBus(size_t frames, const std::vector<float*>& samples);
- ShellAudioBus(size_t channels, size_t frames, float* samples);
- ShellAudioBus(size_t frames, const std::vector<int16*>& samples);
- ShellAudioBus(size_t channels, size_t frames, int16* samples);
-
- size_t channels() const { return channels_; }
- size_t frames() const { return frames_; }
- SampleType sample_type() const { return sample_type_; }
- StorageType storage_type() const { return storage_type_; }
- size_t GetSampleSizeInBytes() const;
- const uint8* interleaved_data() const;
- const uint8* planar_data(size_t channel) const;
-
- int16 GetInt16Sample(size_t channel, size_t frame) const {
- DCHECK_EQ(sample_type_, kInt16);
- return *reinterpret_cast<const int16*>(GetSamplePtr(channel, frame));
- }
- float GetFloat32Sample(size_t channel, size_t frame) const {
- DCHECK_EQ(sample_type_, kFloat32);
- return *reinterpret_cast<const float*>(GetSamplePtr(channel, frame));
- }
-
- void ZeroFrames(size_t start_frame, size_t end_frame);
- void ZeroAllFrames() { ZeroFrames(0, frames()); }
-
- // Copy frames from |source| provided that it has the same number of channels
- // as the destination object (this). This function does any necessary
- // conversion between different sample types and storage types. When source
- // has less frames than the destination object, it will only copy these frames
- // and will not fill the rest frames in our buffer with 0.
- void Assign(const ShellAudioBus& source);
-
- // The same as the above function except that this function also does mixing.
- // |matrix| is a |dest.channels()| row * |source.channels()| column matrix in
- // row major.
- // dest.sample[dest_channel][frame] =
- // source.sample[0][frame] * matrix[dest_channel * source.channels() + 0]
- // + source.sample[1][frame] * matrix[dest_channel * source.channels() + 1]
- // ...
- // + source.sample[source.channels() - 1][frame] *
- // matrix[channels() * source.channels() + source.channels() - 1];
- // Note: Both objects must have storage type of kFloat32.
- void Assign(const ShellAudioBus& source, const std::vector<float>& matrix);
-
- // The following functions are the same as the Assign() functions except that
- // they add the calculated samples to the target samples instead of replacing
- // the target samples with the calculated samples.
- // Note: Both objects must have storage type of kFloat32.
- void Mix(const ShellAudioBus& source);
- void Mix(const ShellAudioBus& source, const std::vector<float>& matrix);
-
- public:
- // The .*ForTypes? functions below are optimized versions that assume what
- // storage type the bus is using. They are meant to be called after
- // checking what storage type the bus is once, and then performing a batch
- // of operations, where it is known that the type will not change.
- template <typename SampleTypeName, StorageType T>
- inline uint8* GetSamplePtrForType(size_t channel, size_t frame) const {
- DCHECK_LT(channel, channels_);
- DCHECK_LT(frame, frames_);
-
- if (T == kInterleaved) {
- return channel_data_[0] +
- sizeof(SampleTypeName) * (channels_ * frame + channel);
- } else if (T == kPlanar) {
- return channel_data_[channel] + sizeof(SampleTypeName) * frame;
- } else {
- NOTREACHED();
- }
-
- return NULL;
- }
-
- template <typename SampleTypeName, StorageType T>
- inline SampleTypeName GetSampleForType(size_t channel, size_t frame) const {
- return *reinterpret_cast<const SampleTypeName*>(
- GetSamplePtrForType<SampleTypeName, T>(channel, frame));
- }
-
- template <typename SourceSampleType,
- typename DestSampleType,
- StorageType SourceStorageType,
- StorageType DestStorageType>
- void MixForTypes(const ShellAudioBus& source);
-
- private:
- void SetFloat32Sample(size_t channel, size_t frame, float sample) {
- DCHECK_EQ(sample_type_, kFloat32);
- *reinterpret_cast<float*>(GetSamplePtr(channel, frame)) = sample;
- }
- uint8* GetSamplePtr(size_t channel, size_t frame);
- const uint8* GetSamplePtr(size_t channel, size_t frame) const;
-
- // Contiguous block of channel memory if the memory is owned by this object.
- scoped_ptr_malloc<uint8, base::ScopedPtrAlignedFree> data_;
-
- std::vector<uint8*> channel_data_;
- size_t channels_;
- size_t frames_;
- SampleType sample_type_;
- StorageType storage_type_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellAudioBus);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_AUDIO_BUS_H_
diff --git a/src/media/base/shell_audio_bus_test.cc b/src/media/base/shell_audio_bus_test.cc
deleted file mode 100644
index db5240a..0000000
--- a/src/media/base/shell_audio_bus_test.cc
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_audio_bus.h"
-
-#include <vector>
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-namespace testing {
-namespace {
-
-template <typename T>
-bool VerifyValues(const T* data, size_t size, T value) {
- for (size_t i = 0; i < size; ++i) {
- if (data[i] != value) {
- return false;
- }
- }
- return true;
-}
-
-template <typename T>
-void FillValues(T* data, size_t size, T value) {
- for (size_t i = 0; i < size; ++i) {
- data[i] = value;
- }
-}
-
-void Scribble(uint8* data, size_t size) {
- for (size_t i = 0; i < size; ++i) {
- data[i] = static_cast<uint8>(i + 0x34);
- }
-}
-
-bool VerifyScribble(const uint8* data, size_t size) {
- for (size_t i = 0; i < size; ++i) {
- if (data[i] != static_cast<uint8>(i + 0x34)) {
- return false;
- }
- }
- return true;
-}
-
-// This function only works with audio bus containing float32 samples.
-bool IsSameChannel(const ShellAudioBus& audio_bus_1,
- size_t channel_1,
- const ShellAudioBus& audio_bus_2,
- size_t channel_2) {
- if (audio_bus_1.frames() != audio_bus_2.frames()) {
- return false;
- }
- for (size_t frame = 0; frame < audio_bus_1.frames(); ++frame) {
- if (fabsf(audio_bus_1.GetFloat32Sample(channel_1, frame) -
- audio_bus_2.GetFloat32Sample(channel_2, frame)) > 0.001) {
- return false;
- }
- }
- return true;
-}
-
-// This class allocates buffer with extra guard bytes in the front and back of
-// the buffer. It can be used to verify if the ShellAudioBus implementation
-// writes any data out of boundary.
-class GuardedBuffers {
- public:
- GuardedBuffers(size_t number_of_buffers, size_t bytes_per_buffer) {
- std::vector<uint8> buffer(bytes_per_buffer + kGuardBytes * 2);
- buffers_.resize(number_of_buffers, buffer);
- ScribbleContent();
- }
-
- void ScribbleContent() {
- for (size_t i = 0; i < buffers_.size(); ++i) {
- Scribble(&buffers_[i][0], kGuardBytes);
- Scribble(&buffers_[i][buffers_[i].size() - kGuardBytes], kGuardBytes);
- Scribble(&buffers_[i][kGuardBytes], buffers_[i].size() - kGuardBytes * 2);
- }
- }
-
- bool VerifyGuardBytes() const {
- for (size_t i = 0; i < buffers_.size(); ++i) {
- if (!VerifyScribble(&buffers_[i][0], kGuardBytes) ||
- !VerifyScribble(&buffers_[i][buffers_[i].size() - kGuardBytes],
- kGuardBytes)) {
- return false;
- }
- }
- return true;
- }
-
- template <typename T>
- T* GetBuffer(size_t index) {
- return reinterpret_cast<T*>(&buffers_.at(index)[kGuardBytes]);
- }
-
- private:
- typedef std::vector<uint8> Buffer;
- typedef std::vector<Buffer> Buffers;
-
- static const size_t kGuardBytes = 256;
-
- Buffers buffers_;
-};
-
-class ShellAudioBusTest : public ::testing::Test {
- public:
- typedef ShellAudioBus::SampleType SampleType;
- typedef ShellAudioBus::StorageType StorageType;
-
- ~ShellAudioBusTest() {
- // We do an extra call to VerifyGuardBytes() just in case if it was omitted
- // in a test. It is still recommended to verify the integrity in individual
- // tests so the failure message is more informational.
- if (guarded_buffers_) {
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
- }
-
- template <typename T>
- void CreateAudioBus(StorageType storage_type) {
- // We do an extra call to VerifyGuardBytes() just in case if it was omitted
- // in a test. It is still recommended to verify the integrity in individual
- // tests so the failure message is more informational.
- if (guarded_buffers_) {
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
- if (storage_type == ShellAudioBus::kPlanar) {
- guarded_buffers_.reset(
- new GuardedBuffers(kChannels, sizeof(T) * kFrames));
- std::vector<T*> samples;
- for (size_t channel = 0; channel < kChannels; ++channel) {
- samples.push_back(guarded_buffers_->GetBuffer<T>(channel));
- }
- audio_bus_.reset(new ShellAudioBus(kFrames, samples));
- } else {
- guarded_buffers_.reset(
- new GuardedBuffers(1, sizeof(T) * kFrames * kChannels));
- audio_bus_.reset(new ShellAudioBus(kChannels, kFrames,
- guarded_buffers_->GetBuffer<T>(0)));
- }
- }
-
- protected:
- static const size_t kChannels;
- static const size_t kFrames;
-
- scoped_ptr<GuardedBuffers> guarded_buffers_;
- scoped_ptr<ShellAudioBus> audio_bus_;
-};
-
-const size_t ShellAudioBusTest::kChannels = 3;
-const size_t ShellAudioBusTest::kFrames = 809;
-
-TEST_F(ShellAudioBusTest, ConstructorWithAllocation) {
- ShellAudioBus audio_bus(kChannels, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kPlanar);
- EXPECT_EQ(audio_bus.channels(), kChannels);
- EXPECT_EQ(audio_bus.frames(), kFrames);
- for (size_t channel = 0; channel < audio_bus.channels(); ++channel) {
- const uint8* samples = audio_bus.planar_data(channel);
- EXPECT_EQ(reinterpret_cast<intptr_t>(samples) %
- ShellAudioBus::kChannelAlignmentInBytes,
- 0);
- }
-}
-
-TEST_F(ShellAudioBusTest, ConstructorWithoutAllocation) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- EXPECT_EQ(audio_bus_->channels(), kChannels);
- EXPECT_EQ(audio_bus_->frames(), kFrames);
- const float* samples =
- reinterpret_cast<const float*>(audio_bus_->interleaved_data());
- EXPECT_EQ(samples, guarded_buffers_->GetBuffer<float>(0));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- CreateAudioBus<int16>(ShellAudioBus::kPlanar);
- EXPECT_EQ(audio_bus_->channels(), kChannels);
- EXPECT_EQ(audio_bus_->frames(), kFrames);
- for (size_t channel = 0; channel < audio_bus_->channels(); ++channel) {
- const int16* samples =
- reinterpret_cast<const int16*>(audio_bus_->planar_data(channel));
- EXPECT_EQ(samples, guarded_buffers_->GetBuffer<int16>(channel));
- }
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-}
-
-TEST_F(ShellAudioBusTest, GetSampleSizeInBytes) {
- ShellAudioBus float_audio_bus(2, 4, ShellAudioBus::kFloat32,
- ShellAudioBus::kPlanar);
- EXPECT_EQ(float_audio_bus.GetSampleSizeInBytes(), sizeof(float));
- ShellAudioBus int16_audio_bus(2, 4, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- EXPECT_EQ(int16_audio_bus.GetSampleSizeInBytes(), sizeof(int16));
-}
-
-TEST_F(ShellAudioBusTest, GetSample) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- // Mark the first sample of the first channel as 1.f.
- guarded_buffers_->GetBuffer<float>(0)[0] = 1.f;
- // Mark the last sample of the last channel as 1.f.
- guarded_buffers_->GetBuffer<float>(0)[kFrames * kChannels - 1] = 1.f;
- EXPECT_EQ(audio_bus_->GetFloat32Sample(0, 0), 1.f);
- EXPECT_EQ(audio_bus_->GetFloat32Sample(kChannels - 1, kFrames - 1), 1.f);
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- CreateAudioBus<int16>(ShellAudioBus::kPlanar);
- for (size_t channel = 0; channel < audio_bus_->channels(); ++channel) {
- // Mark the first sample of the channel as 100.
- guarded_buffers_->GetBuffer<int16>(channel)[0] = 100;
- // Mark the last sample of the channel as 100.
- guarded_buffers_->GetBuffer<int16>(channel)[kFrames - 1] = 100;
- EXPECT_EQ(audio_bus_->GetInt16Sample(channel, 0), 100);
- EXPECT_EQ(audio_bus_->GetInt16Sample(channel, kFrames - 1), 100);
- }
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-}
-
-TEST_F(ShellAudioBusTest, ZeroFrames) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- Scribble(guarded_buffers_->GetBuffer<uint8>(0),
- sizeof(float) * kFrames * kChannels);
- audio_bus_->ZeroFrames(0, 0);
- audio_bus_->ZeroFrames(kFrames, kFrames);
- EXPECT_TRUE(VerifyScribble(guarded_buffers_->GetBuffer<uint8>(0),
- sizeof(float) * kFrames * kChannels));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- // Mark the first sample of last channel and second sample of the first
- // channel as 1.f.
- guarded_buffers_->GetBuffer<float>(0)[kChannels - 1] = 1.f;
- guarded_buffers_->GetBuffer<float>(0)[kChannels] = 1.f;
- audio_bus_->ZeroFrames(0, 1);
- EXPECT_EQ(guarded_buffers_->GetBuffer<float>(0)[kChannels - 1], 0.f);
- EXPECT_EQ(guarded_buffers_->GetBuffer<float>(0)[kChannels], 1.f);
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- // Mark the first sample of last channel and second but last sample of the
- // first channel as 1.f.
- guarded_buffers_->GetBuffer<float>(0)[kChannels * (kFrames - 1)] = 1.f;
- guarded_buffers_->GetBuffer<float>(0)[kChannels * (kFrames - 1) - 1] = 1.f;
- audio_bus_->ZeroFrames(kFrames - 1, kFrames);
- EXPECT_EQ(guarded_buffers_->GetBuffer<float>(0)[kChannels * (kFrames - 1)],
- 0.f);
- EXPECT_EQ(
- guarded_buffers_->GetBuffer<float>(0)[kChannels * (kFrames - 1) - 1],
- 1.f);
-
- audio_bus_->ZeroFrames(1, kFrames - 1);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames * kChannels, 0.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- CreateAudioBus<int16>(ShellAudioBus::kPlanar);
- for (size_t channel = 0; channel < audio_bus_->channels(); ++channel) {
- // Mark the first two samples of the channel as 100.
- guarded_buffers_->GetBuffer<int16>(channel)[0] = 100;
- guarded_buffers_->GetBuffer<int16>(channel)[1] = 100;
- audio_bus_->ZeroFrames(0, 1);
- EXPECT_EQ(guarded_buffers_->GetBuffer<int16>(channel)[0], 0);
- EXPECT_EQ(guarded_buffers_->GetBuffer<int16>(channel)[1], 100);
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- // Mark the last two samples of the channel as 100.
- guarded_buffers_->GetBuffer<int16>(channel)[kFrames - 1] = 100;
- guarded_buffers_->GetBuffer<int16>(channel)[kFrames - 2] = 100;
- audio_bus_->ZeroFrames(kFrames - 1, kFrames);
- EXPECT_EQ(guarded_buffers_->GetBuffer<int16>(channel)[kFrames - 1], 0);
- EXPECT_EQ(guarded_buffers_->GetBuffer<int16>(channel)[kFrames - 2], 100);
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
-
- audio_bus_->ZeroFrames(1, kFrames - 1);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames, static_cast<int16>(0)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-}
-
-TEST_F(ShellAudioBusTest, AssignWithSameSampleTypeAndStorageType) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- FillValues(guarded_buffers_->GetBuffer<float>(0), kFrames * kChannels, 1.f);
- {
- ShellAudioBus source(kChannels, kFrames / 2, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames / 2 * kChannels, 0.f));
- EXPECT_TRUE(VerifyValues(
- guarded_buffers_->GetBuffer<float>(0) + kFrames / 2 * kChannels,
- (kFrames - kFrames / 2) * kChannels, 1.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames * kChannels, 0.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames * 2, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames * kChannels, 0.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- CreateAudioBus<int16>(ShellAudioBus::kPlanar);
- for (size_t channel = 0; channel < audio_bus_->channels(); ++channel) {
- FillValues(guarded_buffers_->GetBuffer<int16>(channel), kFrames,
- static_cast<int16>(100));
- {
- ShellAudioBus source(kChannels, kFrames / 2, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames / 2, static_cast<int16>(0)));
- EXPECT_TRUE(VerifyValues(
- guarded_buffers_->GetBuffer<int16>(channel) + kFrames / 2,
- kFrames - kFrames / 2, static_cast<int16>(100)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames, static_cast<int16>(0)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames * 2, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames, static_cast<int16>(0)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
- }
-}
-
-TEST_F(ShellAudioBusTest, AssignWithDifferentSampleTypesAndStorageTypes) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- FillValues(guarded_buffers_->GetBuffer<float>(0), kFrames * kChannels, 1.f);
- {
- ShellAudioBus source(kChannels, kFrames / 2, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames / 2 * kChannels, 0.f));
- EXPECT_TRUE(VerifyValues(
- guarded_buffers_->GetBuffer<float>(0) + kFrames / 2 * kChannels,
- (kFrames - kFrames / 2) * kChannels, 1.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames * kChannels, 0.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames * 2, ShellAudioBus::kInt16,
- ShellAudioBus::kPlanar);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<float>(0),
- kFrames * kChannels, 0.f));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- CreateAudioBus<int16>(ShellAudioBus::kPlanar);
- for (size_t channel = 0; channel < audio_bus_->channels(); ++channel) {
- FillValues(guarded_buffers_->GetBuffer<int16>(channel), kFrames,
- static_cast<int16>(100));
- {
- ShellAudioBus source(kChannels, kFrames / 2, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames / 2, static_cast<int16>(0)));
- EXPECT_TRUE(VerifyValues(
- guarded_buffers_->GetBuffer<int16>(channel) + kFrames / 2,
- kFrames - kFrames / 2, static_cast<int16>(100)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames, static_cast<int16>(0)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-
- {
- ShellAudioBus source(kChannels, kFrames * 2, ShellAudioBus::kFloat32,
- ShellAudioBus::kInterleaved);
- source.ZeroAllFrames();
- audio_bus_->Assign(source);
- EXPECT_TRUE(VerifyValues(guarded_buffers_->GetBuffer<int16>(channel),
- kFrames, static_cast<int16>(0)));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
- }
-}
-
-TEST_F(ShellAudioBusTest, AssignWithMatrix) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- // Fill the 1st channel with incrementally positive samples, fill the 2nd
- // channel with decrementally negative samples, and fill the 3rd channel with
- // 0.
- for (size_t channel = 0; channel < kChannels; ++channel) {
- for (size_t frame = 0; frame < kFrames; ++frame) {
- float sample = 0.f;
- if (channel == 0) {
- sample = static_cast<float>(frame) / static_cast<float>(kFrames);
- } else if (channel == 1) {
- sample = -static_cast<float>(frame) / static_cast<float>(kFrames);
- }
- guarded_buffers_->GetBuffer<float>(0)[frame * kChannels + channel] =
- sample;
- }
- }
- {
- ShellAudioBus other(kChannels * 2, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kPlanar);
- const float kConversionMatrix[] = {
- 0.f, 1.f, 0.f, // dest channel 0 = source channel 1.
- 1.f, 0.f, 0.f, // dest channel 1 = source channel 0.
- 1.f, 1.f, 1.f, // dest channel 2 = sum of all source channels,
- // effectively make it equal to source channel 2.
- -1.f, 0.f, 0.f, // dest channel 3 = negative source channel 0,
- // effectively make it equal to source channel 1.
- 0.f, -1.f, 0.f, // dest channel 4 = negative source channel 1,
- // effectively make it equal to source channel 0.
- 2.f, 1.f, -1.f, // dest channel 5 = 2 * source channel 0 + source
- // channel 2 - source channel 2, effectively make it
- // equal to source channel 0.
- };
- std::vector<float> matrix(kConversionMatrix,
- kConversionMatrix + arraysize(kConversionMatrix));
- other.Assign(*audio_bus_, matrix);
-
- EXPECT_TRUE(IsSameChannel(other, 0, *audio_bus_, 1));
- EXPECT_TRUE(IsSameChannel(other, 1, *audio_bus_, 0));
- EXPECT_TRUE(IsSameChannel(other, 2, *audio_bus_, 2));
- EXPECT_TRUE(IsSameChannel(other, 3, *audio_bus_, 1));
- EXPECT_TRUE(IsSameChannel(other, 4, *audio_bus_, 0));
- EXPECT_TRUE(IsSameChannel(other, 5, *audio_bus_, 0));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-}
-
-TEST_F(ShellAudioBusTest, MixWithoutMatrix) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- // Fill the 1st channel with incrementally positive samples, fill the 2nd
- // channel with decrementally negative samples, and fill the 3rd channel with
- // 0.
- for (size_t channel = 0; channel < kChannels; ++channel) {
- for (size_t frame = 0; frame < kFrames; ++frame) {
- float sample = 0.f;
- if (channel == 0) {
- sample = static_cast<float>(frame) / static_cast<float>(kFrames);
- } else if (channel == 1) {
- sample = -static_cast<float>(frame) / static_cast<float>(kFrames);
- }
- guarded_buffers_->GetBuffer<float>(0)[frame * kChannels + channel] =
- sample;
- }
- }
- {
- ShellAudioBus other(kChannels, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kPlanar);
- other.Assign(*audio_bus_);
- // By call Mix() here, we effectively multiplies every sample in |other| by
- // 2.
- other.Mix(*audio_bus_);
- // Adjust the original audio bus by multiplying every sample by 2.
- for (size_t channel = 0; channel < kChannels; ++channel) {
- for (size_t frame = 0; frame < kFrames; ++frame) {
- guarded_buffers_->GetBuffer<float>(0)[frame * kChannels + channel] *= 2;
- }
- }
-
- EXPECT_TRUE(IsSameChannel(other, 0, *audio_bus_, 0));
- EXPECT_TRUE(IsSameChannel(other, 1, *audio_bus_, 1));
- EXPECT_TRUE(IsSameChannel(other, 2, *audio_bus_, 2));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-}
-
-TEST_F(ShellAudioBusTest, MixWithMatrix) {
- CreateAudioBus<float>(ShellAudioBus::kInterleaved);
- // Fill the 1st channel with incrementally positive samples, fill the 2nd
- // channel with decrementally negative samples, and fill the 3rd channel with
- // 0.
- for (size_t channel = 0; channel < kChannels; ++channel) {
- for (size_t frame = 0; frame < kFrames; ++frame) {
- float sample = 0.f;
- if (channel == 0) {
- sample = static_cast<float>(frame) / static_cast<float>(kFrames);
- } else if (channel == 1) {
- sample = -static_cast<float>(frame) / static_cast<float>(kFrames);
- }
- guarded_buffers_->GetBuffer<float>(0)[frame * kChannels + channel] =
- sample;
- }
- }
- {
- ShellAudioBus other(kChannels * 2, kFrames, ShellAudioBus::kFloat32,
- ShellAudioBus::kPlanar);
- const float kConversionMatrix[] = {
- 0.f, 1.f, 0.f, // dest channel 0 = source channel 1.
- 1.f, 0.f, 0.f, // dest channel 1 = source channel 0.
- 1.f, 1.f, 1.f, // dest channel 2 = sum of all source channels,
- // effectively make it equal to source channel 2.
- -1.f, 0.f, 0.f, // dest channel 3 = negative source channel 0,
- // effectively make it equal to source channel 1.
- 0.f, -1.f, 0.f, // dest channel 4 = negative source channel 1,
- // effectively make it equal to source channel 0.
- 2.f, 1.f, -1.f, // dest channel 5 = 2 * source channel 0 + source
- // channel 2 - source channel 2, effectively make it
- // equal to source channel 0.
- };
- std::vector<float> matrix(kConversionMatrix,
- kConversionMatrix + arraysize(kConversionMatrix));
- other.Assign(*audio_bus_, matrix);
- // By call Mix() here, we effectively multiplies every sample in |other| by
- // 2.
- other.Mix(*audio_bus_, matrix);
- // Adjust the original audio bus by multiplying every sample by 2.
- for (size_t channel = 0; channel < kChannels; ++channel) {
- for (size_t frame = 0; frame < kFrames; ++frame) {
- guarded_buffers_->GetBuffer<float>(0)[frame * kChannels + channel] *= 2;
- }
- }
-
- EXPECT_TRUE(IsSameChannel(other, 0, *audio_bus_, 1));
- EXPECT_TRUE(IsSameChannel(other, 1, *audio_bus_, 0));
- EXPECT_TRUE(IsSameChannel(other, 2, *audio_bus_, 2));
- EXPECT_TRUE(IsSameChannel(other, 3, *audio_bus_, 1));
- EXPECT_TRUE(IsSameChannel(other, 4, *audio_bus_, 0));
- EXPECT_TRUE(IsSameChannel(other, 5, *audio_bus_, 0));
- EXPECT_TRUE(guarded_buffers_->VerifyGuardBytes());
- }
-}
-
-} // namespace
-} // namespace testing
-} // namespace media
diff --git a/src/media/base/shell_buffer_factory.cc b/src/media/base/shell_buffer_factory.cc
deleted file mode 100644
index dc78fe9..0000000
--- a/src/media/base/shell_buffer_factory.cc
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_buffer_factory.h"
-
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/stringprintf.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/shell_media_platform.h"
-#include "media/base/shell_media_statistics.h"
-
-namespace media {
-
-// ==== ShellScopedArray =======================================================
-
-ShellScopedArray::ShellScopedArray(uint8* reusable_buffer, size_t size)
- : array_(reusable_buffer), size_(size) {
- if (array_) {
- // Retain a reference to the buffer factory, to ensure that we do not
- // outlive it.
- buffer_factory_ = ShellBufferFactory::Instance();
- }
-}
-
-ShellScopedArray::~ShellScopedArray() {
- TRACE_EVENT0("media_stack", "ShellScopedArray::~ShellScopedArray()");
- if (array_) {
- buffer_factory_->Reclaim(array_);
- }
-}
-
-// ==== ShellBufferFactory =====================================================
-
-scoped_refptr<ShellBufferFactory> ShellBufferFactory::instance_ = NULL;
-
-// static
-void ShellBufferFactory::Initialize() {
- // safe to call multiple times
- if (!instance_) {
- instance_ = new ShellBufferFactory();
- }
-}
-
-bool ShellBufferFactory::AllocateBuffer(size_t size,
- bool is_keyframe,
- AllocCB cb) {
- TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateBuffer()", "size",
- size);
- // Zero-size buffers are allocation error, allocate an EOS buffer explicity
- // with the provided EOS method.
- if (size == 0) {
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateBuffer() failed as size is 0.");
- return false;
- }
-
- // If we can allocate a buffer right now save a pointer to it so that we don't
- // run the callback while holding the memory lock, for safety's sake.
- scoped_refptr<DecoderBuffer> instant_buffer = NULL;
-
- {
- base::AutoLock lock(lock_);
- // We only service requests directly if there's no callbacks pending and
- // we can accommodate a buffer of the requested size
- if (pending_allocs_.size() == 0) {
- uint8* bytes = Allocate_Locked(size);
- if (bytes) {
- instant_buffer = new DecoderBuffer(bytes, size, is_keyframe);
- TRACE_EVENT0(
- "media_stack",
- "ShellBufferFactory::AllocateBuffer() finished allocation.");
- DCHECK(!instant_buffer->IsEndOfStream());
- }
- }
- if (!instant_buffer) {
- // Alright, we have to wait, enqueue the buffer and size.
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateBuffer() deferred.");
- pending_allocs_.push_back(
- std::make_pair(cb, new DecoderBuffer(NULL, size, is_keyframe)));
- }
- }
-
- // If we managed to create a buffer run the callback after releasing the lock.
- if (instant_buffer) {
- cb.Run(instant_buffer);
- }
- return true;
-}
-
-scoped_refptr<DecoderBuffer> ShellBufferFactory::AllocateBufferNow(
- size_t size,
- bool is_keyframe) {
- TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateBufferNow()", "size",
- size);
- // Zero-size buffers are allocation error, allocate an EOS buffer explicity
- // with the provided EOS method.
- if (size == 0) {
- TRACE_EVENT0(
- "media_stack",
- "ShellBufferFactory::AllocateBufferNow() failed as size is 0.");
- return NULL;
- }
-
- base::AutoLock lock(lock_);
- uint8* bytes = Allocate_Locked(size);
- if (!bytes) {
- TRACE_EVENT0(
- "media_stack",
- "ShellBufferFactory::AllocateBufferNow() failed as size is too large.");
- return NULL;
- }
- scoped_refptr<DecoderBuffer> buffer =
- new DecoderBuffer(bytes, size, is_keyframe);
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateBufferNow() finished allocation.");
- DCHECK(!buffer->IsEndOfStream());
-
- return buffer;
-}
-
-uint8* ShellBufferFactory::AllocateNow(size_t size) {
- // we skip to the head of the line for these allocations, if there's
- // room we allocate it.
- base::AutoLock lock(lock_);
- uint8* bytes = Allocate_Locked(size);
-
- if (!bytes) {
- DLOG(ERROR) << base::StringPrintf("Failed to allocate %d bytes!",
- (int)size);
- }
-
- return bytes;
-}
-
-scoped_refptr<ShellScopedArray> ShellBufferFactory::AllocateArray(size_t size) {
- TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateArray()", "size",
- size);
- uint8* allocated_bytes = NULL;
- if (size == 0) {
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateArray() failed as size is 0.");
- return NULL;
- }
-
- if (size <= kShellMaxArraySize) {
- base::AutoLock lock(lock_);
- // there should not already be somebody waiting on an array
- if (array_requested_size_ > 0) {
- TRACE_EVENT0(
- "media_stack",
- "ShellBufferFactory::AllocateArray() failed as another allocation is"
- " in progress.");
- NOTREACHED() << "Max one thread blocking on array allocation at a time.";
- return NULL;
- }
- // Attempt to allocate.
- allocated_bytes = Allocate_Locked(size);
- // If we don't have room save state while we still have the lock
- if (!allocated_bytes) {
- array_requested_size_ = size;
- }
- } else { // oversized requests always fail instantly.
- TRACE_EVENT0(
- "media_stack",
- "ShellBufferFactory::AllocateArray() failed as size is too large.");
- return NULL;
- }
- // Lock is released. Now safe to block this thread if we need to.
- if (!allocated_bytes) {
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateArray() deferred.");
- // Wait until enough memory has been released to service this allocation.
- array_allocation_event_.Wait();
- {
- // acquire lock to get address and clear requested size
- base::AutoLock lock(lock_);
- // make sure this allocation makes sense
- DCHECK_EQ(size, array_requested_size_);
- DCHECK(array_allocation_);
- allocated_bytes = array_allocation_;
- array_allocation_ = NULL;
- array_requested_size_ = 0;
- }
- }
- // Whether we blocked or not we should now have a pointer
- DCHECK(allocated_bytes);
- TRACE_EVENT0("media_stack",
- "ShellBufferFactory::AllocateArray() finished allocation.");
- return scoped_refptr<ShellScopedArray>(
- new ShellScopedArray(allocated_bytes, size));
-}
-
-void ShellBufferFactory::Reclaim(uint8* p) {
- TRACE_EVENT0("media_stack", "ShellBufferFactory::Reclaim()");
- typedef std::list<std::pair<AllocCB, scoped_refptr<DecoderBuffer> > >
- FinishList;
- FinishList finished_allocs;
-
- // Reclaim() on a NULL buffer is a no-op, don't even acquire the lock.
- if (p) {
- base::AutoLock lock(lock_);
- ShellMediaPlatform::Instance()->FreeBuffer(p);
-
- // Try to service a blocking array request if there is one, and it hasn't
- // already been serviced. If we can't service it then we won't allocate any
- // additional ShellBuffers as arrays get priority treatment.
- bool service_buffers = true;
- if (array_requested_size_ > 0 && !array_allocation_) {
- array_allocation_ = Allocate_Locked(array_requested_size_);
- if (array_allocation_) {
- // Wake up blocked thread
- array_allocation_event_.Signal();
- } else {
- // Not enough room for the array so don't give away what room we have
- // to the buffers.
- service_buffers = false;
- }
- }
- // Try to process any enqueued allocs in FIFO order until we run out of room
- while (service_buffers && pending_allocs_.size()) {
- size_t size = pending_allocs_.front().second->GetAllocatedSize();
- uint8* bytes = Allocate_Locked(size);
- if (bytes) {
- scoped_refptr<DecoderBuffer> alloc_buff =
- pending_allocs_.front().second;
- alloc_buff->SetBuffer(bytes);
- TRACE_EVENT1("media_stack",
- "ShellBufferFactory::Reclaim() finished allocation.",
- "size", size);
- finished_allocs.push_back(
- std::make_pair(pending_allocs_.front().first, alloc_buff));
- pending_allocs_.pop_front();
- } else {
- service_buffers = false;
- }
- }
- }
- // OK, lock released, do callbacks for finished allocs
- for (FinishList::iterator it = finished_allocs.begin();
- it != finished_allocs.end(); ++it) {
- it->first.Run(it->second);
- }
-}
-
-uint8* ShellBufferFactory::Allocate_Locked(size_t size) {
- // should have acquired the lock already
- lock_.AssertAcquired();
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_ALLOCATED_SHELL_BUFFER_SIZE, size);
- return static_cast<uint8*>(
- ShellMediaPlatform::Instance()->AllocateBuffer(size));
-}
-
-// static
-void ShellBufferFactory::Terminate() {
- instance_ = NULL;
-}
-
-ShellBufferFactory::ShellBufferFactory()
- : array_allocation_event_(false, false),
- array_requested_size_(0),
- array_allocation_(NULL) {}
-
-// Will be called when all ShellBuffers have been deleted AND instance_ has
-// been set to NULL.
-ShellBufferFactory::~ShellBufferFactory() {
- // and no outstanding array requests
- DCHECK_EQ(array_requested_size_, 0);
-}
-
-} // namespace media
diff --git a/src/media/base/shell_buffer_factory.h b/src/media/base/shell_buffer_factory.h
deleted file mode 100644
index 8a18156..0000000
--- a/src/media/base/shell_buffer_factory.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_BUFFER_FACTORY_H_
-#define MEDIA_BASE_SHELL_BUFFER_FACTORY_H_
-
-#include <list>
-#include <map>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-static const size_t kShellMaxArraySize = 1024 * 1024;
-
-class DecoderBuffer;
-class DecryptConfig;
-class ShellBufferFactory;
-
-// A simple scoped array class designed to re-use the memory allocated by
-// ShellBufferFactory. If needed would be trivial to make generic.
-class MEDIA_EXPORT ShellScopedArray
- : public base::RefCountedThreadSafe<ShellScopedArray> {
- public:
- uint8* Get() { return array_; }
- size_t Size() { return size_; }
-
- private:
- friend class base::RefCountedThreadSafe<ShellScopedArray>;
- friend class ShellBufferFactory;
- // Should only be called by ShellBufferFactory, consumers should use
- // ShellBufferFactory::AllocateArray to allocate a ShellScopedArray
- ShellScopedArray(uint8* resuable_buffer, size_t size);
- virtual ~ShellScopedArray();
- uint8* array_;
- size_t size_;
- scoped_refptr<ShellBufferFactory> buffer_factory_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ShellScopedArray);
-};
-
-// Singleton instance class for the management and recycling of media-related
-// buffers. It is assumed that the usage pattern of these buffers is that they
-// are used for only a short amount of time, and their allocation is roughly
-// cyclical. All buffers are allocated using the alignment constants defined
-// above, and all buffers are allocated from the shared pool of memory of
-// size defined above.
-class MEDIA_EXPORT ShellBufferFactory
- : public base::RefCountedThreadSafe<ShellBufferFactory> {
- public:
- static void Initialize();
- static inline scoped_refptr<ShellBufferFactory> Instance() {
- return instance_;
- }
-
- typedef base::Callback<void(scoped_refptr<DecoderBuffer>)> AllocCB;
- // Returns false if the allocator will never be able to allocate a buffer
- // of the requested size. Note that if memory is currently available this
- // function will call the callback provided _before_ returning true.
- bool AllocateBuffer(size_t size, bool is_keyframe, AllocCB cb);
- // This function tries to allocate a DecoderBuffer immediately. It returns
- // NULL on failure.
- scoped_refptr<DecoderBuffer> AllocateBufferNow(size_t size, bool is_keyframe);
- // Returns a newly allocated byte field if there's room for it, or NULL if
- // there isn't. Note that this raw allocation method provides no guarantee
- // that ShellBufferFactory will still exist when the memory is to be freed.
- // If that is important please retain a reference to the buffer factory
- // (using Instance()) until the memory is to be reclaimed.
- uint8* AllocateNow(size_t size);
- // BLOCKS THE CALLING THREAD until an array of size is available and can be
- // allocated. We only allow one thread to block on an array allocation at a
- // time, all subsequents calls on other threads to AllocateArray will assert
- // and return NULL.
- scoped_refptr<ShellScopedArray> AllocateArray(size_t size);
-
- // Only called by DecoderBuffer and ShellScopedArray, informs the factory
- // that these objects have gone out of scoped and we can reclaim the memory
- void Reclaim(uint8* p);
-
- static void Terminate();
-
- private:
- friend class base::RefCountedThreadSafe<ShellBufferFactory>;
- ShellBufferFactory();
- ~ShellBufferFactory();
- uint8* Allocate_Locked(size_t aligned_size);
-
- static scoped_refptr<ShellBufferFactory> instance_;
-
- // protects all following members.
- base::Lock lock_;
-
- // queue of pending buffer allocation requests and their sizes
- typedef std::list<std::pair<AllocCB, scoped_refptr<DecoderBuffer> > >
- AllocList;
- AllocList pending_allocs_;
-
- // event used for blocking calls for array allocation
- base::WaitableEvent array_allocation_event_;
- // set to 0 when no thread is blocking on an array allocation
- size_t array_requested_size_;
- // set to an allocation address when allocation has succeeded
- uint8* array_allocation_;
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_BUFFER_FACTORY_H_
diff --git a/src/media/base/shell_cached_decoder_buffer.cc b/src/media/base/shell_cached_decoder_buffer.cc
deleted file mode 100644
index 28873ba..0000000
--- a/src/media/base/shell_cached_decoder_buffer.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_cached_decoder_buffer.h"
-
-namespace media {
-
-ShellCachedDecoderBuffer::ShellCachedDecoderBuffer(
- const scoped_refptr<media::DecoderBuffer>& source_buffer,
- void* destination,
- FreeCB free_cb)
- : media::DecoderBuffer(NULL, 0, source_buffer->IsKeyframe()),
- source_buffer_(source_buffer),
- free_cb_(free_cb) {
- DCHECK(source_buffer);
- DCHECK(destination);
- DCHECK(!free_cb.is_null());
- DCHECK(!source_buffer->IsEndOfStream());
-
- SetTimestamp(source_buffer->GetTimestamp());
- SetDuration(source_buffer->GetDuration());
-
- buffer_ = static_cast<uint8*>(destination);
- memcpy(buffer_, source_buffer_->GetData(), source_buffer_->GetDataSize());
- size_ = source_buffer_->GetDataSize();
-
- // The buffer is not expandable.
- allocated_size_ = source_buffer_->GetDataSize();
- is_decrypted_ = source_buffer_->IsAlreadyDecrypted();
-}
-
-ShellCachedDecoderBuffer::~ShellCachedDecoderBuffer() {
- free_cb_.Run(buffer_);
- // Set the buffer_ to NULL to stop the base class dtor from freeing it.
- buffer_ = NULL;
-}
-
-} // namespace media
diff --git a/src/media/base/shell_cached_decoder_buffer.h b/src/media/base/shell_cached_decoder_buffer.h
deleted file mode 100644
index db3baad..0000000
--- a/src/media/base/shell_cached_decoder_buffer.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_SHELL_CACHED_DECODER_BUFFER_H_
-#define MEDIA_SHELL_CACHED_DECODER_BUFFER_H_
-
-#include "base/callback.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/decoder_buffer.h"
-
-namespace media {
-
-// This class can cache the content of |source_buffer| into main memory. So it
-// is possible to store DecoderBuffer in memory space that cannot be accessed by
-// the decoder and copy them over to main memory just before they are decoded.
-class ShellCachedDecoderBuffer : public media::DecoderBuffer {
- public:
- // Callback to free memory passed in.
- typedef base::Callback<void(void*)> FreeCB;
-
- ShellCachedDecoderBuffer(
- const scoped_refptr<media::DecoderBuffer>& source_buffer,
- void* destination,
- FreeCB free_cb);
- ~ShellCachedDecoderBuffer();
-
- const media::DecryptConfig* GetDecryptConfig() const OVERRIDE {
- return source_buffer_->GetDecryptConfig();
- }
- void SetDecryptConfig(scoped_ptr<media::DecryptConfig>) OVERRIDE {
- NOTREACHED();
- }
-
- private:
- scoped_refptr<media::DecoderBuffer> source_buffer_;
- FreeCB free_cb_;
-};
-
-} // namespace media
-
-#endif // MEDIA_SHELL_CACHED_DECODER_BUFFER_H_
diff --git a/src/media/base/shell_data_source_reader.cc b/src/media/base/shell_data_source_reader.cc
deleted file mode 100644
index 00e65f1..0000000
--- a/src/media/base/shell_data_source_reader.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_data_source_reader.h"
-
-#include <limits.h> // for ULLONG_MAX
-
-namespace media {
-
-const int ShellDataSourceReader::kReadError = DataSource::kReadError;
-
-ShellDataSourceReader::ShellDataSourceReader()
- : data_source_(NULL),
- blocking_read_event_(false, false),
- file_size_(-1),
- read_has_failed_(false),
- last_bytes_read_(0) {}
-
-ShellDataSourceReader::~ShellDataSourceReader() {}
-
-void ShellDataSourceReader::SetDataSource(DataSource* data_source) {
- DCHECK(data_source);
- data_source_ = data_source;
-}
-
-// currently only single-threaded reads supported
-int ShellDataSourceReader::BlockingRead(int64 position, int size, uint8* data) {
- // read failures are unrecoverable, all subsequent reads will also fail
- if (read_has_failed_) {
- return kReadError;
- }
-
- // check bounds of read at or past EOF
- if (file_size_ >= 0 && position >= file_size_) {
- return 0;
- }
-
- int total_bytes_read = 0;
- while (size > 0 && !read_has_failed_) {
- {
- base::AutoLock auto_lock(lock_);
- if (!data_source_) {
- break;
- }
- data_source_->Read(
- position, size, data,
- base::Bind(&ShellDataSourceReader::BlockingReadCompleted, this));
- }
-
- // wait for callback on read completion
- blocking_read_event_.Wait();
-
- if (last_bytes_read_ == DataSource::kReadError) {
- // make all future reads fail
- read_has_failed_ = true;
- return kReadError;
- }
-
- DCHECK_LE(last_bytes_read_, size);
- if (last_bytes_read_ > size) {
- // make all future reads fail
- read_has_failed_ = true;
- return kReadError;
- }
-
- // Avoid entering an endless loop here.
- if (last_bytes_read_ == 0)
- break;
-
- total_bytes_read += last_bytes_read_;
- position += last_bytes_read_;
- size -= last_bytes_read_;
- data += last_bytes_read_;
- }
-
- if (read_has_failed_) {
- return kReadError;
- }
- return total_bytes_read;
-}
-
-void ShellDataSourceReader::Stop(const base::Closure& callback) {
- if (data_source_) {
- // stop the data source, it can call the callback
- data_source_->Stop();
-
- base::AutoLock auto_lock(lock_);
- data_source_ = NULL;
- }
- callback.Run();
-}
-
-void ShellDataSourceReader::BlockingReadCompleted(int bytes_read) {
- last_bytes_read_ = bytes_read;
- // wake up blocked thread
- blocking_read_event_.Signal();
-}
-
-int64 ShellDataSourceReader::FileSize() {
- if (file_size_ == -1) {
- base::AutoLock auto_lock(lock_);
- if (data_source_ && !data_source_->GetSize(&file_size_)) {
- file_size_ = -1;
- }
- }
- return file_size_;
-}
-
-} // namespace media
diff --git a/src/media/base/shell_data_source_reader.h b/src/media/base/shell_data_source_reader.h
deleted file mode 100644
index 9e50be7..0000000
--- a/src/media/base/shell_data_source_reader.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
-#define MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/message_loop.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/base/data_source.h"
-
-namespace media {
-
-// Allows sharing of a DataSource object between multiple objects on a single
-// thread, and exposes a simple BlockingRead() method to block the thread until
-// data is available or error. To avoid circular smart pointer references this
-// object is also the sole owner of a pointer to DataSource. If we want to add
-// asynchronous reading to this object it will need its own thread and a
-// callback queue.
-class ShellDataSourceReader
- : public base::RefCountedThreadSafe<ShellDataSourceReader> {
- public:
- static const int kReadError;
-
- ShellDataSourceReader();
- virtual void SetDataSource(DataSource* data_source);
-
- // Block the calling thread's message loop until read is complete.
- // returns number of bytes read or kReadError on error.
- // Currently only single-threaded support.
- virtual int BlockingRead(int64 position, int size, uint8* data);
-
- // returns size of file in bytes, or -1 if file size not known. If error will
- // retry getting file size on subsequent calls to FileSize().
- virtual int64 FileSize();
-
- // abort any pending read, then stop the data source
- virtual void Stop(const base::Closure& callback);
-
- protected:
- friend class base::RefCountedThreadSafe<ShellDataSourceReader>;
- virtual ~ShellDataSourceReader();
- // blocking read callback
- virtual void BlockingReadCompleted(int bytes_read);
-
- base::Lock lock_;
- DataSource* data_source_;
- base::WaitableEvent blocking_read_event_;
- int64 file_size_;
- bool read_has_failed_;
- int last_bytes_read_; // protected implicitly by blocking_read_event_
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
diff --git a/src/media/base/shell_filter_graph_log_constants.h b/src/media/base/shell_filter_graph_log_constants.h
deleted file mode 100644
index 2faa11d..0000000
--- a/src/media/base/shell_filter_graph_log_constants.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_FILTER_GRAPH_LOG_CONSTANTS_H_
-#define MEDIA_BASE_SHELL_FILTER_GRAPH_LOG_CONSTANTS_H_
-
-namespace media {
-
-// 4 bytes for object type and 4 bytes for signal type following mp4-like
-// binary conventions of packed UTF-8 characters in machine-endian quads.
-
-// this file is also parsed by the python-based log pretty-printer tool.
-// to support easy python parsing, all values should be in hex, and all
-// names should be prefixed with one of ObjectId,Event,or State.
-
-static const uint32 kObjectIdBufferFactory = 0x62756672; // 'bufr'
-static const uint32 kObjectIdDemuxer = 0x646d7578; // 'dmux'
-static const uint32 kObjectIdAudioDemuxerStream = 0x75617364; // 'dsau'
-static const uint32 kObjectIdVideoDemuxerStream = 0x69767364; // 'dsvi'
-static const uint32 kObjectIdAudioDecoder = 0x61646563; // 'adec'
-static const uint32 kObjectIdAudioRenderer = 0x61726e64; // 'arnd'
-static const uint32 kObjectIdAudioSink = 0x6b6e6973; // 'sink'
-static const uint32 kObjectIdVideoDecoder = 0x76646563; // 'vdec'
-static const uint32 kObjectIdVideoRenderer = 0x76726e64; // 'vrnd'
-static const uint32 kObjectIdGraphics = 0x67726166; // 'graf'
-
-static const uint32 kEventArrayAllocationError = 0x61796572; // 'ayer'
-static const uint32 kEventArrayAllocationDeferred = 0x61797774; // 'aywt'
-static const uint32 kEventArrayAllocationReclaim = 0x61797263; // 'ayrc'
-static const uint32 kEventArrayAllocationRequest = 0x61797270; // 'ayrq'
-static const uint32 kEventArrayAllocationSuccess = 0x61796f6b; // 'ayok'
-static const uint32 kEventAudioClock = 0x61636c6b; // 'aclk'
-static const uint32 kEventBufferAllocationError = 0x62666572; // 'bfer'
-static const uint32 kEventBufferAllocationDeferred = 0x62667774; // 'bfwt'
-static const uint32 kEventBufferAllocationReclaim = 0x62667263; // 'bfrc'
-static const uint32 kEventBufferAllocationRequest = 0x62667270; // 'bfrq'
-static const uint32 kEventBufferAllocationSuccess = 0x62666f6b; // 'bfok'
-static const uint32 kEventConstructor = 0x63746f72; // 'ctor'
-static const uint32 kEventDataDecoded = 0x64617461; // 'data'
-static const uint32 kEventDecode = 0x64636f64; // 'dcod'
-static const uint32 kEventDownloadAudio = 0x6c646175; // 'ldau'
-static const uint32 kEventDownloadVideo = 0x6c647664; // 'ldvd
-static const uint32 kEventDropFrame = 0x64726f70; // 'drop'
-static const uint32 kEventEndOfStreamReceived = 0x656f7372; // 'eosr'
-static const uint32 kEventEndOfStreamSent = 0x656f7373; // 'eoss'
-static const uint32 kEventEnqueue = 0x6e717565; // 'nque'
-static const uint32 kEventFatalError = 0x65726f72; // 'eror'
-static const uint32 kEventFlush = 0x666c7368; // 'flsh'
-static const uint32 kEventFrameComposite = 0x64726177; // 'draw'
-static const uint32 kEventFrameFlip = 0x666c6970; // 'flip'
-static const uint32 kEventFreeInputBuffer = 0x6672696e; // 'frin'
-static const uint32 kEventFreeOutputBuffer = 0x66726f75; // 'frou'
-static const uint32 kEventInitialize = 0x696e6974; // 'init'
-static const uint32 kEventOutputBufferFull = 0x66756c6c; // 'full'
-static const uint32 kEventPause = 0x70617573; // 'paus'
-static const uint32 kEventPlay = 0x706c6179; // 'play'
-static const uint32 kEventPop = 0x706f7020; // 'pop '
-static const uint32 kEventPreroll = 0x70726f6c; // 'prol'
-static const uint32 kEventPush = 0x70757368; // 'push'
-static const uint32 kEventRead = 0x72656164; // 'read'
-static const uint32 kEventRender = 0x726e6472; // 'rndr'
-static const uint32 kEventRequestAudio = 0x72657161; // 'reqa'
-static const uint32 kEventRequestInterrupt = 0x69726570; // 'ireq'
-static const uint32 kEventRequestVideo = 0x72657176; // 'reqv'
-static const uint32 kEventReset = 0x72736574; // 'rset'
-static const uint32 kEventResume = 0x7273756d; // 'rsum'
-static const uint32 kEventSeek = 0x7365656b; // 'seek'
-static const uint32 kEventStart = 0x73747274; // 'strt'
-static const uint32 kEventStop = 0x73746f70; // 'stop'
-static const uint32 kEventTimeCallback = 0x74696d65; // 'time'
-static const uint32 kEventUnderflow = 0x75666c77; // 'uflw'
-static const uint32 kEventViewHostComposite = 0x76686365; // 'vhce'
-static const uint32 kEventWebKitComposite = 0x776b6365; // 'wkce'
-
-// instead of timestamp the following state flags log individual pipeline
-// state information.
-// two uint32s of buffer queue size and read cb queue size
-static const uint32 kStateDemuxerStreamQueues = 0x73657571; // 'ques'
-// one uint32 either zero or one depending on state, and a zero
-static const uint32 kStateDemuxerStreamBuffering = 0x66667562; // 'buff'
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_FILTER_GRAPH_LOG_CONSTANTS_H_
diff --git a/src/media/base/shell_media_platform.cc b/src/media/base/shell_media_platform.cc
deleted file mode 100644
index 3d33287..0000000
--- a/src/media/base/shell_media_platform.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_media_platform.h"
-
-namespace {
-
-media::ShellMediaPlatform* s_shell_media_platform_;
-
-} // namespace
-
-namespace media {
-
-// static
-ShellMediaPlatform* ShellMediaPlatform::Instance() {
- return s_shell_media_platform_;
-}
-
-// static
-void ShellMediaPlatform::SetInstance(ShellMediaPlatform* shell_media_platform) {
- s_shell_media_platform_ = shell_media_platform;
-}
-
-} // namespace media
diff --git a/src/media/base/shell_media_platform.h b/src/media/base/shell_media_platform.h
deleted file mode 100644
index 86f2d79..0000000
--- a/src/media/base/shell_media_platform.h
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_MEDIA_PLATFORM_H_
-#define MEDIA_BASE_SHELL_MEDIA_PLATFORM_H_
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/limits.h"
-#include "media/base/media_export.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_video_data_allocator.h"
-#include "media/base/shell_video_frame_provider.h"
-#include "starboard/decode_target.h"
-
-namespace cobalt {
-namespace render_tree {
-class ResourceProvider;
-} // namespace render_tree
-} // namespace cobalt
-
-namespace media {
-
-// This class is meant to be the single point to attach platform specific media
-// classes and settings. Each platform should implement its own implementation
-// and provide it through ShellMediaPlatform::Instance.
-class MEDIA_EXPORT ShellMediaPlatform {
- public:
- ShellMediaPlatform() {}
- virtual ~ShellMediaPlatform() {}
-
- // Individual platforms should implement the following two functions to
- // ensure that a valid ShellMediaPlatform instance is available during the
- // life time of media stack
- static void Initialize();
- static void Terminate();
-
- static ShellMediaPlatform* Instance();
-
- // The following functions will be called when the application enters or
- // leaves suspending status.
- virtual void Suspend() {}
- virtual void Resume(
- cobalt::render_tree::ResourceProvider* /*resource_provider*/) {}
-
- // Media stack buffer allocate/free functions currently only used by
- // ShellBufferFactory.
- virtual void* AllocateBuffer(size_t size) = 0;
- virtual void FreeBuffer(void* ptr) = 0;
-
- // The maximum audio and video buffer size used by SourceBufferStream.
- // See implementation of SourceBufferStream for more details.
- virtual size_t GetSourceBufferStreamAudioMemoryLimit() const = 0;
- virtual size_t GetSourceBufferStreamVideoMemoryLimit() const = 0;
-
- virtual scoped_refptr<ShellVideoFrameProvider> GetVideoFrameProvider() {
- return NULL;
- }
-
-#if SB_API_VERSION >= 4
- virtual SbDecodeTargetGraphicsContextProvider*
- GetSbDecodeTargetGraphicsContextProvider() { return NULL; }
-#elif SB_API_VERSION >= 3
- virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() { return NULL; }
-#endif // SB_API_VERSION >= 4
-
- // Total number of video frames which are populating in the pipeline when
- // prerolling.
- // You can expect more start delay by increasing this.
- virtual int GetMaxVideoPrerollFrames() const {
- return limits::kMaxVideoFrames;
- }
- // When the video frame backlog contains less frames than this value, the
- // video renderer will send out underflow notification to the video decoder.
- virtual int GetVideoUnderflowFrames() const {
- return GetMaxVideoPrerollFrames();
- }
- // Total number of video frames which are populating in the pipeline.
- // You can expect more memory usage and less jitter by increasing this.
- virtual int GetMaxVideoFrames() const { return limits::kMaxVideoFrames; }
-
- // This function is called before the decoder buffer leaves the demuxer and
- // is being sent to the media pipeline for decrypting and decoding. The
- // default implementation simply returns the buffer indicateing that there is
- // no processing necessary.
- virtual scoped_refptr<DecoderBuffer> ProcessBeforeLeavingDemuxer(
- const scoped_refptr<DecoderBuffer>& buffer) {
- return buffer;
- }
-
- // Returns true if output is protected (i.e. HDCP is present).
- virtual bool IsOutputProtected() = 0;
-
- protected:
- static void SetInstance(ShellMediaPlatform* shell_media_platform);
-
- private:
- // Platform specific media Init and Tear down.
- virtual void InternalInitialize() {}
- virtual void InternalTerminate() {}
-
- DISALLOW_COPY_AND_ASSIGN(ShellMediaPlatform);
-};
-
-} // namespace media
-
-#define REGISTER_SHELL_MEDIA_PLATFORM(ClassName) \
- namespace media { \
- void ShellMediaPlatform::Initialize() { \
- DCHECK(!Instance()); \
- SetInstance(new ClassName); \
- ShellBufferFactory::Initialize(); \
- Instance()->InternalInitialize(); \
- } \
- void ShellMediaPlatform::Terminate() { \
- DCHECK(Instance()); \
- Instance()->InternalTerminate(); \
- ShellBufferFactory::Terminate(); \
- delete Instance(); \
- SetInstance(NULL); \
- } \
- } // namespace media
-
-#endif // MEDIA_BASE_SHELL_MEDIA_PLATFORM_H_
diff --git a/src/media/base/shell_media_statistics.cc b/src/media/base/shell_media_statistics.cc
deleted file mode 100644
index 14f6b82..0000000
--- a/src/media/base/shell_media_statistics.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_media_statistics.h"
-
-#include <limits>
-
-#include "base/basictypes.h"
-
-namespace media {
-
-ShellMediaStatistics::ShellMediaStatistics() {
- Reset(true); // reset all stats, include global stats.
-}
-
-void ShellMediaStatistics::OnPlaybackBegin() {
- Reset(false); // reset non-global stats.
-}
-
-void ShellMediaStatistics::record(StatType type, int64 value) {
- if (type == STAT_TYPE_VIDEO_WIDTH)
- type = STAT_TYPE_VIDEO_WIDTH;
- ++stats_[type].times_;
- stats_[type].total_ += value;
- if (value > stats_[type].max_)
- stats_[type].max_ = value;
- if (value < stats_[type].min_)
- stats_[type].min_ = value;
- stats_[type].current_ = value;
-}
-
-void ShellMediaStatistics::record(StatType type,
- const base::TimeDelta& duration) {
- record(type, duration.ToInternalValue());
-}
-
-double ShellMediaStatistics::GetElapsedTime() const {
- return (base::Time::Now() - start_).InSecondsF();
-}
-
-int64 ShellMediaStatistics::GetTimes(StatType type) const {
- return stats_[type].times_;
-}
-
-int64 ShellMediaStatistics::GetTotal(StatType type) const {
- return stats_[type].total_;
-}
-
-int64 ShellMediaStatistics::GetCurrent(StatType type) const {
- return stats_[type].current_;
-}
-
-int64 ShellMediaStatistics::GetAverage(StatType type) const {
- if (stats_[type].times_ == 0)
- return 0;
- return stats_[type].total_ / stats_[type].times_;
-}
-
-int64 ShellMediaStatistics::GetMin(StatType type) const {
- return stats_[type].min_;
-}
-
-int64 ShellMediaStatistics::GetMax(StatType type) const {
- return stats_[type].max_;
-}
-
-double ShellMediaStatistics::GetTotalDuration(StatType type) const {
- return base::TimeDelta::FromInternalValue(GetTotal(type)).InSecondsF();
-}
-
-double ShellMediaStatistics::GetCurrentDuration(StatType type) const {
- return base::TimeDelta::FromInternalValue(GetCurrent(type)).InSecondsF();
-}
-
-double ShellMediaStatistics::GetAverageDuration(StatType type) const {
- return base::TimeDelta::FromInternalValue(GetAverage(type)).InSecondsF();
-}
-
-double ShellMediaStatistics::GetMinDuration(StatType type) const {
- return base::TimeDelta::FromInternalValue(GetMin(type)).InSecondsF();
-}
-
-double ShellMediaStatistics::GetMaxDuration(StatType type) const {
- return base::TimeDelta::FromInternalValue(GetMax(type)).InSecondsF();
-}
-
-// static
-ShellMediaStatistics& ShellMediaStatistics::Instance() {
- static ShellMediaStatistics media_statistics;
- return media_statistics;
-}
-
-void ShellMediaStatistics::Reset(bool include_global_stats) {
- start_ = base::Time::Now();
- int items_to_reset =
- include_global_stats ? arraysize(stats_) : STAT_TYPE_START_OF_GLOBAL_STAT;
- for (int i = 0; i < items_to_reset; ++i) {
- // We deliberately not reset current_ so its value can be kept after reset.
- stats_[i].times_ = 0;
- stats_[i].total_ = 0;
- stats_[i].min_ = std::numeric_limits<int64>::max();
- stats_[i].max_ = std::numeric_limits<int64>::min();
- }
-}
-
-ShellScopedMediaStat::ShellScopedMediaStat(ShellMediaStatistics::StatType type)
- : type_(type), start_(base::Time::Now()) {}
-
-ShellScopedMediaStat::~ShellScopedMediaStat() {
- ShellMediaStatistics::Instance().record(type_, base::Time::Now() - start_);
-}
-
-} // namespace media
diff --git a/src/media/base/shell_media_statistics.h b/src/media/base/shell_media_statistics.h
deleted file mode 100644
index e44a794..0000000
--- a/src/media/base/shell_media_statistics.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_MEDIA_STATISTICS_H_
-#define MEDIA_BASE_SHELL_MEDIA_STATISTICS_H_
-
-#include "base/synchronization/lock.h"
-#include "base/time.h"
-
-namespace media {
-
-// This class collects events and their durations in the media stack.
-// Note that it is not thread safe but as its purpose is just to collect
-// performance data it is ok to call it from different threads.
-class ShellMediaStatistics {
- public:
- enum StatType {
- STAT_TYPE_AUDIO_CODEC,
- STAT_TYPE_AUDIO_CHANNELS,
- STAT_TYPE_AUDIO_SAMPLE_PER_SECOND,
- STAT_TYPE_AUDIO_UNDERFLOW,
- STAT_TYPE_VIDEO_CODEC,
- STAT_TYPE_VIDEO_WIDTH,
- STAT_TYPE_VIDEO_HEIGHT,
- // Decode a video frame
- STAT_TYPE_VIDEO_FRAME_DECODE,
- // Drop a video frame that we don't have enough time to render
- STAT_TYPE_VIDEO_FRAME_DROP,
- // The frame arrives too late to the renderer, usually indicates that it
- // takes too many time in the decoder.
- STAT_TYPE_VIDEO_FRAME_LATE,
- // How many frames we cached in the video renderer. If this value drops it
- // is more likely that we will have jitter.
- STAT_TYPE_VIDEO_RENDERER_BACKLOG,
- // Time spend in decrypting a buffer
- STAT_TYPE_DECRYPT,
- // The stat types after the following are global stats. i.e. their values
- // will be preserved between playng back of different videos.
- STAT_TYPE_START_OF_GLOBAL_STAT,
- // The size of the shell buffer block just allocated.
- STAT_TYPE_ALLOCATED_SHELL_BUFFER_SIZE,
- STAT_TYPE_MAX
- };
-
- // The structure is used to track all statistics. int64 is used here so we
- // can track a TimeDelta. Note that some of the fields may not be meaningful
- // to all Stat types. For example, `total` is not meanful to video resolution
- // as we don't care about the sum of video resolultion over time. But for
- // simplicity we just keep all the information and it is the responsibility
- // of the user of this class to use the individual statistics correctly.
- struct Stat {
- int64 times_; // How many times the stat has been updated.
- int64 current_;
- int64 total_;
- int64 min_;
- int64 max_;
- };
-
- ShellMediaStatistics();
-
- void OnPlaybackBegin();
- void record(StatType type, int64 value);
- void record(StatType type, const base::TimeDelta& duration);
-
- // Returns the time elapsed since last reset in the unit of second.
- double GetElapsedTime() const;
- int64 GetTimes(StatType type) const;
-
- int64 GetTotal(StatType type) const;
- int64 GetCurrent(StatType type) const;
- int64 GetAverage(StatType type) const;
- int64 GetMin(StatType type) const;
- int64 GetMax(StatType type) const;
-
- // The following access functions are just provided for easy of use. They are
- // not applicable to all stats. it is the responsibility of the user of these
- // functions to ensure that the call is valid.
- // The unit of time is second.
- double GetTotalDuration(StatType type) const;
- double GetCurrentDuration(StatType type) const;
- double GetAverageDuration(StatType type) const;
- double GetMinDuration(StatType type) const;
- double GetMaxDuration(StatType type) const;
-
- static ShellMediaStatistics& Instance();
-
- private:
- void Reset(bool include_global_stats);
-
- base::Time start_;
- Stat stats_[STAT_TYPE_MAX];
-};
-
-class ShellScopedMediaStat {
- public:
- ShellScopedMediaStat(ShellMediaStatistics::StatType type);
- ~ShellScopedMediaStat();
-
- private:
- ShellMediaStatistics::StatType type_;
- base::Time start_;
-};
-
-} // namespace media
-
-#if defined(__LB_SHELL__FOR_RELEASE__)
-#define UPDATE_MEDIA_STATISTICS(type, value) \
- do { \
- } while (false)
-
-// This macro reports a media stat with its duration
-#define SCOPED_MEDIA_STATISTICS(type) \
- do { \
- } while (false)
-#else // defined(__LB_SHELL__FOR_RELEASE__)
-// This macro reports a media stat with its new value
-#define UPDATE_MEDIA_STATISTICS(type, value) \
- media::ShellMediaStatistics::Instance().record( \
- media::ShellMediaStatistics::type, value)
-
-// This macro reports a media stat with its duration
-#define SCOPED_MEDIA_STATISTICS(type) \
- media::ShellScopedMediaStat statistics_event( \
- media::ShellMediaStatistics::type)
-#endif // defined(__LB_SHELL__FOR_RELEASE__)
-
-#endif // MEDIA_BASE_SHELL_MEDIA_STATISTICS_H_
diff --git a/src/media/base/shell_video_data_allocator.cc b/src/media/base/shell_video_data_allocator.cc
deleted file mode 100644
index e47e518..0000000
--- a/src/media/base/shell_video_data_allocator.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_video_data_allocator.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-ShellVideoDataAllocator::YV12Param::YV12Param(int decoded_width,
- int decoded_height,
- const gfx::Rect& visible_rect,
- uint8* data)
- : decoded_width_(decoded_width),
- decoded_height_(decoded_height),
- visible_rect_(visible_rect),
- y_pitch_(decoded_width),
- uv_pitch_(decoded_width / 2),
- y_data_(data),
- u_data_(y_data_ + y_pitch_ * decoded_height_),
- v_data_(u_data_ + uv_pitch_ * decoded_height_ / 2) {}
-
-ShellVideoDataAllocator::YV12Param::YV12Param(int width,
- int height,
- int y_pitch,
- int uv_pitch,
- uint8* y_data,
- uint8* u_data,
- uint8* v_data)
- : decoded_width_(width),
- decoded_height_(height),
- visible_rect_(0, 0, width, height),
- y_pitch_(y_pitch),
- uv_pitch_(uv_pitch),
- y_data_(y_data),
- u_data_(u_data),
- v_data_(v_data) {
- DCHECK_NE(y_pitch_, 0);
- DCHECK_NE(uv_pitch_, 0);
- DCHECK(y_data_);
- DCHECK(u_data);
- DCHECK(v_data);
-}
-
-ShellVideoDataAllocator::NV12Param::NV12Param(int width,
- int height,
- int y_pitch,
- const gfx::Rect& visible_rect)
- : decoded_width_(width),
- decoded_height_(height),
- y_pitch_(y_pitch),
- visible_rect_(visible_rect) {}
-
-} // namespace media
diff --git a/src/media/base/shell_video_data_allocator.h b/src/media/base/shell_video_data_allocator.h
deleted file mode 100644
index ca164a2..0000000
--- a/src/media/base/shell_video_data_allocator.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_VIDEO_DATA_ALLOCATOR_H_
-#define MEDIA_BASE_SHELL_VIDEO_DATA_ALLOCATOR_H_
-
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/video_frame.h"
-
-namespace media {
-
-class ShellRawVideoDecoder;
-
-// This class is introduced to remove the hidden dependency on the platform
-// dependent graphics code from low level video decoders. It is possible that
-// this can be achieved via interfaces created on each platform. However, to
-// abstract them into a common interface is more explicit.
-class MEDIA_EXPORT ShellVideoDataAllocator {
- public:
- class FrameBuffer : public base::RefCountedThreadSafe<FrameBuffer> {
- public:
- FrameBuffer() {}
- virtual ~FrameBuffer() {}
- virtual uint8* data() const = 0;
- virtual size_t size() const = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FrameBuffer);
- };
-
- class YV12Param {
- public:
- YV12Param(int decoded_width,
- int decoded_height,
- const gfx::Rect& visible_rect,
- uint8* data);
-
- // Create with data pointer to individual planes. All pointers should be in
- // the same memory block controlled by the accompanied FrameBuffer passed to
- // CreateYV12Frame. The decoded size and visible rect are assumed to be the
- // same. It is only used for decoding vp9 frames.
- YV12Param(int width,
- int height,
- int y_pitch,
- int uv_pitch,
- uint8* y_data,
- uint8* u_data,
- uint8* v_data);
- int y_pitch() const {
- DCHECK_NE(y_pitch_, 0);
- return y_pitch_;
- }
- int uv_pitch() const {
- DCHECK_NE(uv_pitch_, 0);
- return uv_pitch_;
- }
- uint8* y_data() const {
- DCHECK(y_data_);
- return y_data_;
- }
- uint8* u_data() const {
- DCHECK(u_data_);
- return u_data_;
- }
- uint8* v_data() const {
- DCHECK(v_data_);
- return v_data_;
- }
-
- int decoded_width() const { return decoded_width_; }
- int decoded_height() const { return decoded_height_; }
- const gfx::Rect& visible_rect() const { return visible_rect_; }
-
- private:
- int decoded_width_;
- int decoded_height_;
-
- gfx::Rect visible_rect_;
-
- int y_pitch_;
- int uv_pitch_;
- uint8* y_data_;
- uint8* u_data_;
- uint8* v_data_;
- };
-
- // Only used for some platforms' hardware AVC decoder that only support NV12
- // output.
- class NV12Param {
- public:
- NV12Param(int decoded_width,
- int decoded_height,
- int y_pitch,
- const gfx::Rect& visible_rect);
-
- int decoded_width() const { return decoded_width_; }
- int decoded_height() const { return decoded_height_; }
- int y_pitch() const { return y_pitch_; }
- const gfx::Rect& visible_rect() const { return visible_rect_; }
-
- private:
- int decoded_width_;
- int decoded_height_;
- int y_pitch_;
- gfx::Rect visible_rect_;
- };
-
- ShellVideoDataAllocator() {}
- virtual ~ShellVideoDataAllocator() {}
-
- // Allocate a buffer to store the video frame to be decoded.
- virtual scoped_refptr<FrameBuffer> AllocateFrameBuffer(size_t size,
- size_t alignment) = 0;
- virtual scoped_refptr<VideoFrame> CreateYV12Frame(
- const scoped_refptr<FrameBuffer>& frame_buffer,
- const YV12Param& param,
- const base::TimeDelta& timestamp) = 0;
-
- // Some hardware AVC decoders only support NV12 output. They are perfectly
- // aligned for rendering as texture though.
- virtual scoped_refptr<VideoFrame> CreateNV12Frame(
- const scoped_refptr<FrameBuffer>& frame_buffer,
- const NV12Param& param,
- const base::TimeDelta& timestamp) {
- UNREFERENCED_PARAMETER(frame_buffer);
- UNREFERENCED_PARAMETER(param);
- UNREFERENCED_PARAMETER(timestamp);
- NOTREACHED();
- return NULL;
- }
-
- // Return a video frame filled with RGBA zero on platforms that require punch
- // out. Return NULL otherwise.
- virtual scoped_refptr<VideoFrame> GetPunchOutFrame() { return NULL; }
-
- // Most platforms limit the number of active raw video decoders to one. The
- // following functions enable the implementations on these platforms to check
- // if there is more than one video decoder active.
- virtual void Acquire(ShellRawVideoDecoder* owner) {
- UNREFERENCED_PARAMETER(owner);
- }
- virtual void Release(ShellRawVideoDecoder* owner) {
- UNREFERENCED_PARAMETER(owner);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ShellVideoDataAllocator);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_VIDEO_DATA_ALLOCATOR_H_
diff --git a/src/media/base/shell_video_frame_provider.cc b/src/media/base/shell_video_frame_provider.cc
deleted file mode 100644
index 0cf2f98..0000000
--- a/src/media/base/shell_video_frame_provider.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/shell_video_frame_provider.h"
-
-#include "base/logging.h"
-#if SB_API_VERSION >= 4
-#include "starboard/decode_target.h"
-#endif // #if SB_API_VERSION >= 4
-
-namespace media {
-
-ShellVideoFrameProvider::ShellVideoFrameProvider(
- scoped_refptr<VideoFrame> punch_out)
- : punch_out_(punch_out), has_consumed_frames_(false), dropped_frames_(0),
- output_mode_(kOutputModeInvalid) {
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- max_delay_in_microseconds_ = 0;
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-}
-
-void ShellVideoFrameProvider::RegisterMediaTimeAndSeekingStateCB(
- const MediaTimeAndSeekingStateCB& media_time_and_seeking_state_cb) {
- DCHECK(!media_time_and_seeking_state_cb.is_null());
- base::AutoLock auto_lock(frames_lock_);
- media_time_and_seeking_state_cb_ = media_time_and_seeking_state_cb;
-}
-
-void ShellVideoFrameProvider::UnregisterMediaTimeAndSeekingStateCB(
- const MediaTimeAndSeekingStateCB& media_time_and_seeking_state_cb) {
- base::AutoLock auto_lock(frames_lock_);
- // It is possible that the register of a new callback happens earlier than the
- // unregister of the previous callback. Always ensure that the callback
- // passed in is the current one before resetting.
- if (media_time_and_seeking_state_cb_.Equals(
- media_time_and_seeking_state_cb)) {
- media_time_and_seeking_state_cb_.Reset();
- }
-}
-
-const scoped_refptr<VideoFrame>& ShellVideoFrameProvider::GetCurrentFrame() {
- if (punch_out_) {
- current_frame_ = punch_out_;
- return current_frame_;
- }
-
- const int kEpsilonInMicroseconds =
- base::Time::kMicrosecondsPerSecond / 60 / 2;
-
- base::AutoLock auto_lock(frames_lock_);
-
- base::TimeDelta media_time;
- bool is_seeking;
- GetMediaTimeAndSeekingState_Locked(&media_time, &is_seeking);
- while (!frames_.empty()) {
- int64_t frame_time = frames_[0]->GetTimestamp().InMicroseconds();
- if (frame_time >= media_time.InMicroseconds())
- break;
- if (current_frame_ != frames_[0] &&
- frame_time + kEpsilonInMicroseconds >= media_time.InMicroseconds())
- break;
-
- if (current_frame_ != frames_[0] && !is_seeking) {
- ++dropped_frames_;
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- if (media_time.InMicroseconds() - frame_time > max_delay_in_microseconds_)
- max_delay_in_microseconds_ = media_time.InMicroseconds() - frame_time;
- const bool kLogFrameDrops ALLOW_UNUSED = false;
- LOG_IF(WARNING, kLogFrameDrops)
- << "dropped one frame with timestamp "
- << frames_[0]->GetTimestamp().InMicroseconds() << " at media time "
- << media_time.InMicroseconds() << " total dropped " << dropped_frames_
- << " frames with a max delay of " << max_delay_in_microseconds_
- << " ms";
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
- }
-
- if (frames_.size() == 1) {
- current_frame_ = frames_[0];
- }
-
- frames_.erase(frames_.begin());
- has_consumed_frames_ = true;
- }
- if (!frames_.empty()) {
- current_frame_ = frames_[0];
- }
- return current_frame_;
-}
-
-void ShellVideoFrameProvider::SetOutputMode(OutputMode output_mode) {
- base::AutoLock auto_lock(frames_lock_);
- output_mode_ = output_mode;
-}
-
-ShellVideoFrameProvider::OutputMode ShellVideoFrameProvider::GetOutputMode()
- const {
- base::AutoLock auto_lock(frames_lock_);
- return output_mode_;
-}
-
-#if SB_API_VERSION >= 4
-
-void ShellVideoFrameProvider::SetGetCurrentSbDecodeTargetFunction(
- GetCurrentSbDecodeTargetFunction function) {
- base::AutoLock auto_lock(frames_lock_);
- get_current_sb_decode_target_function_ = function;
-}
-
-void ShellVideoFrameProvider::ResetGetCurrentSbDecodeTargetFunction() {
- base::AutoLock auto_lock(frames_lock_);
- get_current_sb_decode_target_function_.Reset();
-}
-
-SbDecodeTarget ShellVideoFrameProvider::GetCurrentSbDecodeTarget() const {
- base::AutoLock auto_lock(frames_lock_);
- if (get_current_sb_decode_target_function_.is_null()) {
- return kSbDecodeTargetInvalid;
- } else {
- return get_current_sb_decode_target_function_.Run();
- }
-}
-
-#endif // #if SB_API_VERSION >= 4
-
-void ShellVideoFrameProvider::AddFrame(const scoped_refptr<VideoFrame>& frame) {
- base::AutoLock auto_lock(frames_lock_);
- frames_.push_back(frame);
-}
-
-void ShellVideoFrameProvider::Flush() {
- base::AutoLock auto_lock(frames_lock_);
- frames_.clear();
-}
-
-void ShellVideoFrameProvider::Stop() {
- base::AutoLock auto_lock(frames_lock_);
- frames_.clear();
- current_frame_ = NULL;
- dropped_frames_ = 0;
-}
-
-size_t ShellVideoFrameProvider::GetNumOfFramesCached() const {
- base::AutoLock auto_lock(frames_lock_);
- return frames_.size();
-}
-
-void ShellVideoFrameProvider::GetMediaTimeAndSeekingState_Locked(
- base::TimeDelta* media_time,
- bool* is_seeking) const {
- DCHECK(media_time);
- DCHECK(is_seeking);
- frames_lock_.AssertAcquired();
- if (media_time_and_seeking_state_cb_.is_null()) {
- *media_time = base::TimeDelta();
- *is_seeking = false;
- return;
- }
- media_time_and_seeking_state_cb_.Run(media_time, is_seeking);
-}
-
-bool ShellVideoFrameProvider::QueryAndResetHasConsumedFrames() {
- base::AutoLock auto_lock(frames_lock_);
- bool previous_value = has_consumed_frames_;
- has_consumed_frames_ = false;
- return previous_value;
-}
-
-int ShellVideoFrameProvider::ResetAndReturnDroppedFrames() {
- base::AutoLock auto_lock(frames_lock_);
- int dropped_frames = dropped_frames_;
- dropped_frames_ = 0;
- return dropped_frames;
-}
-
-} // namespace media
diff --git a/src/media/base/shell_video_frame_provider.h b/src/media/base/shell_video_frame_provider.h
deleted file mode 100644
index ae1e2d0..0000000
--- a/src/media/base/shell_video_frame_provider.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_SHELL_VIDEO_FRAME_PROVIDER_H_
-#define MEDIA_BASE_SHELL_VIDEO_FRAME_PROVIDER_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/lock.h"
-#include "base/time.h"
-#include "media/base/video_frame.h"
-#include "starboard/decode_target.h"
-
-namespace media {
-
-// TODO: Remove Shell prefix.
-// The ShellVideoFrameProvider manages the backlog for video frames. It has the
-// following functionalities:
-// 1. It caches the video frames ready to be displayed.
-// 2. It decides which frame to be displayed at the current time.
-// 3. It removes frames that will no longer be displayed.
-class ShellVideoFrameProvider
- : public base::RefCountedThreadSafe<ShellVideoFrameProvider> {
- public:
-#if SB_API_VERSION >= 4
- typedef base::Callback<SbDecodeTarget()> GetCurrentSbDecodeTargetFunction;
-#endif // SB_API_VERSION >= 4
-
- enum OutputMode {
- kOutputModePunchOut,
- kOutputModeDecodeToTexture,
- kOutputModeInvalid,
- };
-
- explicit ShellVideoFrameProvider(scoped_refptr<VideoFrame> punch_out = NULL);
-
- // The calling back returns the current media time and a bool which is set to
- // true when a seek is in progress.
- typedef base::Callback<void(base::TimeDelta*, bool*)>
- MediaTimeAndSeekingStateCB;
- // This class uses the media time to decide which frame is current. It
- // retrieves the media time from the registered media_time_cb. There can only
- // be one registered media_time_cb at a certain time, a call to
- // RegisterMediaTimeAndSeekingStateCB() will overwrite the previously
- // registered callback.
- void RegisterMediaTimeAndSeekingStateCB(
- const MediaTimeAndSeekingStateCB& media_time_and_seeking_state_cb);
- // This function unregisters the media time callback if it hasn't been
- // overwritten by another callback.
- void UnregisterMediaTimeAndSeekingStateCB(
- const MediaTimeAndSeekingStateCB& media_time_and_seeking_state_cb);
-
- // Returns the current frame to be displayed if there is one. Otherwise it
- // returns NULL.
- const scoped_refptr<VideoFrame>& GetCurrentFrame();
-
- void SetOutputMode(OutputMode output_mode);
- OutputMode GetOutputMode() const;
-
-#if SB_API_VERSION >= 4
- // For Starboard platforms that have a decode-to-texture player, we enable
- // this ShellVideoFrameProvider to act as a bridge for Cobalt code to query
- // for the current SbDecodeTarget. In effect, we bypass all of
- // ShellVideoFrameProvider's logic in this case, instead relying on the
- // Starboard implementation to provide us with the current video frame when
- // needed.
- void SetGetCurrentSbDecodeTargetFunction(
- GetCurrentSbDecodeTargetFunction function);
-
- void ResetGetCurrentSbDecodeTargetFunction();
-
- SbDecodeTarget GetCurrentSbDecodeTarget() const;
-#endif // SB_API_VERSION >= 4
-
- void AddFrame(const scoped_refptr<VideoFrame>& frame);
- // Flush will clear all cached frames except the current frame. So the current
- // frame can still be displayed during seek.
- void Flush();
- // Stop will clear all cached frames including the current frame.
- void Stop();
- size_t GetNumOfFramesCached() const;
-
- // Return true if VideoFrames have been released from the internal frames_
- // queue since the last time this was called.
- bool QueryAndResetHasConsumedFrames();
-
- // Return the value of |dropped_frames_| and reset it to 0.
- int ResetAndReturnDroppedFrames();
-
- private:
- void GetMediaTimeAndSeekingState_Locked(base::TimeDelta* media_time,
- bool* is_seeking) const;
-
- scoped_refptr<VideoFrame> punch_out_;
-
- mutable base::Lock frames_lock_;
- MediaTimeAndSeekingStateCB media_time_and_seeking_state_cb_;
- std::vector<scoped_refptr<VideoFrame> > frames_;
- scoped_refptr<VideoFrame> current_frame_;
- bool has_consumed_frames_;
- int dropped_frames_;
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- int max_delay_in_microseconds_;
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-
- OutputMode output_mode_;
-#if SB_API_VERSION >= 4
- GetCurrentSbDecodeTargetFunction get_current_sb_decode_target_function_;
-#endif // SB_API_VERSION >= 4
-
- DISALLOW_COPY_AND_ASSIGN(ShellVideoFrameProvider);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SHELL_VIDEO_FRAME_PROVIDER_H_
diff --git a/src/media/base/simd/convert_rgb_to_yuv.h b/src/media/base/simd/convert_rgb_to_yuv.h
deleted file mode 100644
index 03fe114..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_H_
-#define MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_H_
-
-#include "base/basictypes.h"
-#include "media/base/yuv_convert.h"
-
-namespace media {
-
-// Converts an ARGB image to a YV12 image. This function calls ASM functions
-// implemented in "convert_rgb_to_yuv_ssse3.asm" to convert the specified ARGB
-// image to a YV12 image.
-void ConvertRGB32ToYUV_SSSE3(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-// Converts an RGB image to a YV12 image. This function is almost same as
-// ConvertRGB32ToYUV_SSSE3 except its first argument is a pointer to RGB pixels.
-void ConvertRGB24ToYUV_SSSE3(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-// SSE2 version of converting RGBA to YV12.
-void ConvertRGB32ToYUV_SSE2(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-// This is a C reference implementation of the above routine.
-// This method should only be used in unit test.
-// TODO(hclam): Should use this as the C version of RGB to YUV.
-void ConvertRGB32ToYUV_SSE2_Reference(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-// C version of converting RGBA to YV12.
-void ConvertRGB32ToYUV_C(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-// C version of converting RGB24 to YV12.
-void ConvertRGB24ToYUV_C(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-} // namespace media
-
-#endif // MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_H_
diff --git a/src/media/base/simd/convert_rgb_to_yuv_c.cc b/src/media/base/simd/convert_rgb_to_yuv_c.cc
deleted file mode 100644
index ae4c731..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_c.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/simd/convert_rgb_to_yuv.h"
-
-namespace media {
-
-static int clip_byte(int x) {
- if (x > 255)
- return 255;
- else if (x < 0)
- return 0;
- else
- return x;
-}
-
-void ConvertRGB32ToYUV_C(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- for (int i = 0; i < height; ++i) {
- for (int j = 0; j < width; ++j) {
- // Since the input pixel format is RGB32, there are 4 bytes per pixel.
- const uint8* pixel = rgbframe + 4 * j;
- yplane[j] = clip_byte(((pixel[2] * 66 + pixel[1] * 129 +
- pixel[0] * 25 + 128) >> 8) + 16);
- if (i % 2 == 0 && j % 2 == 0) {
- uplane[j / 2] = clip_byte(((pixel[2] * -38 + pixel[1] * -74 +
- pixel[0] * 112 + 128) >> 8) + 128);
- vplane[j / 2] = clip_byte(((pixel[2] * 112 + pixel[1] * -94 +
- pixel[0] * -18 + 128) >> 8) + 128);
- }
- }
-
- rgbframe += rgbstride;
- yplane += ystride;
- if (i % 2 == 0) {
- uplane += uvstride;
- vplane += uvstride;
- }
- }
-}
-
-void ConvertRGB24ToYUV_C(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- for (int i = 0; i < height; ++i) {
- for (int j = 0; j < width; ++j) {
- // Since the input pixel format is RGB24, there are 3 bytes per pixel.
- const uint8* pixel = rgbframe + 3 * j;
- yplane[j] = clip_byte(((pixel[2] * 66 + pixel[1] * 129 +
- pixel[0] * 25 + 128) >> 8) + 16);
- if (i % 2 == 0 && j % 2 == 0) {
- uplane[j / 2] = clip_byte(((pixel[2] * -38 + pixel[1] * -74 +
- pixel[0] * 112 + 128) >> 8) + 128);
- vplane[j / 2] = clip_byte(((pixel[2] * 112 + pixel[1] * -94 +
- pixel[0] * -18 + 128) >> 8) + 128);
- }
- }
-
- rgbframe += rgbstride;
- yplane += ystride;
- if (i % 2 == 0) {
- uplane += uvstride;
- vplane += uvstride;
- }
- }
-}
-
-} // namespace media
diff --git a/src/media/base/simd/convert_rgb_to_yuv_sse2.cc b/src/media/base/simd/convert_rgb_to_yuv_sse2.cc
deleted file mode 100644
index f99a2fe..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_sse2.cc
+++ /dev/null
@@ -1,397 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
-#include "media/base/simd/yuv_to_rgb_table.h"
-
-#if defined(COMPILER_MSVC)
-#include <intrin.h>
-#else
-#include <mmintrin.h>
-#include <emmintrin.h>
-#endif
-
-namespace media {
-
-#define FIX_SHIFT 12
-#define FIX(x) ((x) * (1 << FIX_SHIFT))
-
-// Define a convenient macro to do static cast.
-#define INT16_FIX(x) static_cast<int16>(FIX(x))
-
-SIMD_ALIGNED(const int16 ConvertRGBAToYUV_kTable[8 * 3]) = {
- INT16_FIX(0.098), INT16_FIX(0.504), INT16_FIX(0.257), 0,
- INT16_FIX(0.098), INT16_FIX(0.504), INT16_FIX(0.257), 0,
- INT16_FIX(0.439), -INT16_FIX(0.291), -INT16_FIX(0.148), 0,
- INT16_FIX(0.439), -INT16_FIX(0.291), -INT16_FIX(0.148), 0,
- -INT16_FIX(0.071), -INT16_FIX(0.368), INT16_FIX(0.439), 0,
- -INT16_FIX(0.071), -INT16_FIX(0.368), INT16_FIX(0.439), 0,
-};
-
-#undef INT16_FIX
-
-// This is the final offset for the conversion from signed yuv values to
-// unsigned values. It is arranged so that offset of 16 is applied to Y
-// components and 128 is added to UV components for 2 pixels.
-SIMD_ALIGNED(const int32 kYOffset[4]) = {16, 16, 16, 16};
-
-static inline int Clamp(int value) {
- if (value < 0)
- return 0;
- if (value > 255)
- return 255;
- return value;
-}
-
-static inline int RGBToY(int r, int g, int b) {
- int y = ConvertRGBAToYUV_kTable[0] * b +
- ConvertRGBAToYUV_kTable[1] * g +
- ConvertRGBAToYUV_kTable[2] * r;
- y >>= FIX_SHIFT;
- return Clamp(y + 16);
-}
-
-static inline int RGBToU(int r, int g, int b, int shift) {
- int u = ConvertRGBAToYUV_kTable[8] * b +
- ConvertRGBAToYUV_kTable[9] * g +
- ConvertRGBAToYUV_kTable[10] * r;
- u >>= FIX_SHIFT + shift;
- return Clamp(u + 128);
-}
-
-static inline int RGBToV(int r, int g, int b, int shift) {
- int v = ConvertRGBAToYUV_kTable[16] * b +
- ConvertRGBAToYUV_kTable[17] * g +
- ConvertRGBAToYUV_kTable[18] * r;
- v >>= FIX_SHIFT + shift;
- return Clamp(v + 128);
-}
-
-#define CONVERT_Y(rgb_buf, y_buf) \
- b = *rgb_buf++; \
- g = *rgb_buf++; \
- r = *rgb_buf++; \
- ++rgb_buf; \
- sum_b += b; \
- sum_g += g; \
- sum_r += r; \
- *y_buf++ = RGBToY(r, g, b);
-
-static inline void ConvertRGBToYUV_V2H2(const uint8* rgb_buf_1,
- const uint8* rgb_buf_2,
- uint8* y_buf_1,
- uint8* y_buf_2,
- uint8* u_buf,
- uint8* v_buf) {
- int sum_b = 0;
- int sum_g = 0;
- int sum_r = 0;
- int r, g, b;
-
-
-
- CONVERT_Y(rgb_buf_1, y_buf_1);
- CONVERT_Y(rgb_buf_1, y_buf_1);
- CONVERT_Y(rgb_buf_2, y_buf_2);
- CONVERT_Y(rgb_buf_2, y_buf_2);
- *u_buf++ = RGBToU(sum_r, sum_g, sum_b, 2);
- *v_buf++ = RGBToV(sum_r, sum_g, sum_b, 2);
-}
-
-static inline void ConvertRGBToYUV_V2H1(const uint8* rgb_buf_1,
- const uint8* rgb_buf_2,
- uint8* y_buf_1,
- uint8* y_buf_2,
- uint8* u_buf,
- uint8* v_buf) {
- int sum_b = 0;
- int sum_g = 0;
- int sum_r = 0;
- int r, g, b;
-
- CONVERT_Y(rgb_buf_1, y_buf_1);
- CONVERT_Y(rgb_buf_2, y_buf_2);
- *u_buf++ = RGBToU(sum_r, sum_g, sum_b, 1);
- *v_buf++ = RGBToV(sum_r, sum_g, sum_b, 1);
-}
-
-static inline void ConvertRGBToYUV_V1H2(const uint8* rgb_buf,
- uint8* y_buf,
- uint8* u_buf,
- uint8* v_buf) {
- int sum_b = 0;
- int sum_g = 0;
- int sum_r = 0;
- int r, g, b;
-
- CONVERT_Y(rgb_buf, y_buf);
- CONVERT_Y(rgb_buf, y_buf);
- *u_buf++ = RGBToU(sum_r, sum_g, sum_b, 1);
- *v_buf++ = RGBToV(sum_r, sum_g, sum_b, 1);
-}
-
-static inline void ConvertRGBToYUV_V1H1(const uint8* rgb_buf,
- uint8* y_buf,
- uint8* u_buf,
- uint8* v_buf) {
- int sum_b = 0;
- int sum_g = 0;
- int sum_r = 0;
- int r, g, b;
-
- CONVERT_Y(rgb_buf, y_buf);
- *u_buf++ = RGBToU(r, g, b, 0);
- *v_buf++ = RGBToV(r, g, b, 0);
-}
-
-static void ConvertRGB32ToYUVRow_SSE2(const uint8* rgb_buf_1,
- const uint8* rgb_buf_2,
- uint8* y_buf_1,
- uint8* y_buf_2,
- uint8* u_buf,
- uint8* v_buf,
- int width) {
- while (width >= 4) {
- // Name for the Y pixels:
- // Row 1: a b c d
- // Row 2: e f g h
- //
- // First row 4 pixels.
- __m128i rgb_row_1 = _mm_loadu_si128(
- reinterpret_cast<const __m128i*>(rgb_buf_1));
- __m128i zero_1 = _mm_xor_si128(rgb_row_1, rgb_row_1);
-
- __m128i y_table = _mm_load_si128(
- reinterpret_cast<const __m128i*>(ConvertRGBAToYUV_kTable));
-
- __m128i rgb_a_b = _mm_unpackhi_epi8(rgb_row_1, zero_1);
- rgb_a_b = _mm_madd_epi16(rgb_a_b, y_table);
-
- __m128i rgb_c_d = _mm_unpacklo_epi8(rgb_row_1, zero_1);
- rgb_c_d = _mm_madd_epi16(rgb_c_d, y_table);
-
- // Do a crazh shuffle so that we get:
- // v------------ Multiply Add
- // BG: a b c d
- // A0: a b c d
- __m128i bg_abcd = _mm_castps_si128(
- _mm_shuffle_ps(
- _mm_castsi128_ps(rgb_c_d),
- _mm_castsi128_ps(rgb_a_b),
- (3 << 6) | (1 << 4) | (3 << 2) | 1));
- __m128i r_abcd = _mm_castps_si128(
- _mm_shuffle_ps(
- _mm_castsi128_ps(rgb_c_d),
- _mm_castsi128_ps(rgb_a_b),
- (2 << 6) | (2 << 2)));
- __m128i y_abcd = _mm_add_epi32(bg_abcd, r_abcd);
-
- // Down shift back to 8bits range.
- __m128i y_offset = _mm_load_si128(
- reinterpret_cast<const __m128i*>(kYOffset));
- y_abcd = _mm_srai_epi32(y_abcd, FIX_SHIFT);
- y_abcd = _mm_add_epi32(y_abcd, y_offset);
- y_abcd = _mm_packs_epi32(y_abcd, y_abcd);
- y_abcd = _mm_packus_epi16(y_abcd, y_abcd);
- *reinterpret_cast<uint32*>(y_buf_1) = _mm_cvtsi128_si32(y_abcd);
- y_buf_1 += 4;
-
- // Second row 4 pixels.
- __m128i rgb_row_2 = _mm_loadu_si128(
- reinterpret_cast<const __m128i*>(rgb_buf_2));
- __m128i zero_2 = _mm_xor_si128(rgb_row_2, rgb_row_2);
- __m128i rgb_e_f = _mm_unpackhi_epi8(rgb_row_2, zero_2);
- __m128i rgb_g_h = _mm_unpacklo_epi8(rgb_row_2, zero_2);
-
- // Add two rows together.
- __m128i rgb_ae_bf =
- _mm_add_epi16(_mm_unpackhi_epi8(rgb_row_1, zero_2), rgb_e_f);
- __m128i rgb_cg_dh =
- _mm_add_epi16(_mm_unpacklo_epi8(rgb_row_1, zero_2), rgb_g_h);
-
- // Multiply add like the previous row.
- rgb_e_f = _mm_madd_epi16(rgb_e_f, y_table);
- rgb_g_h = _mm_madd_epi16(rgb_g_h, y_table);
-
- __m128i bg_efgh = _mm_castps_si128(
- _mm_shuffle_ps(_mm_castsi128_ps(rgb_g_h),
- _mm_castsi128_ps(rgb_e_f),
- (3 << 6) | (1 << 4) | (3 << 2) | 1));
- __m128i r_efgh = _mm_castps_si128(
- _mm_shuffle_ps(_mm_castsi128_ps(rgb_g_h),
- _mm_castsi128_ps(rgb_e_f),
- (2 << 6) | (2 << 2)));
- __m128i y_efgh = _mm_add_epi32(bg_efgh, r_efgh);
- y_efgh = _mm_srai_epi32(y_efgh, FIX_SHIFT);
- y_efgh = _mm_add_epi32(y_efgh, y_offset);
- y_efgh = _mm_packs_epi32(y_efgh, y_efgh);
- y_efgh = _mm_packus_epi16(y_efgh, y_efgh);
- *reinterpret_cast<uint32*>(y_buf_2) = _mm_cvtsi128_si32(y_efgh);
- y_buf_2 += 4;
-
- __m128i rgb_ae_cg = _mm_castps_si128(
- _mm_shuffle_ps(_mm_castsi128_ps(rgb_cg_dh),
- _mm_castsi128_ps(rgb_ae_bf),
- (3 << 6) | (2 << 4) | (3 << 2) | 2));
- __m128i rgb_bf_dh = _mm_castps_si128(
- _mm_shuffle_ps(_mm_castsi128_ps(rgb_cg_dh),
- _mm_castsi128_ps(rgb_ae_bf),
- (1 << 6) | (1 << 2)));
-
- // This is a 2x2 subsampling for 2 pixels.
- __m128i rgb_abef_cdgh = _mm_add_epi16(rgb_ae_cg, rgb_bf_dh);
-
- // Do a multiply add with U table.
- __m128i u_a_b = _mm_madd_epi16(
- rgb_abef_cdgh,
- _mm_load_si128(
- reinterpret_cast<const __m128i*>(ConvertRGBAToYUV_kTable + 8)));
- u_a_b = _mm_add_epi32(_mm_shuffle_epi32(u_a_b, ((3 << 2) | 1)),
- _mm_shuffle_epi32(u_a_b, (2 << 2)));
- // Right shift 14 because of 12 from fixed point and 2 from subsampling.
- u_a_b = _mm_srai_epi32(u_a_b, FIX_SHIFT + 2);
- __m128i uv_offset = _mm_slli_epi32(y_offset, 3);
- u_a_b = _mm_add_epi32(u_a_b, uv_offset);
- u_a_b = _mm_packs_epi32(u_a_b, u_a_b);
- u_a_b = _mm_packus_epi16(u_a_b, u_a_b);
- *reinterpret_cast<uint16*>(u_buf) = _mm_extract_epi16(u_a_b, 0);
- u_buf += 2;
-
- __m128i v_a_b = _mm_madd_epi16(
- rgb_abef_cdgh,
- _mm_load_si128(
- reinterpret_cast<const __m128i*>(ConvertRGBAToYUV_kTable + 16)));
- v_a_b = _mm_add_epi32(_mm_shuffle_epi32(v_a_b, ((3 << 2) | 1)),
- _mm_shuffle_epi32(v_a_b, (2 << 2)));
- v_a_b = _mm_srai_epi32(v_a_b, FIX_SHIFT + 2);
- v_a_b = _mm_add_epi32(v_a_b, uv_offset);
- v_a_b = _mm_packs_epi32(v_a_b, v_a_b);
- v_a_b = _mm_packus_epi16(v_a_b, v_a_b);
- *reinterpret_cast<uint16*>(v_buf) = _mm_extract_epi16(v_a_b, 0);
- v_buf += 2;
-
- rgb_buf_1 += 16;
- rgb_buf_2 += 16;
-
- // Move forward by 4 pixels.
- width -= 4;
- }
-
- // Just use C code to convert the remaining pixels.
- if (width >= 2) {
- ConvertRGBToYUV_V2H2(rgb_buf_1, rgb_buf_2, y_buf_1, y_buf_2, u_buf, v_buf);
- rgb_buf_1 += 8;
- rgb_buf_2 += 8;
- y_buf_1 += 2;
- y_buf_2 += 2;
- ++u_buf;
- ++v_buf;
- width -= 2;
- }
-
- if (width)
- ConvertRGBToYUV_V2H1(rgb_buf_1, rgb_buf_2, y_buf_1, y_buf_2, u_buf, v_buf);
-}
-
-extern void ConvertRGB32ToYUV_SSE2(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- while (height >= 2) {
- ConvertRGB32ToYUVRow_SSE2(rgbframe,
- rgbframe + rgbstride,
- yplane,
- yplane + ystride,
- uplane,
- vplane,
- width);
- rgbframe += 2 * rgbstride;
- yplane += 2 * ystride;
- uplane += uvstride;
- vplane += uvstride;
- height -= 2;
- }
-
- if (!height)
- return;
-
- // Handle the last row.
- while (width >= 2) {
- ConvertRGBToYUV_V1H2(rgbframe, yplane, uplane, vplane);
- rgbframe += 8;
- yplane += 2;
- ++uplane;
- ++vplane;
- width -= 2;
- }
-
- if (width)
- ConvertRGBToYUV_V1H1(rgbframe, yplane, uplane, vplane);
-}
-
-void ConvertRGB32ToYUV_SSE2_Reference(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- while (height >= 2) {
- int i = 0;
-
- // Convert a 2x2 block.
- while (i + 2 <= width) {
- ConvertRGBToYUV_V2H2(rgbframe + i * 4,
- rgbframe + rgbstride + i * 4,
- yplane + i,
- yplane + ystride + i,
- uplane + i / 2,
- vplane + i / 2);
- i += 2;
- }
-
- // Convert the last pixel of two rows.
- if (i < width) {
- ConvertRGBToYUV_V2H1(rgbframe + i * 4,
- rgbframe + rgbstride + i * 4,
- yplane + i,
- yplane + ystride + i,
- uplane + i / 2,
- vplane + i / 2);
- }
-
- rgbframe += 2 * rgbstride;
- yplane += 2 * ystride;
- uplane += uvstride;
- vplane += uvstride;
- height -= 2;
- }
-
- if (!height)
- return;
-
- // Handle the last row.
- while (width >= 2) {
- ConvertRGBToYUV_V1H2(rgbframe, yplane, uplane, vplane);
- rgbframe += 8;
- yplane += 2;
- ++uplane;
- ++vplane;
- width -= 2;
- }
-
- // Handle the last pixel in the last row.
- if (width)
- ConvertRGBToYUV_V1H1(rgbframe, yplane, uplane, vplane);
-}
-
-} // namespace media
diff --git a/src/media/base/simd/convert_rgb_to_yuv_ssse3.asm b/src/media/base/simd/convert_rgb_to_yuv_ssse3.asm
deleted file mode 100644
index f445e98..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_ssse3.asm
+++ /dev/null
@@ -1,317 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses SSE, SSE2, SSE3, and SSSE3, which are supported by all ATOM
-; processors.
-;
- SECTION_TEXT
- CPU SSE, SSE3, SSE3, SSSE3
-
-;
-; XMM registers representing constants. We must not use these registers as
-; destination operands.
-; for (int i = 0; i < 16; i += 4) {
-; xmm7.b[i] = 25; xmm7.b[i+1] = 2; xmm7.b[i+2] = 66; xmm7.b[i+3] = 0;
-; xmm6.b[i] = 0; xmm6.b[i+1] = 127; xmm6.b[i+2] = 0; xmm6.b[i+3] = 0;
-; xmm5.b[i] = 112; xmm5.b[i+1] = -74; xmm5.b[i+2] = -38; xmm5.b[i+3] = 0;
-; xmm4.b[i] = -18; xmm4.b[i+1] = -94; xmm4.b[i+2] = 112; xmm4.b[i+3] = 0;
-; }
-;
-%define XMM_CONST_Y0 xmm7
-%define XMM_CONST_Y1 xmm6
-%define XMM_CONST_U xmm5
-%define XMM_CONST_V xmm4
-%define XMM_CONST_128 xmm3
-
-;
-; LOAD_XMM %1 (xmm), %2 (imm32)
-; Loads an immediate value to an XMM register.
-; %1.d[0] = %1.d[1] = %1.d[2] = %1.d[3] = %2;
-;
-%macro LOAD_XMM 2
- mov TEMPd, %2
- movd %1, TEMPd
- pshufd %1, %1, 00000000B
-%endmacro
-
-;
-; UNPACKRGB %1 (xmm), %2 (imm8)
-; Unpacks one RGB pixel in the specified XMM register.
-; for (int i = 15; i > %2; --i) %1.b[i] = %1.b[i - 1];
-; %1.b[%2] = 0;
-; for (int i = %2 - 1; i >= 0; --i) %1.b[i] = %1.b[i];
-;
-%macro UNPACKRGB 2
- movdqa xmm1, %1
- psrldq xmm1, %2
- pslldq xmm1, %2
- pxor %1, xmm1
- pslldq xmm1, 1
- por %1, xmm1
-%endmacro
-
-;
-; READ_ARGB %1 (xmm), %2 (imm)
-; Read the specified number of ARGB (or RGB) pixels from the source and store
-; them to the destination xmm register. If the input format is RGB, we read RGB
-; pixels and convert them to ARGB pixels. (For this case, the alpha values of
-; the output pixels become 0.)
-;
-%macro READ_ARGB 2
-
-%if PIXELSIZE == 4
-
- ; Read ARGB pixels from the source. (This macro assumes the input buffer may
- ; not be aligned to a 16-byte boundary.)
-%if %2 == 1
- movd %1, DWORD [ARGBq + WIDTHq * 4 * 2]
-%elif %2 == 2
- movq %1, QWORD [ARGBq + WIDTHq * 4 * 2]
-%elif %2 == 4
- movdqu %1, DQWORD [ARGBq + WIDTHq * 4 * 2]
-%else
-%error unsupported number of pixels.
-%endif
-
-%elif PIXELSIZE == 3
-
- ; Read RGB pixels from the source and convert them to ARGB pixels.
-%if %2 == 1
- ; Read one RGB pixel and convert it to one ARGB pixel.
- ; Save the WIDTH register to xmm1. (This macro needs to break it.)
- MOVq xmm1, WIDTHq
-
- ; Once read three bytes from the source to TEMPd, and copy it to the
- ; destination xmm register.
- lea WIDTHq, [WIDTHq + WIDTHq * 2]
- movzx TEMPd, BYTE [ARGBq + WIDTHq * 2 + 2]
- shl TEMPd, 16
- mov TEMPw, WORD [ARGBq + WIDTHq * 2]
- movd %1, TEMPd
-
- ; Restore the WIDTH register.
- MOVq WIDTHq, xmm1
-%elif %2 == 2
- ; Read two RGB pixels and convert them to two ARGB pixels.
- ; Read six bytes from the source to the destination xmm register.
- mov TEMPq, WIDTHq
- lea TEMPq, [TEMPq + TEMPq * 2]
- movd %1, DWORD [ARGBq + TEMPq * 2]
- pinsrw %1, WORD [ARGBq + TEMPq * 2 + 4], 3
-
- ; Fill the alpha values of these RGB pixels with 0 and convert them to two
- ; ARGB pixels.
- UNPACKRGB %1, 3
-%elif %2 == 4
- ; Read four RGB pixels and convert them to four ARGB pixels.
- ; Read twelve bytes from the source to the destination xmm register.
- mov TEMPq, WIDTHq
- lea TEMPq, [TEMPq + TEMPq * 2]
- movq %1, QWORD [ARGBq + TEMPq * 2]
- movd xmm1, DWORD [ARGBq + TEMPq * 2 + 8]
- shufps %1, xmm1, 01000100B
-
- ; Fill the alpha values of these RGB pixels with 0 and convert them to four
- ; ARGB pixels.
- UNPACKRGB %1, 3
- UNPACKRGB %1, 4 + 3
- UNPACKRGB %1, 4 + 4 + 3
-%else
-%error unsupported number of pixels.
-%endif
-
-%else
-%error unsupported PIXELSIZE value.
-%endif
-
-%endmacro
-
-;
-; CALC_Y %1 (xmm), %2 (xmm)
-; Calculates four Y values from four ARGB pixels stored in %2.
-; %1.b[0] = ToByte((25 * B(0) + 129 * G(0) + 66 * R(0) + 128) / 256 + 16);
-; %1.b[1] = ToByte((25 * B(1) + 129 * G(1) + 66 * R(1) + 128) / 256 + 16);
-; %1.b[2] = ToByte((25 * B(2) + 129 * G(2) + 66 * R(2) + 128) / 256 + 16);
-; %1.b[3] = ToByte((25 * B(3) + 129 * G(3) + 66 * R(3) + 128) / 256 + 16);
-;
-%macro CALC_Y 2
- ; To avoid signed saturation, we divide this conversion formula into two
- ; formulae and store their results into two XMM registers %1 and xmm2.
- ; %1.w[0] = 25 * %2.b[0] + 2 * %2.b[1] + 66 * %2.b[2] + 0 * %2.b[3];
- ; %1.w[1] = 25 * %2.b[4] + 2 * %2.b[5] + 66 * %2.b[6] + 0 * %2.b[7];
- ; %1.w[2] = 25 * %2.b[8] + 2 * %2.b[9] + 66 * %2.b[10] + 0 * %2.b[11];
- ; %1.w[3] = 25 * %2.b[12] + 2 * %2.b[13] + 66 * %2.b[14] + 0 * %2.b[15];
- ; xmm2.w[0] = 0 * %2.b[0] + 127 * %2.b[1] + 0 * %2.b[2] + 0 * %2.b[3];
- ; xmm2.w[1] = 0 * %2.b[4] + 127 * %2.b[5] + 0 * %2.b[6] + 0 * %2.b[7];
- ; xmm2.w[2] = 0 * %2.b[8] + 127 * %2.b[9] + 0 * %2.b[10] + 0 * %2.b[11];
- ; xmm2.w[3] = 0 * %2.b[12] + 127 * %2.b[13] + 0 * %2.b[14] + 0 * %2.b[15];
- movdqa %1, %2
- pmaddubsw %1, XMM_CONST_Y0
- phaddsw %1, %1
- movdqa xmm2, %2
- pmaddubsw xmm2, XMM_CONST_Y1
- phaddsw xmm2, xmm2
-
- ; %1.b[0] = ToByte((%1.w[0] + xmm2.w[0] + 128) / 256 + 16);
- ; %1.b[1] = ToByte((%1.w[1] + xmm2.w[1] + 128) / 256 + 16);
- ; %1.b[2] = ToByte((%1.w[2] + xmm2.w[2] + 128) / 256 + 16);
- ; %1.b[3] = ToByte((%1.w[3] + xmm2.w[3] + 128) / 256 + 16);
- paddw %1, xmm2
- movdqa xmm2, XMM_CONST_128
- paddw %1, xmm2
- psrlw %1, 8
- psrlw xmm2, 3
- paddw %1, xmm2
- packuswb %1, %1
-%endmacro
-
-;
-; INIT_UV %1 (r32), %2 (reg) %3 (imm)
-;
-%macro INIT_UV 3
-
-%if SUBSAMPLING == 1 && LINE == 1
-%if %3 == 1 || %3 == 2
- movzx %1, BYTE [%2 + WIDTHq]
-%elif %3 == 4
- movzx %1, WORD [%2 + WIDTHq]
-%else
-%error unsupported number of pixels.
-%endif
-%endif
-
-%endmacro
-
-;
-; CALC_UV %1 (xmm), %2 (xmm), %3 (xmm), %4 (r32)
-; Calculates two U (or V) values from four ARGB pixels stored in %2.
-; if %3 == XMM_CONST_U
-; if (SUBSAMPLING) {
-; %1.b[0] = ToByte((112 * B(0) - 74 * G(0) - 38 * R(0) + 128) / 256 + 128);
-; %1.b[0] = ToByte((112 * B(0) - 74 * G(0) - 38 * R(0) + 128) / 256 + 128);
-; %1.b[1] = ToByte((112 * B(2) - 74 * G(2) - 38 * R(2) + 128) / 256 + 128);
-; %1.b[1] = ToByte((112 * B(2) - 74 * G(2) - 38 * R(2) + 128) / 256 + 128);
-; } else {
-; %1.b[0] = ToByte((112 * B(0) - 74 * G(0) - 38 * R(0) + 128) / 256 + 128);
-; %1.b[1] = ToByte((112 * B(2) - 74 * G(2) - 38 * R(2) + 128) / 256 + 128);
-; }
-; if %3 == XMM_CONST_V
-; %1.b[0] = ToByte((-18 * B(0) - 94 * G(0) + 112 * R(0) + 128) / 256 + 128);
-; %1.b[1] = ToByte((-18 * B(2) - 94 * G(2) + 112 * R(2) + 128) / 256 + 128);
-;
-%macro CALC_UV 4
- ; for (int i = 0; i < 4; ++i) {
- ; %1.w[i] = 0;
- ; for (int j = 0; j < 4; ++j)
- ; %1.w[i] += %3.b[i * 4 + j] + %2.b[i * 4 + j];
- ; }
- movdqa %1, %2
- pmaddubsw %1, %3
- phaddsw %1, %1
-
-%if SUBSAMPLING == 1
- ; %1.w[0] = (%1.w[0] + %1.w[1] + 1) / 2;
- ; %1.w[1] = (%1.w[1] + %1.w[0] + 1) / 2;
- ; %1.w[2] = (%1.w[2] + %1.w[3] + 1) / 2;
- ; %1.w[3] = (%1.w[3] + %1.w[2] + 1) / 2;
- pshuflw xmm2, %1, 10110001B
- pavgw %1, xmm2
-%endif
-
- ; %1.b[0] = ToByte((%1.w[0] + 128) / 256 + 128);
- ; %1.b[1] = ToByte((%1.w[2] + 128) / 256 + 128);
- pshuflw %1, %1, 10001000B
- paddw %1, XMM_CONST_128
- psraw %1, 8
- paddw %1, XMM_CONST_128
- packuswb %1, %1
-
-%if SUBSAMPLING == 1 && LINE == 1
- ; %1.b[0] = (%1.b[0] + %3.b[0] + 1) / 2;
- ; %1.b[1] = (%1.b[1] + %3.b[1] + 1) / 2;
- movd xmm2, %4
- pavgb %1, xmm2
-%endif
-%endmacro
-
-;
-; extern "C" void ConvertARGBToYUVRow_SSSE3(const uint8* argb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertARGBToYUVRow_SSSE3
-%define PIXELSIZE 4
-%define SUBSAMPLING 0
-%define LINE 0
-%include "convert_rgb_to_yuv_ssse3.inc"
-
-;
-; extern "C" void ConvertRGBToYUVRow_SSSE3(const uint8* rgb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertRGBToYUVRow_SSSE3
-%define PIXELSIZE 3
-%define SUBSAMPLING 0
-%define LINE 0
-%include "convert_rgb_to_yuv_ssse3.inc"
-
-;
-; extern "C" void ConvertARGBToYUVEven_SSSE3(const uint8* argb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertARGBToYUVEven_SSSE3
-%define PIXELSIZE 4
-%define SUBSAMPLING 1
-%define LINE 0
-%include "convert_rgb_to_yuv_ssse3.inc"
-
-;
-; extern "C" void ConvertARGBToYUVOdd_SSSE3(const uint8* argb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertARGBToYUVOdd_SSSE3
-%define PIXELSIZE 4
-%define SUBSAMPLING 1
-%define LINE 1
-%include "convert_rgb_to_yuv_ssse3.inc"
-
-;
-; extern "C" void ConvertRGBToYUVEven_SSSE3(const uint8* rgb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertRGBToYUVEven_SSSE3
-%define PIXELSIZE 3
-%define SUBSAMPLING 1
-%define LINE 0
-%include "convert_rgb_to_yuv_ssse3.inc"
-
-;
-; extern "C" void ConvertRGBToYUVOdd_SSSE3(const uint8* rgb,
-; uint8* y,
-; uint8* u,
-; uint8* v,
-; int width);
-;
-%define SYMBOL ConvertRGBToYUVOdd_SSSE3
-%define PIXELSIZE 3
-%define SUBSAMPLING 1
-%define LINE 1
-%include "convert_rgb_to_yuv_ssse3.inc"
diff --git a/src/media/base/simd/convert_rgb_to_yuv_ssse3.cc b/src/media/base/simd/convert_rgb_to_yuv_ssse3.cc
deleted file mode 100644
index e956926..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_ssse3.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/simd/convert_rgb_to_yuv.h"
-
-#include "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv_ssse3.h"
-
-namespace media {
-
-void ConvertRGB32ToYUV_SSSE3(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- for (; height >= 2; height -= 2) {
- ConvertARGBToYUVRow_SSSE3(rgbframe, yplane, uplane, vplane, width);
- rgbframe += rgbstride;
- yplane += ystride;
-
- ConvertARGBToYUVRow_SSSE3(rgbframe, yplane, NULL, NULL, width);
- rgbframe += rgbstride;
- yplane += ystride;
-
- uplane += uvstride;
- vplane += uvstride;
- }
-
- if (height)
- ConvertARGBToYUVRow_SSSE3(rgbframe, yplane, uplane, vplane, width);
-}
-
-void ConvertRGB24ToYUV_SSSE3(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- for (; height >= 2; height -= 2) {
- ConvertRGBToYUVRow_SSSE3(rgbframe, yplane, uplane, vplane, width);
- rgbframe += rgbstride;
- yplane += ystride;
-
- ConvertRGBToYUVRow_SSSE3(rgbframe, yplane, NULL, NULL, width);
- rgbframe += rgbstride;
- yplane += ystride;
-
- uplane += uvstride;
- vplane += uvstride;
- }
-
- if (height)
- ConvertRGBToYUVRow_SSSE3(rgbframe, yplane, uplane, vplane, width);
-}
-
-} // namespace media
diff --git a/src/media/base/simd/convert_rgb_to_yuv_ssse3.h b/src/media/base/simd/convert_rgb_to_yuv_ssse3.h
deleted file mode 100644
index 84557d1..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_ssse3.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_SSSE3_H_
-#define MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_SSSE3_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// The header file for ASM functions that convert a row of RGB pixels with SSSE3
-// instructions so we can call them from C++ code. These functions are
-// implemented in "convert_rgb_to_yuv_ssse3.asm".
-
-// Convert a row of 24-bit RGB pixels to YV12 pixels.
-void ConvertRGBToYUVRow_SSSE3(const uint8* rgb,
- uint8* y,
- uint8* u,
- uint8* v,
- int width);
-
-// Convert a row of 32-bit RGB pixels to YV12 pixels.
-void ConvertARGBToYUVRow_SSSE3(const uint8* argb,
- uint8* y,
- uint8* u,
- uint8* v,
- int width);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_SSSE3_H_
diff --git a/src/media/base/simd/convert_rgb_to_yuv_ssse3.inc b/src/media/base/simd/convert_rgb_to_yuv_ssse3.inc
deleted file mode 100644
index 35c0db9..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_ssse3.inc
+++ /dev/null
@@ -1,200 +0,0 @@
-; Copyright (c) 2011 The Chromium Authors. All rights reserved.
-; Use of this source code is governed by a BSD-style license that can be
-; found in the LICENSE file.
-
-;
-; void SYMBOL(const uint8* argb, uint8* y, uint8* u, uint8* v, int width);
-;
-; The main code that converts RGB pixels to YUV pixels. This function roughly
-; consists of three parts: converting one ARGB pixel to YUV pixels, converting
-; two ARGB pixels to YUV pixels, and converting four ARGB pixels to YUV pixels.
-; To write the structure of this function in C, it becomes the snippet listed
-; below.
-;
-; if (width & 1) {
-; --width;
-; // Convert one ARGB pixel to one Y pixel, one U pixel, and one V pixel.
-; }
-;
-; if (width & 2) {
-; width -= 2;
-; // Convert two ARGB pixels to two Y pixels, one U pixel, and one V pixel.
-; }
-;
-; while (width) {
-; width -= 4;
-; // Convert four ARGB pixels to four Y pixels, two U pixels, and two V
-; // pixels.
-; }
-;
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-mangle(SYMBOL):
- %assign stack_offset 0
- PROLOGUE 5, 6, 8, ARGB, Y, U, V, WIDTH, TEMP
-
- ; Initialize constants used in this function. (We use immediates to avoid
- ; dependency onto GOT.)
- LOAD_XMM XMM_CONST_Y0, 0x00420219
- LOAD_XMM XMM_CONST_Y1, 0x00007F00
- LOAD_XMM XMM_CONST_U, 0x00DAB670
- LOAD_XMM XMM_CONST_V, 0x0070A2EE
- LOAD_XMM XMM_CONST_128, 0x00800080
-
-.convert_one_pixel:
- ; Divide the input width by two so it represents the offsets for u[] and v[].
- ; When the width is odd, We read the rightmost ARGB pixel and convert its
- ; colorspace to YUV. This code stores one Y pixel, one U pixel, and one V
- ; pixel.
- sar WIDTHq, 1
- jnc .convert_two_pixels
-
- ; Read one ARGB (or RGB) pixel.
- READ_ARGB xmm0, 1
-
- ; Calculate y[0] from one RGB pixel read above.
- CALC_Y xmm1, xmm0
- movd TEMPd, xmm1
- mov BYTE [Yq + WIDTHq * 2], TEMPb
-
- ; Calculate u[0] from one RGB pixel read above. If this is an odd line, the
- ; output pixel contains the U value calculated in the previous call. We also
- ; read this pixel and calculate their average.
- INIT_UV TEMPd, Uq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_U, TEMPd
- movd TEMPd, xmm1
- mov BYTE [Uq + WIDTHq], TEMPb
-
- ; Calculate v[0] from one RGB pixel. Same as u[0], we read the result of the
- ; previous call and get their average.
- INIT_UV TEMPd, Uq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_V, TEMPd
- movd TEMPd, xmm1
- mov BYTE [Vq + WIDTHq], TEMPb
-
-.convert_two_pixels:
- ; If the input width is not a multiple of four, read the rightmost two ARGB
- ; pixels and convert their colorspace to YUV. This code stores two Y pixels,
- ; one U pixel, and one V pixel.
- test WIDTHb, 2 / 2
- jz .convert_four_pixels
- sub WIDTHb, 2 / 2
-
- ; Read two ARGB (or RGB) pixels.
- READ_ARGB xmm0, 2
-
- ; Calculate r[0] and r[1] from two RGB pixels read above.
- CALC_Y xmm1, xmm0
- movd TEMPd, xmm1
- mov WORD [Yq + WIDTHq * 2], TEMPw
-
- ; Skip calculating u and v if the output buffer is NULL.
- test Uq, Uq
- jz .convert_four_pixels
-
- ; Calculate u[0] from two RGB pixels read above. (For details, read the above
- ; comment in .convert_one_pixel).
- INIT_UV TEMPd, Uq, 2
- CALC_UV xmm1, xmm0, XMM_CONST_U, TEMPd
- movd TEMPd, xmm1
- mov BYTE [Uq + WIDTHq], TEMPb
-
- ; Calculate v[0] from two RGB pixels read above.
- INIT_UV TEMPd, Vq, 2
- CALC_UV xmm1, xmm0, XMM_CONST_V, TEMPd
- movd TEMPd, xmm1
- mov BYTE [Vq + WIDTHq], TEMPb
-
-.convert_four_pixels:
- ; Read four ARGB pixels and convert their colorspace to YUV. This code stores
- ; four Y pixels, two U pixels, and two V pixels.
- test WIDTHq, WIDTHq
- jz .convert_finish
-
-%if PIXELSIZE == 4
- ; Check if the input buffer is aligned to a 16-byte boundary and use movdqa
- ; for reading the ARGB pixels.
- test ARGBw, 15
- jnz .convert_four_pixels_unaligned
-
-.convert_four_pixels_aligned:
- sub WIDTHq, 4 / 2
-
- ; Read four ARGB pixels. (We can use movdqa here since we have checked if the
- ; source address is aligned.)
- movdqa xmm0, DQWORD [ARGBq + WIDTHq * 4 * 2]
-
- ; Calculate y[0], y[1], y[2],and, y[3] from the input ARGB pixels.
- CALC_Y xmm1, xmm0
- movd DWORD [Yq + WIDTHq * 2], xmm1
-
-%if SUBSAMPLING == 0
- ; Skip calculating u and v if the output buffer is NULL, which means we are
- ; converting an odd line. (When we enable subsampling, these buffers must
- ; contain the u and v values for the previous call, i.e. these variables must
- ; not be NULL.)
- test Uq, Uq
- jz .convert_four_pixels_aligned_next
-%endif
-
- ; Calculate u[0] and u[1] from four ARGB pixels read above.
- INIT_UV TEMPd, Uq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_U, TEMPd
- movd TEMPd, xmm1
- mov WORD [Uq + WIDTHq], TEMPw
-
- ; Calculate v[0] and v[1] from four ARGB pixels read above.
- INIT_UV TEMPd, Vq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_V, TEMPd
- movd TEMPd, xmm1
- mov WORD [Vq + WIDTHq], TEMPw
-
-%if SUBSAMPLING == 0
-.convert_four_pixels_aligned_next:
-%endif
-
- test WIDTHq, WIDTHq
- jnz .convert_four_pixels_aligned
-
- jmp .convert_finish
-%endif
-
-.convert_four_pixels_unaligned:
- sub WIDTHq, 4 / 2
-
- ; Read four ARGB (or RGB) pixels.
- READ_ARGB xmm0, 4
-
- ; Calculate y[0], y[1], y[2],and, y[3] from the input ARGB pixels.
- CALC_Y xmm1, xmm0
- movd DWORD [Yq + WIDTHq * 2], xmm1
-
-%if SUBSAMPLING == 0
- ; Skip calculating u and v if the output buffer is NULL.
- test Uq, Uq
- jz .convert_four_pixels_unaligned_next
-%endif
-
- ; Calculate u[0] and u[1] from the input ARGB pixels.
- INIT_UV TEMPd, Uq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_U, TEMPd
- movd TEMPd, xmm1
- mov WORD [Uq + WIDTHq], TEMPw
-
- ; Calculate v[0] and v[1] from the input ARGB pixels.
- INIT_UV TEMPd, Vq, 4
- CALC_UV xmm1, xmm0, XMM_CONST_V, TEMPd
- movd TEMPd, xmm1
- mov WORD [Vq + WIDTHq], TEMPw
-
-%if SUBSAMPLING == 0
-.convert_four_pixels_unaligned_next:
-%endif
-
- test WIDTHq, WIDTHq
- jnz .convert_four_pixels_unaligned
-
-.convert_finish:
- ; Just exit this function since this is a void function.
- RET
diff --git a/src/media/base/simd/convert_rgb_to_yuv_unittest.cc b/src/media/base/simd/convert_rgb_to_yuv_unittest.cc
deleted file mode 100644
index 08aa4df..0000000
--- a/src/media/base/simd/convert_rgb_to_yuv_unittest.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/cpu.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-// Reference code that converts RGB pixels to YUV pixels.
-int ConvertRGBToY(const uint8* rgb) {
- int y = 25 * rgb[0] + 129 * rgb[1] + 66 * rgb[2];
- y = ((y + 128) >> 8) + 16;
- return std::max(0, std::min(255, y));
-}
-
-int ConvertRGBToU(const uint8* rgb, int size) {
- int u = 112 * rgb[0] - 74 * rgb[1] - 38 * rgb[2];
- u = ((u + 128) >> 8) + 128;
- return std::max(0, std::min(255, u));
-}
-
-int ConvertRGBToV(const uint8* rgb, int size) {
- int v = -18 * rgb[0] - 94 * rgb[1] + 112 * rgb[2];
- v = ((v + 128) >> 8) + 128;
- return std::max(0, std::min(255, v));
-}
-
-} // namespace
-
-// A side-by-side test that verifies our ASM functions that convert RGB pixels
-// to YUV pixels can output the expected results. This test converts RGB pixels
-// to YUV pixels with our ASM functions (which use SSE, SSE2, SSE3, and SSSE3)
-// and compare the output YUV pixels with the ones calculated with out reference
-// functions implemented in C++.
-TEST(YUVConvertTest, SideBySideRGB) {
- // We skip this test on PCs which does not support SSE3 because this test
- // needs it.
- base::CPU cpu;
- if (!cpu.has_ssse3())
- return;
-
- // This test checks a subset of all RGB values so this test does not take so
- // long time.
- const int kStep = 8;
- const int kWidth = 256 / kStep;
-
- for (int size = 3; size <= 4; ++size) {
- // Create the output buffers.
- scoped_array<uint8> rgb(new uint8[kWidth * size]);
- scoped_array<uint8> y(new uint8[kWidth]);
- scoped_array<uint8> u(new uint8[kWidth / 2]);
- scoped_array<uint8> v(new uint8[kWidth / 2]);
-
- // Choose the function that converts from RGB pixels to YUV ones.
- void (*convert)(const uint8*, uint8*, uint8*, uint8*,
- int, int, int, int, int) = NULL;
- if (size == 3)
- convert = media::ConvertRGB24ToYUV_SSSE3;
- else
- convert = media::ConvertRGB32ToYUV_SSSE3;
-
- int total_error = 0;
- for (int r = 0; r < kWidth; ++r) {
- for (int g = 0; g < kWidth; ++g) {
-
- // Fill the input pixels.
- for (int b = 0; b < kWidth; ++b) {
- rgb[b * size + 0] = b * kStep;
- rgb[b * size + 1] = g * kStep;
- rgb[b * size + 2] = r * kStep;
- if (size == 4)
- rgb[b * size + 3] = 255;
- }
-
- // Convert the input RGB pixels to YUV ones.
- convert(rgb.get(), y.get(), u.get(), v.get(), kWidth, 1, kWidth * size,
- kWidth, kWidth / 2);
-
- // Check the output Y pixels.
- for (int i = 0; i < kWidth; ++i) {
- const uint8* p = &rgb[i * size];
- int error = ConvertRGBToY(p) - y[i];
- total_error += error > 0 ? error : -error;
- }
-
- // Check the output U pixels.
- for (int i = 0; i < kWidth / 2; ++i) {
- const uint8* p = &rgb[i * 2 * size];
- int error = ConvertRGBToU(p, size) - u[i];
- total_error += error > 0 ? error : -error;
- }
-
- // Check the output V pixels.
- for (int i = 0; i < kWidth / 2; ++i) {
- const uint8* p = &rgb[i * 2 * size];
- int error = ConvertRGBToV(p, size) - v[i];
- total_error += error > 0 ? error : -error;
- }
- }
- }
-
- EXPECT_EQ(0, total_error);
- }
-}
diff --git a/src/media/base/simd/convert_yuv_to_rgb.h b/src/media/base/simd/convert_yuv_to_rgb.h
deleted file mode 100644
index 164ad11..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb.h
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SIMD_CONVERT_YUV_TO_RGB_H_
-#define MEDIA_BASE_SIMD_CONVERT_YUV_TO_RGB_H_
-
-#include "base/basictypes.h"
-#include "media/base/yuv_convert.h"
-
-namespace media {
-
-typedef void (*ConvertYUVToRGB32Proc)(const uint8*,
- const uint8*,
- const uint8*,
- uint8*,
- int,
- int,
- int,
- int,
- int,
- YUVType);
-
-void ConvertYUVToRGB32_C(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type);
-
-void ConvertYUVToRGB32_SSE(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type);
-
-void ConvertYUVToRGB32_MMX(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type);
-
-} // namespace media
-
-// Assembly functions are declared without namespace.
-extern "C" {
-
-typedef void (*ConvertYUVToRGB32RowProc)(const uint8*,
- const uint8*,
- const uint8*,
- uint8*,
- int);
-typedef void (*ScaleYUVToRGB32RowProc)(const uint8*,
- const uint8*,
- const uint8*,
- uint8*,
- int,
- int);
-
-void ConvertYUVToRGB32Row_C(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width);
-
-void ConvertYUVToRGB32Row_MMX(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width);
-
-void ConvertYUVToRGB32Row_SSE(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width);
-
-void ScaleYUVToRGB32Row_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void ScaleYUVToRGB32Row_MMX(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void ScaleYUVToRGB32Row_SSE(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void ScaleYUVToRGB32Row_SSE2_X64(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void LinearScaleYUVToRGB32Row_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void LinearScaleYUVToRGB32RowWithRange_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int dest_width,
- int source_x,
- int source_dx);
-
-void LinearScaleYUVToRGB32Row_MMX(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void LinearScaleYUVToRGB32Row_SSE(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-void LinearScaleYUVToRGB32Row_MMX_X64(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx);
-
-} // extern "C"
-
-#endif // MEDIA_BASE_SIMD_CONVERT_YUV_TO_RGB_H_
diff --git a/src/media/base/simd/convert_yuv_to_rgb_c.cc b/src/media/base/simd/convert_yuv_to_rgb_c.cc
deleted file mode 100644
index db6e557..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb_c.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/simd/convert_yuv_to_rgb.h"
-#include "media/base/simd/yuv_to_rgb_table.h"
-
-#define packuswb(x) ((x) < 0 ? 0 : ((x) > 255 ? 255 : (x)))
-#define paddsw(x, y) (((x) + (y)) < -32768 ? -32768 : \
- (((x) + (y)) > 32767 ? 32767 : ((x) + (y))))
-
-static inline void ConvertYUVToRGB32_C(uint8 y,
- uint8 u,
- uint8 v,
- uint8* rgb_buf) {
- int b = kCoefficientsRgbY[256+u][0];
- int g = kCoefficientsRgbY[256+u][1];
- int r = kCoefficientsRgbY[256+u][2];
- int a = kCoefficientsRgbY[256+u][3];
-
- b = paddsw(b, kCoefficientsRgbY[512+v][0]);
- g = paddsw(g, kCoefficientsRgbY[512+v][1]);
- r = paddsw(r, kCoefficientsRgbY[512+v][2]);
- a = paddsw(a, kCoefficientsRgbY[512+v][3]);
-
- b = paddsw(b, kCoefficientsRgbY[y][0]);
- g = paddsw(g, kCoefficientsRgbY[y][1]);
- r = paddsw(r, kCoefficientsRgbY[y][2]);
- a = paddsw(a, kCoefficientsRgbY[y][3]);
-
- b >>= 6;
- g >>= 6;
- r >>= 6;
- a >>= 6;
-
- *reinterpret_cast<uint32*>(rgb_buf) = (packuswb(b)) |
- (packuswb(g) << 8) |
- (packuswb(r) << 16) |
- (packuswb(a) << 24);
-}
-
-extern "C" {
-
-void ConvertYUVToRGB32Row_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width) {
- for (int x = 0; x < width; x += 2) {
- uint8 u = u_buf[x >> 1];
- uint8 v = v_buf[x >> 1];
- uint8 y0 = y_buf[x];
- ConvertYUVToRGB32_C(y0, u, v, rgb_buf);
- if ((x + 1) < width) {
- uint8 y1 = y_buf[x + 1];
- ConvertYUVToRGB32_C(y1, u, v, rgb_buf + 4);
- }
- rgb_buf += 8; // Advance 2 pixels.
- }
-}
-
-// 16.16 fixed point is used. A shift by 16 isolates the integer.
-// A shift by 17 is used to further subsample the chrominence channels.
-// & 0xffff isolates the fixed point fraction. >> 2 to get the upper 2 bits,
-// for 1/65536 pixel accurate interpolation.
-void ScaleYUVToRGB32Row_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx) {
- int x = 0;
- for (int i = 0; i < width; i += 2) {
- int y = y_buf[x >> 16];
- int u = u_buf[(x >> 17)];
- int v = v_buf[(x >> 17)];
- ConvertYUVToRGB32_C(y, u, v, rgb_buf);
- x += source_dx;
- if ((i + 1) < width) {
- y = y_buf[x >> 16];
- ConvertYUVToRGB32_C(y, u, v, rgb_buf+4);
- x += source_dx;
- }
- rgb_buf += 8;
- }
-}
-
-void LinearScaleYUVToRGB32Row_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int width,
- int source_dx) {
- // Avoid point-sampling for down-scaling by > 2:1.
- int source_x = 0;
- if (source_dx >= 0x20000)
- source_x += 0x8000;
- LinearScaleYUVToRGB32RowWithRange_C(y_buf, u_buf, v_buf, rgb_buf, width,
- source_x, source_dx);
-}
-
-void LinearScaleYUVToRGB32RowWithRange_C(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int dest_width,
- int x,
- int source_dx) {
- for (int i = 0; i < dest_width; i += 2) {
- int y0 = y_buf[x >> 16];
- int y1 = y_buf[(x >> 16) + 1];
- int u0 = u_buf[(x >> 17)];
- int u1 = u_buf[(x >> 17) + 1];
- int v0 = v_buf[(x >> 17)];
- int v1 = v_buf[(x >> 17) + 1];
- int y_frac = (x & 65535);
- int uv_frac = ((x >> 1) & 65535);
- int y = (y_frac * y1 + (y_frac ^ 65535) * y0) >> 16;
- int u = (uv_frac * u1 + (uv_frac ^ 65535) * u0) >> 16;
- int v = (uv_frac * v1 + (uv_frac ^ 65535) * v0) >> 16;
- ConvertYUVToRGB32_C(y, u, v, rgb_buf);
- x += source_dx;
- if ((i + 1) < dest_width) {
- y0 = y_buf[x >> 16];
- y1 = y_buf[(x >> 16) + 1];
- y_frac = (x & 65535);
- y = (y_frac * y1 + (y_frac ^ 65535) * y0) >> 16;
- ConvertYUVToRGB32_C(y, u, v, rgb_buf+4);
- x += source_dx;
- }
- rgb_buf += 8;
- }
-}
-
-}
-
-namespace media {
-
-void ConvertYUVToRGB32_C(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type) {
- unsigned int y_shift = yuv_type;
- for (int y = 0; y < height; ++y) {
- uint8* rgb_row = rgbframe + y * rgbstride;
- const uint8* y_ptr = yplane + y * ystride;
- const uint8* u_ptr = uplane + (y >> y_shift) * uvstride;
- const uint8* v_ptr = vplane + (y >> y_shift) * uvstride;
-
- ConvertYUVToRGB32Row_C(y_ptr,
- u_ptr,
- v_ptr,
- rgb_row,
- width);
- }
-}
-
-} // namespace media
diff --git a/src/media/base/simd/convert_yuv_to_rgb_mmx.asm b/src/media/base/simd/convert_yuv_to_rgb_mmx.asm
deleted file mode 100644
index e044474..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb_mmx.asm
+++ /dev/null
@@ -1,22 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX instructions.
-;
- SECTION_TEXT
- CPU MMX
-
-; Use movq to save the output.
-%define MOVQ movq
-
-; extern "C" void ConvertYUVToRGB32Row_MMX(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width);
-%define SYMBOL ConvertYUVToRGB32Row_MMX
-%include "convert_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/convert_yuv_to_rgb_mmx.inc b/src/media/base/simd/convert_yuv_to_rgb_mmx.inc
deleted file mode 100644
index b9555ce..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb_mmx.inc
+++ /dev/null
@@ -1,119 +0,0 @@
-; Copyright (c) 2011 The Chromium Authors. All rights reserved.
-; Use of this source code is governed by a BSD-style license that can be
-; found in the LICENSE file.
-
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-; Non-PIC code is the fastest so use this if possible.
-%ifndef PIC
-mangle(SYMBOL):
- %assign stack_offset 0
- PROLOGUE 5, 7, 3, Y, U, V, ARGB, WIDTH, TEMPU, TEMPV
- extern mangle(kCoefficientsRgbY)
- jmp .convertend
-
-.convertloop:
- movzx TEMPUd, BYTE [Uq]
- add Uq, 1
- movzx TEMPVd, BYTE [Vq]
- add Vq, 1
- movq mm0, [mangle(kCoefficientsRgbY) + 2048 + 8 * TEMPUq]
- movzx TEMPUd, BYTE [Yq]
- paddsw mm0, [mangle(kCoefficientsRgbY) + 4096 + 8 * TEMPVq]
- movzx TEMPVd, BYTE [Yq + 1]
- movq mm1, [mangle(kCoefficientsRgbY) + 8 * TEMPUq]
- add Yq, 2
- movq mm2, [mangle(kCoefficientsRgbY) + 8 * TEMPVq]
- paddsw mm1, mm0
- paddsw mm2, mm0
- psraw mm1, 6
- psraw mm2, 6
- packuswb mm1, mm2
- MOVQ [ARGBq], mm1
- add ARGBq, 8
-
-.convertend:
- sub WIDTHq, 2
- jns .convertloop
-
- ; If number of pixels is odd then compute it.
- and WIDTHq, 1
- jz .convertdone
-
- movzx TEMPUd, BYTE [Uq]
- movq mm0, [mangle(kCoefficientsRgbY) + 2048 + 8 * TEMPUq]
- movzx TEMPVd, BYTE [Vq]
- paddsw mm0, [mangle(kCoefficientsRgbY) + 4096 + 8 * TEMPVq]
- movzx TEMPUd, BYTE [Yq]
- movq mm1, [mangle(kCoefficientsRgbY) + 8 * TEMPUq]
- paddsw mm1, mm0
- psraw mm1, 6
- packuswb mm1, mm1
- movd [ARGBq], mm1
-
-.convertdone:
- RET
-%endif
-
-; With PIC code we need to load the address of mangle(kCoefficientsRgbY).
-; This code is slower than the above version.
-%ifdef PIC
-mangle(SYMBOL):
- %assign stack_offset 0
- PROLOGUE 5, 7, 3, Y, U, V, ARGB, WIDTH, TEMP, TABLE
-
- extern mangle(kCoefficientsRgbY)
- LOAD_SYM TABLEq, mangle(kCoefficientsRgbY)
-
- jmp .convertend
-
-.convertloop:
- movzx TEMPd, BYTE [Uq]
- movq mm0, [TABLEq + 2048 + 8 * TEMPq]
- add Uq, 1
-
- movzx TEMPd, BYTE [Vq]
- paddsw mm0, [TABLEq + 4096 + 8 * TEMPq]
- add Vq, 1
-
- movzx TEMPd, BYTE [Yq]
- movq mm1, [TABLEq + 8 * TEMPq]
-
- movzx TEMPd, BYTE [Yq + 1]
- movq mm2, [TABLEq + 8 * TEMPq]
- add Yq, 2
-
- ; Add UV components to Y component.
- paddsw mm1, mm0
- paddsw mm2, mm0
-
- ; Down shift and then pack.
- psraw mm1, 6
- psraw mm2, 6
- packuswb mm1, mm2
- MOVQ [ARGBq], mm1
- add ARGBq, 8
-
-.convertend:
- sub WIDTHq, 2
- jns .convertloop
-
- ; If number of pixels is odd then compute it.
- and WIDTHq, 1
- jz .convertdone
-
- movzx TEMPd, BYTE [Uq]
- movq mm0, [TABLEq + 2048 + 8 * TEMPq]
- movzx TEMPd, BYTE [Vq]
- paddsw mm0, [TABLEq + 4096 + 8 * TEMPq]
- movzx TEMPd, BYTE [Yq]
- movq mm1, [TABLEq + 8 * TEMPq]
- paddsw mm1, mm0
- psraw mm1, 6
- packuswb mm1, mm1
- movd [ARGBq], mm1
-
-.convertdone:
- RET
-%endif
diff --git a/src/media/base/simd/convert_yuv_to_rgb_sse.asm b/src/media/base/simd/convert_yuv_to_rgb_sse.asm
deleted file mode 100644
index 2f1967a..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb_sse.asm
+++ /dev/null
@@ -1,23 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX and SSE instructions.
-;
- SECTION_TEXT
- CPU MMX, SSE
-
-; Use SSE instruction movntq can write faster.
-%define MOVQ movntq
-
-;
-; extern "C" void ConvertYUVToRGB32Row_SSE(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width);
-%define SYMBOL ConvertYUVToRGB32Row_SSE
-%include "convert_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/convert_yuv_to_rgb_x86.cc b/src/media/base/simd/convert_yuv_to_rgb_x86.cc
deleted file mode 100644
index 3825bdb..0000000
--- a/src/media/base/simd/convert_yuv_to_rgb_x86.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#if defined(_MSC_VER)
-#include <intrin.h>
-#else
-#include <mmintrin.h>
-#endif
-
-#include "media/base/simd/convert_yuv_to_rgb.h"
-#include "media/base/yuv_convert.h"
-
-namespace media {
-
-void ConvertYUVToRGB32_MMX(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type) {
- unsigned int y_shift = yuv_type;
- for (int y = 0; y < height; ++y) {
- uint8* rgb_row = rgbframe + y * rgbstride;
- const uint8* y_ptr = yplane + y * ystride;
- const uint8* u_ptr = uplane + (y >> y_shift) * uvstride;
- const uint8* v_ptr = vplane + (y >> y_shift) * uvstride;
-
- ConvertYUVToRGB32Row_MMX(y_ptr,
- u_ptr,
- v_ptr,
- rgb_row,
- width);
- }
-
- _mm_empty();
-}
-
-void ConvertYUVToRGB32_SSE(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type) {
- unsigned int y_shift = yuv_type;
- for (int y = 0; y < height; ++y) {
- uint8* rgb_row = rgbframe + y * rgbstride;
- const uint8* y_ptr = yplane + y * ystride;
- const uint8* u_ptr = uplane + (y >> y_shift) * uvstride;
- const uint8* v_ptr = vplane + (y >> y_shift) * uvstride;
-
- ConvertYUVToRGB32Row_SSE(y_ptr,
- u_ptr,
- v_ptr,
- rgb_row,
- width);
- }
-
- _mm_empty();
-}
-
-} // namespace media
diff --git a/src/media/base/simd/filter_yuv.h b/src/media/base/simd/filter_yuv.h
deleted file mode 100644
index 5a9cf11..0000000
--- a/src/media/base/simd/filter_yuv.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SIMD_FILTER_YUV_H_
-#define MEDIA_BASE_SIMD_FILTER_YUV_H_
-
-#include "base/basictypes.h"
-
-namespace media {
-
-typedef void (*FilterYUVRowsProc)(uint8*,
- const uint8*,
- const uint8*,
- int,
- int);
-
-void FilterYUVRows_C(uint8* ybuf, const uint8* y0_ptr, const uint8* y1_ptr,
- int source_width, int source_y_fraction);
-
-void FilterYUVRows_MMX(uint8* ybuf, const uint8* y0_ptr, const uint8* y1_ptr,
- int source_width, int source_y_fraction);
-
-void FilterYUVRows_SSE2(uint8* ybuf, const uint8* y0_ptr, const uint8* y1_ptr,
- int source_width, int source_y_fraction);
-
-} // namespace media
-
-#endif // MEDIA_BASE_SIMD_FILTER_YUV_H_
diff --git a/src/media/base/simd/filter_yuv_c.cc b/src/media/base/simd/filter_yuv_c.cc
deleted file mode 100644
index f292d21..0000000
--- a/src/media/base/simd/filter_yuv_c.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/base/simd/filter_yuv.h"
-
-namespace media {
-
-void FilterYUVRows_C(uint8* ybuf, const uint8* y0_ptr, const uint8* y1_ptr,
- int source_width, int source_y_fraction) {
- int y1_fraction = source_y_fraction;
- int y0_fraction = 256 - y1_fraction;
- uint8* end = ybuf + source_width;
- uint8* rounded_end = ybuf + (source_width & ~7);
-
- while (ybuf < rounded_end) {
- ybuf[0] = (y0_ptr[0] * y0_fraction + y1_ptr[0] * y1_fraction) >> 8;
- ybuf[1] = (y0_ptr[1] * y0_fraction + y1_ptr[1] * y1_fraction) >> 8;
- ybuf[2] = (y0_ptr[2] * y0_fraction + y1_ptr[2] * y1_fraction) >> 8;
- ybuf[3] = (y0_ptr[3] * y0_fraction + y1_ptr[3] * y1_fraction) >> 8;
- ybuf[4] = (y0_ptr[4] * y0_fraction + y1_ptr[4] * y1_fraction) >> 8;
- ybuf[5] = (y0_ptr[5] * y0_fraction + y1_ptr[5] * y1_fraction) >> 8;
- ybuf[6] = (y0_ptr[6] * y0_fraction + y1_ptr[6] * y1_fraction) >> 8;
- ybuf[7] = (y0_ptr[7] * y0_fraction + y1_ptr[7] * y1_fraction) >> 8;
- y0_ptr += 8;
- y1_ptr += 8;
- ybuf += 8;
- }
-
- while (ybuf < end) {
- ybuf[0] = (y0_ptr[0] * y0_fraction + y1_ptr[0] * y1_fraction) >> 8;
- ++ybuf;
- ++y0_ptr;
- ++y1_ptr;
- }
-}
-
-} // namespace media
diff --git a/src/media/base/simd/filter_yuv_mmx.cc b/src/media/base/simd/filter_yuv_mmx.cc
deleted file mode 100644
index 09d62e3..0000000
--- a/src/media/base/simd/filter_yuv_mmx.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#if defined(_MSC_VER)
-#include <intrin.h>
-#else
-#include <mmintrin.h>
-#include <emmintrin.h>
-#endif
-
-#include "build/build_config.h"
-#include "media/base/simd/filter_yuv.h"
-
-namespace media {
-
-#if defined(COMPILER_MSVC)
-// Warning 4799 is about calling emms before the function exits.
-// We calls emms in a frame level so suppress this warning.
-#pragma warning(disable: 4799)
-#endif
-
-void FilterYUVRows_MMX(uint8* dest,
- const uint8* src0,
- const uint8* src1,
- int width,
- int fraction) {
- int pixel = 0;
-
- // Process the unaligned bytes first.
- int unaligned_width =
- (8 - (reinterpret_cast<uintptr_t>(dest) & 7)) & 7;
- while (pixel < width && pixel < unaligned_width) {
- dest[pixel] = (src0[pixel] * (256 - fraction) +
- src1[pixel] * fraction) >> 8;
- ++pixel;
- }
-
- __m64 zero = _mm_setzero_si64();
- __m64 src1_fraction = _mm_set1_pi16(fraction);
- __m64 src0_fraction = _mm_set1_pi16(256 - fraction);
- const __m64* src0_64 = reinterpret_cast<const __m64*>(src0 + pixel);
- const __m64* src1_64 = reinterpret_cast<const __m64*>(src1 + pixel);
- __m64* dest64 = reinterpret_cast<__m64*>(dest + pixel);
- __m64* end64 = reinterpret_cast<__m64*>(
- reinterpret_cast<uintptr_t>(dest + width) & ~7);
-
- while (dest64 < end64) {
- __m64 src0 = *src0_64++;
- __m64 src1 = *src1_64++;
- __m64 src2 = _mm_unpackhi_pi8(src0, zero);
- __m64 src3 = _mm_unpackhi_pi8(src1, zero);
- src0 = _mm_unpacklo_pi8(src0, zero);
- src1 = _mm_unpacklo_pi8(src1, zero);
- src0 = _mm_mullo_pi16(src0, src0_fraction);
- src1 = _mm_mullo_pi16(src1, src1_fraction);
- src2 = _mm_mullo_pi16(src2, src0_fraction);
- src3 = _mm_mullo_pi16(src3, src1_fraction);
- src0 = _mm_add_pi16(src0, src1);
- src2 = _mm_add_pi16(src2, src3);
- src0 = _mm_srli_pi16(src0, 8);
- src2 = _mm_srli_pi16(src2, 8);
- src0 = _mm_packs_pu16(src0, src2);
- *dest64++ = src0;
- pixel += 8;
- }
-
- while (pixel < width) {
- dest[pixel] = (src0[pixel] * (256 - fraction) +
- src1[pixel] * fraction) >> 8;
- ++pixel;
- }
-}
-
-#if defined(COMPILER_MSVC)
-#pragma warning(default: 4799)
-#endif
-
-} // namespace media
diff --git a/src/media/base/simd/filter_yuv_sse2.cc b/src/media/base/simd/filter_yuv_sse2.cc
deleted file mode 100644
index 84dba5a..0000000
--- a/src/media/base/simd/filter_yuv_sse2.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#if defined(_MSC_VER)
-#include <intrin.h>
-#else
-#include <mmintrin.h>
-#include <emmintrin.h>
-#endif
-
-#include "media/base/simd/filter_yuv.h"
-
-namespace media {
-
-void FilterYUVRows_SSE2(uint8* dest,
- const uint8* src0,
- const uint8* src1,
- int width,
- int fraction) {
- int pixel = 0;
-
- // Process the unaligned bytes first.
- int unaligned_width =
- (16 - (reinterpret_cast<uintptr_t>(dest) & 15)) & 15;
- while (pixel < width && pixel < unaligned_width) {
- dest[pixel] = (src0[pixel] * (256 - fraction) +
- src1[pixel] * fraction) >> 8;
- ++pixel;
- }
-
- __m128i zero = _mm_setzero_si128();
- __m128i src1_fraction = _mm_set1_epi16(fraction);
- __m128i src0_fraction = _mm_set1_epi16(256 - fraction);
- const __m128i* src0_128 =
- reinterpret_cast<const __m128i*>(src0 + pixel);
- const __m128i* src1_128 =
- reinterpret_cast<const __m128i*>(src1 + pixel);
- __m128i* dest128 = reinterpret_cast<__m128i*>(dest + pixel);
- __m128i* end128 = reinterpret_cast<__m128i*>(
- reinterpret_cast<uintptr_t>(dest + width) & ~15);
-
- while (dest128 < end128) {
- __m128i src0 = _mm_loadu_si128(src0_128);
- __m128i src1 = _mm_loadu_si128(src1_128);
- __m128i src2 = _mm_unpackhi_epi8(src0, zero);
- __m128i src3 = _mm_unpackhi_epi8(src1, zero);
- src0 = _mm_unpacklo_epi8(src0, zero);
- src1 = _mm_unpacklo_epi8(src1, zero);
- src0 = _mm_mullo_epi16(src0, src0_fraction);
- src1 = _mm_mullo_epi16(src1, src1_fraction);
- src2 = _mm_mullo_epi16(src2, src0_fraction);
- src3 = _mm_mullo_epi16(src3, src1_fraction);
- src0 = _mm_add_epi16(src0, src1);
- src2 = _mm_add_epi16(src2, src3);
- src0 = _mm_srli_epi16(src0, 8);
- src2 = _mm_srli_epi16(src2, 8);
- src0 = _mm_packus_epi16(src0, src2);
- *dest128++ = src0;
- ++src0_128;
- ++src1_128;
- pixel += 16;
- }
-
- while (pixel < width) {
- dest[pixel] = (src0[pixel] * (256 - fraction) +
- src1[pixel] * fraction) >> 8;
- ++pixel;
- }
-}
-
-} // namespace media
diff --git a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.asm b/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.asm
deleted file mode 100644
index 7f7e0e8..0000000
--- a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.asm
+++ /dev/null
@@ -1,23 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX instructions.
-;
- SECTION_TEXT
- CPU MMX
-
-; Use movq to save the output.
-%define MOVQ movq
-
-; void LinearScaleYUVToRGB32Row_MMX(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width,
-; int source_dx);
-%define SYMBOL LinearScaleYUVToRGB32Row_MMX
-%include "linear_scale_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.inc b/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.inc
deleted file mode 100644
index 91c06a5..0000000
--- a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx.inc
+++ /dev/null
@@ -1,166 +0,0 @@
-; Copyright (c) 2011 The Chromium Authors. All rights reserved.
-; Use of this source code is governed by a BSD-style license that can be
-; found in the LICENSE file.
-
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-mangle(SYMBOL):
- %assign stack_offset 0
-
- extern mangle(kCoefficientsRgbY)
-
-; Parameters are in the following order:
-; 1. Y plane
-; 2. U plane
-; 3. V plane
-; 4. ARGB frame
-; 5. Width
-; 6. Source dx
-
-PROLOGUE 6, 7, 3, Y, R0, R1, ARGB, R2, R3, TEMP
-
-%if gprsize == 8
-%define WORD_SIZE QWORD
-%else
-%define WORD_SIZE DWORD
-%endif
-
-; Define register aliases.
-%define Xq R1q ; Current X position
-%define COMPLq R2q ; Component A value
-%define COMPLd R2d ; Component A value
-%define U_ARG_REGq R0q ; U plane address argument
-%define V_ARG_REGq R1q ; V plane address argument
-%define SOURCE_DX_ARG_REGq R3q ; Source dx argument
-%define WIDTH_ARG_REGq R2q ; Width argument
-
-%ifdef PIC
-; PIC code shared COMPR, U and V with the same register. Need to be careful in the
-; code they don't mix up. This allows R3q to be used for YUV table.
-%define COMPRq R0q ; Component B value
-%define COMPRd R0d ; Component B value
-%define Uq R0q ; U plane address
-%define Vq R0q ; V plane address
-%define U_PLANE WORD_SIZE [rsp + 3 * gprsize]
-%define TABLE R3q ; Address of the table
-%else
-; Non-PIC code defines.
-%define COMPRq R3q ; Component B value
-%define COMPRd R3d ; Component B value
-%define Uq R0q ; U plane address
-%define Vq R3q ; V plane address
-%define TABLE mangle(kCoefficientsRgbY)
-%endif
-
-; Defines for stack variables. These are used in both PIC and non-PIC code.
-%define V_PLANE WORD_SIZE [rsp + 2 * gprsize]
-%define SOURCE_DX WORD_SIZE [rsp + gprsize]
-%define SOURCE_WIDTH WORD_SIZE [rsp]
-
-; Handle stack variables differently for PIC and non-PIC code.
-
-%ifdef PIC
-; Define stack usage for PIC code. PIC code push U plane onto stack.
- PUSH U_ARG_REGq
- PUSH V_ARG_REGq
- PUSH SOURCE_DX_ARG_REGq
- imul WIDTH_ARG_REGq, SOURCE_DX_ARG_REGq ; source_width = width * source_dx
- PUSH WIDTH_ARG_REGq
-
-; Load the address of kCoefficientsRgbY into TABLE
- mov TEMPq, SOURCE_DX_ARG_REGq ; Need to save source_dx first
- LOAD_SYM TABLE, mangle(kCoefficientsRgbY)
-%define SOURCE_DX_ARG_REGq TEMPq ; Overwrite SOURCE_DX_ARG_REGq to TEMPq
-%else
-; Define stack usage. Non-PIC code just push 3 registers to stack.
- PUSH V_ARG_REGq
- PUSH SOURCE_DX_ARG_REGq
- imul WIDTH_ARG_REGq, SOURCE_DX_ARG_REGq ; source_width = width * source_dx
- PUSH WIDTH_ARG_REGq
-%endif
-
-%macro EPILOGUE 0
-%ifdef PIC
- ADD rsp, 4 * gprsize
-%else
- ADD rsp, 3 * gprsize
-%endif
-%endmacro
-
- xor Xq, Xq ; x = 0
- cmp SOURCE_DX_ARG_REGq, 0x20000
- jl .lscaleend
- mov Xq, 0x8000 ; x = 0.5 for 1/2 or less
- jmp .lscaleend
-
-.lscaleloop:
-%ifdef PIC
- mov Uq, U_PLANE ; PIC code saves U_PLANE on stack.
-%endif
-
-; Define macros for scaling YUV components since they are reused.
-%macro SCALEUV 1
- mov TEMPq, Xq
- sar TEMPq, 0x11
- movzx COMPLd, BYTE [%1 + TEMPq]
- movzx COMPRd, BYTE [%1 + TEMPq + 1]
- mov TEMPq, Xq
- and TEMPq, 0x1fffe
- imul COMPRq, TEMPq
- xor TEMPq, 0x1fffe
- imul COMPLq, TEMPq
- add COMPLq, COMPRq
- shr COMPLq, 17
-%endmacro
- SCALEUV Uq ; Use the above macro to scale U
- movq mm0, [TABLE + 2048 + 8 * COMPLq]
-
- mov Vq, V_PLANE ; Read V address from stack
- SCALEUV Vq ; Use the above macro to scale V
- paddsw mm0, [TABLE + 4096 + 8 * COMPLq]
-
-%macro SCALEY 0
- mov TEMPq, Xq
- sar TEMPq, 0x10
- movzx COMPLd, BYTE [Yq + TEMPq]
- movzx COMPRd, BYTE [Yq + TEMPq + 1]
- mov TEMPq, Xq
- add Xq, SOURCE_DX ; Add source_dx from stack
- and TEMPq, 0xffff
- imul COMPRq, TEMPq
- xor TEMPq, 0xffff
- imul COMPLq, TEMPq
- add COMPLq, COMPRq
- shr COMPLq, 16
-%endmacro
- SCALEY ; Use the above macro to scale Y1
- movq mm1, [TABLE + 8 * COMPLq]
-
- cmp Xq, SOURCE_WIDTH ; Compare source_width from stack
- jge .lscalelastpixel
-
- SCALEY ; Use the above macro to sacle Y2
- movq mm2, [TABLE + 8 * COMPLq]
-
- paddsw mm1, mm0
- paddsw mm2, mm0
- psraw mm1, 0x6
- psraw mm2, 0x6
- packuswb mm1, mm2
- MOVQ [ARGBq], mm1
- add ARGBq, 0x8
-
-.lscaleend:
- cmp Xq, SOURCE_WIDTH ; Compare source_width from stack
- jl .lscaleloop
- EPILOGUE
- RET
-
-.lscalelastpixel:
- paddsw mm1, mm0
- psraw mm1, 6
- packuswb mm1, mm1
- movd [ARGBq], mm1
- EPILOGUE
- RET
diff --git a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx_x64.asm b/src/media/base/simd/linear_scale_yuv_to_rgb_mmx_x64.asm
deleted file mode 100644
index db78544..0000000
--- a/src/media/base/simd/linear_scale_yuv_to_rgb_mmx_x64.asm
+++ /dev/null
@@ -1,142 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX instructions.
-;
- SECTION_TEXT
- CPU MMX
-
-%define SYMBOL LinearScaleYUVToRGB32Row_MMX_X64
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-mangle(SYMBOL):
- %assign stack_offset 0
- extern mangle(kCoefficientsRgbY)
-
-; Parameters are in the following order:
-; 1. Y plane
-; 2. U plane
-; 3. V plane
-; 4. ARGB frame
-; 5. Width
-; 6. Source dx
-
-PROLOGUE 6, 7, 3, Y, U, V, ARGB, WIDTH, SOURCE_DX, COMPL
-
-%define TABLEq r10
-%define Xq r11
-%define INDEXq r12
-%define COMPRd r13d
-%define COMPRq r13
-%define FRACTIONq r14
-
- PUSH TABLEq
- PUSH Xq
- PUSH INDEXq
- PUSH COMPRq
- PUSH FRACTIONq
-
-%macro EPILOGUE 0
- POP FRACTIONq
- POP COMPRq
- POP INDEXq
- POP Xq
- POP TABLEq
-%endmacro
-
- LOAD_SYM TABLEq, mangle(kCoefficientsRgbY)
-
- imul WIDTHq, SOURCE_DXq ; source_width = width * source_dx
- xor Xq, Xq ; x = 0
- cmp SOURCE_DXq, 0x20000
- jl .lscaleend
- mov Xq, 0x8000 ; x = 0.5 for 1/2 or less
- jmp .lscaleend
-
-.lscaleloop:
- ; Interpolate U
- mov INDEXq, Xq
- sar INDEXq, 0x11
- movzx COMPLd, BYTE [Uq + INDEXq]
- movzx COMPRd, BYTE [Uq + INDEXq + 1]
- mov FRACTIONq, Xq
- and FRACTIONq, 0x1fffe
- imul COMPRq, FRACTIONq
- xor FRACTIONq, 0x1fffe
- imul COMPLq, FRACTIONq
- add COMPLq, COMPRq
- shr COMPLq, 17
- movq mm0, [TABLEq + 2048 + 8 * COMPLq]
-
- ; Interpolate V
- movzx COMPLd, BYTE [Vq + INDEXq]
- movzx COMPRd, BYTE [Vq + INDEXq + 1]
- ; Trick here to imul COMPL first then COMPR.
- ; Saves two instruction. :)
- imul COMPLq, FRACTIONq
- xor FRACTIONq, 0x1fffe
- imul COMPRq, FRACTIONq
- add COMPLq, COMPRq
- shr COMPLq, 17
- paddsw mm0, [TABLEq + 4096 + 8 * COMPLq]
-
- ; Interpolate first Y1.
- lea INDEXq, [Xq + SOURCE_DXq] ; INDEXq now points to next pixel.
- ; Xq points to current pixel.
- mov FRACTIONq, Xq
- sar Xq, 0x10
- movzx COMPLd, BYTE [Yq + Xq]
- movzx COMPRd, BYTE [Yq + Xq + 1]
- and FRACTIONq, 0xffff
- imul COMPRq, FRACTIONq
- xor FRACTIONq, 0xffff
- imul COMPLq, FRACTIONq
- add COMPLq, COMPRq
- shr COMPLq, 16
- movq mm1, [TABLEq + 8 * COMPLq]
-
- ; Interpolate Y2 if available.
- cmp INDEXq, WIDTHq
- jge .lscalelastpixel
-
- lea Xq, [INDEXq + SOURCE_DXq] ; Xq points to next pixel.
- ; INDEXq points to current pixel.
- mov FRACTIONq, INDEXq
- sar INDEXq, 0x10
- movzx COMPLd, BYTE [Yq + INDEXq]
- movzx COMPRd, BYTE [Yq + INDEXq + 1]
- and FRACTIONq, 0xffff
- imul COMPRq, FRACTIONq
- xor FRACTIONq, 0xffff
- imul COMPLq, FRACTIONq
- add COMPLq, COMPRq
- shr COMPLq, 16
- movq mm2, [TABLEq + 8 * COMPLq]
-
- paddsw mm1, mm0
- paddsw mm2, mm0
- psraw mm1, 0x6
- psraw mm2, 0x6
- packuswb mm1, mm2
- movntq [ARGBq], mm1
- add ARGBq, 0x8
-
-.lscaleend:
- cmp Xq, WIDTHq
- jl .lscaleloop
- jmp .epilogue
-
-.lscalelastpixel:
- paddsw mm1, mm0
- psraw mm1, 6
- packuswb mm1, mm1
- movd [ARGBq], mm1
-
-.epilogue
- EPILOGUE
- RET
diff --git a/src/media/base/simd/linear_scale_yuv_to_rgb_sse.asm b/src/media/base/simd/linear_scale_yuv_to_rgb_sse.asm
deleted file mode 100644
index 847911c..0000000
--- a/src/media/base/simd/linear_scale_yuv_to_rgb_sse.asm
+++ /dev/null
@@ -1,23 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX and SSE instructions.
-;
- SECTION_TEXT
- CPU MMX, SSE
-
-; Use movq to save the output.
-%define MOVQ movntq
-
-; void LinearScaleYUVToRGB32Row_SSE(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width,
-; int source_dx);
-%define SYMBOL LinearScaleYUVToRGB32Row_SSE
-%include "linear_scale_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/scale_yuv_to_rgb_mmx.asm b/src/media/base/simd/scale_yuv_to_rgb_mmx.asm
deleted file mode 100644
index 6a83757..0000000
--- a/src/media/base/simd/scale_yuv_to_rgb_mmx.asm
+++ /dev/null
@@ -1,23 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX instructions.
-;
- SECTION_TEXT
- CPU MMX
-
-; Use movq to save the output.
-%define MOVQ movq
-
-; void ScaleYUVToRGB32Row_MMX(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width,
-; int source_dx);
-%define SYMBOL ScaleYUVToRGB32Row_MMX
-%include "scale_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/scale_yuv_to_rgb_mmx.inc b/src/media/base/simd/scale_yuv_to_rgb_mmx.inc
deleted file mode 100644
index 94c101c..0000000
--- a/src/media/base/simd/scale_yuv_to_rgb_mmx.inc
+++ /dev/null
@@ -1,115 +0,0 @@
-; Copyright (c) 2011 The Chromium Authors. All rights reserved.
-; Use of this source code is governed by a BSD-style license that can be
-; found in the LICENSE file.
-
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-mangle(SYMBOL):
- %assign stack_offset 0
-
- extern mangle(kCoefficientsRgbY)
-
-; Parameters are in the following order:
-; 1. Y plane
-; 2. U plane
-; 3. V plane
-; 4. ARGB frame
-; 5. Width
-; 6. Source dx
-
-PROLOGUE 6, 7, 3, Y, U, V, ARGB, R1, R2, TEMP
-
-%ifdef ARCH_X86_64
-%define WORD_SIZE QWORD
-%else
-%define WORD_SIZE DWORD
-%endif
-
-%ifdef PIC
- PUSH R1q ; Width
-%endif
- PUSH R2q ; Source dx
-
-%define SOURCE_DX WORD_SIZE [rsp]
-
-; PIC code.
-%ifdef PIC
- LOAD_SYM R1q, mangle(kCoefficientsRgbY)
-%define WIDTH WORD_SIZE [rsp + gprsize]
-%define TABLE R1q
-%define Xq R2q
-
-; Non-PIC code.
-%else
-%define WIDTH R1q
-%define TABLE mangle(kCoefficientsRgbY)
-%define Xq R2q
-%endif
-
- ; Set Xq index to 0.
- xor Xq, Xq
- jmp .scaleend
-
-.scaleloop:
- ; TABLE can either be a register or a symbol depending on this is
- ; PIC or not.
- mov TEMPq, Xq
- sar TEMPq, 17
- movzx TEMPd, BYTE [Uq + TEMPq]
- movq mm0, [TABLE + 2048 + 8 * TEMPq]
- mov TEMPq, Xq
- sar TEMPq, 17
- movzx TEMPd, BYTE [Vq + TEMPq]
- paddsw mm0, [TABLE + 4096 + 8 * TEMPq]
- mov TEMPq, Xq
- add Xq, SOURCE_DX
- sar TEMPq, 16
- movzx TEMPd, BYTE [Yq + TEMPq]
- movq mm1, [TABLE + 8 * TEMPq]
- mov TEMPq, Xq
- add Xq, SOURCE_DX
- sar TEMPq, 16
- movzx TEMPd, BYTE [Yq + TEMPq]
- movq mm2, [TABLE + 8 * TEMPq]
- paddsw mm1, mm0
- paddsw mm2, mm0
- psraw mm1, 6
- psraw mm2, 6
- packuswb mm1, mm2
- MOVQ QWORD [ARGBq], mm1
- add ARGBq, 8
-
-.scaleend:
- ; WIDTH can either be a register or memory depending on this is
- ; PIC or not.
- sub WIDTH, 2
- jns .scaleloop
-
- and WIDTH, 1 ; odd number of pixels?
- jz .scaledone
-
- mov TEMPq, Xq
- sar TEMPq, 17
- movzx TEMPd, BYTE [Uq + TEMPq]
- movq mm0, [TABLE + 2048 + 8 * TEMPq]
- mov TEMPq, Xq
- sar TEMPq, 17
- movzx TEMPd, BYTE [Vq + TEMPq]
- paddsw mm0, [TABLE + 4096 + 8 * TEMPq]
- mov TEMPq, Xq
- sar TEMPq, 16
- movzx TEMPd, BYTE [Yq + TEMPq]
- movq mm1, [TABLE + 8 * TEMPq]
- paddsw mm1, mm0
- psraw mm1, 6
- packuswb mm1, mm1
- movd DWORD [ARGBq], mm1
-
-.scaledone:
-%ifdef PIC
- ADD rsp, 2 * gprsize
-%else
- ADD rsp, gprsize
-%endif
- RET
diff --git a/src/media/base/simd/scale_yuv_to_rgb_sse.asm b/src/media/base/simd/scale_yuv_to_rgb_sse.asm
deleted file mode 100644
index 5b849a6..0000000
--- a/src/media/base/simd/scale_yuv_to_rgb_sse.asm
+++ /dev/null
@@ -1,23 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX and SSE instructions.
-;
- SECTION_TEXT
- CPU MMX, SSE
-
-; Use movq to save the output.
-%define MOVQ movntq
-
-; void ScaleYUVToRGB32Row_SSE(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width,
-; int source_dx);
-%define SYMBOL ScaleYUVToRGB32Row_SSE
-%include "scale_yuv_to_rgb_mmx.inc"
diff --git a/src/media/base/simd/scale_yuv_to_rgb_sse2_x64.asm b/src/media/base/simd/scale_yuv_to_rgb_sse2_x64.asm
deleted file mode 100644
index 5e58146..0000000
--- a/src/media/base/simd/scale_yuv_to_rgb_sse2_x64.asm
+++ /dev/null
@@ -1,110 +0,0 @@
-; Copyright (c) 2011 The Chromium 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 "x86inc.asm"
-
-;
-; This file uses MMX, SSE2 and instructions.
-;
- SECTION_TEXT
- CPU SSE2
-
-; void ScaleYUVToRGB32Row_SSE2_X64(const uint8* y_buf,
-; const uint8* u_buf,
-; const uint8* v_buf,
-; uint8* rgb_buf,
-; int width,
-; int source_dx);
-%define SYMBOL ScaleYUVToRGB32Row_SSE2_X64
-
- global mangle(SYMBOL) PRIVATE
- align function_align
-
-mangle(SYMBOL):
- %assign stack_offset 0
- extern mangle(kCoefficientsRgbY)
-
-; Parameters are in the following order:
-; 1. Y plane
-; 2. U plane
-; 3. V plane
-; 4. ARGB frame
-; 5. Width
-; 6. Source dx
-
-PROLOGUE 6, 7, 3, Y, U, V, ARGB, WIDTH, SOURCE_DX, COMP
-
-%define TABLEq r10
-%define Xq r11
-%define INDEXq r12
- PUSH r10
- PUSH r11
- PUSH r12
-
- LOAD_SYM TABLEq, mangle(kCoefficientsRgbY)
-
- ; Set Xq index to 0.
- xor Xq, Xq
- jmp .scaleend
-
-.scaleloop:
- ; Read UV pixels.
- mov INDEXq, Xq
- sar INDEXq, 17
- movzx COMPd, BYTE [Uq + INDEXq]
- movq xmm0, [TABLEq + 2048 + 8 * COMPq]
- movzx COMPd, BYTE [Vq + INDEXq]
- movq xmm1, [TABLEq + 4096 + 8 * COMPq]
-
- ; Read first Y pixel.
- lea INDEXq, [Xq + SOURCE_DXq] ; INDEXq nows points to next pixel.
- sar Xq, 16
- movzx COMPd, BYTE [Yq + Xq]
- paddsw xmm0, xmm1 ; Hide a ADD after memory load.
- movq xmm1, [TABLEq + 8 * COMPq]
-
- ; Read next Y pixel.
- lea Xq, [INDEXq + SOURCE_DXq] ; Xq now points to next pixel.
- sar INDEXq, 16
- movzx COMPd, BYTE [Yq + INDEXq]
- movq xmm2, [TABLEq + 8 * COMPq]
- paddsw xmm1, xmm0
- paddsw xmm2, xmm0
- shufps xmm1, xmm2, 0x44 ; Join two pixels into one XMM register
- psraw xmm1, 6
- packuswb xmm1, xmm1
- movq QWORD [ARGBq], xmm1
- add ARGBq, 8
-
-.scaleend:
- sub WIDTHq, 2
- jns .scaleloop
-
- and WIDTHq, 1 ; odd number of pixels?
- jz .scaledone
-
- ; Read U V components.
- mov INDEXq, Xq
- sar INDEXq, 17
- movzx COMPd, BYTE [Uq + INDEXq]
- movq xmm0, [TABLEq + 2048 + 8 * COMPq]
- movzx COMPd, BYTE [Vq + INDEXq]
- movq xmm1, [TABLEq + 4096 + 8 * COMPq]
- paddsw xmm0, xmm1
-
- ; Read one Y component.
- mov INDEXq, Xq
- sar INDEXq, 16
- movzx COMPd, BYTE [Yq + INDEXq]
- movq xmm1, [TABLEq + 8 * COMPq]
- paddsw xmm1, xmm0
- psraw xmm1, 6
- packuswb xmm1, xmm1
- movd DWORD [ARGBq], xmm1
-
-.scaledone:
- POP r12
- POP r11
- POP r10
- RET
diff --git a/src/media/base/simd/x86inc.asm b/src/media/base/simd/x86inc.asm
deleted file mode 100644
index 223ea3d..0000000
--- a/src/media/base/simd/x86inc.asm
+++ /dev/null
@@ -1,1012 +0,0 @@
-;*****************************************************************************
-;* x86inc.asm
-;*****************************************************************************
-;* Copyright (C) 2005-2011 x264 project
-;*
-;* Authors: Loren Merritt <lorenm@u.washington.edu>
-;* Anton Mitrofanov <BugMaster@narod.ru>
-;* Jason Garrett-Glaser <darkshikari@gmail.com>
-;*
-;* Permission to use, copy, modify, and/or distribute this software for any
-;* purpose with or without fee is hereby granted, provided that the above
-;* copyright notice and this permission notice appear in all copies.
-;*
-;* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-;* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-;* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-;* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-;* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-;* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-;* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-;*****************************************************************************
-
-; This is a header file for the x264ASM assembly language, which uses
-; NASM/YASM syntax combined with a large number of macros to provide easy
-; abstraction between different calling conventions (x86_32, win64, linux64).
-; It also has various other useful features to simplify writing the kind of
-; DSP functions that are most often used in x264.
-
-; Unlike the rest of x264, this file is available under an ISC license, as it
-; has significant usefulness outside of x264 and we want it to be available
-; to the largest audience possible. Of course, if you modify it for your own
-; purposes to add a new feature, we strongly encourage contributing a patch
-; as this feature might be useful for others as well. Send patches or ideas
-; to x264-devel@videolan.org .
-
-%ifndef MEDIA_BASE_SIMD_X86INC_ASM_
-%define MEDIA_BASE_SIMD_X86INC_ASM_
-
-%define program_name ff
-
-%ifdef ARCH_X86_64
- %ifidn __OUTPUT_FORMAT__,win32
- %define WIN64
- %else
- %define UNIX64
- %endif
-%endif
-
-%ifdef PREFIX
- %define mangle(x) _ %+ x
-%else
- %define mangle(x) x
-%endif
-
-; FIXME: All of the 64bit asm functions that take a stride as an argument
-; via register, assume that the high dword of that register is filled with 0.
-; This is true in practice (since we never do any 64bit arithmetic on strides,
-; and x264's strides are all positive), but is not guaranteed by the ABI.
-
-; Name of the .rodata section.
-; Kludge: Something on OS X fails to align .rodata even given an align attribute,
-; so use a different read-only section.
-%ifdef CHROMIUM
-%macro SECTION_RODATA 0-1 16
- %ifidn __OUTPUT_FORMAT__,macho64
- SECTION .text align=%1
- %elifidn __OUTPUT_FORMAT__,macho
- SECTION .text align=%1
- fakegot:
- %elifidn __OUTPUT_FORMAT__,aout
- section .text
- %else
- SECTION .rodata align=%1
- %endif
-%endmacro
-%else
-%macro SECTION_RODATA 0-1 16
- %ifidn __OUTPUT_FORMAT__,aout
- section .text
- %else
- SECTION .rodata align=%1
- %endif
-%endmacro
-%endif
-
-; aout does not support align=
-%macro SECTION_TEXT 0-1 16
- %ifidn __OUTPUT_FORMAT__,aout
- SECTION .text
- %else
- SECTION .text align=%1
- %endif
-%endmacro
-
-%ifdef WIN64
- %define PIC
-%elifndef ARCH_X86_64
-; For chromium we may build PIC code even for 32 bits system.
-%ifndef CHROMIUM
-; x86_32 doesn't require PIC.
-; Some distros prefer shared objects to be PIC, but nothing breaks if
-; the code contains a few textrels, so we'll skip that complexity.
- %undef PIC
-%endif
-%endif
-%ifdef PIC
- default rel
-%endif
-
-; Macros to eliminate most code duplication between x86_32 and x86_64:
-; Currently this works only for leaf functions which load all their arguments
-; into registers at the start, and make no other use of the stack. Luckily that
-; covers most of x264's asm.
-
-; PROLOGUE:
-; %1 = number of arguments. loads them from stack if needed.
-; %2 = number of registers used. pushes callee-saved regs if needed.
-; %3 = number of xmm registers used. pushes callee-saved xmm regs if needed.
-; %4 = list of names to define to registers
-; PROLOGUE can also be invoked by adding the same options to cglobal
-
-; e.g.
-; cglobal foo, 2,3,0, dst, src, tmp
-; declares a function (foo), taking two args (dst and src) and one local variable (tmp)
-
-; TODO Some functions can use some args directly from the stack. If they're the
-; last args then you can just not declare them, but if they're in the middle
-; we need more flexible macro.
-
-; RET:
-; Pops anything that was pushed by PROLOGUE
-
-; REP_RET:
-; Same, but if it doesn't pop anything it becomes a 2-byte ret, for athlons
-; which are slow when a normal ret follows a branch.
-
-; registers:
-; rN and rNq are the native-size register holding function argument N
-; rNd, rNw, rNb are dword, word, and byte size
-; rNm is the original location of arg N (a register or on the stack), dword
-; rNmp is native size
-
-%macro DECLARE_REG 6
- %define r%1q %2
- %define r%1d %3
- %define r%1w %4
- %define r%1b %5
- %define r%1m %6
- %ifid %6 ; i.e. it's a register
- %define r%1mp %2
- %elifdef ARCH_X86_64 ; memory
- %define r%1mp qword %6
- %else
- %define r%1mp dword %6
- %endif
- %define r%1 %2
-%endmacro
-
-%macro DECLARE_REG_SIZE 2
- %define r%1q r%1
- %define e%1q r%1
- %define r%1d e%1
- %define e%1d e%1
- %define r%1w %1
- %define e%1w %1
- %define r%1b %2
- %define e%1b %2
-%ifndef ARCH_X86_64
- %define r%1 e%1
-%endif
-%endmacro
-
-DECLARE_REG_SIZE ax, al
-DECLARE_REG_SIZE bx, bl
-DECLARE_REG_SIZE cx, cl
-DECLARE_REG_SIZE dx, dl
-DECLARE_REG_SIZE si, sil
-DECLARE_REG_SIZE di, dil
-DECLARE_REG_SIZE bp, bpl
-
-; t# defines for when per-arch register allocation is more complex than just function arguments
-
-%macro DECLARE_REG_TMP 1-*
- %assign %%i 0
- %rep %0
- CAT_XDEFINE t, %%i, r%1
- %assign %%i %%i+1
- %rotate 1
- %endrep
-%endmacro
-
-%macro DECLARE_REG_TMP_SIZE 0-*
- %rep %0
- %define t%1q t%1 %+ q
- %define t%1d t%1 %+ d
- %define t%1w t%1 %+ w
- %define t%1b t%1 %+ b
- %rotate 1
- %endrep
-%endmacro
-
-DECLARE_REG_TMP_SIZE 0,1,2,3,4,5,6,7,8,9
-
-%ifdef ARCH_X86_64
- %define gprsize 8
-%else
- %define gprsize 4
-%endif
-
-%macro PUSH 1
- push %1
- %assign stack_offset stack_offset+gprsize
-%endmacro
-
-%macro POP 1
- pop %1
- %assign stack_offset stack_offset-gprsize
-%endmacro
-
-%macro SUB 2
- sub %1, %2
- %ifidn %1, rsp
- %assign stack_offset stack_offset+(%2)
- %endif
-%endmacro
-
-%macro ADD 2
- add %1, %2
- %ifidn %1, rsp
- %assign stack_offset stack_offset-(%2)
- %endif
-%endmacro
-
-%macro movifnidn 2
- %ifnidn %1, %2
- mov %1, %2
- %endif
-%endmacro
-
-%macro movsxdifnidn 2
- %ifnidn %1, %2
- movsxd %1, %2
- %endif
-%endmacro
-
-%macro ASSERT 1
- %if (%1) == 0
- %error assert failed
- %endif
-%endmacro
-
-%macro DEFINE_ARGS 0-*
- %ifdef n_arg_names
- %assign %%i 0
- %rep n_arg_names
- CAT_UNDEF arg_name %+ %%i, q
- CAT_UNDEF arg_name %+ %%i, d
- CAT_UNDEF arg_name %+ %%i, w
- CAT_UNDEF arg_name %+ %%i, b
- CAT_UNDEF arg_name %+ %%i, m
- CAT_UNDEF arg_name, %%i
- %assign %%i %%i+1
- %endrep
- %endif
-
- %assign %%i 0
- %rep %0
- %xdefine %1q r %+ %%i %+ q
- %xdefine %1d r %+ %%i %+ d
- %xdefine %1w r %+ %%i %+ w
- %xdefine %1b r %+ %%i %+ b
- %xdefine %1m r %+ %%i %+ m
- CAT_XDEFINE arg_name, %%i, %1
- %assign %%i %%i+1
- %rotate 1
- %endrep
- %assign n_arg_names %%i
-%endmacro
-
-%ifdef WIN64 ; Windows x64 ;=================================================
-
-DECLARE_REG 0, rcx, ecx, cx, cl, ecx
-DECLARE_REG 1, rdx, edx, dx, dl, edx
-DECLARE_REG 2, r8, r8d, r8w, r8b, r8d
-DECLARE_REG 3, r9, r9d, r9w, r9b, r9d
-DECLARE_REG 4, rdi, edi, di, dil, [rsp + stack_offset + 40]
-DECLARE_REG 5, rsi, esi, si, sil, [rsp + stack_offset + 48]
-DECLARE_REG 6, rax, eax, ax, al, [rsp + stack_offset + 56]
-%define r7m [rsp + stack_offset + 64]
-%define r8m [rsp + stack_offset + 72]
-
-%macro LOAD_IF_USED 2 ; reg_id, number_of_args
- %if %1 < %2
- mov r%1, [rsp + stack_offset + 8 + %1*8]
- %endif
-%endmacro
-
-%macro PROLOGUE 2-4+ 0 ; #args, #regs, #xmm_regs, arg_names...
- ASSERT %2 >= %1
- %assign regs_used %2
- ASSERT regs_used <= 7
- %if regs_used > 4
- push r4
- push r5
- %assign stack_offset stack_offset+16
- %endif
- WIN64_SPILL_XMM %3
- LOAD_IF_USED 4, %1
- LOAD_IF_USED 5, %1
- LOAD_IF_USED 6, %1
- DEFINE_ARGS %4
-%endmacro
-
-%macro WIN64_SPILL_XMM 1
- %assign xmm_regs_used %1
- ASSERT xmm_regs_used <= 16
- %if xmm_regs_used > 6
- sub rsp, (xmm_regs_used-6)*16+16
- %assign stack_offset stack_offset+(xmm_regs_used-6)*16+16
- %assign %%i xmm_regs_used
- %rep (xmm_regs_used-6)
- %assign %%i %%i-1
- movdqa [rsp + (%%i-6)*16+8], xmm %+ %%i
- %endrep
- %endif
-%endmacro
-
-%macro WIN64_RESTORE_XMM_INTERNAL 1
- %if xmm_regs_used > 6
- %assign %%i xmm_regs_used
- %rep (xmm_regs_used-6)
- %assign %%i %%i-1
- movdqa xmm %+ %%i, [%1 + (%%i-6)*16+8]
- %endrep
- add %1, (xmm_regs_used-6)*16+16
- %endif
-%endmacro
-
-%macro WIN64_RESTORE_XMM 1
- WIN64_RESTORE_XMM_INTERNAL %1
- %assign stack_offset stack_offset-(xmm_regs_used-6)*16+16
- %assign xmm_regs_used 0
-%endmacro
-
-%macro RET 0
- WIN64_RESTORE_XMM_INTERNAL rsp
- %if regs_used > 4
- pop r5
- pop r4
- %endif
- ret
-%endmacro
-
-%macro REP_RET 0
- %if regs_used > 4 || xmm_regs_used > 6
- RET
- %else
- rep ret
- %endif
-%endmacro
-
-%elifdef ARCH_X86_64 ; *nix x64 ;=============================================
-
-DECLARE_REG 0, rdi, edi, di, dil, edi
-DECLARE_REG 1, rsi, esi, si, sil, esi
-DECLARE_REG 2, rdx, edx, dx, dl, edx
-DECLARE_REG 3, rcx, ecx, cx, cl, ecx
-DECLARE_REG 4, r8, r8d, r8w, r8b, r8d
-DECLARE_REG 5, r9, r9d, r9w, r9b, r9d
-DECLARE_REG 6, rax, eax, ax, al, [rsp + stack_offset + 8]
-%define r7m [rsp + stack_offset + 16]
-%define r8m [rsp + stack_offset + 24]
-
-%macro LOAD_IF_USED 2 ; reg_id, number_of_args
- %if %1 < %2
- mov r%1, [rsp - 40 + %1*8]
- %endif
-%endmacro
-
-%macro PROLOGUE 2-4+ ; #args, #regs, #xmm_regs, arg_names...
- ASSERT %2 >= %1
- ASSERT %2 <= 7
- LOAD_IF_USED 6, %1
- DEFINE_ARGS %4
-%endmacro
-
-%macro RET 0
- ret
-%endmacro
-
-%macro REP_RET 0
- rep ret
-%endmacro
-
-%else ; X86_32 ;==============================================================
-
-; Begin chromium edits
-%ifdef CHROMIUM
-; Change the order of registers so we can get the lower 8-bit or the 5th and 6th
-; arguments.
-DECLARE_REG 0, esi, esi, si, null, [esp + stack_offset + 4]
-DECLARE_REG 1, edi, edi, di, null, [esp + stack_offset + 8]
-DECLARE_REG 2, ecx, ecx, cx, cl, [esp + stack_offset + 12]
-DECLARE_REG 3, edx, edx, dx, dl, [esp + stack_offset + 16]
-DECLARE_REG 4, eax, eax, ax, al, [esp + stack_offset + 20]
-DECLARE_REG 5, ebx, ebx, bx, bl, [esp + stack_offset + 24]
-%else
-DECLARE_REG 0, eax, eax, ax, al, [esp + stack_offset + 4]
-DECLARE_REG 1, ecx, ecx, cx, cl, [esp + stack_offset + 8]
-DECLARE_REG 2, edx, edx, dx, dl, [esp + stack_offset + 12]
-DECLARE_REG 3, ebx, ebx, bx, bl, [esp + stack_offset + 16]
-DECLARE_REG 4, esi, esi, si, null, [esp + stack_offset + 20]
-DECLARE_REG 5, edi, edi, di, null, [esp + stack_offset + 24]
-%endif
-; End chromium edits
-DECLARE_REG 6, ebp, ebp, bp, null, [esp + stack_offset + 28]
-%define r7m [esp + stack_offset + 32]
-%define r8m [esp + stack_offset + 36]
-%define rsp esp
-
-%macro PUSH_IF_USED 1 ; reg_id
- %if %1 < regs_used
- push r%1
- %assign stack_offset stack_offset+4
- %endif
-%endmacro
-
-%macro POP_IF_USED 1 ; reg_id
- %if %1 < regs_used
- pop r%1
- %endif
-%endmacro
-
-%macro LOAD_IF_USED 2 ; reg_id, number_of_args
- %if %1 < %2
- mov r%1, [esp + stack_offset + 4 + %1*4]
- %endif
-%endmacro
-
-%macro PROLOGUE 2-4+ ; #args, #regs, #xmm_regs, arg_names...
- ASSERT %2 >= %1
- %assign regs_used %2
- ASSERT regs_used <= 7
-%ifdef CHROMIUM
- PUSH_IF_USED 0
- PUSH_IF_USED 1
- PUSH_IF_USED 5
-%else
- PUSH_IF_USED 3
- PUSH_IF_USED 4
- PUSH_IF_USED 5
-%endif
- PUSH_IF_USED 6
- LOAD_IF_USED 0, %1
- LOAD_IF_USED 1, %1
- LOAD_IF_USED 2, %1
- LOAD_IF_USED 3, %1
- LOAD_IF_USED 4, %1
- LOAD_IF_USED 5, %1
- LOAD_IF_USED 6, %1
- DEFINE_ARGS %4
-%endmacro
-
-%macro RET 0
- POP_IF_USED 6
-%ifdef CHROMIUM
- POP_IF_USED 5
- POP_IF_USED 1
- POP_IF_USED 0
-%else
- POP_IF_USED 5
- POP_IF_USED 4
- POP_IF_USED 3
-%endif
- ret
-%endmacro
-
-%macro REP_RET 0
- %if regs_used > 3
- RET
- %else
- rep ret
- %endif
-%endmacro
-
-%endif ;======================================================================
-
-%ifndef WIN64
-%macro WIN64_SPILL_XMM 1
-%endmacro
-%macro WIN64_RESTORE_XMM 1
-%endmacro
-%endif
-
-
-
-;=============================================================================
-; arch-independent part
-;=============================================================================
-
-%assign function_align 16
-
-; Symbol prefix for C linkage
-%macro cglobal 1-2+
- %xdefine %1 mangle(program_name %+ _ %+ %1)
- %xdefine %1.skip_prologue %1 %+ .skip_prologue
- %ifidn __OUTPUT_FORMAT__,elf
- global %1:function hidden
- %else
- global %1
- %endif
- align function_align
- %1:
- RESET_MM_PERMUTATION ; not really needed, but makes disassembly somewhat nicer
- %assign stack_offset 0
- %if %0 > 1
- PROLOGUE %2
- %endif
-%endmacro
-
-%macro cextern 1
- %xdefine %1 mangle(program_name %+ _ %+ %1)
- extern %1
-%endmacro
-
-;like cextern, but without the prefix
-%macro cextern_naked 1
- %xdefine %1 mangle(%1)
- extern %1
-%endmacro
-
-%macro const 2+
- %xdefine %1 mangle(program_name %+ _ %+ %1)
- global %1
- %1: %2
-%endmacro
-
-; This is needed for ELF, otherwise the GNU linker assumes the stack is
-; executable by default.
-%ifidn __OUTPUT_FORMAT__,elf
-SECTION .note.GNU-stack noalloc noexec nowrite progbits
-%endif
-
-; merge mmx and sse*
-
-%macro CAT_XDEFINE 3
- %xdefine %1%2 %3
-%endmacro
-
-%macro CAT_UNDEF 2
- %undef %1%2
-%endmacro
-
-%macro INIT_MMX 0
- %assign avx_enabled 0
- %define RESET_MM_PERMUTATION INIT_MMX
- %define mmsize 8
- %define num_mmregs 8
- %define mova movq
- %define movu movq
- %define movh movd
- %define movnta movntq
- %assign %%i 0
- %rep 8
- CAT_XDEFINE m, %%i, mm %+ %%i
- CAT_XDEFINE nmm, %%i, %%i
- %assign %%i %%i+1
- %endrep
- %rep 8
- CAT_UNDEF m, %%i
- CAT_UNDEF nmm, %%i
- %assign %%i %%i+1
- %endrep
-%endmacro
-
-%macro INIT_XMM 0
- %assign avx_enabled 0
- %define RESET_MM_PERMUTATION INIT_XMM
- %define mmsize 16
- %define num_mmregs 8
- %ifdef ARCH_X86_64
- %define num_mmregs 16
- %endif
- %define mova movdqa
- %define movu movdqu
- %define movh movq
- %define movnta movntdq
- %assign %%i 0
- %rep num_mmregs
- CAT_XDEFINE m, %%i, xmm %+ %%i
- CAT_XDEFINE nxmm, %%i, %%i
- %assign %%i %%i+1
- %endrep
-%endmacro
-
-%macro INIT_AVX 0
- INIT_XMM
- %assign avx_enabled 1
- %define PALIGNR PALIGNR_SSSE3
- %define RESET_MM_PERMUTATION INIT_AVX
-%endmacro
-
-%macro INIT_YMM 0
- %assign avx_enabled 1
- %define RESET_MM_PERMUTATION INIT_YMM
- %define mmsize 32
- %define num_mmregs 8
- %ifdef ARCH_X86_64
- %define num_mmregs 16
- %endif
- %define mova vmovaps
- %define movu vmovups
- %assign %%i 0
- %rep num_mmregs
- CAT_XDEFINE m, %%i, ymm %+ %%i
- CAT_XDEFINE nymm, %%i, %%i
- %assign %%i %%i+1
- %endrep
-%endmacro
-
-INIT_MMX
-
-; I often want to use macros that permute their arguments. e.g. there's no
-; efficient way to implement butterfly or transpose or dct without swapping some
-; arguments.
-;
-; I would like to not have to manually keep track of the permutations:
-; If I insert a permutation in the middle of a function, it should automatically
-; change everything that follows. For more complex macros I may also have multiple
-; implementations, e.g. the SSE2 and SSSE3 versions may have different permutations.
-;
-; Hence these macros. Insert a PERMUTE or some SWAPs at the end of a macro that
-; permutes its arguments. It's equivalent to exchanging the contents of the
-; registers, except that this way you exchange the register names instead, so it
-; doesn't cost any cycles.
-
-%macro PERMUTE 2-* ; takes a list of pairs to swap
-%rep %0/2
- %xdefine tmp%2 m%2
- %xdefine ntmp%2 nm%2
- %rotate 2
-%endrep
-%rep %0/2
- %xdefine m%1 tmp%2
- %xdefine nm%1 ntmp%2
- %undef tmp%2
- %undef ntmp%2
- %rotate 2
-%endrep
-%endmacro
-
-%macro SWAP 2-* ; swaps a single chain (sometimes more concise than pairs)
-%rep %0-1
-%ifdef m%1
- %xdefine tmp m%1
- %xdefine m%1 m%2
- %xdefine m%2 tmp
- CAT_XDEFINE n, m%1, %1
- CAT_XDEFINE n, m%2, %2
-%else
- ; If we were called as "SWAP m0,m1" rather than "SWAP 0,1" infer the original numbers here.
- ; Be careful using this mode in nested macros though, as in some cases there may be
- ; other copies of m# that have already been dereferenced and don't get updated correctly.
- %xdefine %%n1 n %+ %1
- %xdefine %%n2 n %+ %2
- %xdefine tmp m %+ %%n1
- CAT_XDEFINE m, %%n1, m %+ %%n2
- CAT_XDEFINE m, %%n2, tmp
- CAT_XDEFINE n, m %+ %%n1, %%n1
- CAT_XDEFINE n, m %+ %%n2, %%n2
-%endif
- %undef tmp
- %rotate 1
-%endrep
-%endmacro
-
-; If SAVE_MM_PERMUTATION is placed at the end of a function and given the
-; function name, then any later calls to that function will automatically
-; load the permutation, so values can be returned in mmregs.
-%macro SAVE_MM_PERMUTATION 1 ; name to save as
- %assign %%i 0
- %rep num_mmregs
- CAT_XDEFINE %1_m, %%i, m %+ %%i
- %assign %%i %%i+1
- %endrep
-%endmacro
-
-%macro LOAD_MM_PERMUTATION 1 ; name to load from
- %assign %%i 0
- %rep num_mmregs
- CAT_XDEFINE m, %%i, %1_m %+ %%i
- CAT_XDEFINE n, m %+ %%i, %%i
- %assign %%i %%i+1
- %endrep
-%endmacro
-
-%macro call 1
- call %1
- %ifdef %1_m0
- LOAD_MM_PERMUTATION %1
- %endif
-%endmacro
-
-; Substitutions that reduce instruction size but are functionally equivalent
-%macro add 2
- %ifnum %2
- %if %2==128
- sub %1, -128
- %else
- add %1, %2
- %endif
- %else
- add %1, %2
- %endif
-%endmacro
-
-%macro sub 2
- %ifnum %2
- %if %2==128
- add %1, -128
- %else
- sub %1, %2
- %endif
- %else
- sub %1, %2
- %endif
-%endmacro
-
-;=============================================================================
-; AVX abstraction layer
-;=============================================================================
-
-%assign i 0
-%rep 16
- %if i < 8
- CAT_XDEFINE sizeofmm, i, 8
- %endif
- CAT_XDEFINE sizeofxmm, i, 16
- CAT_XDEFINE sizeofymm, i, 32
-%assign i i+1
-%endrep
-%undef i
-
-;%1 == instruction
-;%2 == 1 if float, 0 if int
-;%3 == 0 if 3-operand (xmm, xmm, xmm), 1 if 4-operand (xmm, xmm, xmm, imm)
-;%4 == number of operands given
-;%5+: operands
-%macro RUN_AVX_INSTR 6-7+
- %if sizeof%5==32
- v%1 %5, %6, %7
- %else
- %if sizeof%5==8
- %define %%regmov movq
- %elif %2
- %define %%regmov movaps
- %else
- %define %%regmov movdqa
- %endif
-
- %if %4>=3+%3
- %ifnidn %5, %6
- %if avx_enabled && sizeof%5==16
- v%1 %5, %6, %7
- %else
- %%regmov %5, %6
- %1 %5, %7
- %endif
- %else
- %1 %5, %7
- %endif
- %elif %3
- %1 %5, %6, %7
- %else
- %1 %5, %6
- %endif
- %endif
-%endmacro
-
-;%1 == instruction
-;%2 == 1 if float, 0 if int
-;%3 == 0 if 3-operand (xmm, xmm, xmm), 1 if 4-operand (xmm, xmm, xmm, imm)
-%macro AVX_INSTR 3
- %macro %1 2-8 fnord, fnord, fnord, %1, %2, %3
- %ifidn %3, fnord
- RUN_AVX_INSTR %6, %7, %8, 2, %1, %2
- %elifidn %4, fnord
- RUN_AVX_INSTR %6, %7, %8, 3, %1, %2, %3
- %elifidn %5, fnord
- RUN_AVX_INSTR %6, %7, %8, 4, %1, %2, %3, %4
- %else
- RUN_AVX_INSTR %6, %7, %8, 5, %1, %2, %3, %4, %5
- %endif
- %endmacro
-%endmacro
-
-AVX_INSTR addpd, 1, 0
-AVX_INSTR addps, 1, 0
-AVX_INSTR addsd, 1, 0
-AVX_INSTR addss, 1, 0
-AVX_INSTR addsubpd, 1, 0
-AVX_INSTR addsubps, 1, 0
-AVX_INSTR andpd, 1, 0
-AVX_INSTR andps, 1, 0
-AVX_INSTR andnpd, 1, 0
-AVX_INSTR andnps, 1, 0
-AVX_INSTR blendpd, 1, 0
-AVX_INSTR blendps, 1, 0
-AVX_INSTR blendvpd, 1, 0
-AVX_INSTR blendvps, 1, 0
-AVX_INSTR cmppd, 1, 0
-AVX_INSTR cmpps, 1, 0
-AVX_INSTR cmpsd, 1, 0
-AVX_INSTR cmpss, 1, 0
-AVX_INSTR divpd, 1, 0
-AVX_INSTR divps, 1, 0
-AVX_INSTR divsd, 1, 0
-AVX_INSTR divss, 1, 0
-AVX_INSTR dppd, 1, 0
-AVX_INSTR dpps, 1, 0
-AVX_INSTR haddpd, 1, 0
-AVX_INSTR haddps, 1, 0
-AVX_INSTR hsubpd, 1, 0
-AVX_INSTR hsubps, 1, 0
-AVX_INSTR maxpd, 1, 0
-AVX_INSTR maxps, 1, 0
-AVX_INSTR maxsd, 1, 0
-AVX_INSTR maxss, 1, 0
-AVX_INSTR minpd, 1, 0
-AVX_INSTR minps, 1, 0
-AVX_INSTR minsd, 1, 0
-AVX_INSTR minss, 1, 0
-AVX_INSTR mpsadbw, 0, 1
-AVX_INSTR mulpd, 1, 0
-AVX_INSTR mulps, 1, 0
-AVX_INSTR mulsd, 1, 0
-AVX_INSTR mulss, 1, 0
-AVX_INSTR orpd, 1, 0
-AVX_INSTR orps, 1, 0
-AVX_INSTR packsswb, 0, 0
-AVX_INSTR packssdw, 0, 0
-AVX_INSTR packuswb, 0, 0
-AVX_INSTR packusdw, 0, 0
-AVX_INSTR paddb, 0, 0
-AVX_INSTR paddw, 0, 0
-AVX_INSTR paddd, 0, 0
-AVX_INSTR paddq, 0, 0
-AVX_INSTR paddsb, 0, 0
-AVX_INSTR paddsw, 0, 0
-AVX_INSTR paddusb, 0, 0
-AVX_INSTR paddusw, 0, 0
-AVX_INSTR palignr, 0, 1
-AVX_INSTR pand, 0, 0
-AVX_INSTR pandn, 0, 0
-AVX_INSTR pavgb, 0, 0
-AVX_INSTR pavgw, 0, 0
-AVX_INSTR pblendvb, 0, 0
-AVX_INSTR pblendw, 0, 1
-AVX_INSTR pcmpestri, 0, 0
-AVX_INSTR pcmpestrm, 0, 0
-AVX_INSTR pcmpistri, 0, 0
-AVX_INSTR pcmpistrm, 0, 0
-AVX_INSTR pcmpeqb, 0, 0
-AVX_INSTR pcmpeqw, 0, 0
-AVX_INSTR pcmpeqd, 0, 0
-AVX_INSTR pcmpeqq, 0, 0
-AVX_INSTR pcmpgtb, 0, 0
-AVX_INSTR pcmpgtw, 0, 0
-AVX_INSTR pcmpgtd, 0, 0
-AVX_INSTR pcmpgtq, 0, 0
-AVX_INSTR phaddw, 0, 0
-AVX_INSTR phaddd, 0, 0
-AVX_INSTR phaddsw, 0, 0
-AVX_INSTR phsubw, 0, 0
-AVX_INSTR phsubd, 0, 0
-AVX_INSTR phsubsw, 0, 0
-AVX_INSTR pmaddwd, 0, 0
-AVX_INSTR pmaddubsw, 0, 0
-AVX_INSTR pmaxsb, 0, 0
-AVX_INSTR pmaxsw, 0, 0
-AVX_INSTR pmaxsd, 0, 0
-AVX_INSTR pmaxub, 0, 0
-AVX_INSTR pmaxuw, 0, 0
-AVX_INSTR pmaxud, 0, 0
-AVX_INSTR pminsb, 0, 0
-AVX_INSTR pminsw, 0, 0
-AVX_INSTR pminsd, 0, 0
-AVX_INSTR pminub, 0, 0
-AVX_INSTR pminuw, 0, 0
-AVX_INSTR pminud, 0, 0
-AVX_INSTR pmulhuw, 0, 0
-AVX_INSTR pmulhrsw, 0, 0
-AVX_INSTR pmulhw, 0, 0
-AVX_INSTR pmullw, 0, 0
-AVX_INSTR pmulld, 0, 0
-AVX_INSTR pmuludq, 0, 0
-AVX_INSTR pmuldq, 0, 0
-AVX_INSTR por, 0, 0
-AVX_INSTR psadbw, 0, 0
-AVX_INSTR pshufb, 0, 0
-AVX_INSTR psignb, 0, 0
-AVX_INSTR psignw, 0, 0
-AVX_INSTR psignd, 0, 0
-AVX_INSTR psllw, 0, 0
-AVX_INSTR pslld, 0, 0
-AVX_INSTR psllq, 0, 0
-AVX_INSTR pslldq, 0, 0
-AVX_INSTR psraw, 0, 0
-AVX_INSTR psrad, 0, 0
-AVX_INSTR psrlw, 0, 0
-AVX_INSTR psrld, 0, 0
-AVX_INSTR psrlq, 0, 0
-AVX_INSTR psrldq, 0, 0
-AVX_INSTR psubb, 0, 0
-AVX_INSTR psubw, 0, 0
-AVX_INSTR psubd, 0, 0
-AVX_INSTR psubq, 0, 0
-AVX_INSTR psubsb, 0, 0
-AVX_INSTR psubsw, 0, 0
-AVX_INSTR psubusb, 0, 0
-AVX_INSTR psubusw, 0, 0
-AVX_INSTR punpckhbw, 0, 0
-AVX_INSTR punpckhwd, 0, 0
-AVX_INSTR punpckhdq, 0, 0
-AVX_INSTR punpckhqdq, 0, 0
-AVX_INSTR punpcklbw, 0, 0
-AVX_INSTR punpcklwd, 0, 0
-AVX_INSTR punpckldq, 0, 0
-AVX_INSTR punpcklqdq, 0, 0
-AVX_INSTR pxor, 0, 0
-AVX_INSTR shufps, 0, 1
-AVX_INSTR subpd, 1, 0
-AVX_INSTR subps, 1, 0
-AVX_INSTR subsd, 1, 0
-AVX_INSTR subss, 1, 0
-AVX_INSTR unpckhpd, 1, 0
-AVX_INSTR unpckhps, 1, 0
-AVX_INSTR unpcklpd, 1, 0
-AVX_INSTR unpcklps, 1, 0
-AVX_INSTR xorpd, 1, 0
-AVX_INSTR xorps, 1, 0
-
-; 3DNow instructions, for sharing code between AVX, SSE and 3DN
-AVX_INSTR pfadd, 1, 0
-AVX_INSTR pfsub, 1, 0
-AVX_INSTR pfmul, 1, 0
-
-;=============================================================================
-; Chromium extensions
-;=============================================================================
-
-%ifdef CHROMIUM
-; Always build PIC code on Mac or Linux for Chromium.
-%ifdef MACHO
-%define PIC
-%endif
-%ifdef ELF
-%define PIC
-%endif
-
-;
-; LOAD_SYM %1 (reg), %2 (sym)
-; Copies the address to a local symbol to the specified register.
-;
-
-%macro LOAD_SYM 2
-
-%ifdef PIC
- call %%geteip
- add %1, %2 - $
- jmp %%end
-%%geteip:
- mov %1, [rsp]
- ret
-%%end:
-
-%else
- lea %1, [%2]
-%endif
-
-%endmacro
-
-;
-; MOVq %1 (xmm), %2 (reg)
-; MOVq %1 (reg), %2 (xmm)
-; Copies a general-purpose register to an XMM register, and vice versa.
-;
-%macro MOVq 2
-%if gprsize == 8
- movq %1, %2
-%else
- movd %1, %2
-%endif
-%endmacro
-
-;
-; PRIVATE
-; A flag representing the specified symbol is a private symbol. This define adds
-; a hidden flag on Linux and a private_extern flag on Mac. (We can use this
-; private_extern flag only on the latest yasm.)
-;
-%ifdef MACHO
-%define PRIVATE :private_extern
-%elifdef ELF
-%define PRIVATE :hidden
-%else
-%define PRIVATE
-%endif
-
-%endif ; CHROMIUM
-
-%endif ; MEDIA_BASE_SIMD_X86INC_ASM_
diff --git a/src/media/base/simd/yuv_to_rgb_table.cc b/src/media/base/simd/yuv_to_rgb_table.cc
deleted file mode 100644
index f998e85..0000000
--- a/src/media/base/simd/yuv_to_rgb_table.cc
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (c) 2010 The Chromium 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 "media/base/simd/yuv_to_rgb_table.h"
-
-extern "C" {
-
-#define RGBY(i) { \
- static_cast<int16>(1.164 * 64 * (i - 16) + 0.5), \
- static_cast<int16>(1.164 * 64 * (i - 16) + 0.5), \
- static_cast<int16>(1.164 * 64 * (i - 16) + 0.5), \
- 0 \
-}
-
-#define RGBU(i) { \
- static_cast<int16>(2.018 * 64 * (i - 128) + 0.5), \
- static_cast<int16>(-0.391 * 64 * (i - 128) + 0.5), \
- 0, \
- static_cast<int16>(256 * 64 - 1) \
-}
-
-#define RGBV(i) { \
- 0, \
- static_cast<int16>(-0.813 * 64 * (i - 128) + 0.5), \
- static_cast<int16>(1.596 * 64 * (i - 128) + 0.5), \
- 0 \
-}
-
-SIMD_ALIGNED(int16 kCoefficientsRgbY[256 * 3][4]) = {
- RGBY(0x00), RGBY(0x01), RGBY(0x02), RGBY(0x03),
- RGBY(0x04), RGBY(0x05), RGBY(0x06), RGBY(0x07),
- RGBY(0x08), RGBY(0x09), RGBY(0x0A), RGBY(0x0B),
- RGBY(0x0C), RGBY(0x0D), RGBY(0x0E), RGBY(0x0F),
- RGBY(0x10), RGBY(0x11), RGBY(0x12), RGBY(0x13),
- RGBY(0x14), RGBY(0x15), RGBY(0x16), RGBY(0x17),
- RGBY(0x18), RGBY(0x19), RGBY(0x1A), RGBY(0x1B),
- RGBY(0x1C), RGBY(0x1D), RGBY(0x1E), RGBY(0x1F),
- RGBY(0x20), RGBY(0x21), RGBY(0x22), RGBY(0x23),
- RGBY(0x24), RGBY(0x25), RGBY(0x26), RGBY(0x27),
- RGBY(0x28), RGBY(0x29), RGBY(0x2A), RGBY(0x2B),
- RGBY(0x2C), RGBY(0x2D), RGBY(0x2E), RGBY(0x2F),
- RGBY(0x30), RGBY(0x31), RGBY(0x32), RGBY(0x33),
- RGBY(0x34), RGBY(0x35), RGBY(0x36), RGBY(0x37),
- RGBY(0x38), RGBY(0x39), RGBY(0x3A), RGBY(0x3B),
- RGBY(0x3C), RGBY(0x3D), RGBY(0x3E), RGBY(0x3F),
- RGBY(0x40), RGBY(0x41), RGBY(0x42), RGBY(0x43),
- RGBY(0x44), RGBY(0x45), RGBY(0x46), RGBY(0x47),
- RGBY(0x48), RGBY(0x49), RGBY(0x4A), RGBY(0x4B),
- RGBY(0x4C), RGBY(0x4D), RGBY(0x4E), RGBY(0x4F),
- RGBY(0x50), RGBY(0x51), RGBY(0x52), RGBY(0x53),
- RGBY(0x54), RGBY(0x55), RGBY(0x56), RGBY(0x57),
- RGBY(0x58), RGBY(0x59), RGBY(0x5A), RGBY(0x5B),
- RGBY(0x5C), RGBY(0x5D), RGBY(0x5E), RGBY(0x5F),
- RGBY(0x60), RGBY(0x61), RGBY(0x62), RGBY(0x63),
- RGBY(0x64), RGBY(0x65), RGBY(0x66), RGBY(0x67),
- RGBY(0x68), RGBY(0x69), RGBY(0x6A), RGBY(0x6B),
- RGBY(0x6C), RGBY(0x6D), RGBY(0x6E), RGBY(0x6F),
- RGBY(0x70), RGBY(0x71), RGBY(0x72), RGBY(0x73),
- RGBY(0x74), RGBY(0x75), RGBY(0x76), RGBY(0x77),
- RGBY(0x78), RGBY(0x79), RGBY(0x7A), RGBY(0x7B),
- RGBY(0x7C), RGBY(0x7D), RGBY(0x7E), RGBY(0x7F),
- RGBY(0x80), RGBY(0x81), RGBY(0x82), RGBY(0x83),
- RGBY(0x84), RGBY(0x85), RGBY(0x86), RGBY(0x87),
- RGBY(0x88), RGBY(0x89), RGBY(0x8A), RGBY(0x8B),
- RGBY(0x8C), RGBY(0x8D), RGBY(0x8E), RGBY(0x8F),
- RGBY(0x90), RGBY(0x91), RGBY(0x92), RGBY(0x93),
- RGBY(0x94), RGBY(0x95), RGBY(0x96), RGBY(0x97),
- RGBY(0x98), RGBY(0x99), RGBY(0x9A), RGBY(0x9B),
- RGBY(0x9C), RGBY(0x9D), RGBY(0x9E), RGBY(0x9F),
- RGBY(0xA0), RGBY(0xA1), RGBY(0xA2), RGBY(0xA3),
- RGBY(0xA4), RGBY(0xA5), RGBY(0xA6), RGBY(0xA7),
- RGBY(0xA8), RGBY(0xA9), RGBY(0xAA), RGBY(0xAB),
- RGBY(0xAC), RGBY(0xAD), RGBY(0xAE), RGBY(0xAF),
- RGBY(0xB0), RGBY(0xB1), RGBY(0xB2), RGBY(0xB3),
- RGBY(0xB4), RGBY(0xB5), RGBY(0xB6), RGBY(0xB7),
- RGBY(0xB8), RGBY(0xB9), RGBY(0xBA), RGBY(0xBB),
- RGBY(0xBC), RGBY(0xBD), RGBY(0xBE), RGBY(0xBF),
- RGBY(0xC0), RGBY(0xC1), RGBY(0xC2), RGBY(0xC3),
- RGBY(0xC4), RGBY(0xC5), RGBY(0xC6), RGBY(0xC7),
- RGBY(0xC8), RGBY(0xC9), RGBY(0xCA), RGBY(0xCB),
- RGBY(0xCC), RGBY(0xCD), RGBY(0xCE), RGBY(0xCF),
- RGBY(0xD0), RGBY(0xD1), RGBY(0xD2), RGBY(0xD3),
- RGBY(0xD4), RGBY(0xD5), RGBY(0xD6), RGBY(0xD7),
- RGBY(0xD8), RGBY(0xD9), RGBY(0xDA), RGBY(0xDB),
- RGBY(0xDC), RGBY(0xDD), RGBY(0xDE), RGBY(0xDF),
- RGBY(0xE0), RGBY(0xE1), RGBY(0xE2), RGBY(0xE3),
- RGBY(0xE4), RGBY(0xE5), RGBY(0xE6), RGBY(0xE7),
- RGBY(0xE8), RGBY(0xE9), RGBY(0xEA), RGBY(0xEB),
- RGBY(0xEC), RGBY(0xED), RGBY(0xEE), RGBY(0xEF),
- RGBY(0xF0), RGBY(0xF1), RGBY(0xF2), RGBY(0xF3),
- RGBY(0xF4), RGBY(0xF5), RGBY(0xF6), RGBY(0xF7),
- RGBY(0xF8), RGBY(0xF9), RGBY(0xFA), RGBY(0xFB),
- RGBY(0xFC), RGBY(0xFD), RGBY(0xFE), RGBY(0xFF),
-
- // Chroma U table.
- RGBU(0x00), RGBU(0x01), RGBU(0x02), RGBU(0x03),
- RGBU(0x04), RGBU(0x05), RGBU(0x06), RGBU(0x07),
- RGBU(0x08), RGBU(0x09), RGBU(0x0A), RGBU(0x0B),
- RGBU(0x0C), RGBU(0x0D), RGBU(0x0E), RGBU(0x0F),
- RGBU(0x10), RGBU(0x11), RGBU(0x12), RGBU(0x13),
- RGBU(0x14), RGBU(0x15), RGBU(0x16), RGBU(0x17),
- RGBU(0x18), RGBU(0x19), RGBU(0x1A), RGBU(0x1B),
- RGBU(0x1C), RGBU(0x1D), RGBU(0x1E), RGBU(0x1F),
- RGBU(0x20), RGBU(0x21), RGBU(0x22), RGBU(0x23),
- RGBU(0x24), RGBU(0x25), RGBU(0x26), RGBU(0x27),
- RGBU(0x28), RGBU(0x29), RGBU(0x2A), RGBU(0x2B),
- RGBU(0x2C), RGBU(0x2D), RGBU(0x2E), RGBU(0x2F),
- RGBU(0x30), RGBU(0x31), RGBU(0x32), RGBU(0x33),
- RGBU(0x34), RGBU(0x35), RGBU(0x36), RGBU(0x37),
- RGBU(0x38), RGBU(0x39), RGBU(0x3A), RGBU(0x3B),
- RGBU(0x3C), RGBU(0x3D), RGBU(0x3E), RGBU(0x3F),
- RGBU(0x40), RGBU(0x41), RGBU(0x42), RGBU(0x43),
- RGBU(0x44), RGBU(0x45), RGBU(0x46), RGBU(0x47),
- RGBU(0x48), RGBU(0x49), RGBU(0x4A), RGBU(0x4B),
- RGBU(0x4C), RGBU(0x4D), RGBU(0x4E), RGBU(0x4F),
- RGBU(0x50), RGBU(0x51), RGBU(0x52), RGBU(0x53),
- RGBU(0x54), RGBU(0x55), RGBU(0x56), RGBU(0x57),
- RGBU(0x58), RGBU(0x59), RGBU(0x5A), RGBU(0x5B),
- RGBU(0x5C), RGBU(0x5D), RGBU(0x5E), RGBU(0x5F),
- RGBU(0x60), RGBU(0x61), RGBU(0x62), RGBU(0x63),
- RGBU(0x64), RGBU(0x65), RGBU(0x66), RGBU(0x67),
- RGBU(0x68), RGBU(0x69), RGBU(0x6A), RGBU(0x6B),
- RGBU(0x6C), RGBU(0x6D), RGBU(0x6E), RGBU(0x6F),
- RGBU(0x70), RGBU(0x71), RGBU(0x72), RGBU(0x73),
- RGBU(0x74), RGBU(0x75), RGBU(0x76), RGBU(0x77),
- RGBU(0x78), RGBU(0x79), RGBU(0x7A), RGBU(0x7B),
- RGBU(0x7C), RGBU(0x7D), RGBU(0x7E), RGBU(0x7F),
- RGBU(0x80), RGBU(0x81), RGBU(0x82), RGBU(0x83),
- RGBU(0x84), RGBU(0x85), RGBU(0x86), RGBU(0x87),
- RGBU(0x88), RGBU(0x89), RGBU(0x8A), RGBU(0x8B),
- RGBU(0x8C), RGBU(0x8D), RGBU(0x8E), RGBU(0x8F),
- RGBU(0x90), RGBU(0x91), RGBU(0x92), RGBU(0x93),
- RGBU(0x94), RGBU(0x95), RGBU(0x96), RGBU(0x97),
- RGBU(0x98), RGBU(0x99), RGBU(0x9A), RGBU(0x9B),
- RGBU(0x9C), RGBU(0x9D), RGBU(0x9E), RGBU(0x9F),
- RGBU(0xA0), RGBU(0xA1), RGBU(0xA2), RGBU(0xA3),
- RGBU(0xA4), RGBU(0xA5), RGBU(0xA6), RGBU(0xA7),
- RGBU(0xA8), RGBU(0xA9), RGBU(0xAA), RGBU(0xAB),
- RGBU(0xAC), RGBU(0xAD), RGBU(0xAE), RGBU(0xAF),
- RGBU(0xB0), RGBU(0xB1), RGBU(0xB2), RGBU(0xB3),
- RGBU(0xB4), RGBU(0xB5), RGBU(0xB6), RGBU(0xB7),
- RGBU(0xB8), RGBU(0xB9), RGBU(0xBA), RGBU(0xBB),
- RGBU(0xBC), RGBU(0xBD), RGBU(0xBE), RGBU(0xBF),
- RGBU(0xC0), RGBU(0xC1), RGBU(0xC2), RGBU(0xC3),
- RGBU(0xC4), RGBU(0xC5), RGBU(0xC6), RGBU(0xC7),
- RGBU(0xC8), RGBU(0xC9), RGBU(0xCA), RGBU(0xCB),
- RGBU(0xCC), RGBU(0xCD), RGBU(0xCE), RGBU(0xCF),
- RGBU(0xD0), RGBU(0xD1), RGBU(0xD2), RGBU(0xD3),
- RGBU(0xD4), RGBU(0xD5), RGBU(0xD6), RGBU(0xD7),
- RGBU(0xD8), RGBU(0xD9), RGBU(0xDA), RGBU(0xDB),
- RGBU(0xDC), RGBU(0xDD), RGBU(0xDE), RGBU(0xDF),
- RGBU(0xE0), RGBU(0xE1), RGBU(0xE2), RGBU(0xE3),
- RGBU(0xE4), RGBU(0xE5), RGBU(0xE6), RGBU(0xE7),
- RGBU(0xE8), RGBU(0xE9), RGBU(0xEA), RGBU(0xEB),
- RGBU(0xEC), RGBU(0xED), RGBU(0xEE), RGBU(0xEF),
- RGBU(0xF0), RGBU(0xF1), RGBU(0xF2), RGBU(0xF3),
- RGBU(0xF4), RGBU(0xF5), RGBU(0xF6), RGBU(0xF7),
- RGBU(0xF8), RGBU(0xF9), RGBU(0xFA), RGBU(0xFB),
- RGBU(0xFC), RGBU(0xFD), RGBU(0xFE), RGBU(0xFF),
-
- // Chroma V table.
- RGBV(0x00), RGBV(0x01), RGBV(0x02), RGBV(0x03),
- RGBV(0x04), RGBV(0x05), RGBV(0x06), RGBV(0x07),
- RGBV(0x08), RGBV(0x09), RGBV(0x0A), RGBV(0x0B),
- RGBV(0x0C), RGBV(0x0D), RGBV(0x0E), RGBV(0x0F),
- RGBV(0x10), RGBV(0x11), RGBV(0x12), RGBV(0x13),
- RGBV(0x14), RGBV(0x15), RGBV(0x16), RGBV(0x17),
- RGBV(0x18), RGBV(0x19), RGBV(0x1A), RGBV(0x1B),
- RGBV(0x1C), RGBV(0x1D), RGBV(0x1E), RGBV(0x1F),
- RGBV(0x20), RGBV(0x21), RGBV(0x22), RGBV(0x23),
- RGBV(0x24), RGBV(0x25), RGBV(0x26), RGBV(0x27),
- RGBV(0x28), RGBV(0x29), RGBV(0x2A), RGBV(0x2B),
- RGBV(0x2C), RGBV(0x2D), RGBV(0x2E), RGBV(0x2F),
- RGBV(0x30), RGBV(0x31), RGBV(0x32), RGBV(0x33),
- RGBV(0x34), RGBV(0x35), RGBV(0x36), RGBV(0x37),
- RGBV(0x38), RGBV(0x39), RGBV(0x3A), RGBV(0x3B),
- RGBV(0x3C), RGBV(0x3D), RGBV(0x3E), RGBV(0x3F),
- RGBV(0x40), RGBV(0x41), RGBV(0x42), RGBV(0x43),
- RGBV(0x44), RGBV(0x45), RGBV(0x46), RGBV(0x47),
- RGBV(0x48), RGBV(0x49), RGBV(0x4A), RGBV(0x4B),
- RGBV(0x4C), RGBV(0x4D), RGBV(0x4E), RGBV(0x4F),
- RGBV(0x50), RGBV(0x51), RGBV(0x52), RGBV(0x53),
- RGBV(0x54), RGBV(0x55), RGBV(0x56), RGBV(0x57),
- RGBV(0x58), RGBV(0x59), RGBV(0x5A), RGBV(0x5B),
- RGBV(0x5C), RGBV(0x5D), RGBV(0x5E), RGBV(0x5F),
- RGBV(0x60), RGBV(0x61), RGBV(0x62), RGBV(0x63),
- RGBV(0x64), RGBV(0x65), RGBV(0x66), RGBV(0x67),
- RGBV(0x68), RGBV(0x69), RGBV(0x6A), RGBV(0x6B),
- RGBV(0x6C), RGBV(0x6D), RGBV(0x6E), RGBV(0x6F),
- RGBV(0x70), RGBV(0x71), RGBV(0x72), RGBV(0x73),
- RGBV(0x74), RGBV(0x75), RGBV(0x76), RGBV(0x77),
- RGBV(0x78), RGBV(0x79), RGBV(0x7A), RGBV(0x7B),
- RGBV(0x7C), RGBV(0x7D), RGBV(0x7E), RGBV(0x7F),
- RGBV(0x80), RGBV(0x81), RGBV(0x82), RGBV(0x83),
- RGBV(0x84), RGBV(0x85), RGBV(0x86), RGBV(0x87),
- RGBV(0x88), RGBV(0x89), RGBV(0x8A), RGBV(0x8B),
- RGBV(0x8C), RGBV(0x8D), RGBV(0x8E), RGBV(0x8F),
- RGBV(0x90), RGBV(0x91), RGBV(0x92), RGBV(0x93),
- RGBV(0x94), RGBV(0x95), RGBV(0x96), RGBV(0x97),
- RGBV(0x98), RGBV(0x99), RGBV(0x9A), RGBV(0x9B),
- RGBV(0x9C), RGBV(0x9D), RGBV(0x9E), RGBV(0x9F),
- RGBV(0xA0), RGBV(0xA1), RGBV(0xA2), RGBV(0xA3),
- RGBV(0xA4), RGBV(0xA5), RGBV(0xA6), RGBV(0xA7),
- RGBV(0xA8), RGBV(0xA9), RGBV(0xAA), RGBV(0xAB),
- RGBV(0xAC), RGBV(0xAD), RGBV(0xAE), RGBV(0xAF),
- RGBV(0xB0), RGBV(0xB1), RGBV(0xB2), RGBV(0xB3),
- RGBV(0xB4), RGBV(0xB5), RGBV(0xB6), RGBV(0xB7),
- RGBV(0xB8), RGBV(0xB9), RGBV(0xBA), RGBV(0xBB),
- RGBV(0xBC), RGBV(0xBD), RGBV(0xBE), RGBV(0xBF),
- RGBV(0xC0), RGBV(0xC1), RGBV(0xC2), RGBV(0xC3),
- RGBV(0xC4), RGBV(0xC5), RGBV(0xC6), RGBV(0xC7),
- RGBV(0xC8), RGBV(0xC9), RGBV(0xCA), RGBV(0xCB),
- RGBV(0xCC), RGBV(0xCD), RGBV(0xCE), RGBV(0xCF),
- RGBV(0xD0), RGBV(0xD1), RGBV(0xD2), RGBV(0xD3),
- RGBV(0xD4), RGBV(0xD5), RGBV(0xD6), RGBV(0xD7),
- RGBV(0xD8), RGBV(0xD9), RGBV(0xDA), RGBV(0xDB),
- RGBV(0xDC), RGBV(0xDD), RGBV(0xDE), RGBV(0xDF),
- RGBV(0xE0), RGBV(0xE1), RGBV(0xE2), RGBV(0xE3),
- RGBV(0xE4), RGBV(0xE5), RGBV(0xE6), RGBV(0xE7),
- RGBV(0xE8), RGBV(0xE9), RGBV(0xEA), RGBV(0xEB),
- RGBV(0xEC), RGBV(0xED), RGBV(0xEE), RGBV(0xEF),
- RGBV(0xF0), RGBV(0xF1), RGBV(0xF2), RGBV(0xF3),
- RGBV(0xF4), RGBV(0xF5), RGBV(0xF6), RGBV(0xF7),
- RGBV(0xF8), RGBV(0xF9), RGBV(0xFA), RGBV(0xFB),
- RGBV(0xFC), RGBV(0xFD), RGBV(0xFE), RGBV(0xFF),
-};
-
-#undef RGBY
-#undef RGBU
-#undef RGBV
-
-} // extern "C"
diff --git a/src/media/base/simd/yuv_to_rgb_table.h b/src/media/base/simd/yuv_to_rgb_table.h
deleted file mode 100644
index 0c43a7a..0000000
--- a/src/media/base/simd/yuv_to_rgb_table.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Defines convertion table from YUV to RGB.
-
-#ifndef MEDIA_BASE_SIMD_YUV_TO_RGB_TABLE_H_
-#define MEDIA_BASE_SIMD_YUV_TO_RGB_TABLE_H_
-
-#include "base/basictypes.h"
-#include "build/build_config.h"
-
-extern "C" {
-
-#if defined(COMPILER_MSVC)
-#define SIMD_ALIGNED(var) __declspec(align(16)) var
-#else
-#define SIMD_ALIGNED(var) var __attribute__((aligned(16)))
-#endif
-
-// Align the table to 16-bytes to allow faster reading.
-extern SIMD_ALIGNED(int16 kCoefficientsRgbY[768][4]);
-
-} // extern "C"
-
-#endif // MEDIA_BASE_SIMD_YUV_TO_RGB_TABLE_H_
diff --git a/src/media/base/sinc_resampler.cc b/src/media/base/sinc_resampler.cc
deleted file mode 100644
index d104a1c..0000000
--- a/src/media/base/sinc_resampler.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Input buffer layout, dividing the total buffer into regions (r0_ - r5_):
-//
-// |----------------|-----------------------------------------|----------------|
-//
-// kBlockSize + kKernelSize / 2
-// <--------------------------------------------------------->
-// r0_
-//
-// kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2
-// <---------------> <---------------> <---------------> <--------------->
-// r1_ r2_ r3_ r4_
-//
-// kBlockSize
-// <--------------------------------------->
-// r5_
-//
-// The algorithm:
-//
-// 1) Consume input frames into r0_ (r1_ is zero-initialized).
-// 2) Position kernel centered at start of r0_ (r2_) and generate output frames
-// until kernel is centered at start of r4_ or we've finished generating all
-// the output frames.
-// 3) Copy r3_ to r1_ and r4_ to r2_.
-// 4) Consume input frames into r5_ (zero-pad if we run out of input).
-// 5) Goto (2) until all of input is consumed.
-//
-// Note: we're glossing over how the sub-sample handling works with
-// |virtual_source_idx_|, etc.
-
-#include "media/base/sinc_resampler.h"
-
-#include <cmath>
-
-#include "base/cpu.h"
-#include "base/logging.h"
-#include "build/build_config.h"
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
-#include <xmmintrin.h>
-#endif
-
-#if defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
-#include <arm_neon.h>
-#endif
-
-namespace media {
-
-namespace {
-
-enum {
- // The kernel size can be adjusted for quality (higher is better) at the
- // expense of performance. Must be a multiple of 32.
- // TODO(dalecurtis): Test performance to see if we can jack this up to 64+.
- kKernelSize = 32,
-
- // The number of destination frames generated per processing pass. Affects
- // how often and for how much SincResampler calls back for input. Must be
- // greater than kKernelSize.
- kBlockSize = 512,
-
- // The kernel offset count is used for interpolation and is the number of
- // sub-sample kernel shifts. Can be adjusted for quality (higher is better)
- // at the expense of allocating more memory.
- kKernelOffsetCount = 32,
- kKernelStorageSize = kKernelSize * (kKernelOffsetCount + 1),
-
- // The size (in samples) of the internal buffer used by the resampler.
- kBufferSize = kBlockSize + kKernelSize
-};
-
-} // namespace
-
-const int SincResampler::kMaximumLookAheadSize = kBufferSize;
-
-SincResampler::SincResampler(double io_sample_rate_ratio, const ReadCB& read_cb)
- : io_sample_rate_ratio_(io_sample_rate_ratio),
- virtual_source_idx_(0),
- buffer_primed_(false),
- read_cb_(read_cb),
- // Create input buffers with a 16-byte alignment for SSE optimizations.
- kernel_storage_(static_cast<float*>(
- base::AlignedAlloc(sizeof(float) * kKernelStorageSize, 16))),
- input_buffer_(static_cast<float*>(
- base::AlignedAlloc(sizeof(float) * kBufferSize, 16))),
- // Setup various region pointers in the buffer (see diagram above).
- r0_(input_buffer_.get() + kKernelSize / 2),
- r1_(input_buffer_.get()),
- r2_(r0_),
- r3_(r0_ + kBlockSize - kKernelSize / 2),
- r4_(r0_ + kBlockSize),
- r5_(r0_ + kKernelSize / 2) {
- // Ensure kKernelSize is a multiple of 32 for easy SSE optimizations; causes
- // r0_ and r5_ (used for input) to always be 16-byte aligned by virtue of
- // input_buffer_ being 16-byte aligned.
- DCHECK_EQ(kKernelSize % 32, 0) << "kKernelSize must be a multiple of 32!";
- DCHECK_GT(kBlockSize, kKernelSize)
- << "kBlockSize must be greater than kKernelSize!";
- // Basic sanity checks to ensure buffer regions are laid out correctly:
- // r0_ and r2_ should always be the same position.
- DCHECK_EQ(r0_, r2_);
- // r1_ at the beginning of the buffer.
- DCHECK_EQ(r1_, input_buffer_.get());
- // r1_ left of r2_, r2_ left of r5_ and r1_, r2_ size correct.
- DCHECK_EQ(r2_ - r1_, r5_ - r2_);
- // r3_ left of r4_, r5_ left of r0_ and r3_ size correct.
- DCHECK_EQ(r4_ - r3_, r5_ - r0_);
- // r3_, r4_ size correct and r4_ at the end of the buffer.
- DCHECK_EQ(r4_ + (r4_ - r3_), r1_ + kBufferSize);
- // r5_ size correct and at the end of the buffer.
- DCHECK_EQ(r5_ + kBlockSize, r1_ + kBufferSize);
-
- memset(kernel_storage_.get(), 0,
- sizeof(*kernel_storage_.get()) * kKernelStorageSize);
- memset(input_buffer_.get(), 0, sizeof(*input_buffer_.get()) * kBufferSize);
-
- InitializeKernel();
-}
-
-SincResampler::~SincResampler() {}
-
-void SincResampler::InitializeKernel() {
- // Blackman window parameters.
- static const double kAlpha = 0.16;
- static const double kA0 = 0.5 * (1.0 - kAlpha);
- static const double kA1 = 0.5;
- static const double kA2 = 0.5 * kAlpha;
-
- // |sinc_scale_factor| is basically the normalized cutoff frequency of the
- // low-pass filter.
- double sinc_scale_factor =
- io_sample_rate_ratio_ > 1.0 ? 1.0 / io_sample_rate_ratio_ : 1.0;
-
- // The sinc function is an idealized brick-wall filter, but since we're
- // windowing it the transition from pass to stop does not happen right away.
- // So we should adjust the low pass filter cutoff slightly downward to avoid
- // some aliasing at the very high-end.
- // TODO(crogers): this value is empirical and to be more exact should vary
- // depending on kKernelSize.
- sinc_scale_factor *= 0.9;
-
- // Generates a set of windowed sinc() kernels.
- // We generate a range of sub-sample offsets from 0.0 to 1.0.
- for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) {
- double subsample_offset =
- static_cast<double>(offset_idx) / kKernelOffsetCount;
-
- for (int i = 0; i < kKernelSize; ++i) {
- // Compute the sinc with offset.
- double s =
- sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset);
- double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor;
-
- // Compute Blackman window, matching the offset of the sinc().
- double x = (i - subsample_offset) / kKernelSize;
- double window = kA0 - kA1 * cos(2.0 * M_PI * x) + kA2
- * cos(4.0 * M_PI * x);
-
- // Window the sinc() function and store at the correct offset.
- kernel_storage_.get()[i + offset_idx * kKernelSize] = sinc * window;
- }
- }
-}
-
-void SincResampler::Resample(float* destination, int frames) {
- int remaining_frames = frames;
-
- // Step (1) -- Prime the input buffer at the start of the input stream.
- if (!buffer_primed_) {
- read_cb_.Run(r0_, kBlockSize + kKernelSize / 2);
- buffer_primed_ = true;
- }
-
- // Step (2) -- Resample!
- while (remaining_frames) {
- while (virtual_source_idx_ < kBlockSize) {
- // |virtual_source_idx_| lies in between two kernel offsets so figure out
- // what they are.
- int source_idx = static_cast<int>(virtual_source_idx_);
- double subsample_remainder = virtual_source_idx_ - source_idx;
-
- double virtual_offset_idx = subsample_remainder * kKernelOffsetCount;
- int offset_idx = static_cast<int>(virtual_offset_idx);
-
- // We'll compute "convolutions" for the two kernels which straddle
- // |virtual_source_idx_|.
- float* k1 = kernel_storage_.get() + offset_idx * kKernelSize;
- float* k2 = k1 + kKernelSize;
-
- // Initialize input pointer based on quantized |virtual_source_idx_|.
- float* input_ptr = r1_ + source_idx;
-
- // Figure out how much to weight each kernel's "convolution".
- double kernel_interpolation_factor = virtual_offset_idx - offset_idx;
- *destination++ = Convolve(
- input_ptr, k1, k2, kernel_interpolation_factor);
-
- // Advance the virtual index.
- virtual_source_idx_ += io_sample_rate_ratio_;
-
- if (!--remaining_frames)
- return;
- }
-
- // Wrap back around to the start.
- virtual_source_idx_ -= kBlockSize;
-
- // Step (3) Copy r3_ to r1_ and r4_ to r2_.
- // This wraps the last input frames back to the start of the buffer.
- memcpy(r1_, r3_, sizeof(*input_buffer_.get()) * (kKernelSize / 2));
- memcpy(r2_, r4_, sizeof(*input_buffer_.get()) * (kKernelSize / 2));
-
- // Step (4)
- // Refresh the buffer with more input.
- read_cb_.Run(r5_, kBlockSize);
- }
-}
-
-int SincResampler::ChunkSize() {
- return kBlockSize / io_sample_rate_ratio_;
-}
-
-void SincResampler::Flush() {
- virtual_source_idx_ = 0;
- buffer_primed_ = false;
- memset(input_buffer_.get(), 0, sizeof(*input_buffer_.get()) * kBufferSize);
-}
-
-float SincResampler::Convolve(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor) {
- // Rely on function level static initialization to keep ConvolveProc selection
- // thread safe.
- typedef float (*ConvolveProc)(const float* src, const float* k1,
- const float* k2,
- double kernel_interpolation_factor);
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
- static const ConvolveProc kConvolveProc =
- base::CPU().has_sse() ? Convolve_SSE : Convolve_C;
-#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
- static const ConvolveProc kConvolveProc = Convolve_NEON;
-#else
- static const ConvolveProc kConvolveProc = Convolve_C;
-#endif
-
- return kConvolveProc(input_ptr, k1, k2, kernel_interpolation_factor);
-}
-
-float SincResampler::Convolve_C(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor) {
- float sum1 = 0;
- float sum2 = 0;
-
- // Generate a single output sample. Unrolling this loop hurt performance in
- // local testing.
- int n = kKernelSize;
- while (n--) {
- sum1 += *input_ptr * *k1++;
- sum2 += *input_ptr++ * *k2++;
- }
-
- // Linearly interpolate the two "convolutions".
- return (1.0 - kernel_interpolation_factor) * sum1
- + kernel_interpolation_factor * sum2;
-}
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
-float SincResampler::Convolve_SSE(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor) {
- // Ensure |k1|, |k2| are 16-byte aligned for SSE usage. Should always be true
- // so long as kKernelSize is a multiple of 16.
- DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(k1) & 0x0F);
- DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(k2) & 0x0F);
-
- __m128 m_input;
- __m128 m_sums1 = _mm_setzero_ps();
- __m128 m_sums2 = _mm_setzero_ps();
-
- // Based on |input_ptr| alignment, we need to use loadu or load. Unrolling
- // these loops hurt performance in local testing.
- if (reinterpret_cast<uintptr_t>(input_ptr) & 0x0F) {
- for (int i = 0; i < kKernelSize; i += 4) {
- m_input = _mm_loadu_ps(input_ptr + i);
- m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i)));
- m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i)));
- }
- } else {
- for (int i = 0; i < kKernelSize; i += 4) {
- m_input = _mm_load_ps(input_ptr + i);
- m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i)));
- m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i)));
- }
- }
-
- // Linearly interpolate the two "convolutions".
- m_sums1 = _mm_mul_ps(m_sums1, _mm_set_ps1(1.0 - kernel_interpolation_factor));
- m_sums2 = _mm_mul_ps(m_sums2, _mm_set_ps1(kernel_interpolation_factor));
- m_sums1 = _mm_add_ps(m_sums1, m_sums2);
-
- // Sum components together.
- float result;
- m_sums2 = _mm_add_ps(_mm_movehl_ps(m_sums1, m_sums1), m_sums1);
- _mm_store_ss(&result, _mm_add_ss(m_sums2, _mm_shuffle_ps(
- m_sums2, m_sums2, 1)));
-
- return result;
-}
-#endif
-
-#if defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
-float SincResampler::Convolve_NEON(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor) {
- float32x4_t m_input;
- float32x4_t m_sums1 = vmovq_n_f32(0);
- float32x4_t m_sums2 = vmovq_n_f32(0);
-
- const float* upper = input_ptr + kKernelSize;
- for (; input_ptr < upper; ) {
- m_input = vld1q_f32(input_ptr);
- input_ptr += 4;
- m_sums1 = vmlaq_f32(m_sums1, m_input, vld1q_f32(k1));
- k1 += 4;
- m_sums2 = vmlaq_f32(m_sums2, m_input, vld1q_f32(k2));
- k2 += 4;
- }
-
- // Linearly interpolate the two "convolutions".
- m_sums1 = vmlaq_f32(
- vmulq_f32(m_sums1, vmovq_n_f32(1.0 - kernel_interpolation_factor)),
- m_sums2, vmovq_n_f32(kernel_interpolation_factor));
-
- // Sum components together.
- float32x2_t m_half = vadd_f32(vget_high_f32(m_sums1), vget_low_f32(m_sums1));
- return vget_lane_f32(vpadd_f32(m_half, m_half), 0);
-}
-#endif
-
-} // namespace media
diff --git a/src/media/base/sinc_resampler.h b/src/media/base/sinc_resampler.h
deleted file mode 100644
index a1d3cf7..0000000
--- a/src/media/base/sinc_resampler.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_SINC_RESAMPLER_H_
-#define MEDIA_BASE_SINC_RESAMPLER_H_
-
-#include "base/callback.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// SincResampler is a high-quality single-channel sample-rate converter.
-class MEDIA_EXPORT SincResampler {
- public:
- // The maximum number of samples that may be requested from the callback ahead
- // of the current position in the stream.
- static const int kMaximumLookAheadSize;
-
- // Callback type for providing more data into the resampler. Expects |frames|
- // of data to be rendered into |destination|; zero padded if not enough frames
- // are available to satisfy the request.
- typedef base::Callback<void(float* destination, int frames)> ReadCB;
-
- // Constructs a SincResampler with the specified |read_cb|, which is used to
- // acquire audio data for resampling. |io_sample_rate_ratio| is the ratio of
- // input / output sample rates.
- SincResampler(double io_sample_rate_ratio, const ReadCB& read_cb);
- virtual ~SincResampler();
-
- // Resample |frames| of data from |read_cb_| into |destination|.
- void Resample(float* destination, int frames);
-
- // The maximum size in frames that guarantees Resample() will only make a
- // single call to |read_cb_| for more data.
- int ChunkSize();
-
- // Flush all buffered data and reset internal indices.
- void Flush();
-
- private:
- FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, Convolve);
- FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, ConvolveBenchmark);
-
- void InitializeKernel();
-
- // Compute convolution of |k1| and |k2| over |input_ptr|, resultant sums are
- // linearly interpolated using |kernel_interpolation_factor|. On x86, the
- // underlying implementation is chosen at run time based on SSE support. On
- // ARM, NEON support is chosen at compile time based on compilation flags.
- static float Convolve(const float* input_ptr, const float* k1,
- const float* k2, double kernel_interpolation_factor);
- static float Convolve_C(const float* input_ptr, const float* k1,
- const float* k2, double kernel_interpolation_factor);
- static float Convolve_SSE(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor);
- static float Convolve_NEON(const float* input_ptr, const float* k1,
- const float* k2,
- double kernel_interpolation_factor);
-
- // The ratio of input / output sample rates.
- double io_sample_rate_ratio_;
-
- // An index on the source input buffer with sub-sample precision. It must be
- // double precision to avoid drift.
- double virtual_source_idx_;
-
- // The buffer is primed once at the very beginning of processing.
- bool buffer_primed_;
-
- // Source of data for resampling.
- ReadCB read_cb_;
-
- // Contains kKernelOffsetCount kernels back-to-back, each of size kKernelSize.
- // The kernel offsets are sub-sample shifts of a windowed sinc shifted from
- // 0.0 to 1.0 sample.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> kernel_storage_;
-
- // Data from the source is copied into this buffer for each processing pass.
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_buffer_;
-
- // Pointers to the various regions inside |input_buffer_|. See the diagram at
- // the top of the .cc file for more information.
- float* const r0_;
- float* const r1_;
- float* const r2_;
- float* const r3_;
- float* const r4_;
- float* const r5_;
-
- DISALLOW_COPY_AND_ASSIGN(SincResampler);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_SINC_RESAMPLER_H_
diff --git a/src/media/base/sinc_resampler_unittest.cc b/src/media/base/sinc_resampler_unittest.cc
deleted file mode 100644
index 59a9f81..0000000
--- a/src/media/base/sinc_resampler_unittest.cc
+++ /dev/null
@@ -1,405 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MSVC++ requires this to be set before any other includes to get M_PI.
-#define _USE_MATH_DEFINES
-
-#include <cmath>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/string_number_conversions.h"
-#include "base/stringize_macros.h"
-#include "base/time.h"
-#include "build/build_config.h"
-#include "media/base/sinc_resampler.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-
-namespace media {
-
-static const double kSampleRateRatio = 192000.0 / 44100.0;
-static const double kKernelInterpolationFactor = 0.5;
-
-// Command line switch for runtime adjustment of ConvolveBenchmark iterations.
-static const char kConvolveIterations[] = "convolve-iterations";
-
-// Helper class to ensure ChunkedResample() functions properly.
-class MockSource {
- public:
- MOCK_METHOD2(ProvideInput, void(float* destination, int frames));
-};
-
-ACTION(ClearBuffer) {
- memset(arg0, 0, arg1 * sizeof(float));
-}
-
-ACTION(FillBuffer) {
- // Value chosen arbitrarily such that SincResampler resamples it to something
- // easily representable on all platforms; e.g., using kSampleRateRatio this
- // becomes 1.81219.
- memset(arg0, 64, arg1 * sizeof(float));
-}
-
-// Test requesting multiples of ChunkSize() frames results in the proper number
-// of callbacks.
-TEST(SincResamplerTest, ChunkedResample) {
- MockSource mock_source;
-
- // Choose a high ratio of input to output samples which will result in quick
- // exhaustion of SincResampler's internal buffers.
- SincResampler resampler(
- kSampleRateRatio,
- base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
-
- static const int kChunks = 2;
- int max_chunk_size = resampler.ChunkSize() * kChunks;
- scoped_array<float> resampled_destination(new float[max_chunk_size]);
-
- // Verify requesting ChunkSize() frames causes a single callback.
- EXPECT_CALL(mock_source, ProvideInput(_, _))
- .Times(1).WillOnce(ClearBuffer());
- resampler.Resample(resampled_destination.get(), resampler.ChunkSize());
-
- // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks.
- testing::Mock::VerifyAndClear(&mock_source);
- EXPECT_CALL(mock_source, ProvideInput(_, _))
- .Times(kChunks).WillRepeatedly(ClearBuffer());
- resampler.Resample(resampled_destination.get(), max_chunk_size);
-}
-
-// Test flush resets the internal state properly.
-TEST(SincResamplerTest, Flush) {
- MockSource mock_source;
- SincResampler resampler(
- kSampleRateRatio,
- base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
- scoped_array<float> resampled_destination(new float[resampler.ChunkSize()]);
-
- // Fill the resampler with junk data.
- EXPECT_CALL(mock_source, ProvideInput(_, _))
- .Times(1).WillOnce(FillBuffer());
- resampler.Resample(resampled_destination.get(), resampler.ChunkSize() / 2);
- ASSERT_NE(resampled_destination[0], 0);
-
- // Flush and request more data, which should all be zeros now.
- resampler.Flush();
- testing::Mock::VerifyAndClear(&mock_source);
- EXPECT_CALL(mock_source, ProvideInput(_, _))
- .Times(1).WillOnce(ClearBuffer());
- resampler.Resample(resampled_destination.get(), resampler.ChunkSize() / 2);
- for (int i = 0; i < resampler.ChunkSize() / 2; ++i)
- ASSERT_FLOAT_EQ(resampled_destination[i], 0);
-}
-
-// Define platform independent function name for Convolve* tests.
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
-#define CONVOLVE_FUNC Convolve_SSE
-#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
-#define CONVOLVE_FUNC Convolve_NEON
-#endif
-
-// Ensure various optimized Convolve() methods return the same value. Only run
-// this test if other optimized methods exist, otherwise the default Convolve()
-// will be tested by the parameterized SincResampler tests below.
-#if defined(CONVOLVE_FUNC)
-TEST(SincResamplerTest, Convolve) {
- // Initialize a dummy resampler.
- MockSource mock_source;
- SincResampler resampler(
- kSampleRateRatio,
- base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
-
- // The optimized Convolve methods are slightly more precise than Convolve_C(),
- // so comparison must be done using an epsilon.
- static const double kEpsilon = 0.00000005;
-
- // Use a kernel from SincResampler as input and kernel data, this has the
- // benefit of already being properly sized and aligned for Convolve_SSE().
- double result = resampler.Convolve_C(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- double result2 = resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- EXPECT_NEAR(result2, result, kEpsilon);
-
- // Test Convolve() w/ unaligned input pointer.
- result = resampler.Convolve_C(
- resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- result2 = resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- EXPECT_NEAR(result2, result, kEpsilon);
-}
-#endif
-
-// Benchmark for the various Convolve() methods. Make sure to build with
-// branding=Chrome so that DCHECKs are compiled out when benchmarking. Original
-// benchmarks were run with --convolve-iterations=50000000.
-TEST(SincResamplerTest, ConvolveBenchmark) {
- // Initialize a dummy resampler.
- MockSource mock_source;
- SincResampler resampler(
- kSampleRateRatio,
- base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
-
- // Retrieve benchmark iterations from command line.
- int convolve_iterations = 10;
- std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- kConvolveIterations));
- if (!iterations.empty())
- base::StringToInt(iterations, &convolve_iterations);
-
- printf("Benchmarking %d iterations:\n", convolve_iterations);
-
- // Benchmark Convolve_C().
- base::TimeTicks start = base::TimeTicks::HighResNow();
- for (int i = 0; i < convolve_iterations; ++i) {
- resampler.Convolve_C(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_c_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("Convolve_C took %.2fms.\n", total_time_c_ms);
-
-#if defined(CONVOLVE_FUNC)
- // Benchmark with unaligned input pointer.
- start = base::TimeTicks::HighResNow();
- for (int j = 0; j < convolve_iterations; ++j) {
- resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_optimized_unaligned_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(CONVOLVE_FUNC) "(unaligned) took %.2fms; which is %.2fx "
- "faster than Convolve_C.\n", total_time_optimized_unaligned_ms,
- total_time_c_ms / total_time_optimized_unaligned_ms);
-
- // Benchmark with aligned input pointer.
- start = base::TimeTicks::HighResNow();
- for (int j = 0; j < convolve_iterations; ++j) {
- resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_optimized_aligned_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(CONVOLVE_FUNC) " (aligned) took %.2fms; which is %.2fx "
- "faster than Convolve_C and %.2fx faster than "
- STRINGIZE(CONVOLVE_FUNC) " (unaligned).\n",
- total_time_optimized_aligned_ms,
- total_time_c_ms / total_time_optimized_aligned_ms,
- total_time_optimized_unaligned_ms / total_time_optimized_aligned_ms);
-#endif
-}
-
-#undef CONVOLVE_FUNC
-
-// Fake audio source for testing the resampler. Generates a sinusoidal linear
-// chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the
-// resampler for the specific sample rate conversion being used.
-class SinusoidalLinearChirpSource {
- public:
- SinusoidalLinearChirpSource(int sample_rate, int samples,
- double max_frequency)
- : sample_rate_(sample_rate),
- total_samples_(samples),
- max_frequency_(max_frequency),
- current_index_(0) {
- // Chirp rate.
- double duration = static_cast<double>(total_samples_) / sample_rate_;
- k_ = (max_frequency_ - kMinFrequency) / duration;
- }
-
- virtual ~SinusoidalLinearChirpSource() {}
-
- void ProvideInput(float* destination, int frames) {
- for (int i = 0; i < frames; ++i, ++current_index_) {
- // Filter out frequencies higher than Nyquist.
- if (Frequency(current_index_) > 0.5 * sample_rate_) {
- destination[i] = 0;
- } else {
- // Calculate time in seconds.
- double t = static_cast<double>(current_index_) / sample_rate_;
-
- // Sinusoidal linear chirp.
- destination[i] = sin(2 * M_PI * (kMinFrequency * t + (k_ / 2) * t * t));
- }
- }
- }
-
- double Frequency(int position) {
- return kMinFrequency + position * (max_frequency_ - kMinFrequency)
- / total_samples_;
- }
-
- private:
- enum {
- kMinFrequency = 5
- };
-
- double sample_rate_;
- int total_samples_;
- double max_frequency_;
- double k_;
- int current_index_;
-
- DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource);
-};
-
-typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData;
-class SincResamplerTest
- : public testing::TestWithParam<SincResamplerTestData> {
- public:
- SincResamplerTest()
- : input_rate_(std::tr1::get<0>(GetParam())),
- output_rate_(std::tr1::get<1>(GetParam())),
- rms_error_(std::tr1::get<2>(GetParam())),
- low_freq_error_(std::tr1::get<3>(GetParam())) {
- }
-
- virtual ~SincResamplerTest() {}
-
- protected:
- int input_rate_;
- int output_rate_;
- double rms_error_;
- double low_freq_error_;
-};
-
-// Tests resampling using a given input and output sample rate.
-TEST_P(SincResamplerTest, Resample) {
- // Make comparisons using one second of data.
- static const double kTestDurationSecs = 1;
- int input_samples = kTestDurationSecs * input_rate_;
- int output_samples = kTestDurationSecs * output_rate_;
-
- // Nyquist frequency for the input sampling rate.
- double input_nyquist_freq = 0.5 * input_rate_;
-
- // Source for data to be resampled.
- SinusoidalLinearChirpSource resampler_source(
- input_rate_, input_samples, input_nyquist_freq);
-
- SincResampler resampler(
- input_rate_ / static_cast<double>(output_rate_),
- base::Bind(&SinusoidalLinearChirpSource::ProvideInput,
- base::Unretained(&resampler_source)));
-
- // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
- // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
- scoped_array<float> resampled_destination(new float[output_samples]);
- scoped_array<float> pure_destination(new float[output_samples]);
-
- // Generate resampled signal.
- resampler.Resample(resampled_destination.get(), output_samples);
-
- // Generate pure signal.
- SinusoidalLinearChirpSource pure_source(
- output_rate_, output_samples, input_nyquist_freq);
- pure_source.ProvideInput(pure_destination.get(), output_samples);
-
- // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
- // we refer to as low and high.
- static const double kLowFrequencyNyquistRange = 0.7;
- static const double kHighFrequencyNyquistRange = 0.9;
-
- // Calculate Root-Mean-Square-Error and maximum error for the resampling.
- double sum_of_squares = 0;
- double low_freq_max_error = 0;
- double high_freq_max_error = 0;
- int minimum_rate = std::min(input_rate_, output_rate_);
- double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
- double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
- for (int i = 0; i < output_samples; ++i) {
- double error = fabs(resampled_destination[i] - pure_destination[i]);
-
- if (pure_source.Frequency(i) < low_frequency_range) {
- if (error > low_freq_max_error)
- low_freq_max_error = error;
- } else if (pure_source.Frequency(i) < high_frequency_range) {
- if (error > high_freq_max_error)
- high_freq_max_error = error;
- }
- // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
-
- sum_of_squares += error * error;
- }
-
- double rms_error = sqrt(sum_of_squares / output_samples);
-
- // Convert each error to dbFS.
- #define DBFS(x) 20 * log10(x)
- rms_error = DBFS(rms_error);
- low_freq_max_error = DBFS(low_freq_max_error);
- high_freq_max_error = DBFS(high_freq_max_error);
-
- EXPECT_LE(rms_error, rms_error_);
- EXPECT_LE(low_freq_max_error, low_freq_error_);
-
- // All conversions currently have a high frequency error around -6 dbFS.
- static const double kHighFrequencyMaxError = -6.02;
- EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
-}
-
-// Almost all conversions have an RMS error of around -14 dbFS.
-static const double kResamplingRMSError = -14.58;
-
-// Thresholds chosen arbitrarily based on what each resampling reported during
-// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
-INSTANTIATE_TEST_CASE_P(
- SincResamplerTest, SincResamplerTest, testing::Values(
- // To 44.1kHz
- std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
- std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
- std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
- std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
- std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
- std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53),
- std::tr1::make_tuple(48000, 44100, -15.01, -64.04),
- std::tr1::make_tuple(96000, 44100, -18.49, -25.51),
- std::tr1::make_tuple(192000, 44100, -20.50, -13.31),
-
- // To 48kHz
- std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
- std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
- std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96),
- std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
- std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
- std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
- std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
- std::tr1::make_tuple(96000, 48000, -18.40, -28.44),
- std::tr1::make_tuple(192000, 48000, -20.43, -14.11),
-
- // To 96kHz
- std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
- std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
- std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
- std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
- std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
- std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
- std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
- std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
- std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
-
- // To 192kHz
- std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
- std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
- std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
- std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
- std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
- std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
- std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
- std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
- std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
-
-} // namespace media
diff --git a/src/media/base/stream_parser.cc b/src/media/base/stream_parser.cc
deleted file mode 100644
index 1240919..0000000
--- a/src/media/base/stream_parser.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/stream_parser.h"
-
-namespace media {
-
-StreamParser::StreamParser() {}
-
-StreamParser::~StreamParser() {}
-
-} // namespace media
diff --git a/src/media/base/stream_parser.h b/src/media/base/stream_parser.h
deleted file mode 100644
index 5c5e6e6..0000000
--- a/src/media/base/stream_parser.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_STREAM_PARSER_H_
-#define MEDIA_BASE_STREAM_PARSER_H_
-
-#include <deque>
-#include <string>
-
-#include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-
-namespace media {
-
-class AudioDecoderConfig;
-class StreamParserBuffer;
-class VideoDecoderConfig;
-
-// Abstract interface for parsing media byte streams.
-class MEDIA_EXPORT StreamParser {
- public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
-
- StreamParser();
- virtual ~StreamParser();
-
- // Indicates completion of parser initialization.
- // First parameter - Indicates initialization success. Set to true if
- // initialization was successful. False if an error
- // occurred.
- // Second parameter - Indicates the stream duration. Only contains a valid
- // value if the first parameter is true.
- typedef base::Callback<void(bool, base::TimeDelta)> InitCB;
-
- // Indicates when new stream configurations have been parsed.
- // First parameter - The new audio configuration. If the config is not valid
- // then it means that there isn't an audio stream.
- // Second parameter - The new video configuration. If the config is not valid
- // then it means that there isn't an audio stream.
- // Return value - True if the new configurations are accepted.
- // False if the new configurations are not supported
- // and indicates that a parsing error should be signalled.
- typedef base::Callback<bool(const AudioDecoderConfig&,
- const VideoDecoderConfig&)> NewConfigCB;
-
- // New stream buffers have been parsed.
- // First parameter - A queue of newly parsed buffers.
- // Return value - True indicates that the buffers are accepted.
- // False if something was wrong with the buffers and a parsing
- // error should be signalled.
- typedef base::Callback<bool(const BufferQueue&)> NewBuffersCB;
-
- // Signals the beginning of a new media segment.
- // First parameter - The earliest timestamp of all the streams in the segment.
- typedef base::Callback<void(base::TimeDelta)> NewMediaSegmentCB;
-
- // A new potentially encrypted stream has been parsed.
- // First parameter - The type of the initialization data associated with the
- // stream.
- // Second parameter - The initialization data associated with the stream.
- // Third parameter - Number of bytes of the initialization data.
- // Return value - True indicates that the initialization data is accepted.
- // False if something was wrong with the initialization data
- // and a parsing error should be signalled.
- typedef base::Callback<bool(const std::string&,
- scoped_array<uint8>, int)> NeedKeyCB;
-
- // Initialize the parser with necessary callbacks. Must be called before any
- // data is passed to Parse(). |init_cb| will be called once enough data has
- // been parsed to determine the initial stream configurations, presentation
- // start time, and duration.
- virtual void Init(const InitCB& init_cb,
- const NewConfigCB& config_cb,
- const NewBuffersCB& audio_cb,
- const NewBuffersCB& video_cb,
- const NeedKeyCB& need_key_cb,
- const NewMediaSegmentCB& new_segment_cb,
- const base::Closure& end_of_segment_cb,
- const LogCB& log_cb) = 0;
-
- // Called when a seek occurs. This flushes the current parser state
- // and puts the parser in a state where it can receive data for the new seek
- // point.
- virtual void Flush() = 0;
-
- // Called when there is new data to parse.
- //
- // Returns true if the parse succeeds.
- virtual bool Parse(const uint8* buf, int size) = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(StreamParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_STREAM_PARSER_H_
diff --git a/src/media/base/stream_parser_buffer.cc b/src/media/base/stream_parser_buffer.cc
deleted file mode 100644
index 6a6c4a5..0000000
--- a/src/media/base/stream_parser_buffer.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/stream_parser_buffer.h"
-
-#include "base/logging.h"
-#include "media/base/shell_buffer_factory.h"
-
-namespace media {
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-scoped_refptr<StreamParserBuffer> StreamParserBuffer::CreateEOSBuffer() {
- return make_scoped_refptr(new StreamParserBuffer(NULL, 0, false));
-}
-
-scoped_refptr<StreamParserBuffer> StreamParserBuffer::CopyFrom(
- const uint8* data,
- int data_size,
- bool is_keyframe) {
- if (!data || data_size == 0) {
- return CreateEOSBuffer();
- }
- DCHECK(data);
- DCHECK_GT(data_size, 0);
-
- uint8* buffer_bytes = ShellBufferFactory::Instance()->AllocateNow(data_size);
- if (!buffer_bytes) {
- return NULL;
- }
- // copy the data over to the resuable buffer area
- memcpy(buffer_bytes, data, data_size);
- return make_scoped_refptr(
- new StreamParserBuffer(buffer_bytes,
- data_size,
- is_keyframe));
-}
-#else
-scoped_refptr<StreamParserBuffer> StreamParserBuffer::CopyFrom(
- const uint8* data, int data_size, bool is_keyframe) {
- return make_scoped_refptr(
- new StreamParserBuffer(data, data_size, is_keyframe));
-}
-#endif
-
-base::TimeDelta StreamParserBuffer::GetDecodeTimestamp() const {
- if (decode_timestamp_ == kNoTimestamp())
- return GetTimestamp();
- return decode_timestamp_;
-}
-
-void StreamParserBuffer::SetDecodeTimestamp(const base::TimeDelta& timestamp) {
- decode_timestamp_ = timestamp;
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-StreamParserBuffer::StreamParserBuffer(uint8* data,
- int data_size,
- bool is_keyframe)
- : DecoderBuffer(data, data_size, is_keyframe),
- decode_timestamp_(kNoTimestamp()),
- config_id_(kInvalidConfigId) {
- SetDuration(kNoTimestamp());
-}
-#else
-StreamParserBuffer::StreamParserBuffer(const uint8* data,
- int data_size,
- bool is_keyframe)
- : DecoderBuffer(data, data_size, is_keyframe),
- decode_timestamp_(kNoTimestamp()),
- config_id_(kInvalidConfigId) {
- SetDuration(kNoTimestamp());
-}
-#endif
-
-StreamParserBuffer::~StreamParserBuffer() {
-}
-
-int StreamParserBuffer::GetConfigId() const {
- return config_id_;
-}
-
-void StreamParserBuffer::SetConfigId(int config_id) {
- config_id_ = config_id;
-}
-
-} // namespace media
diff --git a/src/media/base/stream_parser_buffer.h b/src/media/base/stream_parser_buffer.h
deleted file mode 100644
index a5471d9..0000000
--- a/src/media/base/stream_parser_buffer.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_STREAM_PARSER_BUFFER_H_
-#define MEDIA_BASE_STREAM_PARSER_BUFFER_H_
-
-#include "media/base/decoder_buffer.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer {
- public:
- // Value used to signal an invalid decoder config ID.
- enum { kInvalidConfigId = -1 };
-
- static scoped_refptr<StreamParserBuffer> CreateEOSBuffer();
- static scoped_refptr<StreamParserBuffer> CopyFrom(
- const uint8* data, int data_size, bool is_keyframe);
-
- // Decode timestamp. If not explicitly set, or set to kNoTimestamp(), the
- // value will be taken from the normal timestamp.
- base::TimeDelta GetDecodeTimestamp() const;
- void SetDecodeTimestamp(const base::TimeDelta& timestamp);
-
- // Gets/sets the ID of the decoder config associated with this
- // buffer.
- int GetConfigId() const;
- void SetConfigId(int config_id);
-
- private:
-#if defined(__LB_SHELL__) || defined(COBALT)
- StreamParserBuffer(uint8* data, int data_size, bool is_keyframe);
-#else
- StreamParserBuffer(const uint8* data, int data_size, bool is_keyframe);
-#endif
- virtual ~StreamParserBuffer();
-
- base::TimeDelta decode_timestamp_;
- int config_id_;
- DISALLOW_COPY_AND_ASSIGN(StreamParserBuffer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_STREAM_PARSER_BUFFER_H_
diff --git a/src/media/base/test_data_util.cc b/src/media/base/test_data_util.cc
deleted file mode 100644
index 134655d..0000000
--- a/src/media/base/test_data_util.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/test_data_util.h"
-
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "media/base/decoder_buffer.h"
-
-namespace media {
-
-FilePath GetTestDataFilePath(const std::string& name) {
- FilePath file_path;
- CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
-
- file_path = file_path.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .AppendASCII(name);
- return file_path;
-}
-
-scoped_refptr<DecoderBuffer> ReadTestDataFile(const std::string& name) {
- FilePath file_path;
- CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
-
- file_path = file_path.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .AppendASCII(name);
-
- int64 tmp = 0;
- CHECK(file_util::GetFileSize(file_path, &tmp))
- << "Failed to get file size for '" << name << "'";
-
- int file_size = static_cast<int>(tmp);
-
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(file_size));
- CHECK_EQ(file_size, file_util::ReadFile(
- file_path, reinterpret_cast<char*>(buffer->GetWritableData()), file_size))
- << "Failed to read '" << name << "'";
-
- return buffer;
-}
-
-} // namespace media
diff --git a/src/media/base/test_data_util.h b/src/media/base/test_data_util.h
deleted file mode 100644
index 062bbda..0000000
--- a/src/media/base/test_data_util.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_TEST_DATA_UTIL_H_
-#define MEDIA_BASE_TEST_DATA_UTIL_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/file_path.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-
-namespace media {
-
-class DecoderBuffer;
-
-// Returns a file path for a file in the media/test/data directory.
-FilePath GetTestDataFilePath(const std::string& name);
-
-// Reads a test file from media/test/data directory and stores it in
-// a DecoderBuffer. Use DecoderBuffer vs DataBuffer to ensure no matter
-// what a test does, it's safe to use FFmpeg methods.
-//
-// |name| - The name of the file.
-// |buffer| - The contents of the file.
-scoped_refptr<DecoderBuffer> ReadTestDataFile(const std::string& name);
-
-} // namespace media
-
-#endif // MEDIA_BASE_TEST_DATA_UTIL_H_
diff --git a/src/media/base/test_helpers.cc b/src/media/base/test_helpers.cc
deleted file mode 100644
index 862c9d4..0000000
--- a/src/media/base/test_helpers.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/test_helpers.h"
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/test/test_timeouts.h"
-#include "base/timer.h"
-#include "media/base/bind_to_loop.h"
-
-using ::testing::_;
-using ::testing::StrictMock;
-
-namespace media {
-
-// Utility mock for testing methods expecting Closures and PipelineStatusCBs.
-class MockCallback : public base::RefCountedThreadSafe<MockCallback> {
- public:
- MockCallback();
- MOCK_METHOD0(Run, void());
- MOCK_METHOD1(RunWithStatus, void(PipelineStatus));
-
- protected:
- friend class base::RefCountedThreadSafe<MockCallback>;
- virtual ~MockCallback();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockCallback);
-};
-
-MockCallback::MockCallback() {}
-MockCallback::~MockCallback() {}
-
-base::Closure NewExpectedClosure() {
- StrictMock<MockCallback>* callback = new StrictMock<MockCallback>();
- EXPECT_CALL(*callback, Run());
- return base::Bind(&MockCallback::Run, callback);
-}
-
-PipelineStatusCB NewExpectedStatusCB(PipelineStatus status) {
- StrictMock<MockCallback>* callback = new StrictMock<MockCallback>();
- EXPECT_CALL(*callback, RunWithStatus(status));
- return base::Bind(&MockCallback::RunWithStatus, callback);
-}
-
-WaitableMessageLoopEvent::WaitableMessageLoopEvent()
- : message_loop_(MessageLoop::current()),
- signaled_(false),
- status_(PIPELINE_OK) {
- DCHECK(message_loop_);
-}
-
-WaitableMessageLoopEvent::~WaitableMessageLoopEvent() {}
-
-base::Closure WaitableMessageLoopEvent::GetClosure() {
- DCHECK_EQ(message_loop_, MessageLoop::current());
- return BindToLoop(message_loop_->message_loop_proxy(), base::Bind(
- &WaitableMessageLoopEvent::OnCallback, base::Unretained(this),
- PIPELINE_OK));
-}
-
-PipelineStatusCB WaitableMessageLoopEvent::GetPipelineStatusCB() {
- DCHECK_EQ(message_loop_, MessageLoop::current());
- return BindToLoop(message_loop_->message_loop_proxy(), base::Bind(
- &WaitableMessageLoopEvent::OnCallback, base::Unretained(this)));
-}
-
-void WaitableMessageLoopEvent::RunAndWait() {
- RunAndWaitForStatus(PIPELINE_OK);
-}
-
-void WaitableMessageLoopEvent::RunAndWaitForStatus(PipelineStatus expected) {
- DCHECK_EQ(message_loop_, MessageLoop::current());
- base::Timer timer(false, false);
- timer.Start(FROM_HERE, TestTimeouts::action_timeout(), base::Bind(
- &WaitableMessageLoopEvent::OnTimeout, base::Unretained(this)));
-
- DCHECK(!signaled_) << "Already signaled";
- message_loop_->Run();
- EXPECT_TRUE(signaled_);
- EXPECT_EQ(expected, status_);
-}
-
-void WaitableMessageLoopEvent::OnCallback(PipelineStatus status) {
- DCHECK_EQ(message_loop_, MessageLoop::current());
- signaled_ = true;
- status_ = status;
- message_loop_->QuitWhenIdle();
-}
-
-void WaitableMessageLoopEvent::OnTimeout() {
- DCHECK_EQ(message_loop_, MessageLoop::current());
- ADD_FAILURE() << "Timed out waiting for message loop to quit";
- message_loop_->QuitWhenIdle();
-}
-
-} // namespace media
diff --git a/src/media/base/test_helpers.h b/src/media/base/test_helpers.h
deleted file mode 100644
index 1c38694..0000000
--- a/src/media/base/test_helpers.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_TEST_HELPERS_H_
-#define MEDIA_BASE_TEST_HELPERS_H_
-
-#include "base/callback.h"
-#include "media/base/pipeline_status.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-class MessageLoop;
-
-namespace media {
-
-// Return a callback that expects to be run once.
-base::Closure NewExpectedClosure();
-PipelineStatusCB NewExpectedStatusCB(PipelineStatus status);
-
-// Helper class for running a message loop until a callback has run. Useful for
-// testing classes that run on more than a single thread.
-//
-// Events are intended for single use and cannot be reset.
-class WaitableMessageLoopEvent {
- public:
- WaitableMessageLoopEvent();
- ~WaitableMessageLoopEvent();
-
- // Returns a thread-safe closure that will signal |this| when executed.
- base::Closure GetClosure();
- PipelineStatusCB GetPipelineStatusCB();
-
- // Runs the current message loop until |this| has been signaled.
- //
- // Fails the test if the timeout is reached.
- void RunAndWait();
-
- // Runs the current message loop until |this| has been signaled and asserts
- // that the |expected| status was received.
- //
- // Fails the test if the timeout is reached.
- void RunAndWaitForStatus(PipelineStatus expected);
-
- private:
- void OnCallback(PipelineStatus status);
- void OnTimeout();
-
- MessageLoop* message_loop_;
- bool signaled_;
- PipelineStatus status_;
-
- DISALLOW_COPY_AND_ASSIGN(WaitableMessageLoopEvent);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_TEST_HELPERS_H_
diff --git a/src/media/base/vector_math.cc b/src/media/base/vector_math.cc
deleted file mode 100644
index edd95cd..0000000
--- a/src/media/base/vector_math.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/vector_math.h"
-#include "media/base/vector_math_testing.h"
-
-#include "base/cpu.h"
-#include "base/logging.h"
-#include "build/build_config.h"
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
-#include <xmmintrin.h>
-#endif
-
-namespace media {
-namespace vector_math {
-
-void FMAC(const float src[], float scale, int len, float dest[]) {
- // Ensure |src| and |dest| are 16-byte aligned.
- DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(src) & (kRequiredAlignment - 1));
- DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(dest) & (kRequiredAlignment - 1));
-
- // Rely on function level static initialization to keep VectorFMACProc
- // selection thread safe.
- typedef void (*VectorFMACProc)(const float src[], float scale, int len,
- float dest[]);
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
- static const VectorFMACProc kVectorFMACProc =
- base::CPU().has_sse() ? FMAC_SSE : FMAC_C;
-#else
- static const VectorFMACProc kVectorFMACProc = FMAC_C;
-#endif
-
- return kVectorFMACProc(src, scale, len, dest);
-}
-
-void FMAC_C(const float src[], float scale, int len, float dest[]) {
- for (int i = 0; i < len; ++i)
- dest[i] += src[i] * scale;
-}
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
-void FMAC_SSE(const float src[], float scale, int len, float dest[]) {
- __m128 m_scale = _mm_set_ps1(scale);
- int rem = len % 4;
- for (int i = 0; i < len - rem; i += 4) {
- _mm_store_ps(dest + i, _mm_add_ps(_mm_load_ps(dest + i),
- _mm_mul_ps(_mm_load_ps(src + i), m_scale)));
- }
-
- // Handle any remaining values that wouldn't fit in an SSE pass.
- if (rem)
- FMAC_C(src + len - rem, scale, rem, dest + len - rem);
-}
-#endif
-
-} // namespace vector_math
-} // namespace media
diff --git a/src/media/base/vector_math.h b/src/media/base/vector_math.h
deleted file mode 100644
index 10c3039..0000000
--- a/src/media/base/vector_math.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VECTOR_MATH_H_
-#define MEDIA_BASE_VECTOR_MATH_H_
-
-#include "media/base/media_export.h"
-
-namespace media {
-namespace vector_math {
-
-// Required alignment for inputs and outputs to all vector math functions
-enum { kRequiredAlignment = 16 };
-
-// Multiply each element of |src| (up to |len|) by |scale| and add to |dest|.
-// |src| and |dest| must be aligned by kRequiredAlignment.
-MEDIA_EXPORT void FMAC(const float src[], float scale, int len, float dest[]);
-
-} // namespace vector_math
-} // namespace media
-
-#endif // MEDIA_BASE_VECTOR_MATH_H_
diff --git a/src/media/base/vector_math_testing.h b/src/media/base/vector_math_testing.h
deleted file mode 100644
index d364b74..0000000
--- a/src/media/base/vector_math_testing.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VECTOR_MATH_TESTING_H_
-#define MEDIA_BASE_VECTOR_MATH_TESTING_H_
-
-#include "media/base/media_export.h"
-
-namespace media {
-namespace vector_math {
-
-// Optimized versions of FMAC() function exposed for testing. See vector_math.h
-// for details.
-MEDIA_EXPORT void FMAC_C(const float src[], float scale, int len, float dest[]);
-MEDIA_EXPORT void FMAC_SSE(const float src[], float scale, int len,
- float dest[]);
-
-} // namespace vector_math
-} // namespace media
-
-#endif // MEDIA_BASE_VECTOR_MATH_TESTING_H_
diff --git a/src/media/base/vector_math_unittest.cc b/src/media/base/vector_math_unittest.cc
deleted file mode 100644
index 153378e..0000000
--- a/src/media/base/vector_math_unittest.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// MSVC++ requires this to be set before any other includes to get M_PI.
-#define _USE_MATH_DEFINES
-#include <cmath>
-
-#include "base/command_line.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/string_number_conversions.h"
-#include "base/time.h"
-#include "media/base/vector_math.h"
-#include "media/base/vector_math_testing.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::TimeTicks;
-using std::fill;
-
-// Command line switch for runtime adjustment of benchmark iterations.
-static const char kBenchmarkIterations[] = "vector-math-iterations";
-static const int kDefaultIterations = 10;
-
-// Default test values.
-static const float kScale = 0.5;
-static const float kInputFillValue = 1.0;
-static const float kOutputFillValue = 3.0;
-
-namespace media {
-
-class VectorMathTest : public testing::Test {
- public:
- static const int kVectorSize = 8192;
-
- VectorMathTest() {
- // Initialize input and output vectors.
- input_vector.reset(static_cast<float*>(base::AlignedAlloc(
- sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
- output_vector.reset(static_cast<float*>(base::AlignedAlloc(
- sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
- }
-
- void FillTestVectors(float input, float output) {
- // Setup input and output vectors.
- fill(input_vector.get(), input_vector.get() + kVectorSize, input);
- fill(output_vector.get(), output_vector.get() + kVectorSize, output);
- }
-
- void VerifyOutput(float value) {
- for (int i = 0; i < kVectorSize; ++i)
- ASSERT_FLOAT_EQ(output_vector.get()[i], value);
- }
-
- int BenchmarkIterations() {
- int vector_math_iterations = kDefaultIterations;
- std::string iterations(
- CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- kBenchmarkIterations));
- if (!iterations.empty())
- base::StringToInt(iterations, &vector_math_iterations);
- return vector_math_iterations;
- }
-
- protected:
- int benchmark_iterations;
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_vector;
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> output_vector;
-
- DISALLOW_COPY_AND_ASSIGN(VectorMathTest);
-};
-
-// Ensure each optimized vector_math::FMAC() method returns the same value.
-TEST_F(VectorMathTest, FMAC) {
- static const float kResult = kInputFillValue * kScale + kOutputFillValue;
-
- {
- SCOPED_TRACE("FMAC");
- FillTestVectors(kInputFillValue, kOutputFillValue);
- vector_math::FMAC(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- VerifyOutput(kResult);
- }
-
- {
- SCOPED_TRACE("FMAC_C");
- FillTestVectors(kInputFillValue, kOutputFillValue);
- vector_math::FMAC_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- VerifyOutput(kResult);
- }
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
- {
- SCOPED_TRACE("FMAC_SSE");
- FillTestVectors(kInputFillValue, kOutputFillValue);
- vector_math::FMAC_SSE(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- VerifyOutput(kResult);
- }
-#endif
-}
-
-// Benchmark for each optimized vector_math::FMAC() method. Original benchmarks
-// were run with --vector-fmac-iterations=200000.
-TEST_F(VectorMathTest, FMACBenchmark) {
- static const int kBenchmarkIterations = BenchmarkIterations();
-
- printf("Benchmarking %d iterations:\n", kBenchmarkIterations);
-
- // Benchmark FMAC_C().
- FillTestVectors(kInputFillValue, kOutputFillValue);
- TimeTicks start = TimeTicks::HighResNow();
- for (int i = 0; i < kBenchmarkIterations; ++i) {
- vector_math::FMAC_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_c_ms = (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FMAC_C took %.2fms.\n", total_time_c_ms);
-
-#if defined(ARCH_CPU_X86_FAMILY) && defined(__SSE__)
- // Benchmark FMAC_SSE() with unaligned size.
- ASSERT_NE((kVectorSize - 1) % (vector_math::kRequiredAlignment /
- sizeof(float)), 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMAC_SSE(
- input_vector.get(), kScale, kVectorSize - 1, output_vector.get());
- }
- double total_time_sse_unaligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FMAC_SSE (unaligned size) took %.2fms; which is %.2fx faster than"
- " FMAC_C.\n", total_time_sse_unaligned_ms,
- total_time_c_ms / total_time_sse_unaligned_ms);
-
- // Benchmark FMAC_SSE() with aligned size.
- ASSERT_EQ(kVectorSize % (vector_math::kRequiredAlignment / sizeof(float)),
- 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMAC_SSE(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_sse_aligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FMAC_SSE (aligned size) took %.2fms; which is %.2fx faster than"
- " FMAC_C and %.2fx faster than FMAC_SSE (unaligned size).\n",
- total_time_sse_aligned_ms, total_time_c_ms / total_time_sse_aligned_ms,
- total_time_sse_unaligned_ms / total_time_sse_aligned_ms);
-#endif
-}
-
-} // namespace media
diff --git a/src/media/base/video_decoder.cc b/src/media/base/video_decoder.cc
deleted file mode 100644
index 4c7c4b7..0000000
--- a/src/media/base/video_decoder.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_decoder.h"
-
-namespace media {
-
-VideoDecoder::VideoDecoder() {}
-
-VideoDecoder::~VideoDecoder() {}
-
-bool VideoDecoder::HasAlpha() const {
- return false;
-}
-
-} // namespace media
diff --git a/src/media/base/video_decoder.h b/src/media/base/video_decoder.h
deleted file mode 100644
index fc821e7..0000000
--- a/src/media/base/video_decoder.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_DECODER_H_
-#define MEDIA_BASE_VIDEO_DECODER_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/media_export.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-class DemuxerStream;
-class VideoFrame;
-
-class MEDIA_EXPORT VideoDecoder
- : public base::RefCountedThreadSafe<VideoDecoder> {
- public:
- // Status codes for read operations on VideoDecoder.
- enum Status {
- kOk, // Everything went as planned.
- kDecodeError, // Decoding error happened.
- kDecryptError // Decrypting error happened.
- };
-
- // Initializes a VideoDecoder with the given DemuxerStream, executing the
- // |status_cb| upon completion.
- // |statistics_cb| is used to update the global pipeline statistics.
- // Note: No VideoDecoder calls should be made before |status_cb| is executed.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) = 0;
-
- // Requests a frame to be decoded. The status of the decoder and decoded frame
- // are returned via the provided callback. Only one read may be in flight at
- // any given time.
- //
- // Implementations guarantee that the callback will not be called from within
- // this method.
- //
- // If the returned status is not kOk, some error has occurred in the video
- // decoder. In this case, the returned frame should always be NULL.
- //
- // Otherwise, the video decoder is in good shape. In this case, Non-NULL
- // frames contain decoded video data or may indicate the end of the stream.
- // NULL video frames indicate an aborted read. This can happen if the
- // DemuxerStream gets flushed and doesn't have any more data to return.
- typedef base::Callback<void(Status, const scoped_refptr<VideoFrame>&)> ReadCB;
- virtual void Read(const ReadCB& read_cb) = 0;
-
- // Resets decoder state, fulfilling all pending ReadCB and dropping extra
- // queued decoded data. After this call, the decoder is back to an initialized
- // clean state.
- // Note: No VideoDecoder calls should be made before |closure| is executed.
- virtual void Reset(const base::Closure& closure) = 0;
-
- // Stops decoder, fires any pending callbacks and sets the decoder to an
- // uninitialized state. A VideoDecoder cannot be re-initialized after it has
- // been stopped.
- // Note that if Initialize() has been called, Stop() must be called and
- // complete before deleting the decoder.
- virtual void Stop(const base::Closure& closure) = 0;
-
- // Returns true if the output format has an alpha channel. Most formats do not
- // have alpha so the default is false. Override and return true for decoders
- // that return formats with an alpha channel.
- virtual bool HasAlpha() const;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // Notify the decoder that we are going to overflow if the decoder keeps
- // working at the current speed. The decoder should try to decode faster or
- // even drop frames if possible.
- virtual void NearlyUnderflow() {}
- // Notify the decoder that we have enough frames cached and no longer need to
- // sacrifice quality for speed. Note that it doesn't mean that the decoder no
- // longer needs to decode more frames. The decoding of frames is controlled
- // by Read() and the decoder should keep decoding when there is pending read.
- virtual void HaveEnoughFrames() {}
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- protected:
- friend class base::RefCountedThreadSafe<VideoDecoder>;
- virtual ~VideoDecoder();
- VideoDecoder();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VideoDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_DECODER_H_
diff --git a/src/media/base/video_decoder_config.cc b/src/media/base/video_decoder_config.cc
deleted file mode 100644
index 7d0055f..0000000
--- a/src/media/base/video_decoder_config.cc
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_decoder_config.h"
-
-#include "base/logging.h"
-#include "base/metrics/histogram.h"
-#include "media/base/video_types.h"
-
-namespace media {
-
-VideoDecoderConfig::VideoDecoderConfig()
- : codec_(kUnknownVideoCodec),
- profile_(VIDEO_CODEC_PROFILE_UNKNOWN),
- format_(VideoFrame::INVALID),
- extra_data_size_(0),
- is_encrypted_(false),
- color_space_(COLOR_SPACE_UNSPECIFIED) {}
-
-VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec,
- VideoCodecProfile profile,
- VideoFrame::Format format,
- ColorSpace color_space,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted) {
- Initialize(codec, profile, format, color_space, coded_size, visible_rect,
- natural_size, extra_data, extra_data_size, is_encrypted, true);
-}
-
-VideoDecoderConfig::~VideoDecoderConfig() {}
-
-// Some videos just want to watch the world burn, with a height of 0; cap the
-// "infinite" aspect ratio resulting.
-static const int kInfiniteRatio = 99999;
-
-// Common aspect ratios (multiplied by 100 and truncated) used for histogramming
-// video sizes. These were taken on 20111103 from
-// http://wikipedia.org/wiki/Aspect_ratio_(image)#Previous_and_currently_used_aspect_ratios
-static const int kCommonAspectRatios100[] = {
- 100, 115, 133, 137, 143, 150, 155, 160, 166, 175, 177, 185, 200, 210, 220,
- 221, 235, 237, 240, 255, 259, 266, 276, 293, 400, 1200, kInfiniteRatio,
-};
-
-template<class T> // T has int width() & height() methods.
-static void UmaHistogramAspectRatio(const char* name, const T& size) {
- UMA_HISTOGRAM_CUSTOM_ENUMERATION(
- name,
- // Intentionally use integer division to truncate the result.
- size.height() ? (size.width() * 100) / size.height() : kInfiniteRatio,
- base::CustomHistogram::ArrayToCustomRanges(
- kCommonAspectRatios100, arraysize(kCommonAspectRatios100)));
-}
-
-void VideoDecoderConfig::Initialize(VideoCodec codec,
- VideoCodecProfile profile,
- VideoFrame::Format format,
- ColorSpace color_space,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted,
- bool record_stats) {
- CHECK((extra_data_size != 0) == (extra_data != NULL));
-
- if (record_stats) {
- UMA_HISTOGRAM_ENUMERATION("Media.VideoCodec", codec, kVideoCodecMax + 1);
- // Drop UNKNOWN because U_H_E() uses one bucket for all values less than 1.
- if (profile >= 0) {
- UMA_HISTOGRAM_ENUMERATION("Media.VideoCodecProfile", profile,
- VIDEO_CODEC_PROFILE_MAX + 1);
- }
- UMA_HISTOGRAM_COUNTS_10000("Media.VideoCodedWidth", coded_size.width());
- UmaHistogramAspectRatio("Media.VideoCodedAspectRatio", coded_size);
- UMA_HISTOGRAM_COUNTS_10000("Media.VideoVisibleWidth", visible_rect.width());
- UmaHistogramAspectRatio("Media.VideoVisibleAspectRatio", visible_rect);
- }
-
- codec_ = codec;
- profile_ = profile;
- format_ = format;
- color_space_ = color_space;
- coded_size_ = coded_size;
- visible_rect_ = visible_rect;
- natural_size_ = natural_size;
- extra_data_size_ = extra_data_size;
-
- if (extra_data_size_ > 0) {
- extra_data_.reset(new uint8[extra_data_size_]);
- memcpy(extra_data_.get(), extra_data, extra_data_size_);
- } else {
- extra_data_.reset();
- }
-
- is_encrypted_ = is_encrypted;
-
- switch (color_space) {
- case COLOR_SPACE_JPEG:
- webm_color_metadata_.color_space = gfx::ColorSpace::CreateJpeg();
- break;
- case COLOR_SPACE_HD_REC709:
- webm_color_metadata_.color_space = gfx::ColorSpace::CreateREC709();
- break;
- case COLOR_SPACE_SD_REC601:
- webm_color_metadata_.color_space = gfx::ColorSpace::CreateREC601();
- break;
- case COLOR_SPACE_UNSPECIFIED:
- break;
- default:
- NOTREACHED();
- break;
- }
-}
-
-void VideoDecoderConfig::CopyFrom(const VideoDecoderConfig& video_config) {
- Initialize(video_config.codec(), video_config.profile(),
- video_config.format(), video_config.color_space_,
- video_config.coded_size(), video_config.visible_rect(),
- video_config.natural_size(), video_config.extra_data(),
- video_config.extra_data_size(), video_config.is_encrypted(),
- false);
- webm_color_metadata_ = video_config.webm_color_metadata_;
-}
-
-bool VideoDecoderConfig::IsValidConfig() const {
- return codec_ != kUnknownVideoCodec &&
- natural_size_.width() > 0 &&
- natural_size_.height() > 0 &&
- VideoFrame::IsValidConfig(format_, coded_size_, visible_rect_,
- natural_size_);
-}
-
-bool VideoDecoderConfig::Matches(const VideoDecoderConfig& config) const {
- return ((codec() == config.codec()) && (format() == config.format()) &&
- (webm_color_metadata_ == config.webm_color_metadata()) &&
- (profile() == config.profile()) &&
- (coded_size() == config.coded_size()) &&
- (visible_rect() == config.visible_rect()) &&
- (natural_size() == config.natural_size()) &&
- (extra_data_size() == config.extra_data_size()) &&
- (!extra_data() ||
- !memcmp(extra_data(), config.extra_data(), extra_data_size())) &&
- (is_encrypted() == config.is_encrypted()));
-}
-
-std::string VideoDecoderConfig::AsHumanReadableString() const {
- std::ostringstream s;
- s << "codec: " << codec()
- << " format: " << format()
- << " profile: " << profile()
- << " coded size: [" << coded_size().width()
- << "," << coded_size().height() << "]"
- << " visible rect: [" << visible_rect().x()
- << "," << visible_rect().y()
- << "," << visible_rect().width()
- << "," << visible_rect().height() << "]"
- << " natural size: [" << natural_size().width()
- << "," << natural_size().height() << "]"
- << " has extra data? " << (extra_data() ? "true" : "false")
- << " encrypted? " << (is_encrypted() ? "true" : "false");
- return s.str();
-}
-
-VideoCodec VideoDecoderConfig::codec() const {
- return codec_;
-}
-
-VideoCodecProfile VideoDecoderConfig::profile() const {
- return profile_;
-}
-
-VideoFrame::Format VideoDecoderConfig::format() const {
- return format_;
-}
-
-gfx::Size VideoDecoderConfig::coded_size() const {
- return coded_size_;
-}
-
-gfx::Rect VideoDecoderConfig::visible_rect() const {
- return visible_rect_;
-}
-
-gfx::Size VideoDecoderConfig::natural_size() const {
- return natural_size_;
-}
-
-uint8* VideoDecoderConfig::extra_data() const {
- return extra_data_.get();
-}
-
-size_t VideoDecoderConfig::extra_data_size() const {
- return extra_data_size_;
-}
-
-bool VideoDecoderConfig::is_encrypted() const {
- return is_encrypted_;
-}
-
-} // namespace media
diff --git a/src/media/base/video_decoder_config.h b/src/media/base/video_decoder_config.h
deleted file mode 100644
index f5b6122..0000000
--- a/src/media/base/video_decoder_config.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_DECODER_CONFIG_H_
-#define MEDIA_BASE_VIDEO_DECODER_CONFIG_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/color_space.h"
-#include "media/base/hdr_metadata.h"
-#include "media/base/media_export.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_types.h"
-#include "media/webm/webm_colour_parser.h"
-#include "ui/gfx/rect.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-enum VideoCodec {
- // These values are histogrammed over time; do not change their ordinal
- // values. When deleting a codec replace it with a dummy value; when adding a
- // codec, do so at the bottom (and update kVideoCodecMax).
- kUnknownVideoCodec = 0,
- kCodecH264,
- kCodecVC1,
- kCodecMPEG2,
- kCodecMPEG4,
- kCodecTheora,
- kCodecVP8,
- kCodecVP9,
- // DO NOT ADD RANDOM VIDEO CODECS!
- //
- // The only acceptable time to add a new codec is if there is production code
- // that uses said codec in the same CL.
-
- kVideoCodecMax = kCodecVP8 // Must equal the last "real" codec above.
-};
-
-// Video stream profile. This *must* match PP_VideoDecoder_Profile.
-// (enforced in webkit/plugins/ppapi/ppb_video_decoder_impl.cc)
-enum VideoCodecProfile {
- // Keep the values in this enum unique, as they imply format (h.264 vs. VP8,
- // for example), and keep the values for a particular format grouped
- // together for clarity.
- VIDEO_CODEC_PROFILE_UNKNOWN = -1,
- H264PROFILE_MIN = 0,
- H264PROFILE_BASELINE = H264PROFILE_MIN,
- H264PROFILE_MAIN = 1,
- H264PROFILE_EXTENDED = 2,
- H264PROFILE_HIGH = 3,
- H264PROFILE_HIGH10PROFILE = 4,
- H264PROFILE_HIGH422PROFILE = 5,
- H264PROFILE_HIGH444PREDICTIVEPROFILE = 6,
- H264PROFILE_SCALABLEBASELINE = 7,
- H264PROFILE_SCALABLEHIGH = 8,
- H264PROFILE_STEREOHIGH = 9,
- H264PROFILE_MULTIVIEWHIGH = 10,
- H264PROFILE_MAX = H264PROFILE_MULTIVIEWHIGH,
- VP8PROFILE_MIN = 11,
- VP8PROFILE_MAIN = VP8PROFILE_MIN,
- VP8PROFILE_MAX = VP8PROFILE_MAIN,
- VP9PROFILE_MIN = 12,
- VP9PROFILE_MAIN = VP9PROFILE_MIN,
- VP9PROFILE_MAX = VP9PROFILE_MAIN,
- VIDEO_CODEC_PROFILE_MAX = VP9PROFILE_MAX,
-};
-
-class MEDIA_EXPORT VideoDecoderConfig {
- public:
- // Constructs an uninitialized object. Clients should call Initialize() with
- // appropriate values before using.
- VideoDecoderConfig();
-
- // Constructs an initialized object. It is acceptable to pass in NULL for
- // |extra_data|, otherwise the memory is copied.
- VideoDecoderConfig(VideoCodec codec,
- VideoCodecProfile profile,
- VideoFrame::Format format,
- ColorSpace color_space,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted);
-
- ~VideoDecoderConfig();
-
- // Resets the internal state of this object.
- void Initialize(VideoCodec codec,
- VideoCodecProfile profile,
- VideoFrame::Format format,
- ColorSpace color_space,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- const uint8* extra_data,
- size_t extra_data_size,
- bool is_encrypted,
- bool record_stats);
-
- // Deep copies |video_config|.
- void CopyFrom(const VideoDecoderConfig& video_config);
-
- // Returns true if this object has appropriate configuration values, false
- // otherwise.
- bool IsValidConfig() const;
-
- // Returns true if all fields in |config| match this config.
- // Note: The contents of |extra_data_| are compared not the raw pointers.
- bool Matches(const VideoDecoderConfig& config) const;
-
- // Returns a human-readable string describing |*this|. For debugging & test
- // output only.
- std::string AsHumanReadableString() const;
-
- VideoCodec codec() const;
- VideoCodecProfile profile() const;
-
- // Video format used to determine YUV buffer sizes.
- VideoFrame::Format format() const;
-
- // Width and height of video frame immediately post-decode. Not all pixels
- // in this region are valid.
- gfx::Size coded_size() const;
-
- // Region of |coded_size_| that is visible.
- gfx::Rect visible_rect() const;
-
- // Final visible width and height of a video frame with aspect ratio taken
- // into account.
- gfx::Size natural_size() const;
-
- // Optional byte data required to initialize video decoders, such as H.264
- // AAVC data.
- uint8* extra_data() const;
- size_t extra_data_size() const;
-
- // Whether the video stream is potentially encrypted.
- // Note that in a potentially encrypted video stream, individual buffers
- // can be encrypted or not encrypted.
- bool is_encrypted() const;
-
- void set_webm_color_metadata(const WebMColorMetadata& webm_color_metadata) {
- webm_color_metadata_ = webm_color_metadata;
- }
-
- const WebMColorMetadata& webm_color_metadata() const {
- return webm_color_metadata_;
- }
-
- const ColorSpace& color_space() const { return color_space_; }
-
- private:
- VideoCodec codec_;
- VideoCodecProfile profile_;
-
- VideoFrame::Format format_;
-
- // TODO(servolk): Deprecated, use color_space_info_ instead.
- ColorSpace color_space_;
-
- gfx::Size coded_size_;
- gfx::Rect visible_rect_;
- gfx::Size natural_size_;
-
- scoped_array<uint8> extra_data_;
- size_t extra_data_size_;
-
- bool is_encrypted_;
-
- WebMColorMetadata webm_color_metadata_;
- DISALLOW_COPY_AND_ASSIGN(VideoDecoderConfig);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_DECODER_CONFIG_H_
diff --git a/src/media/base/video_frame.cc b/src/media/base/video_frame.cc
deleted file mode 100644
index 581246c..0000000
--- a/src/media/base/video_frame.cc
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_frame.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "base/string_piece.h"
-#include "media/base/limits.h"
-#include "media/base/video_util.h"
-
-namespace media {
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::CreateFrame(
- VideoFrame::Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp) {
- DCHECK(IsValidConfig(format, coded_size, visible_rect, natural_size));
- scoped_refptr<VideoFrame> frame(new VideoFrame(
- format, coded_size, visible_rect, natural_size, timestamp));
- switch (format) {
- case VideoFrame::RGB32:
- frame->AllocateRGB(4u);
- break;
- case VideoFrame::YV12:
- case VideoFrame::YV16:
- frame->AllocateYUV();
- break;
- default:
- LOG(FATAL) << "Unsupported frame format: " << format;
- }
- return frame;
-}
-
-// static
-bool VideoFrame::IsValidConfig(VideoFrame::Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size) {
- return (format != VideoFrame::INVALID &&
- !coded_size.IsEmpty() &&
- coded_size.GetArea() <= limits::kMaxCanvas &&
- coded_size.width() <= limits::kMaxDimension &&
- coded_size.height() <= limits::kMaxDimension &&
- !visible_rect.IsEmpty() &&
- visible_rect.x() >= 0 && visible_rect.y() >= 0 &&
- visible_rect.right() <= coded_size.width() &&
- visible_rect.bottom() <= coded_size.height() &&
- !natural_size.IsEmpty() &&
- natural_size.GetArea() <= limits::kMaxCanvas &&
- natural_size.width() <= limits::kMaxDimension &&
- natural_size.height() <= limits::kMaxDimension);
-}
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::WrapNativeTexture(
- uintptr_t texture_id,
- uint32 texture_target,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp,
- const ReadPixelsCB& read_pixels_cb,
- const base::Closure& no_longer_needed_cb) {
- scoped_refptr<VideoFrame> frame(new VideoFrame(
- NATIVE_TEXTURE, coded_size, visible_rect, natural_size, timestamp));
- frame->texture_id_ = texture_id;
- frame->texture_target_ = texture_target;
- frame->read_pixels_cb_ = read_pixels_cb;
- frame->no_longer_needed_cb_ = no_longer_needed_cb;
- return frame;
-}
-
-void VideoFrame::ReadPixelsFromNativeTexture(void* pixels) {
- DCHECK_EQ(format_, NATIVE_TEXTURE);
- if (!read_pixels_cb_.is_null())
- read_pixels_cb_.Run(pixels);
-}
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
- Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- int32 y_stride, int32 u_stride, int32 v_stride,
- uint8* y_data, uint8* u_data, uint8* v_data,
- base::TimeDelta timestamp,
- const base::Closure& no_longer_needed_cb) {
- DCHECK(format == YV12 || format == YV16 || format == I420) << format;
- scoped_refptr<VideoFrame> frame(new VideoFrame(
- format, coded_size, visible_rect, natural_size, timestamp));
- frame->strides_[kYPlane] = y_stride;
- frame->strides_[kUPlane] = u_stride;
- frame->strides_[kVPlane] = v_stride;
- frame->data_[kYPlane] = y_data;
- frame->data_[kUPlane] = u_data;
- frame->data_[kVPlane] = v_data;
- frame->no_longer_needed_cb_ = no_longer_needed_cb;
- return frame;
-}
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::CreateEmptyFrame() {
- return new VideoFrame(
- VideoFrame::EMPTY, gfx::Size(), gfx::Rect(), gfx::Size(),
- base::TimeDelta());
-}
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::CreateColorFrame(
- const gfx::Size& size,
- uint8 y, uint8 u, uint8 v,
- base::TimeDelta timestamp) {
- DCHECK(IsValidConfig(VideoFrame::YV12, size, gfx::Rect(size), size));
- scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
- VideoFrame::YV12, size, gfx::Rect(size), size, timestamp);
- FillYUV(frame, y, u, v);
- return frame;
-}
-
-// static
-scoped_refptr<VideoFrame> VideoFrame::CreateBlackFrame(const gfx::Size& size) {
- const uint8 kBlackY = 0x00;
- const uint8 kBlackUV = 0x80;
- const base::TimeDelta kZero;
- return CreateColorFrame(size, kBlackY, kBlackUV, kBlackUV, kZero);
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-// static
-scoped_refptr<VideoFrame> VideoFrame::CreatePunchOutFrame(
- const gfx::Size& size) {
- scoped_refptr<VideoFrame> frame(new VideoFrame(
- VideoFrame::PUNCH_OUT, gfx::Size(size), gfx::Rect(size), gfx::Size(size),
- base::TimeDelta()));
- return frame;
-}
-#endif
-
-static inline size_t RoundUp(size_t value, size_t alignment) {
- // Check that |alignment| is a power of 2.
- DCHECK((alignment + (alignment - 1)) == (alignment | (alignment - 1)));
- return ((value + (alignment - 1)) & ~(alignment-1));
-}
-
-// Release data allocated by AllocateRGB() or AllocateYUV().
-static void ReleaseData(uint8* data) {
- DCHECK(data);
- base::AlignedFree(data);
-}
-
-void VideoFrame::AllocateRGB(size_t bytes_per_pixel) {
- // Round up to align at least at a 16-byte boundary for each row.
- // This is sufficient for MMX and SSE2 reads (movq/movdqa).
- size_t bytes_per_row = RoundUp(coded_size_.width(),
- kFrameSizeAlignment) * bytes_per_pixel;
- size_t aligned_height = RoundUp(coded_size_.height(), kFrameSizeAlignment);
- strides_[VideoFrame::kRGBPlane] = bytes_per_row;
- data_[VideoFrame::kRGBPlane] = reinterpret_cast<uint8*>(
- base::AlignedAlloc(bytes_per_row * aligned_height + kFrameSizePadding,
- kFrameAddressAlignment));
- no_longer_needed_cb_ = base::Bind(&ReleaseData, data_[VideoFrame::kRGBPlane]);
- DCHECK(!(reinterpret_cast<intptr_t>(data_[VideoFrame::kRGBPlane]) & 7));
- COMPILE_ASSERT(0 == VideoFrame::kRGBPlane, RGB_data_must_be_index_0);
-}
-
-void VideoFrame::AllocateYUV() {
- DCHECK(format_ == VideoFrame::YV12 || format_ == VideoFrame::YV16);
- // Align Y rows at least at 16 byte boundaries. The stride for both
- // YV12 and YV16 is 1/2 of the stride of Y. For YV12, every row of bytes for
- // U and V applies to two rows of Y (one byte of UV for 4 bytes of Y), so in
- // the case of YV12 the strides are identical for the same width surface, but
- // the number of bytes allocated for YV12 is 1/2 the amount for U & V as
- // YV16. We also round the height of the surface allocated to be an even
- // number to avoid any potential of faulting by code that attempts to access
- // the Y values of the final row, but assumes that the last row of U & V
- // applies to a full two rows of Y.
- size_t y_stride = RoundUp(row_bytes(VideoFrame::kYPlane),
- kFrameSizeAlignment);
- size_t uv_stride = RoundUp(row_bytes(VideoFrame::kUPlane),
- kFrameSizeAlignment);
- // The *2 here is because some formats (e.g. h264) allow interlaced coding,
- // and then the size needs to be a multiple of two macroblocks (vertically).
- // See libavcodec/utils.c:avcodec_align_dimensions2().
- size_t y_height = RoundUp(coded_size_.height(), kFrameSizeAlignment * 2);
- size_t uv_height = format_ == VideoFrame::YV12 ? y_height / 2 : y_height;
- size_t y_bytes = y_height * y_stride;
- size_t uv_bytes = uv_height * uv_stride;
-
- // The extra line of UV being allocated is because h264 chroma MC
- // overreads by one line in some cases, see libavcodec/utils.c:
- // avcodec_align_dimensions2() and libavcodec/x86/h264_chromamc.asm:
- // put_h264_chroma_mc4_ssse3().
- uint8* data = reinterpret_cast<uint8*>(
- base::AlignedAlloc(
- y_bytes + (uv_bytes * 2 + uv_stride) + kFrameSizePadding,
- kFrameAddressAlignment));
- no_longer_needed_cb_ = base::Bind(&ReleaseData, data);
- COMPILE_ASSERT(0 == VideoFrame::kYPlane, y_plane_data_must_be_index_0);
- data_[VideoFrame::kYPlane] = data;
- data_[VideoFrame::kUPlane] = data + y_bytes;
- data_[VideoFrame::kVPlane] = data + y_bytes + uv_bytes;
- strides_[VideoFrame::kYPlane] = y_stride;
- strides_[VideoFrame::kUPlane] = uv_stride;
- strides_[VideoFrame::kVPlane] = uv_stride;
-}
-
-VideoFrame::VideoFrame(VideoFrame::Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp)
- : format_(format),
- coded_size_(coded_size),
- visible_rect_(visible_rect),
- natural_size_(natural_size),
- texture_id_(0),
- texture_target_(0),
- timestamp_(timestamp) {
- memset(&strides_, 0, sizeof(strides_));
- memset(&data_, 0, sizeof(data_));
-}
-
-VideoFrame::~VideoFrame() {
- if (!no_longer_needed_cb_.is_null())
- base::ResetAndReturn(&no_longer_needed_cb_).Run();
-}
-
-bool VideoFrame::IsValidPlane(size_t plane) const {
- switch (format_) {
- case RGB32:
- return plane == kRGBPlane;
-
- case YV12:
- case YV16:
- return plane == kYPlane || plane == kUPlane || plane == kVPlane;
-
- case NATIVE_TEXTURE:
- NOTREACHED() << "NATIVE_TEXTUREs don't use plane-related methods!";
- return false;
-
- default:
- break;
- }
-
- // Intentionally leave out non-production formats.
- NOTREACHED() << "Unsupported video frame format: " << format_;
- return false;
-}
-
-int VideoFrame::stride(size_t plane) const {
- DCHECK(IsValidPlane(plane));
- return strides_[plane];
-}
-
-int VideoFrame::row_bytes(size_t plane) const {
- DCHECK(IsValidPlane(plane));
- int width = coded_size_.width();
- switch (format_) {
- // 32bpp.
- case RGB32:
- return width * 4;
-
- // Planar, 8bpp.
- case YV12:
- case YV16:
- if (plane == kYPlane)
- return width;
- return RoundUp(width, 2) / 2;
-
- default:
- break;
- }
-
- // Intentionally leave out non-production formats.
- NOTREACHED() << "Unsupported video frame format: " << format_;
- return 0;
-}
-
-int VideoFrame::rows(size_t plane) const {
- DCHECK(IsValidPlane(plane));
- int height = coded_size_.height();
- switch (format_) {
- case RGB32:
- case YV16:
- return height;
-
- case YV12:
- if (plane == kYPlane)
- return height;
- return RoundUp(height, 2) / 2;
-
- default:
- break;
- }
-
- // Intentionally leave out non-production formats.
- NOTREACHED() << "Unsupported video frame format: " << format_;
- return 0;
-}
-
-uint8* VideoFrame::data(size_t plane) const {
- DCHECK(IsValidPlane(plane));
- return data_[plane];
-}
-
-uintptr_t VideoFrame::texture_id() const {
- DCHECK_EQ(format_, NATIVE_TEXTURE);
- return texture_id_;
-}
-
-uint32 VideoFrame::texture_target() const {
- DCHECK_EQ(format_, NATIVE_TEXTURE);
- return texture_target_;
-}
-
-bool VideoFrame::IsEndOfStream() const {
- return format_ == VideoFrame::EMPTY;
-}
-
-void VideoFrame::HashFrameForTesting(base::MD5Context* context) {
- for (int plane = 0; plane < kMaxPlanes; ++plane) {
- if (!IsValidPlane(plane))
- break;
- for (int row = 0; row < rows(plane); ++row) {
- base::MD5Update(context, base::StringPiece(
- reinterpret_cast<char*>(data(plane) + stride(plane) * row),
- row_bytes(plane)));
- }
- }
-}
-
-} // namespace media
diff --git a/src/media/base/video_frame.h b/src/media/base/video_frame.h
deleted file mode 100644
index 879feac..0000000
--- a/src/media/base/video_frame.h
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_FRAME_H_
-#define MEDIA_BASE_VIDEO_FRAME_H_
-
-#include "base/callback.h"
-#include "base/md5.h"
-#include "media/base/buffers.h"
-#include "ui/gfx/rect.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
- public:
- enum {
- kFrameSizeAlignment = 16,
- kFrameSizePadding = 16,
- kFrameAddressAlignment = 32
- };
-
- enum {
- kMaxPlanes = 3,
-
- kRGBPlane = 0,
-
- kYPlane = 0,
- kUPlane = 1,
- kVPlane = 2,
- };
-
- // Surface formats roughly based on FOURCC labels, see:
- // http://www.fourcc.org/rgb.php
- // http://www.fourcc.org/yuv.php
- // Keep in sync with WebKit::WebVideoFrame!
- enum Format {
- INVALID = 0, // Invalid format value. Used for error reporting.
- RGB32 = 4, // 32bpp RGB packed with extra byte 8:8:8
- YV12 = 6, // 12bpp YVU planar 1x1 Y, 2x2 VU samples
- YV16 = 7, // 16bpp YVU planar 1x1 Y, 2x1 VU samples
- EMPTY = 9, // An empty frame.
- I420 = 11, // 12bpp YVU planar 1x1 Y, 2x2 UV samples.
- NATIVE_TEXTURE = 12, // Native texture. Pixel-format agnostic.
-#if defined(__LB_SHELL__) || defined(COBALT)
- PUNCH_OUT= 13, // Punch out frame
-#endif
- };
-
- // Creates a new frame in system memory with given parameters. Buffers for
- // the frame are allocated but not initialized.
- // |coded_size| is the width and height of the frame data in pixels.
- // |visible_rect| is the visible portion of |coded_size|, after cropping (if
- // any) is applied.
- // |natural_size| is the width and height of the frame when the frame's aspect
- // ratio is applied to |visible_rect|.
- static scoped_refptr<VideoFrame> CreateFrame(
- Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp);
-
- // Call prior to CreateFrame to ensure validity of frame configuration. Called
- // automatically by VideoDecoderConfig::IsValidConfig().
- // TODO(scherkus): VideoDecoderConfig shouldn't call this method
- static bool IsValidConfig(Format format, const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size);
-
- // CB to write pixels from the texture backing this frame into the
- // |void*| parameter.
- typedef base::Callback<void(void*)> ReadPixelsCB;
-
- // Wraps a native texture of the given parameters with a VideoFrame. When the
- // frame is destroyed |no_longer_needed_cb.Run()| will be called.
- // |coded_size| is the width and height of the frame data in pixels.
- // |visible_rect| is the visible portion of |coded_size|, after cropping (if
- // any) is applied.
- // |natural_size| is the width and height of the frame when the frame's aspect
- // ratio is applied to |visible_rect|.
- // |read_pixels_cb| may be used to do (slow!) readbacks from the
- // texture to main memory.
- static scoped_refptr<VideoFrame> WrapNativeTexture(
- uintptr_t texture_id,
- uint32 texture_target,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp,
- const ReadPixelsCB& read_pixels_cb,
- const base::Closure& no_longer_needed_cb);
-
- // Read pixels from the native texture backing |*this| and write
- // them to |*pixels| as BGRA. |pixels| must point to a buffer at
- // least as large as 4*visible_rect().width()*visible_rect().height().
- void ReadPixelsFromNativeTexture(void* pixels);
-
- // Wraps external YUV data of the given parameters with a VideoFrame.
- // The returned VideoFrame does not own the data passed in. When the frame
- // is destroyed |no_longer_needed_cb.Run()| will be called.
- static scoped_refptr<VideoFrame> WrapExternalYuvData(
- Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- int32 y_stride,
- int32 u_stride,
- int32 v_stride,
- uint8* y_data,
- uint8* u_data,
- uint8* v_data,
- base::TimeDelta timestamp,
- const base::Closure& no_longer_needed_cb);
-
- // Creates a frame with format equals to VideoFrame::EMPTY, width, height,
- // and timestamp are all 0.
- static scoped_refptr<VideoFrame> CreateEmptyFrame();
-
- // Allocates YV12 frame based on |size|, and sets its data to the YUV(y,u,v).
- static scoped_refptr<VideoFrame> CreateColorFrame(
- const gfx::Size& size,
- uint8 y, uint8 u, uint8 v,
- base::TimeDelta timestamp);
-
- // Allocates YV12 frame based on |size|, and sets its data to the YUV
- // equivalent of RGB(0,0,0).
- static scoped_refptr<VideoFrame> CreateBlackFrame(const gfx::Size& size);
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // Allocates a punch out frame with all stats set to 0.
- // When rendered, the frame will cause a hole to be punched out discarding
- // everything that was rendered underneath it
- static scoped_refptr<VideoFrame> CreatePunchOutFrame(const gfx::Size& size);
-#endif
-
- Format format() const { return format_; }
-
- const gfx::Size& coded_size() const { return coded_size_; }
- const gfx::Rect& visible_rect() const { return visible_rect_; }
- const gfx::Size& natural_size() const { return natural_size_; }
-
- int stride(size_t plane) const;
-
- // Returns the number of bytes per row and number of rows for a given plane.
- //
- // As opposed to stride(), row_bytes() refers to the bytes representing
- // frame data scanlines (coded_size.width() pixels, without stride padding).
- int row_bytes(size_t plane) const;
- int rows(size_t plane) const;
-
- // Returns pointer to the buffer for a given plane. The memory is owned by
- // VideoFrame object and must not be freed by the caller.
- uint8* data(size_t plane) const;
-
- // Returns the ID of the native texture wrapped by this frame. Only valid to
- // call if this is a NATIVE_TEXTURE frame.
- uintptr_t texture_id() const;
-
- // Returns the texture target. Only valid for NATIVE_TEXTURE frames.
- uint32 texture_target() const;
-
- // Returns true if this VideoFrame represents the end of the stream.
- bool IsEndOfStream() const;
-
- base::TimeDelta GetTimestamp() const {
- return timestamp_;
- }
- void SetTimestamp(const base::TimeDelta& timestamp) {
- timestamp_ = timestamp;
- }
-
- // Used to keep a running hash of seen frames. Expects an initialized MD5
- // context. Calls MD5Update with the context and the contents of the frame.
- void HashFrameForTesting(base::MD5Context* context);
-
- private:
- friend class base::RefCountedThreadSafe<VideoFrame>;
- // Clients must use the static CreateFrame() method to create a new frame.
- VideoFrame(Format format,
- const gfx::Size& coded_size,
- const gfx::Rect& visible_rect,
- const gfx::Size& natural_size,
- base::TimeDelta timestamp);
- virtual ~VideoFrame();
-
- // Used internally by CreateFrame().
- void AllocateRGB(size_t bytes_per_pixel);
- void AllocateYUV();
-
- // Used to DCHECK() plane parameters.
- bool IsValidPlane(size_t plane) const;
-
- // Frame format.
- Format format_;
-
- // Width and height of the video frame.
- gfx::Size coded_size_;
-
- // Width, height, and offsets of the visible portion of the video frame.
- gfx::Rect visible_rect_;
-
- // Width and height of the visible portion of the video frame with aspect
- // ratio taken into account.
- gfx::Size natural_size_;
-
- // Array of strides for each plane, typically greater or equal to the width
- // of the surface divided by the horizontal sampling period. Note that
- // strides can be negative.
- int32 strides_[kMaxPlanes];
-
- // Array of data pointers to each plane.
- uint8* data_[kMaxPlanes];
-
- // Native texture ID, if this is a NATIVE_TEXTURE frame.
- uintptr_t texture_id_;
- uint32 texture_target_;
- ReadPixelsCB read_pixels_cb_;
-
- base::Closure no_longer_needed_cb_;
-
- base::TimeDelta timestamp_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoFrame);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_FRAME_H_
diff --git a/src/media/base/video_frame_unittest.cc b/src/media/base/video_frame_unittest.cc
deleted file mode 100644
index 18cc1d3..0000000
--- a/src/media/base/video_frame_unittest.cc
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_frame.h"
-
-#include "base/format_macros.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/stringprintf.h"
-#include "media/base/buffers.h"
-#include "media/base/yuv_convert.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-using base::MD5DigestToBase16;
-
-// Helper function that initializes a YV12 frame with white and black scan
-// lines based on the |white_to_black| parameter. If 0, then the entire
-// frame will be black, if 1 then the entire frame will be white.
-void InitializeYV12Frame(VideoFrame* frame, double white_to_black) {
- EXPECT_EQ(VideoFrame::YV12, frame->format());
- int first_black_row = static_cast<int>(frame->coded_size().height() *
- white_to_black);
- uint8* y_plane = frame->data(VideoFrame::kYPlane);
- for (int row = 0; row < frame->coded_size().height(); ++row) {
- int color = (row < first_black_row) ? 0xFF : 0x00;
- memset(y_plane, color, frame->stride(VideoFrame::kYPlane));
- y_plane += frame->stride(VideoFrame::kYPlane);
- }
- uint8* u_plane = frame->data(VideoFrame::kUPlane);
- uint8* v_plane = frame->data(VideoFrame::kVPlane);
- for (int row = 0; row < frame->coded_size().height(); row += 2) {
- memset(u_plane, 0x80, frame->stride(VideoFrame::kUPlane));
- memset(v_plane, 0x80, frame->stride(VideoFrame::kVPlane));
- u_plane += frame->stride(VideoFrame::kUPlane);
- v_plane += frame->stride(VideoFrame::kVPlane);
- }
-}
-
-// Given a |yv12_frame| this method converts the YV12 frame to RGBA and
-// makes sure that all the pixels of the RBG frame equal |expect_rgb_color|.
-void ExpectFrameColor(media::VideoFrame* yv12_frame, uint32 expect_rgb_color) {
- ASSERT_EQ(VideoFrame::YV12, yv12_frame->format());
- ASSERT_EQ(yv12_frame->stride(VideoFrame::kUPlane),
- yv12_frame->stride(VideoFrame::kVPlane));
-
- scoped_refptr<media::VideoFrame> rgb_frame;
- rgb_frame = media::VideoFrame::CreateFrame(VideoFrame::RGB32,
- yv12_frame->coded_size(),
- yv12_frame->visible_rect(),
- yv12_frame->natural_size(),
- yv12_frame->GetTimestamp());
-
- ASSERT_EQ(yv12_frame->coded_size().width(),
- rgb_frame->coded_size().width());
- ASSERT_EQ(yv12_frame->coded_size().height(),
- rgb_frame->coded_size().height());
-
- media::ConvertYUVToRGB32(yv12_frame->data(VideoFrame::kYPlane),
- yv12_frame->data(VideoFrame::kUPlane),
- yv12_frame->data(VideoFrame::kVPlane),
- rgb_frame->data(VideoFrame::kRGBPlane),
- rgb_frame->coded_size().width(),
- rgb_frame->coded_size().height(),
- yv12_frame->stride(VideoFrame::kYPlane),
- yv12_frame->stride(VideoFrame::kUPlane),
- rgb_frame->stride(VideoFrame::kRGBPlane),
- media::YV12);
-
- for (int row = 0; row < rgb_frame->coded_size().height(); ++row) {
- uint32* rgb_row_data = reinterpret_cast<uint32*>(
- rgb_frame->data(VideoFrame::kRGBPlane) +
- (rgb_frame->stride(VideoFrame::kRGBPlane) * row));
- for (int col = 0; col < rgb_frame->coded_size().width(); ++col) {
- SCOPED_TRACE(
- base::StringPrintf("Checking (%d, %d)", row, col));
- EXPECT_EQ(expect_rgb_color, rgb_row_data[col]);
- }
- }
-}
-
-// Fill each plane to its reported extents and verify accessors report non
-// zero values. Additionally, for the first plane verify the rows and
-// row_bytes values are correct.
-void ExpectFrameExtents(VideoFrame::Format format, int planes,
- int bytes_per_pixel, const char* expected_hash) {
- const unsigned char kFillByte = 0x80;
- const int kWidth = 61;
- const int kHeight = 31;
- const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337);
-
- gfx::Size size(kWidth, kHeight);
- scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
- format, size, gfx::Rect(size), size, kTimestamp);
- ASSERT_TRUE(frame);
-
- for(int plane = 0; plane < planes; plane++) {
- SCOPED_TRACE(base::StringPrintf("Checking plane %d", plane));
- EXPECT_TRUE(frame->data(plane));
- EXPECT_TRUE(frame->stride(plane));
- EXPECT_TRUE(frame->rows(plane));
- EXPECT_TRUE(frame->row_bytes(plane));
-
- if (plane == 0) {
- EXPECT_EQ(frame->rows(plane), kHeight);
- EXPECT_EQ(frame->row_bytes(plane), kWidth * bytes_per_pixel);
- }
-
- memset(frame->data(plane), kFillByte,
- frame->stride(plane) * frame->rows(plane));
- }
-
- base::MD5Context context;
- base::MD5Init(&context);
- frame->HashFrameForTesting(&context);
- base::MD5Digest digest;
- base::MD5Final(&digest, &context);
- EXPECT_EQ(MD5DigestToBase16(digest), expected_hash);
-}
-
-TEST(VideoFrame, CreateFrame) {
- const int kWidth = 64;
- const int kHeight = 48;
- const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337);
-
- // Create a YV12 Video Frame.
- gfx::Size size(kWidth, kHeight);
- scoped_refptr<media::VideoFrame> frame =
- VideoFrame::CreateFrame(media::VideoFrame::YV12, size, gfx::Rect(size),
- size, kTimestamp);
- ASSERT_TRUE(frame);
-
- // Test VideoFrame implementation.
- EXPECT_EQ(media::VideoFrame::YV12, frame->format());
- {
- SCOPED_TRACE("");
- InitializeYV12Frame(frame, 0.0f);
- ExpectFrameColor(frame, 0xFF000000);
- }
- base::MD5Digest digest;
- base::MD5Context context;
- base::MD5Init(&context);
- frame->HashFrameForTesting(&context);
- base::MD5Final(&digest, &context);
- EXPECT_EQ(MD5DigestToBase16(digest), "9065c841d9fca49186ef8b4ef547e79b");
- {
- SCOPED_TRACE("");
- InitializeYV12Frame(frame, 1.0f);
- ExpectFrameColor(frame, 0xFFFFFFFF);
- }
- base::MD5Init(&context);
- frame->HashFrameForTesting(&context);
- base::MD5Final(&digest, &context);
- EXPECT_EQ(MD5DigestToBase16(digest), "911991d51438ad2e1a40ed5f6fc7c796");
-
- // Test an empty frame.
- frame = VideoFrame::CreateEmptyFrame();
- EXPECT_TRUE(frame->IsEndOfStream());
-}
-
-TEST(VideoFrame, CreateBlackFrame) {
- const int kWidth = 2;
- const int kHeight = 2;
- const uint8 kExpectedYRow[] = { 0, 0 };
- const uint8 kExpectedUVRow[] = { 128 };
-
- scoped_refptr<media::VideoFrame> frame =
- VideoFrame::CreateBlackFrame(gfx::Size(kWidth, kHeight));
- ASSERT_TRUE(frame);
-
- // Test basic properties.
- EXPECT_EQ(0, frame->GetTimestamp().InMicroseconds());
- EXPECT_FALSE(frame->IsEndOfStream());
-
- // Test |frame| properties.
- EXPECT_EQ(VideoFrame::YV12, frame->format());
- EXPECT_EQ(kWidth, frame->coded_size().width());
- EXPECT_EQ(kHeight, frame->coded_size().height());
-
- // Test frames themselves.
- uint8* y_plane = frame->data(VideoFrame::kYPlane);
- for (int y = 0; y < frame->coded_size().height(); ++y) {
- EXPECT_EQ(0, memcmp(kExpectedYRow, y_plane, arraysize(kExpectedYRow)));
- y_plane += frame->stride(VideoFrame::kYPlane);
- }
-
- uint8* u_plane = frame->data(VideoFrame::kUPlane);
- uint8* v_plane = frame->data(VideoFrame::kVPlane);
- for (int y = 0; y < frame->coded_size().height() / 2; ++y) {
- EXPECT_EQ(0, memcmp(kExpectedUVRow, u_plane, arraysize(kExpectedUVRow)));
- EXPECT_EQ(0, memcmp(kExpectedUVRow, v_plane, arraysize(kExpectedUVRow)));
- u_plane += frame->stride(VideoFrame::kUPlane);
- v_plane += frame->stride(VideoFrame::kVPlane);
- }
-}
-
-// Ensure each frame is properly sized and allocated. Will trigger OOB reads
-// and writes as well as incorrect frame hashes otherwise.
-TEST(VideoFrame, CheckFrameExtents) {
- // Each call consists of a VideoFrame::Format, # of planes, bytes per pixel,
- // and the expected hash of all planes if filled with kFillByte (defined in
- // ExpectFrameExtents).
- ExpectFrameExtents(
- VideoFrame::RGB32, 1, 4, "de6d3d567e282f6a38d478f04fc81fb0");
- ExpectFrameExtents(
- VideoFrame::YV12, 3, 1, "71113bdfd4c0de6cf62f48fb74f7a0b1");
- ExpectFrameExtents(
- VideoFrame::YV16, 3, 1, "9bb99ac3ff350644ebff4d28dc01b461");
-}
-
-} // namespace media
diff --git a/src/media/base/video_renderer.cc b/src/media/base/video_renderer.cc
deleted file mode 100644
index 00a8f21..0000000
--- a/src/media/base/video_renderer.cc
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_renderer.h"
-
-namespace media {
-
-VideoRenderer::VideoRenderer() {}
-VideoRenderer::~VideoRenderer() {}
-
-} // namespace media
diff --git a/src/media/base/video_renderer.h b/src/media/base/video_renderer.h
deleted file mode 100644
index d2d302f..0000000
--- a/src/media/base/video_renderer.h
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_RENDERER_H_
-#define MEDIA_BASE_VIDEO_RENDERER_H_
-
-#include <list>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/base/pipeline_status.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-class DemuxerStream;
-class VideoDecoder;
-
-class MEDIA_EXPORT VideoRenderer
- : public base::RefCountedThreadSafe<VideoRenderer> {
- public:
- typedef std::list<scoped_refptr<VideoDecoder> > VideoDecoderList;
-
- // Used to update the pipeline's clock time. The parameter is the time that
- // the clock should not exceed.
- typedef base::Callback<void(base::TimeDelta)> TimeCB;
-
- // Executed when the natural size of the video has changed.
- typedef base::Callback<void(const gfx::Size& size)> NaturalSizeChangedCB;
-
- // Used to query the current time or duration of the media.
- typedef base::Callback<base::TimeDelta()> TimeDeltaCB;
-
- // Initialize a VideoRenderer with the given DemuxerStream and
- // VideoDecoderList, executing |init_cb| callback upon completion.
- //
- // |statistics_cb| is executed periodically with video rendering stats, such
- // as dropped frames.
- //
- // |time_cb| is executed whenever time has advanced by way of video rendering.
- //
- // |size_changed_cb| is executed whenever the dimensions of the video has
- // changed.
- //
- // |ended_cb| is executed when video rendering has reached the end of stream.
- //
- // |error_cb| is executed if an error was encountered.
- //
- // |get_time_cb| is used to query the current media playback time.
- //
- // |get_duration_cb| is used to query the media duration.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const VideoDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const TimeCB& time_cb,
- const NaturalSizeChangedCB& size_changed_cb,
- const base::Closure& ended_cb,
- const PipelineStatusCB& error_cb,
- const TimeDeltaCB& get_time_cb,
- const TimeDeltaCB& get_duration_cb) = 0;
-
- // Start audio decoding and rendering at the current playback rate, executing
- // |callback| when playback is underway.
- virtual void Play(const base::Closure& callback) = 0;
-
- // Temporarily suspend decoding and rendering video, executing |callback| when
- // playback has been suspended.
- virtual void Pause(const base::Closure& callback) = 0;
-
- // Discard any video data, executing |callback| when completed.
- virtual void Flush(const base::Closure& callback) = 0;
-
- // Start prerolling video data for samples starting at |time|, executing
- // |callback| when completed.
- //
- // Only valid to call after a successful Initialize() or Flush().
- virtual void Preroll(base::TimeDelta time,
- const PipelineStatusCB& callback) = 0;
-
- // Stop all operations in preparation for being deleted, executing |callback|
- // when complete.
- virtual void Stop(const base::Closure& callback) = 0;
-
- // Updates the current playback rate.
- virtual void SetPlaybackRate(float playback_rate) = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<VideoRenderer>;
-
- VideoRenderer();
- virtual ~VideoRenderer();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VideoRenderer);
-};
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_RENDERER_H_
diff --git a/src/media/base/video_resolution.h b/src/media/base/video_resolution.h
deleted file mode 100644
index 29ae65f..0000000
--- a/src/media/base/video_resolution.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BASE_VIDEO_RESOLUTION_H_
-#define MEDIA_BASE_VIDEO_RESOLUTION_H_
-
-#include "base/logging.h"
-#include "media/base/media_export.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-// Enumerates the various representations of the resolution of videos. Note
-// that except |kVideoResolutionInvalid|, all other values are guaranteed to be
-// in the same order as its (width, height) pair. Note, unlike the other valid
-// resolution levels, |kVideoResolutionHighRes| is not a 16:9 resolution.
-enum VideoResolution {
- kVideoResolution1080p, // 1920 x 1080
- kVideoResolution2k, // 2560 x 1440
- kVideoResolution4k, // 3840 x 2160
- kVideoResolution5k, // 5120 × 2880
- kVideoResolution8k, // 7680 x 4320
- kVideoResolutionHighRes, // 8192 x 8192
- kVideoResolutionInvalid
-};
-
-inline VideoResolution GetVideoResolution(int width, int height) {
- if (width <= 1920 && height <= 1080) {
- return kVideoResolution1080p;
- }
- if (width <= 2560 && height <= 1440) {
- return kVideoResolution2k;
- }
- if (width <= 3840 && height <= 2160) {
- return kVideoResolution4k;
- }
- if (width <= 5120 && height <= 2880) {
- return kVideoResolution5k;
- }
- if (width <= 7680 && height <= 4320) {
- return kVideoResolution8k;
- }
- if (width <= 8192 && height <= 8192) {
- return kVideoResolutionHighRes;
- }
- DLOG(FATAL) << "Invalid VideoResolution: width: " << width
- << " height: " << height;
- return kVideoResolutionInvalid;
-}
-
-inline VideoResolution GetVideoResolution(const gfx::Size& size) {
- return GetVideoResolution(size.width(), size.height());
-}
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_RESOLUTION_H_
diff --git a/src/media/base/video_types.h b/src/media/base/video_types.h
deleted file mode 100644
index f340711..0000000
--- a/src/media/base/video_types.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_TYPES_H_
-#define MEDIA_BASE_VIDEO_TYPES_H_
-
-#include <string>
-
-#include "build/build_config.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Pixel formats roughly based on FOURCC labels, see:
-// http://www.fourcc.org/rgb.php and http://www.fourcc.org/yuv.php
-// Logged to UMA, so never reuse values. Leave gaps if necessary.
-// Ordered as planar, semi-planar, YUV-packed, and RGB formats.
-enum VideoPixelFormat {
- PIXEL_FORMAT_UNKNOWN = 0, // Unknown or unspecified format value.
- PIXEL_FORMAT_I420 =
- 1, // 12bpp YUV planar 1x1 Y, 2x2 UV samples, a.k.a. YU12.
- PIXEL_FORMAT_YV12 = 2, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
- PIXEL_FORMAT_YV16 = 3, // 16bpp YVU planar 1x1 Y, 2x1 VU samples.
- PIXEL_FORMAT_YV12A = 4, // 20bpp YUVA planar 1x1 Y, 2x2 VU, 1x1 A samples.
- PIXEL_FORMAT_YV24 = 5, // 24bpp YUV planar, no subsampling.
- PIXEL_FORMAT_NV12 =
- 6, // 12bpp with Y plane followed by a 2x2 interleaved UV plane.
- PIXEL_FORMAT_NV21 =
- 7, // 12bpp with Y plane followed by a 2x2 interleaved VU plane.
- PIXEL_FORMAT_UYVY =
- 8, // 16bpp interleaved 2x1 U, 1x1 Y, 2x1 V, 1x1 Y samples.
- PIXEL_FORMAT_YUY2 =
- 9, // 16bpp interleaved 1x1 Y, 2x1 U, 1x1 Y, 2x1 V samples.
- PIXEL_FORMAT_ARGB = 10, // 32bpp ARGB, 1 plane.
- PIXEL_FORMAT_XRGB = 11, // 24bpp XRGB, 1 plane.
- PIXEL_FORMAT_RGB24 = 12, // 24bpp BGR, 1 plane.
- PIXEL_FORMAT_RGB32 = 13, // 32bpp BGRA, 1 plane.
- PIXEL_FORMAT_MJPEG = 14, // MJPEG compressed.
- // MediaTek proprietary format. MT21 is similar to NV21 except the memory
- // layout and pixel layout (swizzles). 12bpp with Y plane followed by a 2x2
- // interleaved VU plane. Each image contains two buffers -- Y plane and VU
- // plane. Two planes can be non-contiguous in memory. The starting addresses
- // of Y plane and VU plane are 4KB alignment.
- // Suppose image dimension is (width, height). For both Y plane and VU plane:
- // Row pitch = ((width+15)/16) * 16.
- // Plane size = Row pitch * (((height+31)/32)*32)
- PIXEL_FORMAT_MT21 = 15,
-
- PIXEL_FORMAT_YUV420P9 = 16,
- PIXEL_FORMAT_YUV420P10 = 17,
- PIXEL_FORMAT_YUV422P9 = 18,
- PIXEL_FORMAT_YUV422P10 = 19,
- PIXEL_FORMAT_YUV444P9 = 20,
- PIXEL_FORMAT_YUV444P10 = 21,
-
- PIXEL_FORMAT_YUV420P12 = 22,
- PIXEL_FORMAT_YUV422P12 = 23,
- PIXEL_FORMAT_YUV444P12 = 24,
-
- PIXEL_FORMAT_Y8 = 25, // single 8bpp plane.
- PIXEL_FORMAT_Y16 = 26, // single 16bpp plane.
-
- PIXEL_FORMAT_I422 =
- 27, // 16bpp YUV planar 1x1 Y, 2x1 UV samples, a.k.a. YU16.
-
- // Please update UMA histogram enumeration when adding new formats here.
- PIXEL_FORMAT_MAX =
- PIXEL_FORMAT_I422, // Must always be equal to largest entry logged.
-};
-
-// Color space or color range used for the pixels.
-// Logged to UMA, so never reuse values. Leave gaps if necessary.
-enum ColorSpace {
- COLOR_SPACE_UNSPECIFIED = 0, // In general this is Rec601.
- // The JPEG color space is the combination of Rec.601 and full range colors
- // (aka pc range colors).
- COLOR_SPACE_JPEG = 1,
- COLOR_SPACE_HD_REC709 = 2, // Rec709 "HD" color space.
- COLOR_SPACE_SD_REC601 = 3, // Rec601 "SD" color space.
- COLOR_SPACE_MAX = COLOR_SPACE_SD_REC601,
-};
-
-// Returns the name of a Format as a string.
-MEDIA_EXPORT std::string VideoPixelFormatToString(VideoPixelFormat format);
-
-// Returns true if |format| is a YUV format with multiple planes.
-MEDIA_EXPORT bool IsYuvPlanar(VideoPixelFormat format);
-
-// Returns true if |format| has no Alpha channel (hence is always opaque).
-MEDIA_EXPORT bool IsOpaque(VideoPixelFormat format);
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_TYPES_H_
diff --git a/src/media/base/video_util.cc b/src/media/base/video_util.cc
deleted file mode 100644
index 4bef0ad..0000000
--- a/src/media/base/video_util.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_util.h"
-
-#include <cmath>
-
-#include "base/logging.h"
-#include "media/base/video_frame.h"
-
-namespace media {
-
-gfx::Size GetNaturalSize(const gfx::Size& visible_size,
- int aspect_ratio_numerator,
- int aspect_ratio_denominator) {
- if (aspect_ratio_denominator == 0 ||
- aspect_ratio_numerator < 0 ||
- aspect_ratio_denominator < 0)
- return gfx::Size();
-
- double aspect_ratio = aspect_ratio_numerator /
- static_cast<double>(aspect_ratio_denominator);
-
- int width = floor(visible_size.width() * aspect_ratio + 0.5);
- int height = visible_size.height();
-
- // An even width makes things easier for YV12 and appears to be the behavior
- // expected by WebKit layout tests.
- return gfx::Size(width & ~1, height);
-}
-
-static void CopyPlane(size_t plane, const uint8* source, int stride, int rows,
- VideoFrame* frame) {
- uint8* dest = frame->data(plane);
- int dest_stride = frame->stride(plane);
-
- // Clamp in case source frame has smaller stride.
- int bytes_to_copy_per_row = std::min(frame->row_bytes(plane), stride);
-
- // Clamp in case source frame has smaller height.
- int rows_to_copy = std::min(frame->rows(plane), rows);
-
- // Copy!
- for (int row = 0; row < rows_to_copy; ++row) {
- memcpy(dest, source, bytes_to_copy_per_row);
- source += stride;
- dest += dest_stride;
- }
-}
-
-void CopyYPlane(const uint8* source, int stride, int rows, VideoFrame* frame) {
- CopyPlane(VideoFrame::kYPlane, source, stride, rows, frame);
-}
-
-void CopyUPlane(const uint8* source, int stride, int rows, VideoFrame* frame) {
- CopyPlane(VideoFrame::kUPlane, source, stride, rows, frame);
-}
-
-void CopyVPlane(const uint8* source, int stride, int rows, VideoFrame* frame) {
- CopyPlane(VideoFrame::kVPlane, source, stride, rows, frame);
-}
-
-void FillYUV(VideoFrame* frame, uint8 y, uint8 u, uint8 v) {
- // Fill the Y plane.
- uint8* y_plane = frame->data(VideoFrame::kYPlane);
- int y_rows = frame->rows(VideoFrame::kYPlane);
- int y_row_bytes = frame->row_bytes(VideoFrame::kYPlane);
- for (int i = 0; i < y_rows; ++i) {
- memset(y_plane, y, y_row_bytes);
- y_plane += frame->stride(VideoFrame::kYPlane);
- }
-
- // Fill the U and V planes.
- uint8* u_plane = frame->data(VideoFrame::kUPlane);
- uint8* v_plane = frame->data(VideoFrame::kVPlane);
- int uv_rows = frame->rows(VideoFrame::kUPlane);
- int u_row_bytes = frame->row_bytes(VideoFrame::kUPlane);
- int v_row_bytes = frame->row_bytes(VideoFrame::kVPlane);
- for (int i = 0; i < uv_rows; ++i) {
- memset(u_plane, u, u_row_bytes);
- memset(v_plane, v, v_row_bytes);
- u_plane += frame->stride(VideoFrame::kUPlane);
- v_plane += frame->stride(VideoFrame::kVPlane);
- }
-}
-
-} // namespace media
diff --git a/src/media/base/video_util.h b/src/media/base/video_util.h
deleted file mode 100644
index 562ad7d..0000000
--- a/src/media/base/video_util.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_VIDEO_UTIL_H_
-#define MEDIA_BASE_VIDEO_UTIL_H_
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-class VideoFrame;
-
-// Computes the size of |visible_size| for a given aspect ratio.
-MEDIA_EXPORT gfx::Size GetNaturalSize(const gfx::Size& visible_size,
- int aspect_ratio_numerator,
- int aspect_ratio_denominator);
-
-// Copies a plane of YUV source into a VideoFrame object, taking into account
-// source and destinations dimensions.
-//
-// NOTE: rows is *not* the same as height!
-MEDIA_EXPORT void CopyYPlane(const uint8* source, int stride, int rows,
- VideoFrame* frame);
-MEDIA_EXPORT void CopyUPlane(const uint8* source, int stride, int rows,
- VideoFrame* frame);
-MEDIA_EXPORT void CopyVPlane(const uint8* source, int stride, int rows,
- VideoFrame* frame);
-
-// Fills |frame| containing YUV data to the given color values.
-MEDIA_EXPORT void FillYUV(VideoFrame* frame, uint8 y, uint8 u, uint8 v);
-
-} // namespace media
-
-#endif // MEDIA_BASE_VIDEO_UTIL_H_
diff --git a/src/media/base/video_util_unittest.cc b/src/media/base/video_util_unittest.cc
deleted file mode 100644
index d4f2e29..0000000
--- a/src/media/base/video_util_unittest.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/memory/scoped_ptr.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class VideoUtilTest : public testing::Test {
- public:
- VideoUtilTest()
- : height_(0),
- y_stride_(0),
- u_stride_(0),
- v_stride_(0) {
- }
-
- virtual ~VideoUtilTest() {}
-
- void CreateSourceFrame(int width, int height,
- int y_stride, int u_stride, int v_stride) {
- EXPECT_GE(y_stride, width);
- EXPECT_GE(u_stride, width / 2);
- EXPECT_GE(v_stride, width / 2);
-
- height_ = height;
- y_stride_ = y_stride;
- u_stride_ = u_stride;
- v_stride_ = v_stride;
-
- y_plane_.reset(new uint8[y_stride * height]);
- u_plane_.reset(new uint8[u_stride * height / 2]);
- v_plane_.reset(new uint8[v_stride * height / 2]);
- }
-
- void CreateDestinationFrame(int width, int height) {
- gfx::Size size(width, height);
- destination_frame_ =
- VideoFrame::CreateFrame(VideoFrame::YV12, size, gfx::Rect(size), size,
- base::TimeDelta());
- }
-
- void CopyPlanes() {
- CopyYPlane(y_plane_.get(), y_stride_, height_, destination_frame_);
- CopyUPlane(u_plane_.get(), u_stride_, height_ / 2, destination_frame_);
- CopyVPlane(v_plane_.get(), v_stride_, height_ / 2, destination_frame_);
- }
-
- private:
- scoped_array<uint8> y_plane_;
- scoped_array<uint8> u_plane_;
- scoped_array<uint8> v_plane_;
-
- int height_;
- int y_stride_;
- int u_stride_;
- int v_stride_;
-
- scoped_refptr<VideoFrame> destination_frame_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoUtilTest);
-};
-
-TEST_F(VideoUtilTest, CopyPlane_Exact) {
- CreateSourceFrame(16, 16, 16, 8, 8);
- CreateDestinationFrame(16, 16);
- CopyPlanes();
-}
-
-TEST_F(VideoUtilTest, CopyPlane_SmallerSource) {
- CreateSourceFrame(8, 8, 8, 4, 4);
- CreateDestinationFrame(16, 16);
- CopyPlanes();
-}
-
-TEST_F(VideoUtilTest, CopyPlane_SmallerDestination) {
- CreateSourceFrame(16, 16, 16, 8, 8);
- CreateDestinationFrame(8, 8);
- CopyPlanes();
-}
-
-} // namespace media
diff --git a/src/media/base/yuv_convert.cc b/src/media/base/yuv_convert.cc
deleted file mode 100644
index 4969a5c..0000000
--- a/src/media/base/yuv_convert.cc
+++ /dev/null
@@ -1,594 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This webpage shows layout of YV12 and other YUV formats
-// http://www.fourcc.org/yuv.php
-// The actual conversion is best described here
-// http://en.wikipedia.org/wiki/YUV
-// An article on optimizing YUV conversion using tables instead of multiplies
-// http://lestourtereaux.free.fr/papers/data/yuvrgb.pdf
-//
-// YV12 is a full plane of Y and a half height, half width chroma planes
-// YV16 is a full plane of Y and a full height, half width chroma planes
-//
-// ARGB pixel format is output, which on little endian is stored as BGRA.
-// The alpha is set to 255, allowing the application to use RGBA or RGB32.
-
-#include "media/base/yuv_convert.h"
-
-#include "base/cpu.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
-#include "media/base/simd/convert_yuv_to_rgb.h"
-#include "media/base/simd/filter_yuv.h"
-
-#if defined(ARCH_CPU_X86_FAMILY)
-#if defined(COMPILER_MSVC)
-#include <intrin.h>
-#else
-#include <mmintrin.h>
-#endif
-#endif
-
-namespace media {
-
-static FilterYUVRowsProc ChooseFilterYUVRowsProc() {
-#if defined(ARCH_CPU_X86_FAMILY)
- base::CPU cpu;
- if (cpu.has_sse2())
- return &FilterYUVRows_SSE2;
- if (cpu.has_mmx())
- return &FilterYUVRows_MMX;
-#endif
- return &FilterYUVRows_C;
-}
-
-static ConvertYUVToRGB32RowProc ChooseConvertYUVToRGB32RowProc() {
-#if defined(ARCH_CPU_X86_FAMILY)
- base::CPU cpu;
- if (cpu.has_sse())
- return &ConvertYUVToRGB32Row_SSE;
- if (cpu.has_mmx())
- return &ConvertYUVToRGB32Row_MMX;
-#endif
- return &ConvertYUVToRGB32Row_C;
-}
-
-static ScaleYUVToRGB32RowProc ChooseScaleYUVToRGB32RowProc() {
-#if defined(ARCH_CPU_X86_64)
- // Use 64-bits version if possible.
- return &ScaleYUVToRGB32Row_SSE2_X64;
-#elif defined(ARCH_CPU_X86_FAMILY)
- base::CPU cpu;
- // Choose the best one on 32-bits system.
- if (cpu.has_sse())
- return &ScaleYUVToRGB32Row_SSE;
- if (cpu.has_mmx())
- return &ScaleYUVToRGB32Row_MMX;
-#endif // defined(ARCH_CPU_X86_64)
- return &ScaleYUVToRGB32Row_C;
-}
-
-static ScaleYUVToRGB32RowProc ChooseLinearScaleYUVToRGB32RowProc() {
-#if defined(ARCH_CPU_X86_64)
- // Use 64-bits version if possible.
- return &LinearScaleYUVToRGB32Row_MMX_X64;
-#elif defined(ARCH_CPU_X86_FAMILY)
- base::CPU cpu;
- // 32-bits system.
- if (cpu.has_sse())
- return &LinearScaleYUVToRGB32Row_SSE;
- if (cpu.has_mmx())
- return &LinearScaleYUVToRGB32Row_MMX;
-#endif // defined(ARCH_CPU_X86_64)
- return &LinearScaleYUVToRGB32Row_C;
-}
-
-// Empty SIMD registers state after using them.
-void EmptyRegisterState() {
-#if defined(ARCH_CPU_X86_FAMILY)
- static bool checked = false;
- static bool has_mmx = false;
- if (!checked) {
- base::CPU cpu;
- has_mmx = cpu.has_mmx();
- checked = true;
- }
- if (has_mmx)
- _mm_empty();
-#endif
-}
-
-// 16.16 fixed point arithmetic
-const int kFractionBits = 16;
-const int kFractionMax = 1 << kFractionBits;
-const int kFractionMask = ((1 << kFractionBits) - 1);
-
-// Scale a frame of YUV to 32 bit ARGB.
-void ScaleYUVToRGB32(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int source_width,
- int source_height,
- int width,
- int height,
- int y_pitch,
- int uv_pitch,
- int rgb_pitch,
- YUVType yuv_type,
- Rotate view_rotate,
- ScaleFilter filter) {
- static FilterYUVRowsProc filter_proc = NULL;
- static ConvertYUVToRGB32RowProc convert_proc = NULL;
- static ScaleYUVToRGB32RowProc scale_proc = NULL;
- static ScaleYUVToRGB32RowProc linear_scale_proc = NULL;
-
- if (!filter_proc)
- filter_proc = ChooseFilterYUVRowsProc();
- if (!convert_proc)
- convert_proc = ChooseConvertYUVToRGB32RowProc();
- if (!scale_proc)
- scale_proc = ChooseScaleYUVToRGB32RowProc();
- if (!linear_scale_proc)
- linear_scale_proc = ChooseLinearScaleYUVToRGB32RowProc();
-
- // Handle zero sized sources and destinations.
- if ((yuv_type == YV12 && (source_width < 2 || source_height < 2)) ||
- (yuv_type == YV16 && (source_width < 2 || source_height < 1)) ||
- width == 0 || height == 0)
- return;
-
- // 4096 allows 3 buffers to fit in 12k.
- // Helps performance on CPU with 16K L1 cache.
- // Large enough for 3830x2160 and 30" displays which are 2560x1600.
- const int kFilterBufferSize = 4096;
- // Disable filtering if the screen is too big (to avoid buffer overflows).
- // This should never happen to regular users: they don't have monitors
- // wider than 4096 pixels.
- // TODO(fbarchard): Allow rotated videos to filter.
- if (source_width > kFilterBufferSize || view_rotate)
- filter = FILTER_NONE;
-
- unsigned int y_shift = yuv_type;
- // Diagram showing origin and direction of source sampling.
- // ->0 4<-
- // 7 3
- //
- // 6 5
- // ->1 2<-
- // Rotations that start at right side of image.
- if ((view_rotate == ROTATE_180) ||
- (view_rotate == ROTATE_270) ||
- (view_rotate == MIRROR_ROTATE_0) ||
- (view_rotate == MIRROR_ROTATE_90)) {
- y_buf += source_width - 1;
- u_buf += source_width / 2 - 1;
- v_buf += source_width / 2 - 1;
- source_width = -source_width;
- }
- // Rotations that start at bottom of image.
- if ((view_rotate == ROTATE_90) ||
- (view_rotate == ROTATE_180) ||
- (view_rotate == MIRROR_ROTATE_90) ||
- (view_rotate == MIRROR_ROTATE_180)) {
- y_buf += (source_height - 1) * y_pitch;
- u_buf += ((source_height >> y_shift) - 1) * uv_pitch;
- v_buf += ((source_height >> y_shift) - 1) * uv_pitch;
- source_height = -source_height;
- }
-
- int source_dx = source_width * kFractionMax / width;
-
- if ((view_rotate == ROTATE_90) ||
- (view_rotate == ROTATE_270)) {
- int tmp = height;
- height = width;
- width = tmp;
- tmp = source_height;
- source_height = source_width;
- source_width = tmp;
- int source_dy = source_height * kFractionMax / height;
- source_dx = ((source_dy >> kFractionBits) * y_pitch) << kFractionBits;
- if (view_rotate == ROTATE_90) {
- y_pitch = -1;
- uv_pitch = -1;
- source_height = -source_height;
- } else {
- y_pitch = 1;
- uv_pitch = 1;
- }
- }
-
- // Need padding because FilterRows() will write 1 to 16 extra pixels
- // after the end for SSE2 version.
- uint8 yuvbuf[16 + kFilterBufferSize * 3 + 16];
- uint8* ybuf =
- reinterpret_cast<uint8*>(reinterpret_cast<uintptr_t>(yuvbuf + 15) & ~15);
- uint8* ubuf = ybuf + kFilterBufferSize;
- uint8* vbuf = ubuf + kFilterBufferSize;
-
- // TODO(fbarchard): Fixed point math is off by 1 on negatives.
-
- // We take a y-coordinate in [0,1] space in the source image space, and
- // transform to a y-coordinate in [0,1] space in the destination image space.
- // Note that the coordinate endpoints lie on pixel boundaries, not on pixel
- // centers: e.g. a two-pixel-high image will have pixel centers at 0.25 and
- // 0.75. The formula is as follows (in fixed-point arithmetic):
- // y_dst = dst_height * ((y_src + 0.5) / src_height)
- // dst_pixel = clamp([0, dst_height - 1], floor(y_dst - 0.5))
- // Implement this here as an accumulator + delta, to avoid expensive math
- // in the loop.
- int source_y_subpixel_accum =
- ((kFractionMax / 2) * source_height) / height - (kFractionMax / 2);
- int source_y_subpixel_delta = ((1 << kFractionBits) * source_height) / height;
-
- // TODO(fbarchard): Split this into separate function for better efficiency.
- for (int y = 0; y < height; ++y) {
- uint8* dest_pixel = rgb_buf + y * rgb_pitch;
- int source_y_subpixel = source_y_subpixel_accum;
- source_y_subpixel_accum += source_y_subpixel_delta;
- if (source_y_subpixel < 0)
- source_y_subpixel = 0;
- else if (source_y_subpixel > ((source_height - 1) << kFractionBits))
- source_y_subpixel = (source_height - 1) << kFractionBits;
-
- const uint8* y_ptr = NULL;
- const uint8* u_ptr = NULL;
- const uint8* v_ptr = NULL;
- // Apply vertical filtering if necessary.
- // TODO(fbarchard): Remove memcpy when not necessary.
- if (filter & media::FILTER_BILINEAR_V) {
- int source_y = source_y_subpixel >> kFractionBits;
- y_ptr = y_buf + source_y * y_pitch;
- u_ptr = u_buf + (source_y >> y_shift) * uv_pitch;
- v_ptr = v_buf + (source_y >> y_shift) * uv_pitch;
-
- // Vertical scaler uses 16.8 fixed point.
- int source_y_fraction =
- (source_y_subpixel & kFractionMask) >> 8;
- if (source_y_fraction != 0) {
- filter_proc(ybuf, y_ptr, y_ptr + y_pitch, source_width,
- source_y_fraction);
- } else {
- memcpy(ybuf, y_ptr, source_width);
- }
- y_ptr = ybuf;
- ybuf[source_width] = ybuf[source_width-1];
-
- int uv_source_width = (source_width + 1) / 2;
- int source_uv_fraction;
-
- // For formats with half-height UV planes, each even-numbered pixel row
- // should not interpolate, since the next row to interpolate from should
- // be a duplicate of the current row.
- if (y_shift && (source_y & 0x1) == 0)
- source_uv_fraction = 0;
- else
- source_uv_fraction = source_y_fraction;
-
- if (source_uv_fraction != 0) {
- filter_proc(ubuf, u_ptr, u_ptr + uv_pitch, uv_source_width,
- source_uv_fraction);
- filter_proc(vbuf, v_ptr, v_ptr + uv_pitch, uv_source_width,
- source_uv_fraction);
- } else {
- memcpy(ubuf, u_ptr, uv_source_width);
- memcpy(vbuf, v_ptr, uv_source_width);
- }
- u_ptr = ubuf;
- v_ptr = vbuf;
- ubuf[uv_source_width] = ubuf[uv_source_width - 1];
- vbuf[uv_source_width] = vbuf[uv_source_width - 1];
- } else {
- // Offset by 1/2 pixel for center sampling.
- int source_y = (source_y_subpixel + (kFractionMax / 2)) >> kFractionBits;
- y_ptr = y_buf + source_y * y_pitch;
- u_ptr = u_buf + (source_y >> y_shift) * uv_pitch;
- v_ptr = v_buf + (source_y >> y_shift) * uv_pitch;
- }
- if (source_dx == kFractionMax) { // Not scaled
- convert_proc(y_ptr, u_ptr, v_ptr, dest_pixel, width);
- } else {
- if (filter & FILTER_BILINEAR_H) {
- linear_scale_proc(y_ptr, u_ptr, v_ptr, dest_pixel, width, source_dx);
- } else {
- scale_proc(y_ptr, u_ptr, v_ptr, dest_pixel, width, source_dx);
- }
- }
- }
-
- EmptyRegisterState();
-}
-
-// Scale a frame of YV12 to 32 bit ARGB for a specific rectangle.
-void ScaleYUVToRGB32WithRect(const uint8* y_buf,
- const uint8* u_buf,
- const uint8* v_buf,
- uint8* rgb_buf,
- int source_width,
- int source_height,
- int dest_width,
- int dest_height,
- int dest_rect_left,
- int dest_rect_top,
- int dest_rect_right,
- int dest_rect_bottom,
- int y_pitch,
- int uv_pitch,
- int rgb_pitch) {
- static FilterYUVRowsProc filter_proc = NULL;
- if (!filter_proc)
- filter_proc = ChooseFilterYUVRowsProc();
-
- // This routine doesn't currently support up-scaling.
- CHECK_LE(dest_width, source_width);
- CHECK_LE(dest_height, source_height);
-
- // Sanity-check the destination rectangle.
- DCHECK(dest_rect_left >= 0 && dest_rect_right <= dest_width);
- DCHECK(dest_rect_top >= 0 && dest_rect_bottom <= dest_height);
- DCHECK(dest_rect_right > dest_rect_left);
- DCHECK(dest_rect_bottom > dest_rect_top);
-
- // Fixed-point value of vertical and horizontal scale down factor.
- // Values are in the format 16.16.
- int y_step = kFractionMax * source_height / dest_height;
- int x_step = kFractionMax * source_width / dest_width;
-
- // Determine the coordinates of the rectangle in 16.16 coords.
- // NB: Our origin is the *center* of the top/left pixel, NOT its top/left.
- // If we're down-scaling by more than a factor of two, we start with a 50%
- // fraction to avoid degenerating to point-sampling - we should really just
- // fix the fraction at 50% for all pixels in that case.
- int source_left = dest_rect_left * x_step;
- int source_right = (dest_rect_right - 1) * x_step;
- if (x_step < kFractionMax * 2) {
- source_left += ((x_step - kFractionMax) / 2);
- source_right += ((x_step - kFractionMax) / 2);
- } else {
- source_left += kFractionMax / 2;
- source_right += kFractionMax / 2;
- }
- int source_top = dest_rect_top * y_step;
- if (y_step < kFractionMax * 2) {
- source_top += ((y_step - kFractionMax) / 2);
- } else {
- source_top += kFractionMax / 2;
- }
-
- // Determine the parts of the Y, U and V buffers to interpolate.
- int source_y_left = source_left >> kFractionBits;
- int source_y_right = std::min(
- (source_right >> kFractionBits) + 2,
- source_width + 1);
-
- int source_uv_left = source_y_left / 2;
- int source_uv_right = std::min(
- (source_right >> (kFractionBits + 1)) + 2,
- (source_width + 1) / 2);
-
- int source_y_width = source_y_right - source_y_left;
- int source_uv_width = source_uv_right - source_uv_left;
-
- // Determine number of pixels in each output row.
- int dest_rect_width = dest_rect_right - dest_rect_left;
-
- // Intermediate buffer for vertical interpolation.
- // 4096 bytes allows 3 buffers to fit in 12k, which fits in a 16K L1 cache,
- // and is bigger than most users will generally need.
- // The buffer is 16-byte aligned and padded with 16 extra bytes; some of the
- // FilterYUVRowProcs have alignment requirements, and the SSE version can
- // write up to 16 bytes past the end of the buffer.
- const int kFilterBufferSize = 4096;
- if (source_width > kFilterBufferSize)
- filter_proc = NULL;
- uint8 yuv_temp[16 + kFilterBufferSize * 3 + 16];
- uint8* y_temp =
- reinterpret_cast<uint8*>(
- reinterpret_cast<uintptr_t>(yuv_temp + 15) & ~15);
- uint8* u_temp = y_temp + kFilterBufferSize;
- uint8* v_temp = u_temp + kFilterBufferSize;
-
- // Move to the top-left pixel of output.
- rgb_buf += dest_rect_top * rgb_pitch;
- rgb_buf += dest_rect_left * 4;
-
- // For each destination row perform interpolation and color space
- // conversion to produce the output.
- for (int row = dest_rect_top; row < dest_rect_bottom; ++row) {
- // Round the fixed-point y position to get the current row.
- int source_row = source_top >> kFractionBits;
- int source_uv_row = source_row / 2;
- DCHECK(source_row < source_height);
-
- // Locate the first row for each plane for interpolation.
- const uint8* y0_ptr = y_buf + y_pitch * source_row + source_y_left;
- const uint8* u0_ptr = u_buf + uv_pitch * source_uv_row + source_uv_left;
- const uint8* v0_ptr = v_buf + uv_pitch * source_uv_row + source_uv_left;
- const uint8* y1_ptr = NULL;
- const uint8* u1_ptr = NULL;
- const uint8* v1_ptr = NULL;
-
- // Locate the second row for interpolation, being careful not to overrun.
- if (source_row + 1 >= source_height) {
- y1_ptr = y0_ptr;
- } else {
- y1_ptr = y0_ptr + y_pitch;
- }
- if (source_uv_row + 1 >= (source_height + 1) / 2) {
- u1_ptr = u0_ptr;
- v1_ptr = v0_ptr;
- } else {
- u1_ptr = u0_ptr + uv_pitch;
- v1_ptr = v0_ptr + uv_pitch;
- }
-
- if (filter_proc) {
- // Vertical scaler uses 16.8 fixed point.
- int fraction = (source_top & kFractionMask) >> 8;
- filter_proc(y_temp + source_y_left, y0_ptr, y1_ptr,
- source_y_width, fraction);
- filter_proc(u_temp + source_uv_left, u0_ptr, u1_ptr,
- source_uv_width, fraction);
- filter_proc(v_temp + source_uv_left, v0_ptr, v1_ptr,
- source_uv_width, fraction);
-
- // Perform horizontal interpolation and color space conversion.
- // TODO(hclam): Use the MMX version after more testing.
- LinearScaleYUVToRGB32RowWithRange_C(
- y_temp, u_temp, v_temp, rgb_buf,
- dest_rect_width, source_left, x_step);
- } else {
- // If the frame is too large then we linear scale a single row.
- LinearScaleYUVToRGB32RowWithRange_C(
- y0_ptr, u0_ptr, v0_ptr, rgb_buf,
- dest_rect_width, source_left, x_step);
- }
-
- // Advance vertically in the source and destination image.
- source_top += y_step;
- rgb_buf += rgb_pitch;
- }
-
- EmptyRegisterState();
-}
-
-void ConvertRGB32ToYUV(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
- static void (*convert_proc)(const uint8*, uint8*, uint8*, uint8*,
- int, int, int, int, int) = NULL;
- if (!convert_proc) {
-#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY)
- // For ARM and MIPS processors, always use C version.
- // TODO(hclam): Implement a NEON version.
- convert_proc = &ConvertRGB32ToYUV_C;
-#elif defined(__LB_PS3__)
- // For these, always use C version.
- convert_proc = &ConvertRGB32ToYUV_C;
-#else
- // TODO(hclam): Switch to SSSE3 version when the cyan problem is solved.
- // See: crbug.com/100462
- base::CPU cpu;
- if (cpu.has_sse2())
- convert_proc = &ConvertRGB32ToYUV_SSE2;
- else
- convert_proc = &ConvertRGB32ToYUV_C;
-#endif
- }
-
- convert_proc(rgbframe, yplane, uplane, vplane, width, height,
- rgbstride, ystride, uvstride);
-}
-
-void ConvertRGB24ToYUV(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride) {
-#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY)
- ConvertRGB24ToYUV_C(rgbframe, yplane, uplane, vplane, width, height,
- rgbstride, ystride, uvstride);
-#else
- static void (*convert_proc)(const uint8*, uint8*, uint8*, uint8*,
- int, int, int, int, int) = NULL;
- if (!convert_proc) {
- base::CPU cpu;
- if (cpu.has_ssse3())
- convert_proc = &ConvertRGB24ToYUV_SSSE3;
- else
- convert_proc = &ConvertRGB24ToYUV_C;
- }
- convert_proc(rgbframe, yplane, uplane, vplane, width, height,
- rgbstride, ystride, uvstride);
-#endif
-}
-
-void ConvertYUY2ToYUV(const uint8* src,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height) {
- for (int i = 0; i < height / 2; ++i) {
- for (int j = 0; j < (width / 2); ++j) {
- yplane[0] = src[0];
- *uplane = src[1];
- yplane[1] = src[2];
- *vplane = src[3];
- src += 4;
- yplane += 2;
- uplane++;
- vplane++;
- }
- for (int j = 0; j < (width / 2); ++j) {
- yplane[0] = src[0];
- yplane[1] = src[2];
- src += 4;
- yplane += 2;
- }
- }
-}
-
-void ConvertNV21ToYUV(const uint8* src,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height) {
- int y_plane_size = width * height;
- memcpy(yplane, src, y_plane_size);
-
- src += y_plane_size;
- int u_plane_size = y_plane_size >> 2;
- for (int i = 0; i < u_plane_size; ++i) {
- *vplane++ = *src++;
- *uplane++ = *src++;
- }
-}
-
-void ConvertYUVToRGB32(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type) {
-#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY)
- ConvertYUVToRGB32_C(yplane, uplane, vplane, rgbframe,
- width, height, ystride, uvstride, rgbstride, yuv_type);
-#else
- static ConvertYUVToRGB32Proc convert_proc = NULL;
- if (!convert_proc) {
- base::CPU cpu;
- if (cpu.has_sse())
- convert_proc = &ConvertYUVToRGB32_SSE;
- else if (cpu.has_mmx())
- convert_proc = &ConvertYUVToRGB32_MMX;
- else
- convert_proc = &ConvertYUVToRGB32_C;
- }
-
- convert_proc(yplane, uplane, vplane, rgbframe,
- width, height, ystride, uvstride, rgbstride, yuv_type);
-#endif
-}
-
-} // namespace media
diff --git a/src/media/base/yuv_convert.h b/src/media/base/yuv_convert.h
deleted file mode 100644
index afd47d7..0000000
--- a/src/media/base/yuv_convert.h
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_YUV_CONVERT_H_
-#define MEDIA_BASE_YUV_CONVERT_H_
-
-#include "base/basictypes.h"
-
-namespace media {
-
-// Type of YUV surface.
-// The value of these enums matter as they are used to shift vertical indices.
-enum YUVType {
- YV16 = 0, // YV16 is half width and full height chroma channels.
- YV12 = 1, // YV12 is half width and half height chroma channels.
-};
-
-// Mirror means flip the image horizontally, as in looking in a mirror.
-// Rotate happens after mirroring.
-enum Rotate {
- ROTATE_0, // Rotation off.
- ROTATE_90, // Rotate clockwise.
- ROTATE_180, // Rotate upside down.
- ROTATE_270, // Rotate counter clockwise.
- MIRROR_ROTATE_0, // Mirror horizontally.
- MIRROR_ROTATE_90, // Mirror then Rotate clockwise.
- MIRROR_ROTATE_180, // Mirror vertically.
- MIRROR_ROTATE_270, // Transpose.
-};
-
-// Filter affects how scaling looks.
-enum ScaleFilter {
- FILTER_NONE = 0, // No filter (point sampled).
- FILTER_BILINEAR_H = 1, // Bilinear horizontal filter.
- FILTER_BILINEAR_V = 2, // Bilinear vertical filter.
- FILTER_BILINEAR = 3, // Bilinear filter.
-};
-
-// Convert a frame of YUV to 32 bit ARGB.
-// Pass in YV16/YV12 depending on source format
-void ConvertYUVToRGB32(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type);
-
-// Scale a frame of YUV to 32 bit ARGB.
-// Supports rotation and mirroring.
-void ScaleYUVToRGB32(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int source_width,
- int source_height,
- int width,
- int height,
- int ystride,
- int uvstride,
- int rgbstride,
- YUVType yuv_type,
- Rotate view_rotate,
- ScaleFilter filter);
-
-// Biliner Scale a frame of YV12 to 32 bits ARGB on a specified rectangle.
-// |yplane|, etc and |rgbframe| should point to the top-left pixels of the
-// source and destination buffers.
-void ScaleYUVToRGB32WithRect(const uint8* yplane,
- const uint8* uplane,
- const uint8* vplane,
- uint8* rgbframe,
- int source_width,
- int source_height,
- int dest_width,
- int dest_height,
- int dest_rect_left,
- int dest_rect_top,
- int dest_rect_right,
- int dest_rect_bottom,
- int ystride,
- int uvstride,
- int rgbstride);
-
-void ConvertRGB32ToYUV(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-void ConvertRGB24ToYUV(const uint8* rgbframe,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height,
- int rgbstride,
- int ystride,
- int uvstride);
-
-void ConvertYUY2ToYUV(const uint8* src,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height);
-
-void ConvertNV21ToYUV(const uint8* src,
- uint8* yplane,
- uint8* uplane,
- uint8* vplane,
- int width,
- int height);
-
-// Empty SIMD register state after calling optimized scaler functions.
-// This method is only used in unit test after calling SIMD functions.
-void EmptyRegisterState();
-
-} // namespace media
-
-#endif // MEDIA_BASE_YUV_CONVERT_H_
diff --git a/src/media/base/yuv_convert_unittest.cc b/src/media/base/yuv_convert_unittest.cc
deleted file mode 100644
index c57f715..0000000
--- a/src/media/base/yuv_convert_unittest.cc
+++ /dev/null
@@ -1,937 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/base_paths.h"
-#include "base/cpu.h"
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "media/base/djb2.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
-#include "media/base/simd/convert_yuv_to_rgb.h"
-#include "media/base/simd/filter_yuv.h"
-#include "media/base/yuv_convert.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/rect.h"
-
-// Size of raw image.
-static const int kSourceWidth = 640;
-static const int kSourceHeight = 360;
-static const int kSourceYSize = kSourceWidth * kSourceHeight;
-static const int kSourceUOffset = kSourceYSize;
-static const int kSourceVOffset = kSourceYSize * 5 / 4;
-static const int kScaledWidth = 1024;
-static const int kScaledHeight = 768;
-static const int kDownScaledWidth = 512;
-static const int kDownScaledHeight = 320;
-static const int kBpp = 4;
-
-// Surface sizes for various test files.
-static const int kYUV12Size = kSourceYSize * 12 / 8;
-static const int kYUV16Size = kSourceYSize * 16 / 8;
-static const int kYUY2Size = kSourceYSize * 16 / 8;
-static const int kRGBSize = kSourceYSize * kBpp;
-static const int kRGBSizeScaled = kScaledWidth * kScaledHeight * kBpp;
-static const int kRGB24Size = kSourceYSize * 3;
-static const int kRGBSizeConverted = kSourceYSize * kBpp;
-
-// Helper for reading test data into a scoped_array<uint8>.
-static void ReadData(const FilePath::CharType* filename,
- int expected_size,
- scoped_array<uint8>* data) {
- data->reset(new uint8[expected_size]);
-
- FilePath path;
- CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &path));
- path = path.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .Append(filename);
-
- // Verify file size is correct.
- int64 actual_size = 0;
- file_util::GetFileSize(path, &actual_size);
- CHECK_EQ(actual_size, expected_size);
-
- // Verify bytes read are correct.
- int bytes_read = file_util::ReadFile(
- path, reinterpret_cast<char*>(data->get()), expected_size);
- CHECK_EQ(bytes_read, expected_size);
-}
-
-static void ReadYV12Data(scoped_array<uint8>* data) {
- ReadData(FILE_PATH_LITERAL("bali_640x360_P420.yuv"), kYUV12Size, data);
-}
-
-static void ReadYV16Data(scoped_array<uint8>* data) {
- ReadData(FILE_PATH_LITERAL("bali_640x360_P422.yuv"), kYUV16Size, data);
-}
-
-static void ReadRGB24Data(scoped_array<uint8>* data) {
- ReadData(FILE_PATH_LITERAL("bali_640x360_RGB24.rgb"), kRGB24Size, data);
-}
-
-static void ReadYUY2Data(scoped_array<uint8>* data) {
- ReadData(FILE_PATH_LITERAL("bali_640x360_YUY2.yuv"), kYUY2Size, data);
-}
-
-TEST(YUVConvertTest, YV12) {
- // Allocate all surfaces.
- scoped_array<uint8> yuv_bytes;
- scoped_array<uint8> rgb_bytes(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_converted_bytes(new uint8[kRGBSizeConverted]);
-
- // Read YUV reference data from file.
- ReadYV12Data(&yuv_bytes);
-
- // Convert a frame of YUV to 32 bit ARGB.
- media::ConvertYUVToRGB32(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_converted_bytes.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- media::YV12);
-
- uint32 rgb_hash = DJB2Hash(rgb_converted_bytes.get(), kRGBSizeConverted,
- kDJB2HashSeed);
- EXPECT_EQ(2413171226u, rgb_hash);
-}
-
-TEST(YUVConvertTest, YV16) {
- // Allocate all surfaces.
- scoped_array<uint8> yuv_bytes;
- scoped_array<uint8> rgb_bytes(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_converted_bytes(new uint8[kRGBSizeConverted]);
-
- // Read YUV reference data from file.
- ReadYV16Data(&yuv_bytes);
-
- // Convert a frame of YUV to 32 bit ARGB.
- media::ConvertYUVToRGB32(yuv_bytes.get(), // Y
- yuv_bytes.get() + kSourceUOffset, // U
- yuv_bytes.get() + kSourceYSize * 3 / 2, // V
- rgb_converted_bytes.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- media::YV16);
-
- uint32 rgb_hash = DJB2Hash(rgb_converted_bytes.get(), kRGBSizeConverted,
- kDJB2HashSeed);
- EXPECT_EQ(4222342047u, rgb_hash);
-}
-
-struct YUVScaleTestData {
- YUVScaleTestData(media::YUVType y, media::ScaleFilter s, uint32 r)
- : yuv_type(y),
- scale_filter(s),
- rgb_hash(r) {
- }
-
- media::YUVType yuv_type;
- media::ScaleFilter scale_filter;
- uint32 rgb_hash;
-};
-
-class YUVScaleTest : public ::testing::TestWithParam<YUVScaleTestData> {
- public:
- YUVScaleTest() {
- switch (GetParam().yuv_type) {
- case media::YV12:
- ReadYV12Data(&yuv_bytes_);
- break;
- case media::YV16:
- ReadYV16Data(&yuv_bytes_);
- break;
- }
-
- rgb_bytes_.reset(new uint8[kRGBSizeScaled]);
- }
-
- // Helpers for getting the proper Y, U and V plane offsets.
- uint8* y_plane() { return yuv_bytes_.get(); }
- uint8* u_plane() { return yuv_bytes_.get() + kSourceYSize; }
- uint8* v_plane() {
- switch (GetParam().yuv_type) {
- case media::YV12:
- return yuv_bytes_.get() + kSourceVOffset;
- case media::YV16:
- return yuv_bytes_.get() + kSourceYSize * 3 / 2;
- }
- return NULL;
- }
-
- scoped_array<uint8> yuv_bytes_;
- scoped_array<uint8> rgb_bytes_;
-};
-
-TEST_P(YUVScaleTest, NoScale) {
- media::ScaleYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kSourceWidth * kBpp, // RgbStride
- GetParam().yuv_type,
- media::ROTATE_0,
- GetParam().scale_filter);
-
- uint32 yuv_hash = DJB2Hash(rgb_bytes_.get(), kRGBSize, kDJB2HashSeed);
-
- media::ConvertYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- GetParam().yuv_type);
-
- uint32 rgb_hash = DJB2Hash(rgb_bytes_.get(), kRGBSize, kDJB2HashSeed);
-
- EXPECT_EQ(yuv_hash, rgb_hash);
-}
-
-TEST_P(YUVScaleTest, Normal) {
- media::ScaleYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kScaledWidth, kScaledHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kScaledWidth * kBpp, // RgbStride
- GetParam().yuv_type,
- media::ROTATE_0,
- GetParam().scale_filter);
-
- uint32 rgb_hash = DJB2Hash(rgb_bytes_.get(), kRGBSizeScaled, kDJB2HashSeed);
- EXPECT_EQ(GetParam().rgb_hash, rgb_hash);
-}
-
-TEST_P(YUVScaleTest, ZeroSourceSize) {
- media::ScaleYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- 0, 0, // Dimensions
- kScaledWidth, kScaledHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kScaledWidth * kBpp, // RgbStride
- GetParam().yuv_type,
- media::ROTATE_0,
- GetParam().scale_filter);
-
- // Testing for out-of-bound read/writes with AddressSanitizer.
-}
-
-TEST_P(YUVScaleTest, ZeroDestinationSize) {
- media::ScaleYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- 0, 0, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kScaledWidth * kBpp, // RgbStride
- GetParam().yuv_type,
- media::ROTATE_0,
- GetParam().scale_filter);
-
- // Testing for out-of-bound read/writes with AddressSanitizer.
-}
-
-TEST_P(YUVScaleTest, OddWidthAndHeightNotCrash) {
- media::ScaleYUVToRGB32(y_plane(), // Y
- u_plane(), // U
- v_plane(), // V
- rgb_bytes_.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- 3, 3, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kScaledWidth * kBpp, // RgbStride
- GetParam().yuv_type,
- media::ROTATE_0,
- GetParam().scale_filter);
-}
-
-INSTANTIATE_TEST_CASE_P(
- YUVScaleFormats, YUVScaleTest,
- ::testing::Values(
- YUVScaleTestData(media::YV12, media::FILTER_NONE, 4136904952u),
- YUVScaleTestData(media::YV16, media::FILTER_NONE, 1501777547u),
- YUVScaleTestData(media::YV12, media::FILTER_BILINEAR, 3164274689u),
- YUVScaleTestData(media::YV16, media::FILTER_BILINEAR, 3095878046u)));
-
-// This tests a known worst case YUV value, and for overflow.
-TEST(YUVConvertTest, Clamp) {
- // Allocate all surfaces.
- scoped_array<uint8> yuv_bytes(new uint8[1]);
- scoped_array<uint8> rgb_bytes(new uint8[1]);
- scoped_array<uint8> rgb_converted_bytes(new uint8[1]);
-
- // Values that failed previously in bug report.
- unsigned char y = 255u;
- unsigned char u = 255u;
- unsigned char v = 19u;
-
- // Prefill extra large destination buffer to test for overflow.
- unsigned char rgb[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
- unsigned char expected[8] = { 255, 255, 104, 255, 4, 5, 6, 7 };
- // Convert a frame of YUV to 32 bit ARGB.
- media::ConvertYUVToRGB32(&y, // Y
- &u, // U
- &v, // V
- &rgb[0], // RGB output
- 1, 1, // Dimensions
- 0, // YStride
- 0, // UVStride
- 0, // RGBStride
- media::YV12);
-
- int expected_test = memcmp(rgb, expected, sizeof(expected));
- EXPECT_EQ(0, expected_test);
-}
-
-TEST(YUVConvertTest, RGB24ToYUV) {
- // Allocate all surfaces.
- scoped_array<uint8> rgb_bytes;
- scoped_array<uint8> yuv_converted_bytes(new uint8[kYUV12Size]);
-
- // Read RGB24 reference data from file.
- ReadRGB24Data(&rgb_bytes);
-
- // Convert to I420.
- media::ConvertRGB24ToYUV(rgb_bytes.get(),
- yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth * 3, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- uint32 rgb_hash = DJB2Hash(yuv_converted_bytes.get(), kYUV12Size,
- kDJB2HashSeed);
- EXPECT_EQ(320824432u, rgb_hash);
-}
-
-TEST(YUVConvertTest, RGB32ToYUV) {
- // Allocate all surfaces.
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes(new uint8[kRGBSize]);
- scoped_array<uint8> yuv_converted_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_converted_bytes(new uint8[kRGBSize]);
-
- // Read YUV reference data from file.
- FilePath yuv_url;
- EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &yuv_url));
- yuv_url = yuv_url.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .Append(FILE_PATH_LITERAL("bali_640x360_P420.yuv"));
- EXPECT_EQ(static_cast<int>(kYUV12Size),
- file_util::ReadFile(yuv_url,
- reinterpret_cast<char*>(yuv_bytes.get()),
- static_cast<int>(kYUV12Size)));
-
- // Convert a frame of YUV to 32 bit ARGB.
- media::ConvertYUVToRGB32(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- media::YV12);
-
- // Convert RGB32 to YV12.
- media::ConvertRGB32ToYUV(rgb_bytes.get(),
- yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth * 4, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- // Convert YV12 back to RGB32.
- media::ConvertYUVToRGB32(yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- rgb_converted_bytes.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- media::YV12);
-
- int error = 0;
- for (int i = 0; i < kRGBSize; ++i) {
- int diff = rgb_converted_bytes[i] - rgb_bytes[i];
- if (diff < 0)
- diff = -diff;
- error += diff;
- }
-
- // Make sure error is within bound.
- DVLOG(1) << "Average error per channel: " << error / kRGBSize;
- EXPECT_GT(5, error / kRGBSize);
-}
-
-TEST(YUVConvertTest, YUY2ToYUV) {
- // Allocate all surfaces.
- scoped_array<uint8> yuy_bytes;
- scoped_array<uint8> yuv_converted_bytes(new uint8[kYUV12Size]);
-
- // Read YUY reference data from file.
- ReadYUY2Data(&yuy_bytes);
-
- // Convert to I420.
- media::ConvertYUY2ToYUV(yuy_bytes.get(),
- yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- kSourceWidth, kSourceHeight);
-
- uint32 yuy_hash = DJB2Hash(yuv_converted_bytes.get(), kYUV12Size,
- kDJB2HashSeed);
- EXPECT_EQ(666823187u, yuy_hash);
-}
-
-TEST(YUVConvertTest, DownScaleYUVToRGB32WithRect) {
- // Read YUV reference data from file.
- FilePath yuv_url;
- EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &yuv_url));
- yuv_url = yuv_url.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .Append(FILE_PATH_LITERAL("bali_640x360_P420.yuv"));
- const size_t size_of_yuv = kSourceYSize * 12 / 8; // 12 bpp.
- scoped_array<uint8> yuv_bytes(new uint8[size_of_yuv]);
- EXPECT_EQ(static_cast<int>(size_of_yuv),
- file_util::ReadFile(yuv_url,
- reinterpret_cast<char*>(yuv_bytes.get()),
- static_cast<int>(size_of_yuv)));
-
- // Scale the full frame of YUV to 32 bit ARGB.
- // The API currently only supports down-scaling, so we don't test up-scaling.
- const size_t size_of_rgb_scaled = kDownScaledWidth * kDownScaledHeight * kBpp;
- scoped_array<uint8> rgb_scaled_bytes(new uint8[size_of_rgb_scaled]);
- gfx::Rect sub_rect(0, 0, kDownScaledWidth, kDownScaledHeight);
-
- // We can't compare with the full-frame scaler because it uses slightly
- // different sampling coordinates.
- media::ScaleYUVToRGB32WithRect(
- yuv_bytes.get(), // Y
- yuv_bytes.get() + kSourceUOffset, // U
- yuv_bytes.get() + kSourceVOffset, // V
- rgb_scaled_bytes.get(), // Rgb output
- kSourceWidth, kSourceHeight, // Dimensions
- kDownScaledWidth, kDownScaledHeight, // Dimensions
- sub_rect.x(), sub_rect.y(), // Dest rect
- sub_rect.right(), sub_rect.bottom(), // Dest rect
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kDownScaledWidth * kBpp); // RgbStride
-
- uint32 rgb_hash_full_rect = DJB2Hash(rgb_scaled_bytes.get(),
- size_of_rgb_scaled,
- kDJB2HashSeed);
-
- // Re-scale sub-rectangles and verify the results are the same.
- int next_sub_rect = 0;
- while (!sub_rect.IsEmpty()) {
- // Scale a partial rectangle.
- media::ScaleYUVToRGB32WithRect(
- yuv_bytes.get(), // Y
- yuv_bytes.get() + kSourceUOffset, // U
- yuv_bytes.get() + kSourceVOffset, // V
- rgb_scaled_bytes.get(), // Rgb output
- kSourceWidth, kSourceHeight, // Dimensions
- kDownScaledWidth, kDownScaledHeight, // Dimensions
- sub_rect.x(), sub_rect.y(), // Dest rect
- sub_rect.right(), sub_rect.bottom(), // Dest rect
- kSourceWidth, // YStride
- kSourceWidth / 2, // UvStride
- kDownScaledWidth * kBpp); // RgbStride
- uint32 rgb_hash_sub_rect = DJB2Hash(rgb_scaled_bytes.get(),
- size_of_rgb_scaled,
- kDJB2HashSeed);
-
- EXPECT_EQ(rgb_hash_full_rect, rgb_hash_sub_rect);
-
- // Now pick choose a quarter rect of this sub-rect.
- if (next_sub_rect & 1)
- sub_rect.set_x(sub_rect.x() + sub_rect.width() / 2);
- if (next_sub_rect & 2)
- sub_rect.set_y(sub_rect.y() + sub_rect.height() / 2);
- sub_rect.set_width(sub_rect.width() / 2);
- sub_rect.set_height(sub_rect.height() / 2);
- next_sub_rect++;
- }
-}
-
-#if !defined(ARCH_CPU_ARM_FAMILY)
-TEST(YUVConvertTest, RGB32ToYUV_SSE2_MatchReference) {
- base::CPU cpu;
- if (!cpu.has_sse2()) {
- LOG(WARNING) << "System doesn't support SSE2, test not executed.";
- return;
- }
-
- // Allocate all surfaces.
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes(new uint8[kRGBSize]);
- scoped_array<uint8> yuv_converted_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> yuv_reference_bytes(new uint8[kYUV12Size]);
-
- ReadYV12Data(&yuv_bytes);
-
- // Convert a frame of YUV to 32 bit ARGB.
- media::ConvertYUVToRGB32(
- yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes.get(), // RGB output
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth, // YStride
- kSourceWidth / 2, // UVStride
- kSourceWidth * kBpp, // RGBStride
- media::YV12);
-
- // Convert RGB32 to YV12 with SSE2 version.
- media::ConvertRGB32ToYUV_SSE2(
- rgb_bytes.get(),
- yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth * 4, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- // Convert RGB32 to YV12 with reference version.
- media::ConvertRGB32ToYUV_SSE2_Reference(
- rgb_bytes.get(),
- yuv_reference_bytes.get(),
- yuv_reference_bytes.get() + kSourceUOffset,
- yuv_reference_bytes.get() + kSourceVOffset,
- kSourceWidth, kSourceHeight, // Dimensions
- kSourceWidth * 4, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- // Now convert a odd width and height, this overrides part of the buffer
- // generated above but that is fine because the point of this test is to
- // match the result with the reference code.
-
- // Convert RGB32 to YV12 with SSE2 version.
- media::ConvertRGB32ToYUV_SSE2(
- rgb_bytes.get(),
- yuv_converted_bytes.get(),
- yuv_converted_bytes.get() + kSourceUOffset,
- yuv_converted_bytes.get() + kSourceVOffset,
- 7, 7, // Dimensions
- kSourceWidth * 4, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- // Convert RGB32 to YV12 with reference version.
- media::ConvertRGB32ToYUV_SSE2_Reference(
- rgb_bytes.get(),
- yuv_reference_bytes.get(),
- yuv_reference_bytes.get() + kSourceUOffset,
- yuv_reference_bytes.get() + kSourceVOffset,
- 7, 7, // Dimensions
- kSourceWidth * 4, // RGBStride
- kSourceWidth, // YStride
- kSourceWidth / 2); // UVStride
-
- int error = 0;
- for (int i = 0; i < kYUV12Size; ++i) {
- int diff = yuv_reference_bytes[i] - yuv_converted_bytes[i];
- if (diff < 0)
- diff = -diff;
- error += diff;
- }
-
- // Make sure there's no difference from the reference.
- EXPECT_EQ(0, error);
-}
-
-TEST(YUVConvertTest, ConvertYUVToRGB32Row_MMX) {
- base::CPU cpu;
- if (!cpu.has_mmx()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- ConvertYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth);
- ConvertYUVToRGB32Row_MMX(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, ConvertYUVToRGB32Row_SSE) {
- base::CPU cpu;
- if (!cpu.has_sse()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- ConvertYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth);
- ConvertYUVToRGB32Row_SSE(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, ScaleYUVToRGB32Row_MMX) {
- base::CPU cpu;
- if (!cpu.has_mmx()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- ScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- ScaleYUVToRGB32Row_MMX(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, ScaleYUVToRGB32Row_SSE) {
- base::CPU cpu;
- if (!cpu.has_sse()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- ScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- ScaleYUVToRGB32Row_SSE(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_MMX) {
- base::CPU cpu;
- if (!cpu.has_mmx()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- LinearScaleYUVToRGB32Row_MMX(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_SSE) {
- base::CPU cpu;
- if (!cpu.has_sse()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- LinearScaleYUVToRGB32Row_SSE(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, FilterYUVRows_C_OutOfBounds) {
- scoped_array<uint8> src(new uint8[16]);
- scoped_array<uint8> dst(new uint8[16]);
-
- memset(src.get(), 0xff, 16);
- memset(dst.get(), 0, 16);
-
- media::FilterYUVRows_C(dst.get(), src.get(), src.get(), 1, 255);
-
- EXPECT_EQ(255u, dst[0]);
- for (int i = 1; i < 16; ++i) {
- EXPECT_EQ(0u, dst[i]) << " not equal at " << i;
- }
-}
-
-TEST(YUVConvertTest, FilterYUVRows_MMX_OutOfBounds) {
- base::CPU cpu;
- if (!cpu.has_mmx()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> src(new uint8[16]);
- scoped_array<uint8> dst(new uint8[16]);
-
- memset(src.get(), 0xff, 16);
- memset(dst.get(), 0, 16);
-
- media::FilterYUVRows_MMX(dst.get(), src.get(), src.get(), 1, 255);
- media::EmptyRegisterState();
-
- EXPECT_EQ(255u, dst[0]);
- for (int i = 1; i < 16; ++i) {
- EXPECT_EQ(0u, dst[i]);
- }
-}
-
-TEST(YUVConvertTest, FilterYUVRows_SSE2_OutOfBounds) {
- base::CPU cpu;
- if (!cpu.has_sse2()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- scoped_array<uint8> src(new uint8[16]);
- scoped_array<uint8> dst(new uint8[16]);
-
- memset(src.get(), 0xff, 16);
- memset(dst.get(), 0, 16);
-
- media::FilterYUVRows_SSE2(dst.get(), src.get(), src.get(), 1, 255);
-
- EXPECT_EQ(255u, dst[0]);
- for (int i = 1; i < 16; ++i) {
- EXPECT_EQ(0u, dst[i]);
- }
-}
-
-TEST(YUVConvertTest, FilterYUVRows_MMX_UnalignedDestination) {
- base::CPU cpu;
- if (!cpu.has_mmx()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- const int kSize = 32;
- scoped_array<uint8> src(new uint8[kSize]);
- scoped_array<uint8> dst_sample(new uint8[kSize]);
- scoped_array<uint8> dst(new uint8[kSize]);
-
- memset(dst_sample.get(), 0, kSize);
- memset(dst.get(), 0, kSize);
- for (int i = 0; i < kSize; ++i)
- src[i] = 100 + i;
-
- media::FilterYUVRows_C(dst_sample.get(),
- src.get(), src.get(), 17, 128);
-
- // Generate an unaligned output address.
- uint8* dst_ptr =
- reinterpret_cast<uint8*>(
- (reinterpret_cast<uintptr_t>(dst.get() + 8) & ~7) + 1);
- media::FilterYUVRows_MMX(dst_ptr, src.get(), src.get(), 17, 128);
- media::EmptyRegisterState();
-
- EXPECT_EQ(0, memcmp(dst_sample.get(), dst_ptr, 17));
-}
-
-TEST(YUVConvertTest, FilterYUVRows_SSE2_UnalignedDestination) {
- base::CPU cpu;
- if (!cpu.has_sse2()) {
- LOG(WARNING) << "System not supported. Test skipped.";
- return;
- }
-
- const int kSize = 64;
- scoped_array<uint8> src(new uint8[kSize]);
- scoped_array<uint8> dst_sample(new uint8[kSize]);
- scoped_array<uint8> dst(new uint8[kSize]);
-
- memset(dst_sample.get(), 0, kSize);
- memset(dst.get(), 0, kSize);
- for (int i = 0; i < kSize; ++i)
- src[i] = 100 + i;
-
- media::FilterYUVRows_C(dst_sample.get(),
- src.get(), src.get(), 37, 128);
-
- // Generate an unaligned output address.
- uint8* dst_ptr =
- reinterpret_cast<uint8*>(
- (reinterpret_cast<uintptr_t>(dst.get() + 16) & ~15) + 1);
- media::FilterYUVRows_SSE2(dst_ptr, src.get(), src.get(), 37, 128);
- media::EmptyRegisterState();
-
- EXPECT_EQ(0, memcmp(dst_sample.get(), dst_ptr, 37));
-}
-
-#if defined(ARCH_CPU_X86_64)
-
-TEST(YUVConvertTest, ScaleYUVToRGB32Row_SSE2_X64) {
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- ScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- ScaleYUVToRGB32Row_SSE2_X64(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_MMX_X64) {
- scoped_array<uint8> yuv_bytes(new uint8[kYUV12Size]);
- scoped_array<uint8> rgb_bytes_reference(new uint8[kRGBSize]);
- scoped_array<uint8> rgb_bytes_converted(new uint8[kRGBSize]);
- ReadYV12Data(&yuv_bytes);
-
- const int kWidth = 167;
- const int kSourceDx = 80000; // This value means a scale down.
- LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_reference.get(),
- kWidth,
- kSourceDx);
- LinearScaleYUVToRGB32Row_MMX_X64(yuv_bytes.get(),
- yuv_bytes.get() + kSourceUOffset,
- yuv_bytes.get() + kSourceVOffset,
- rgb_bytes_converted.get(),
- kWidth,
- kSourceDx);
- media::EmptyRegisterState();
- EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
- rgb_bytes_converted.get(),
- kWidth * kBpp));
-}
-
-#endif // defined(ARCH_CPU_X86_64)
-
-#endif // defined(ARCH_CPU_X86_FAMILY)
diff --git a/src/media/crypto/aes_decryptor.cc b/src/media/crypto/aes_decryptor.cc
deleted file mode 100644
index 5431a76..0000000
--- a/src/media/crypto/aes_decryptor.cc
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/crypto/aes_decryptor.h"
-
-#include <vector>
-
-#include "base/logging.h"
-#include "base/stl_util.h"
-#include "base/string_number_conversions.h"
-#include "crypto/encryptor.h"
-#include "crypto/symmetric_key.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/decryptor_client.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-
-namespace media {
-
-uint32 AesDecryptor::next_session_id_ = 1;
-
-enum ClearBytesBufferSel {
- kSrcContainsClearBytes,
- kDstContainsClearBytes
-};
-
-static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples,
- const ClearBytesBufferSel sel,
- const uint8* src,
- uint8* dst) {
- for (size_t i = 0; i < subsamples.size(); i++) {
- const SubsampleEntry& subsample = subsamples[i];
- if (sel == kSrcContainsClearBytes) {
- src += subsample.clear_bytes;
- } else {
- dst += subsample.clear_bytes;
- }
- memcpy(dst, src, subsample.cypher_bytes);
- src += subsample.cypher_bytes;
- dst += subsample.cypher_bytes;
- }
-}
-
-// Decrypts |input| using |key|. Returns a DecoderBuffer with the decrypted
-// data if decryption succeeded or NULL if decryption failed.
-static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input,
- crypto::SymmetricKey* key) {
- CHECK(input.GetDataSize());
- CHECK(input.GetDecryptConfig());
- CHECK(key);
-
- crypto::Encryptor encryptor;
- if (!encryptor.Init(key, crypto::Encryptor::CTR, "")) {
- DVLOG(1) << "Could not initialize decryptor.";
- return NULL;
- }
-
- DCHECK_EQ(input.GetDecryptConfig()->iv().size(),
- static_cast<size_t>(DecryptConfig::kDecryptionKeySize));
- if (!encryptor.SetCounter(input.GetDecryptConfig()->iv())) {
- DVLOG(1) << "Could not set counter block.";
- return NULL;
- }
-
- const int data_offset = input.GetDecryptConfig()->data_offset();
- const char* sample =
- reinterpret_cast<const char*>(input.GetData() + data_offset);
- DCHECK_GT(input.GetDataSize(), data_offset);
- size_t sample_size = static_cast<size_t>(input.GetDataSize() - data_offset);
-
- DCHECK_GT(sample_size, 0U) << "No sample data to be decrypted.";
- if (sample_size == 0)
- return NULL;
-
- if (input.GetDecryptConfig()->subsamples().empty()) {
- std::string decrypted_text;
- base::StringPiece encrypted_text(sample, sample_size);
- if (!encryptor.Decrypt(encrypted_text, &decrypted_text)) {
- DVLOG(1) << "Could not decrypt data.";
- return NULL;
- }
-
- // TODO(xhwang): Find a way to avoid this data copy.
- return DecoderBuffer::CopyFrom(
- reinterpret_cast<const uint8*>(decrypted_text.data()),
- decrypted_text.size());
- }
-
- const std::vector<SubsampleEntry>& subsamples =
- input.GetDecryptConfig()->subsamples();
-
- size_t total_clear_size = 0;
- size_t total_encrypted_size = 0;
- for (size_t i = 0; i < subsamples.size(); i++) {
- total_clear_size += subsamples[i].clear_bytes;
- total_encrypted_size += subsamples[i].cypher_bytes;
- // Check for overflow. This check is valid because *_size is unsigned.
- DCHECK(total_clear_size >= subsamples[i].clear_bytes);
- if (total_encrypted_size < subsamples[i].cypher_bytes)
- return NULL;
- }
- size_t total_size = total_clear_size + total_encrypted_size;
- if (total_size < total_clear_size || total_size != sample_size) {
- DVLOG(1) << "Subsample sizes do not equal input size";
- return NULL;
- }
-
- // The encrypted portions of all subsamples must form a contiguous block,
- // such that an encrypted subsample that ends away from a block boundary is
- // immediately followed by the start of the next encrypted subsample. We
- // copy all encrypted subsamples to a contiguous buffer, decrypt them, then
- // copy the decrypted bytes over the encrypted bytes in the output.
- // TODO(strobe): attempt to reduce number of memory copies
- scoped_array<uint8> encrypted_bytes(new uint8[total_encrypted_size]);
- CopySubsamples(subsamples, kSrcContainsClearBytes,
- reinterpret_cast<const uint8*>(sample), encrypted_bytes.get());
-
- base::StringPiece encrypted_text(
- reinterpret_cast<const char*>(encrypted_bytes.get()),
- total_encrypted_size);
- std::string decrypted_text;
- if (!encryptor.Decrypt(encrypted_text, &decrypted_text)) {
- DVLOG(1) << "Could not decrypt data.";
- return NULL;
- }
-
- scoped_refptr<DecoderBuffer> output = DecoderBuffer::CopyFrom(
- reinterpret_cast<const uint8*>(sample), sample_size);
- CopySubsamples(subsamples, kDstContainsClearBytes,
- reinterpret_cast<const uint8*>(decrypted_text.data()),
- output->GetWritableData());
- return output;
-}
-
-AesDecryptor::AesDecryptor(DecryptorClient* client)
- : client_(client) {
-}
-
-AesDecryptor::~AesDecryptor() {
- STLDeleteValues(&key_map_);
-}
-
-bool AesDecryptor::GenerateKeyRequest(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length) {
- std::string session_id_string(base::UintToString(next_session_id_++));
-
- // For now, the AesDecryptor does not care about |key_system| and |type|;
- // just fire the event with the |init_data| as the request.
- std::string message;
- if (init_data && init_data_length) {
- message = std::string(reinterpret_cast<const char*>(init_data),
- init_data_length);
- }
-
- client_->KeyMessage(key_system, session_id_string, message, "");
- return true;
-}
-
-void AesDecryptor::AddKey(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id) {
- CHECK(key);
- CHECK_GT(key_length, 0);
-
- // TODO(xhwang): Add |session_id| check after we figure out how:
- // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16550
- if (key_length != DecryptConfig::kDecryptionKeySize) {
- DVLOG(1) << "Invalid key length: " << key_length;
- client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0);
- return;
- }
-
- // TODO(xhwang): Fix the decryptor to accept no |init_data|. See
- // http://crbug.com/123265. Until then, ensure a non-empty value is passed.
- static const uint8 kDummyInitData[1] = { 0 };
- if (!init_data) {
- init_data = kDummyInitData;
- init_data_length = arraysize(kDummyInitData);
- }
-
- // TODO(xhwang): For now, use |init_data| for key ID. Make this more spec
- // compliant later (http://crbug.com/123262, http://crbug.com/123265).
- std::string key_id_string(reinterpret_cast<const char*>(init_data),
- init_data_length);
- std::string key_string(reinterpret_cast<const char*>(key) , key_length);
- scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string));
- if (!decryption_key.get()) {
- DVLOG(1) << "Could not create key.";
- client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0);
- return;
- }
-
- if (!decryption_key->Init()) {
- DVLOG(1) << "Could not initialize decryption key.";
- client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0);
- return;
- }
-
- SetKey(key_id_string, decryption_key.Pass());
-
- if (!audio_key_added_cb_.is_null())
- audio_key_added_cb_.Run();
-
- if (!video_key_added_cb_.is_null())
- video_key_added_cb_.Run();
-
- client_->KeyAdded(key_system, session_id);
-}
-
-void AesDecryptor::CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) {
-}
-
-void AesDecryptor::RegisterKeyAddedCB(StreamType stream_type,
- const KeyAddedCB& key_added_cb) {
- switch (stream_type) {
- case kAudio:
- audio_key_added_cb_ = key_added_cb;
- break;
- case kVideo:
- video_key_added_cb_ = key_added_cb;
- break;
- default:
- NOTREACHED();
- }
-}
-
-void AesDecryptor::Decrypt(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb) {
- CHECK(encrypted->GetDecryptConfig());
- const std::string& key_id = encrypted->GetDecryptConfig()->key_id();
-
- DecryptionKey* key = GetKey(key_id);
- if (!key) {
- DVLOG(1) << "Could not find a matching key for the given key ID.";
- decrypt_cb.Run(kNoKey, NULL);
- return;
- }
-
- scoped_refptr<DecoderBuffer> decrypted;
- // An empty iv string signals that the frame is unencrypted.
- if (encrypted->GetDecryptConfig()->iv().empty()) {
- int data_offset = encrypted->GetDecryptConfig()->data_offset();
- decrypted = DecoderBuffer::CopyFrom(encrypted->GetData() + data_offset,
- encrypted->GetDataSize() - data_offset);
- } else {
- crypto::SymmetricKey* decryption_key = key->decryption_key();
- decrypted = DecryptData(*encrypted, decryption_key);
- if (!decrypted) {
- DVLOG(1) << "Decryption failed.";
- decrypt_cb.Run(kError, NULL);
- return;
- }
- }
-
- decrypted->SetTimestamp(encrypted->GetTimestamp());
- decrypted->SetDuration(encrypted->GetDuration());
- decrypt_cb.Run(kSuccess, decrypted);
-}
-
-void AesDecryptor::CancelDecrypt(StreamType stream_type) {
- // Decrypt() calls the DecryptCB synchronously so there's nothing to cancel.
-}
-
-void AesDecryptor::InitializeAudioDecoder(scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- // AesDecryptor does not support audio decoding.
- init_cb.Run(false);
-}
-
-void AesDecryptor::InitializeVideoDecoder(scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- // AesDecryptor does not support video decoding.
- init_cb.Run(false);
-}
-
-void AesDecryptor::DecryptAndDecodeAudio(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb) {
- NOTREACHED() << "AesDecryptor does not support audio decoding";
-}
-
-void AesDecryptor::DecryptAndDecodeVideo(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb) {
- NOTREACHED() << "AesDecryptor does not support video decoding";
-}
-
-void AesDecryptor::ResetDecoder(StreamType stream_type) {
- NOTREACHED() << "AesDecryptor does not support audio/video decoding";
-}
-
-void AesDecryptor::DeinitializeDecoder(StreamType stream_type) {
- NOTREACHED() << "AesDecryptor does not support audio/video decoding";
-}
-
-void AesDecryptor::SetKey(const std::string& key_id,
- scoped_ptr<DecryptionKey> decryption_key) {
- base::AutoLock auto_lock(key_map_lock_);
- KeyMap::iterator found = key_map_.find(key_id);
- if (found != key_map_.end()) {
- delete found->second;
- key_map_.erase(found);
- }
- key_map_[key_id] = decryption_key.release();
-}
-
-AesDecryptor::DecryptionKey* AesDecryptor::GetKey(
- const std::string& key_id) const {
- base::AutoLock auto_lock(key_map_lock_);
- KeyMap::const_iterator found = key_map_.find(key_id);
- if (found == key_map_.end())
- return NULL;
-
- return found->second;
-}
-
-AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret)
- : secret_(secret) {
-}
-
-AesDecryptor::DecryptionKey::~DecryptionKey() {}
-
-bool AesDecryptor::DecryptionKey::Init() {
- CHECK(!secret_.empty());
- decryption_key_.reset(crypto::SymmetricKey::Import(
- crypto::SymmetricKey::AES, secret_));
- if (!decryption_key_.get())
- return false;
- return true;
-}
-
-} // namespace media
diff --git a/src/media/crypto/aes_decryptor.h b/src/media/crypto/aes_decryptor.h
deleted file mode 100644
index e983805..0000000
--- a/src/media/crypto/aes_decryptor.h
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_CRYPTO_AES_DECRYPTOR_H_
-#define MEDIA_CRYPTO_AES_DECRYPTOR_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/hash_tables.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/string_piece.h"
-#include "base/synchronization/lock.h"
-#include "media/base/decryptor.h"
-#include "media/base/media_export.h"
-
-namespace crypto {
-class SymmetricKey;
-}
-
-namespace media {
-
-class DecryptorClient;
-
-// Decrypts an AES encrypted buffer into an unencrypted buffer. The AES
-// encryption must be CTR with a key size of 128bits.
-class MEDIA_EXPORT AesDecryptor : public Decryptor {
- public:
- // The AesDecryptor does not take ownership of the |client|. The |client|
- // must be valid throughout the lifetime of the AesDecryptor.
- explicit AesDecryptor(DecryptorClient* client);
- virtual ~AesDecryptor();
-
- // Decryptor implementation.
- virtual bool GenerateKeyRequest(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length) OVERRIDE;
- virtual void AddKey(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id) OVERRIDE;
- virtual void CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) OVERRIDE;
- virtual void RegisterKeyAddedCB(StreamType stream_type,
- const KeyAddedCB& key_added_cb) OVERRIDE;
- virtual void Decrypt(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb) OVERRIDE;
- virtual void CancelDecrypt(StreamType stream_type) OVERRIDE;
- virtual void InitializeAudioDecoder(scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
- virtual void InitializeVideoDecoder(scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
- virtual void DecryptAndDecodeAudio(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb) OVERRIDE;
- virtual void DecryptAndDecodeVideo(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb) OVERRIDE;
- virtual void ResetDecoder(StreamType stream_type) OVERRIDE;
- virtual void DeinitializeDecoder(StreamType stream_type) OVERRIDE;
-
- private:
- // TODO(fgalligan): Remove this and change KeyMap to use crypto::SymmetricKey
- // as there are no decryptors that are performing an integrity check.
- // Helper class that manages the decryption key.
- class DecryptionKey {
- public:
- explicit DecryptionKey(const std::string& secret);
- ~DecryptionKey();
-
- // Creates the encryption key.
- bool Init();
-
- crypto::SymmetricKey* decryption_key() { return decryption_key_.get(); }
-
- private:
- // The base secret that is used to create the decryption key.
- const std::string secret_;
-
- // The key used to decrypt the data.
- scoped_ptr<crypto::SymmetricKey> decryption_key_;
-
- DISALLOW_COPY_AND_ASSIGN(DecryptionKey);
- };
-
- // Sets |key| for |key_id|. The AesDecryptor takes the ownership of the |key|.
- void SetKey(const std::string& key_id, scoped_ptr<DecryptionKey> key);
-
- // Gets a DecryptionKey associated with |key_id|. The AesDecryptor still owns
- // the key. Returns NULL if no key is associated with |key_id|.
- DecryptionKey* GetKey(const std::string& key_id) const;
-
- // KeyMap owns the DecryptionKey* and must delete them when they are
- // not needed any more.
- typedef base::hash_map<std::string, DecryptionKey*> KeyMap;
-
- // Since only Decrypt() is called off the renderer thread, we only need to
- // protect |key_map_|, the only member variable that is shared between
- // Decrypt() and other methods.
- KeyMap key_map_; // Protected by the |key_map_lock_|.
- mutable base::Lock key_map_lock_; // Protects the |key_map_|.
-
- // Make session ID unique per renderer by making it static.
- // TODO(xhwang): Make session ID more strictly defined if needed:
- // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16739#c0
- static uint32 next_session_id_;
-
- DecryptorClient* const client_;
-
- KeyAddedCB audio_key_added_cb_;
- KeyAddedCB video_key_added_cb_;
-
- DISALLOW_COPY_AND_ASSIGN(AesDecryptor);
-};
-
-} // namespace media
-
-#endif // MEDIA_CRYPTO_AES_DECRYPTOR_H_
diff --git a/src/media/crypto/aes_decryptor_unittest.cc b/src/media/crypto/aes_decryptor_unittest.cc
deleted file mode 100644
index 2fab86d..0000000
--- a/src/media/crypto/aes_decryptor_unittest.cc
+++ /dev/null
@@ -1,594 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/mock_filters.h"
-#include "media/crypto/aes_decryptor.h"
-#include "media/webm/webm_constants.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::Gt;
-using ::testing::IsNull;
-using ::testing::NotNull;
-using ::testing::SaveArg;
-using ::testing::StrEq;
-using ::testing::StrNe;
-
-namespace media {
-
-// |encrypted_data| is encrypted from |plain_text| using |key|. |key_id| is
-// used to distinguish |key|.
-struct WebmEncryptedData {
- uint8 plain_text[32];
- int plain_text_size;
- uint8 key_id[32];
- int key_id_size;
- uint8 key[32];
- int key_size;
- uint8 encrypted_data[64];
- int encrypted_data_size;
-};
-
-static const char kClearKeySystem[] = "org.w3.clearkey";
-
-// Frames 0 & 1 are encrypted with the same key. Frame 2 is encrypted with a
-// different key. Frame 3 is unencrypted.
-const WebmEncryptedData kWebmEncryptedFrames[] = {
- {
- // plaintext
- "Original data.", 14,
- // key_id
- { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
- 0x10, 0x11, 0x12, 0x13
- }, 20,
- // key
- { 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
- 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
- }, 16,
- // encrypted_data
- { 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xf0, 0xd1, 0x12, 0xd5, 0x24, 0x81, 0x96,
- 0x55, 0x1b, 0x68, 0x9f, 0x38, 0x91, 0x85
- }, 23
- }, {
- // plaintext
- "Changed Original data.", 22,
- // key_id
- { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
- 0x10, 0x11, 0x12, 0x13
- }, 20,
- // key
- { 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
- 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
- }, 16,
- // encrypted_data
- { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5,
- 0x79, 0x1c, 0x8e, 0x25, 0xd7, 0x17, 0xe7, 0x5e,
- 0x16, 0xe3, 0x40, 0x08, 0x27, 0x11, 0xe9
- }, 31
- }, {
- // plaintext
- "Original data.", 14,
- // key_id
- { 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
- 0x2c, 0x2d, 0x2e, 0x2f, 0x30
- }, 13,
- // key
- { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
- 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
- }, 16,
- // encrypted_data
- { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x9c, 0x71, 0x26, 0x57, 0x3e, 0x25, 0x37,
- 0xf7, 0x31, 0x81, 0x19, 0x64, 0xce, 0xbc
- }, 23
- }, {
- // plaintext
- "Changed Original data.", 22,
- // key_id
- { 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
- 0x2c, 0x2d, 0x2e, 0x2f, 0x30
- }, 13,
- // key
- { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
- 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
- }, 16,
- // encrypted_data
- { 0x00, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
- 0x20, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61,
- 0x6c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e
- }, 23
- }
-};
-
-static const uint8 kWebmWrongSizedKey[] = { 0x20, 0x20 };
-
-static const uint8 kSubsampleOriginalData[] = "Original subsample data.";
-static const int kSubsampleOriginalDataSize = 24;
-
-static const uint8 kSubsampleKeyId[] = { 0x00, 0x01, 0x02, 0x03 };
-
-static const uint8 kSubsampleKey[] = {
- 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
- 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13
-};
-
-static const uint8 kSubsampleIv[] = {
- 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8 kSubsampleData[] = {
- 0x4f, 0x72, 0x09, 0x16, 0x09, 0xe6, 0x79, 0xad,
- 0x70, 0x73, 0x75, 0x62, 0x09, 0xbb, 0x83, 0x1d,
- 0x4d, 0x08, 0xd7, 0x78, 0xa4, 0xa7, 0xf1, 0x2e
-};
-
-static const uint8 kPaddedSubsampleData[] = {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x4f, 0x72, 0x09, 0x16, 0x09, 0xe6, 0x79, 0xad,
- 0x70, 0x73, 0x75, 0x62, 0x09, 0xbb, 0x83, 0x1d,
- 0x4d, 0x08, 0xd7, 0x78, 0xa4, 0xa7, 0xf1, 0x2e
-};
-
-// Encrypted with kSubsampleKey and kSubsampleIv but without subsamples.
-static const uint8 kNoSubsampleData[] = {
- 0x2f, 0x03, 0x09, 0xef, 0x71, 0xaf, 0x31, 0x16,
- 0xfa, 0x9d, 0x18, 0x43, 0x1e, 0x96, 0x71, 0xb5,
- 0xbf, 0xf5, 0x30, 0x53, 0x9a, 0x20, 0xdf, 0x95
-};
-
-static const SubsampleEntry kSubsampleEntries[] = {
- { 2, 7 },
- { 3, 11 },
- { 1, 0 }
-};
-
-// Generates a 16 byte CTR counter block. The CTR counter block format is a
-// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
-// |iv_size| is the size of |iv| in btyes. Returns a string of
-// kDecryptionKeySize bytes.
-static std::string GenerateCounterBlock(const uint8* iv, int iv_size) {
- CHECK_GT(iv_size, 0);
- CHECK_LE(iv_size, DecryptConfig::kDecryptionKeySize);
-
- std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
- counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
- return counter_block;
-}
-
-// Creates a WebM encrypted buffer that the demuxer would pass to the
-// decryptor. |data| is the payload of a WebM encrypted Block. |key_id| is
-// initialization data from the WebM file. Every encrypted Block has
-// a signal byte prepended to a frame. If the frame is encrypted then an IV is
-// prepended to the Block. Current encrypted WebM request for comments
-// specification is here
-// http://wiki.webmproject.org/encryption/webm-encryption-rfc
-static scoped_refptr<DecoderBuffer> CreateWebMEncryptedBuffer(
- const uint8* data, int data_size,
- const uint8* key_id, int key_id_size) {
- scoped_refptr<DecoderBuffer> encrypted_buffer = DecoderBuffer::CopyFrom(
- data, data_size);
- CHECK(encrypted_buffer);
- DCHECK_EQ(kWebMSignalByteSize, 1);
-
- uint8 signal_byte = data[0];
- int data_offset = kWebMSignalByteSize;
-
- // Setting the DecryptConfig object of the buffer while leaving the
- // initialization vector empty will tell the decryptor that the frame is
- // unencrypted.
- std::string counter_block_str;
-
- if (signal_byte & kWebMFlagEncryptedFrame) {
- counter_block_str = GenerateCounterBlock(data + data_offset, kWebMIvSize);
- data_offset += kWebMIvSize;
- }
-
- encrypted_buffer->SetDecryptConfig(
- scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(key_id), key_id_size),
- counter_block_str,
- data_offset,
- std::vector<SubsampleEntry>())));
- return encrypted_buffer;
-}
-
-static scoped_refptr<DecoderBuffer> CreateSubsampleEncryptedBuffer(
- const uint8* data, int data_size,
- const uint8* key_id, int key_id_size,
- const uint8* iv, int iv_size,
- int data_offset,
- const std::vector<SubsampleEntry>& subsample_entries) {
- scoped_refptr<DecoderBuffer> encrypted_buffer =
- DecoderBuffer::CopyFrom(data, data_size);
- CHECK(encrypted_buffer);
- encrypted_buffer->SetDecryptConfig(
- scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(key_id), key_id_size),
- std::string(reinterpret_cast<const char*>(iv), iv_size),
- data_offset,
- subsample_entries)));
- return encrypted_buffer;
-}
-
-class AesDecryptorTest : public testing::Test {
- public:
- AesDecryptorTest()
- : decryptor_(&client_),
- decrypt_cb_(base::Bind(&AesDecryptorTest::BufferDecrypted,
- base::Unretained(this))),
- subsample_entries_(kSubsampleEntries,
- kSubsampleEntries + arraysize(kSubsampleEntries)) {
- }
-
- protected:
- void GenerateKeyRequest(const uint8* key_id, int key_id_size) {
- std::string key_id_string(reinterpret_cast<const char*>(key_id),
- key_id_size);
- EXPECT_CALL(client_, KeyMessage(kClearKeySystem,
- StrNe(""), StrEq(key_id_string), ""))
- .WillOnce(SaveArg<1>(&session_id_string_));
- EXPECT_TRUE(decryptor_.GenerateKeyRequest(kClearKeySystem, "",
- key_id, key_id_size));
- }
-
- void AddKeyAndExpectToSucceed(const uint8* key_id, int key_id_size,
- const uint8* key, int key_size) {
- EXPECT_CALL(client_, KeyAdded(kClearKeySystem, session_id_string_));
- decryptor_.AddKey(kClearKeySystem, key, key_size, key_id, key_id_size,
- session_id_string_);
- }
-
- void AddKeyAndExpectToFail(const uint8* key_id, int key_id_size,
- const uint8* key, int key_size) {
- EXPECT_CALL(client_, KeyError(kClearKeySystem, session_id_string_,
- Decryptor::kUnknownError, 0));
- decryptor_.AddKey(kClearKeySystem, key, key_size, key_id, key_id_size,
- session_id_string_);
- }
-
- MOCK_METHOD2(BufferDecrypted, void(Decryptor::Status,
- const scoped_refptr<DecoderBuffer>&));
-
- void DecryptAndExpectToSucceed(const scoped_refptr<DecoderBuffer>& encrypted,
- const uint8* plain_text, int plain_text_size) {
- scoped_refptr<DecoderBuffer> decrypted;
- EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kSuccess, NotNull()))
- .WillOnce(SaveArg<1>(&decrypted));
-
- decryptor_.Decrypt(Decryptor::kVideo, encrypted, decrypt_cb_);
- ASSERT_TRUE(decrypted);
- ASSERT_EQ(plain_text_size, decrypted->GetDataSize());
- EXPECT_EQ(0, memcmp(plain_text, decrypted->GetData(), plain_text_size));
- }
-
- void DecryptAndExpectDataMismatch(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const uint8* plain_text, int plain_text_size) {
- scoped_refptr<DecoderBuffer> decrypted;
- EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kSuccess, NotNull()))
- .WillOnce(SaveArg<1>(&decrypted));
-
- decryptor_.Decrypt(Decryptor::kVideo, encrypted, decrypt_cb_);
- ASSERT_TRUE(decrypted);
- ASSERT_EQ(plain_text_size, decrypted->GetDataSize());
- EXPECT_NE(0, memcmp(plain_text, decrypted->GetData(), plain_text_size));
- }
-
- void DecryptAndExpectSizeDataMismatch(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const uint8* plain_text, int plain_text_size) {
- scoped_refptr<DecoderBuffer> decrypted;
- EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kSuccess, NotNull()))
- .WillOnce(SaveArg<1>(&decrypted));
-
- decryptor_.Decrypt(Decryptor::kVideo, encrypted, decrypt_cb_);
- ASSERT_TRUE(decrypted);
- EXPECT_NE(plain_text_size, decrypted->GetDataSize());
- EXPECT_NE(0, memcmp(plain_text, decrypted->GetData(), plain_text_size));
- }
-
- void DecryptAndExpectToFail(const scoped_refptr<DecoderBuffer>& encrypted) {
- EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kError, IsNull()));
- decryptor_.Decrypt(Decryptor::kVideo, encrypted, decrypt_cb_);
- }
-
- MockDecryptorClient client_;
- AesDecryptor decryptor_;
- std::string session_id_string_;
- AesDecryptor::DecryptCB decrypt_cb_;
- std::vector<SubsampleEntry> subsample_entries_;
-};
-
-TEST_F(AesDecryptorTest, GenerateKeyRequestWithNullInitData) {
- EXPECT_CALL(client_, KeyMessage(kClearKeySystem, StrNe(""), "", ""));
- EXPECT_TRUE(decryptor_.GenerateKeyRequest(kClearKeySystem, "", NULL, 0));
-}
-
-TEST_F(AesDecryptorTest, NormalWebMDecryption) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data,
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, UnencryptedFrameWebMDecryption) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[3];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data,
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, WrongKey) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
-
- // Change the first byte of the key.
- std::vector<uint8> wrong_key(frame.key, frame.key + frame.key_size);
- wrong_key[0]++;
-
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- &wrong_key[0], frame.key_size);
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data,
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, NoKey) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
-
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data, frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kNoKey, IsNull()));
- decryptor_.Decrypt(Decryptor::kVideo, encrypted_data, decrypt_cb_);
-}
-
-TEST_F(AesDecryptorTest, KeyReplacement) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
-
- // Change the first byte of the key.
- std::vector<uint8> wrong_key(frame.key, frame.key + frame.key_size);
- wrong_key[0]++;
-
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- &wrong_key[0], frame.key_size);
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data,
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, WrongSizedKey) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToFail(frame.key_id, frame.key_id_size,
- kWebmWrongSizedKey, arraysize(kWebmWrongSizedKey));
-}
-
-TEST_F(AesDecryptorTest, MultipleKeysAndFrames) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(frame.encrypted_data,
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-
- const WebmEncryptedData& frame2 = kWebmEncryptedFrames[2];
- GenerateKeyRequest(frame2.key_id, frame2.key_id_size);
- AddKeyAndExpectToSucceed(frame2.key_id, frame2.key_id_size,
- frame2.key, frame2.key_size);
-
- const WebmEncryptedData& frame1 = kWebmEncryptedFrames[1];
- scoped_refptr<DecoderBuffer> encrypted_data1 =
- CreateWebMEncryptedBuffer(frame1.encrypted_data,
- frame1.encrypted_data_size,
- frame1.key_id, frame1.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data1,
- frame1.plain_text,
- frame1.plain_text_size));
-
- scoped_refptr<DecoderBuffer> encrypted_data2 =
- CreateWebMEncryptedBuffer(frame2.encrypted_data,
- frame2.encrypted_data_size,
- frame2.key_id, frame2.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data2,
- frame2.plain_text,
- frame2.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, CorruptedIv) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
-
- // Change byte 13 to modify the IV. Bytes 13-20 of WebM encrypted data
- // contains the IV.
- std::vector<uint8> frame_with_bad_iv(
- frame.encrypted_data, frame.encrypted_data + frame.encrypted_data_size);
- frame_with_bad_iv[1]++;
-
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(&frame_with_bad_iv[0],
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, CorruptedData) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
-
- // Change last byte to modify the data. Bytes 21+ of WebM encrypted data
- // contains the encrypted frame.
- std::vector<uint8> frame_with_bad_vp8_data(
- frame.encrypted_data, frame.encrypted_data + frame.encrypted_data_size);
- frame_with_bad_vp8_data[frame.encrypted_data_size - 1]++;
-
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(&frame_with_bad_vp8_data[0],
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, EncryptedAsUnencryptedFailure) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[0];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
-
- // Change signal byte from an encrypted frame to an unencrypted frame. Byte
- // 12 of WebM encrypted data contains the signal byte.
- std::vector<uint8> frame_with_wrong_signal_byte(
- frame.encrypted_data, frame.encrypted_data + frame.encrypted_data_size);
- frame_with_wrong_signal_byte[0] = 0;
-
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(&frame_with_wrong_signal_byte[0],
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(
- DecryptAndExpectSizeDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, UnencryptedAsEncryptedFailure) {
- const WebmEncryptedData& frame = kWebmEncryptedFrames[3];
- GenerateKeyRequest(frame.key_id, frame.key_id_size);
- AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
- frame.key, frame.key_size);
-
- // Change signal byte from an unencrypted frame to an encrypted frame. Byte
- // 0 of WebM encrypted data contains the signal byte.
- std::vector<uint8> frame_with_wrong_signal_byte(
- frame.encrypted_data, frame.encrypted_data + frame.encrypted_data_size);
- frame_with_wrong_signal_byte[0] = kWebMFlagEncryptedFrame;
-
- scoped_refptr<DecoderBuffer> encrypted_data =
- CreateWebMEncryptedBuffer(&frame_with_wrong_signal_byte[0],
- frame.encrypted_data_size,
- frame.key_id, frame.key_id_size);
- ASSERT_NO_FATAL_FAILURE(
- DecryptAndExpectSizeDataMismatch(encrypted_data,
- frame.plain_text,
- frame.plain_text_size));
-}
-
-TEST_F(AesDecryptorTest, SubsampleDecryption) {
- GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId));
- AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleKey, arraysize(kSubsampleKey));
- scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer(
- kSubsampleData, arraysize(kSubsampleData),
- kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleIv, arraysize(kSubsampleIv),
- 0,
- subsample_entries_);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(
- encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize));
-}
-
-// Ensures noninterference of data offset and subsample mechanisms. We never
-// expect to encounter this in the wild, but since the DecryptConfig doesn't
-// disallow such a configuration, it should be covered.
-TEST_F(AesDecryptorTest, SubsampleDecryptionWithOffset) {
- GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId));
- AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleKey, arraysize(kSubsampleKey));
- scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer(
- kPaddedSubsampleData, arraysize(kPaddedSubsampleData),
- kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleIv, arraysize(kSubsampleIv),
- arraysize(kPaddedSubsampleData) - arraysize(kSubsampleData),
- subsample_entries_);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(
- encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize));
-}
-
-// No subsample or offset.
-TEST_F(AesDecryptorTest, NormalDecryption) {
- GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId));
- AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleKey, arraysize(kSubsampleKey));
- scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer(
- kNoSubsampleData, arraysize(kNoSubsampleData),
- kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleIv, arraysize(kSubsampleIv),
- 0,
- std::vector<SubsampleEntry>());
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(
- encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize));
-}
-
-TEST_F(AesDecryptorTest, IncorrectSubsampleSize) {
- GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId));
- AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleKey, arraysize(kSubsampleKey));
- std::vector<SubsampleEntry> entries = subsample_entries_;
- entries[2].cypher_bytes += 1;
-
- scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer(
- kSubsampleData, arraysize(kSubsampleData),
- kSubsampleKeyId, arraysize(kSubsampleKeyId),
- kSubsampleIv, arraysize(kSubsampleIv),
- 0,
- entries);
- ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data));
-}
-
-} // namespace media
diff --git a/src/media/crypto/shell_decryptor_factory.cc b/src/media/crypto/shell_decryptor_factory.cc
deleted file mode 100644
index ceaf4d2..0000000
--- a/src/media/crypto/shell_decryptor_factory.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/crypto/shell_decryptor_factory.h"
-
-namespace media {
-
-// static
-ShellDecryptorFactory::DecryptorRegistry ShellDecryptorFactory::registry_;
-
-// static
-bool ShellDecryptorFactory::Supports(const std::string& key_system) {
- DecryptorRegistry::iterator it = registry_.find(key_system);
- return it != registry_.end();
-}
-
-// static
-Decryptor* ShellDecryptorFactory::Create(const std::string& key_system,
- DecryptorClient* client) {
- DecryptorRegistry::iterator it = registry_.find(key_system);
- if (it == registry_.end()) {
- return NULL;
- }
- return it->second.Run(client);
-}
-
-// static
-void ShellDecryptorFactory::RegisterDecryptor(const std::string& key_system,
- const CreateCB& create_cb) {
- registry_[key_system] = create_cb;
-}
-
-} // namespace media
diff --git a/src/media/crypto/shell_decryptor_factory.h b/src/media/crypto/shell_decryptor_factory.h
deleted file mode 100644
index 408677b..0000000
--- a/src/media/crypto/shell_decryptor_factory.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_CRYPTO_SHELL_DECRYPTOR_FACTORY_H_
-#define MEDIA_CRYPTO_SHELL_DECRYPTOR_FACTORY_H_
-
-#include <map>
-#include <string>
-
-#include "media/base/decryptor.h"
-#include "media/base/decryptor_client.h"
-
-namespace media {
-
-// Serves as a registry and factory for decryptors.
-// This allows a complete decoupling so that the media stack does not need
-// explicit knowledge of any particular decryptor or how it should be created.
-class MEDIA_EXPORT ShellDecryptorFactory {
- public:
- // Creates a decryptor. Returns NULL on failure.
- typedef base::Callback<media::Decryptor*(media::DecryptorClient*)> CreateCB;
-
- static bool Supports(const std::string& key_system);
-
- static Decryptor* Create(const std::string& key_system,
- DecryptorClient* client);
-
- static void RegisterDecryptor(const std::string& key_system,
- const CreateCB& create_cb);
-
- private:
- typedef std::map<std::string, CreateCB> DecryptorRegistry;
- static DecryptorRegistry registry_;
-};
-
-} // namespace media
-
-#endif // MEDIA_CRYPTO_SHELL_DECRYPTOR_FACTORY_H_
diff --git a/src/media/ffmpeg/ffmpeg_common.cc b/src/media/ffmpeg/ffmpeg_common.cc
deleted file mode 100644
index 1fd5cd9..0000000
--- a/src/media/ffmpeg/ffmpeg_common.cc
+++ /dev/null
@@ -1,444 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/ffmpeg/ffmpeg_common.h"
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_util.h"
-
-namespace media {
-
-// Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are
-// padded. Check here to ensure FFmpeg only receives data padded to its
-// specifications.
-COMPILE_ASSERT(DecoderBuffer::kPaddingSize >= FF_INPUT_BUFFER_PADDING_SIZE,
- decoder_buffer_padding_size_does_not_fit_ffmpeg_requirement);
-
-// Alignment requirement by FFmpeg for input and output buffers. This need to
-// be updated to match FFmpeg when it changes.
-#if defined(ARCH_CPU_ARM_FAMILY)
-static const int kFFmpegBufferAddressAlignment = 16;
-#else
-static const int kFFmpegBufferAddressAlignment = 32;
-#endif
-
-// Check here to ensure FFmpeg only receives data aligned to its specifications.
-COMPILE_ASSERT(
- DecoderBuffer::kAlignmentSize >= kFFmpegBufferAddressAlignment &&
- DecoderBuffer::kAlignmentSize % kFFmpegBufferAddressAlignment == 0,
- decoder_buffer_alignment_size_does_not_fit_ffmpeg_requirement);
-
-// Allows faster SIMD YUV convert. Also, FFmpeg overreads/-writes occasionally.
-// See video_get_buffer() in libavcodec/utils.c.
-static const int kFFmpegOutputBufferPaddingSize = 16;
-
-COMPILE_ASSERT(VideoFrame::kFrameSizePadding >= kFFmpegOutputBufferPaddingSize,
- video_frame_padding_size_does_not_fit_ffmpeg_requirement);
-
-COMPILE_ASSERT(
- VideoFrame::kFrameAddressAlignment >= kFFmpegBufferAddressAlignment &&
- VideoFrame::kFrameAddressAlignment % kFFmpegBufferAddressAlignment == 0,
- video_frame_address_alignment_does_not_fit_ffmpeg_requirement);
-
-static const AVRational kMicrosBase = { 1, base::Time::kMicrosecondsPerSecond };
-
-base::TimeDelta ConvertFromTimeBase(const AVRational& time_base,
- int64 timestamp) {
- int64 microseconds = av_rescale_q(timestamp, time_base, kMicrosBase);
- return base::TimeDelta::FromMicroseconds(microseconds);
-}
-
-int64 ConvertToTimeBase(const AVRational& time_base,
- const base::TimeDelta& timestamp) {
- return av_rescale_q(timestamp.InMicroseconds(), kMicrosBase, time_base);
-}
-
-AudioCodec CodecIDToAudioCodec(CodecID codec_id) {
- switch (codec_id) {
- case CODEC_ID_AAC:
- return kCodecAAC;
- case CODEC_ID_MP3:
- return kCodecMP3;
- case CODEC_ID_VORBIS:
- return kCodecVorbis;
- case CODEC_ID_PCM_U8:
- case CODEC_ID_PCM_S16LE:
- case CODEC_ID_PCM_S24LE:
- return kCodecPCM;
- case CODEC_ID_PCM_S16BE:
- return kCodecPCM_S16BE;
- case CODEC_ID_PCM_S24BE:
- return kCodecPCM_S24BE;
- case CODEC_ID_FLAC:
- return kCodecFLAC;
- case CODEC_ID_AMR_NB:
- return kCodecAMR_NB;
- case CODEC_ID_AMR_WB:
- return kCodecAMR_WB;
- case CODEC_ID_GSM_MS:
- return kCodecGSM_MS;
- case CODEC_ID_PCM_MULAW:
- return kCodecPCM_MULAW;
- case CODEC_ID_OPUS:
- return kCodecOpus;
- default:
- DVLOG(1) << "Unknown audio CodecID: " << codec_id;
- }
- return kUnknownAudioCodec;
-}
-
-static CodecID AudioCodecToCodecID(AudioCodec audio_codec,
- int bits_per_channel) {
- switch (audio_codec) {
- case kCodecAAC:
- return CODEC_ID_AAC;
- case kCodecMP3:
- return CODEC_ID_MP3;
- case kCodecPCM:
- switch (bits_per_channel) {
- case 8:
- return CODEC_ID_PCM_U8;
- case 16:
- return CODEC_ID_PCM_S16LE;
- case 32:
- return CODEC_ID_PCM_S24LE;
- default:
- DVLOG(1) << "Unsupported bits per channel: " << bits_per_channel;
- }
- break;
- case kCodecPCM_S16BE:
- return CODEC_ID_PCM_S16BE;
- case kCodecPCM_S24BE:
- return CODEC_ID_PCM_S24BE;
- case kCodecVorbis:
- return CODEC_ID_VORBIS;
- case kCodecFLAC:
- return CODEC_ID_FLAC;
- case kCodecAMR_NB:
- return CODEC_ID_AMR_NB;
- case kCodecAMR_WB:
- return CODEC_ID_AMR_WB;
- case kCodecGSM_MS:
- return CODEC_ID_GSM_MS;
- case kCodecPCM_MULAW:
- return CODEC_ID_PCM_MULAW;
- case kCodecOpus:
- return CODEC_ID_OPUS;
- default:
- DVLOG(1) << "Unknown AudioCodec: " << audio_codec;
- }
- return CODEC_ID_NONE;
-}
-
-VideoCodec CodecIDToVideoCodec(CodecID codec_id) {
- switch (codec_id) {
- case CODEC_ID_VC1:
- return kCodecVC1;
- case CODEC_ID_H264:
- return kCodecH264;
- case CODEC_ID_THEORA:
- return kCodecTheora;
- case CODEC_ID_MPEG2VIDEO:
- return kCodecMPEG2;
- case CODEC_ID_MPEG4:
- return kCodecMPEG4;
- case CODEC_ID_VP8:
- return kCodecVP8;
- default:
- DVLOG(1) << "Unknown video CodecID: " << codec_id;
- }
- return kUnknownVideoCodec;
-}
-
-static CodecID VideoCodecToCodecID(VideoCodec video_codec) {
- switch (video_codec) {
- case kCodecVC1:
- return CODEC_ID_VC1;
- case kCodecH264:
- return CODEC_ID_H264;
- case kCodecTheora:
- return CODEC_ID_THEORA;
- case kCodecMPEG2:
- return CODEC_ID_MPEG2VIDEO;
- case kCodecMPEG4:
- return CODEC_ID_MPEG4;
- case kCodecVP8:
- return CODEC_ID_VP8;
- default:
- DVLOG(1) << "Unknown VideoCodec: " << video_codec;
- }
- return CODEC_ID_NONE;
-}
-
-static VideoCodecProfile ProfileIDToVideoCodecProfile(int profile) {
- // Clear out the CONSTRAINED & INTRA flags which are strict subsets of the
- // corresponding profiles with which they're used.
- profile &= ~FF_PROFILE_H264_CONSTRAINED;
- profile &= ~FF_PROFILE_H264_INTRA;
- switch (profile) {
- case FF_PROFILE_H264_BASELINE:
- return H264PROFILE_BASELINE;
- case FF_PROFILE_H264_MAIN:
- return H264PROFILE_MAIN;
- case FF_PROFILE_H264_EXTENDED:
- return H264PROFILE_EXTENDED;
- case FF_PROFILE_H264_HIGH:
- return H264PROFILE_HIGH;
- case FF_PROFILE_H264_HIGH_10:
- return H264PROFILE_HIGH10PROFILE;
- case FF_PROFILE_H264_HIGH_422:
- return H264PROFILE_HIGH422PROFILE;
- case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
- return H264PROFILE_HIGH444PREDICTIVEPROFILE;
- default:
- DVLOG(1) << "Unknown profile id: " << profile;
- }
- return VIDEO_CODEC_PROFILE_UNKNOWN;
-}
-
-static int VideoCodecProfileToProfileID(VideoCodecProfile profile) {
- switch (profile) {
- case H264PROFILE_BASELINE:
- return FF_PROFILE_H264_BASELINE;
- case H264PROFILE_MAIN:
- return FF_PROFILE_H264_MAIN;
- case H264PROFILE_EXTENDED:
- return FF_PROFILE_H264_EXTENDED;
- case H264PROFILE_HIGH:
- return FF_PROFILE_H264_HIGH;
- case H264PROFILE_HIGH10PROFILE:
- return FF_PROFILE_H264_HIGH_10;
- case H264PROFILE_HIGH422PROFILE:
- return FF_PROFILE_H264_HIGH_422;
- case H264PROFILE_HIGH444PREDICTIVEPROFILE:
- return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
- default:
- DVLOG(1) << "Unknown VideoCodecProfile: " << profile;
- }
- return FF_PROFILE_UNKNOWN;
-}
-
-void AVCodecContextToAudioDecoderConfig(
- const AVCodecContext* codec_context,
- AudioDecoderConfig* config) {
- DCHECK_EQ(codec_context->codec_type, AVMEDIA_TYPE_AUDIO);
-
- AudioCodec codec = CodecIDToAudioCodec(codec_context->codec_id);
-
- AVSampleFormat sample_format = codec_context->sample_fmt;
- if (codec == kCodecOpus) {
- // TODO(tomfinegan): |sample_fmt| in |codec_context| is -1... because
- // libopusdec.c isn't built into ffmpegsumo...? Maybe it's not *that* big
- // a deal since libopus will produce either float or S16 samples, and
- // OpusAudioDecoder is the only provider of Opus support.
- sample_format = AV_SAMPLE_FMT_S16;
- }
-
- int bytes_per_channel = av_get_bytes_per_sample(sample_format);
- ChannelLayout channel_layout =
- ChannelLayoutToChromeChannelLayout(codec_context->channel_layout,
- codec_context->channels);
- int samples_per_second = codec_context->sample_rate;
-
- config->Initialize(codec,
- bytes_per_channel << 3,
- channel_layout,
- samples_per_second,
- codec_context->extradata,
- codec_context->extradata_size,
- false, // Not encrypted.
- true);
-}
-
-void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config,
- AVCodecContext* codec_context) {
- codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
- codec_context->codec_id = AudioCodecToCodecID(config.codec(),
- config.bits_per_channel());
-
- switch (config.bits_per_channel()) {
- case 8:
- codec_context->sample_fmt = AV_SAMPLE_FMT_U8;
- break;
- case 16:
- codec_context->sample_fmt = AV_SAMPLE_FMT_S16;
- break;
- case 32:
- codec_context->sample_fmt = AV_SAMPLE_FMT_S32;
- break;
- default:
- DVLOG(1) << "Unsupported bits per channel: " << config.bits_per_channel();
- codec_context->sample_fmt = AV_SAMPLE_FMT_NONE;
- }
-
- // TODO(scherkus): should we set |channel_layout|? I'm not sure if FFmpeg uses
- // said information to decode.
- codec_context->channels =
- ChannelLayoutToChannelCount(config.channel_layout());
- codec_context->sample_rate = config.samples_per_second();
-
- if (config.extra_data()) {
- codec_context->extradata_size = config.extra_data_size();
- codec_context->extradata = reinterpret_cast<uint8_t*>(
- av_malloc(config.extra_data_size() + FF_INPUT_BUFFER_PADDING_SIZE));
- memcpy(codec_context->extradata, config.extra_data(),
- config.extra_data_size());
- memset(codec_context->extradata + config.extra_data_size(), '\0',
- FF_INPUT_BUFFER_PADDING_SIZE);
- } else {
- codec_context->extradata = NULL;
- codec_context->extradata_size = 0;
- }
-}
-
-void AVStreamToVideoDecoderConfig(
- const AVStream* stream,
- VideoDecoderConfig* config) {
- gfx::Size coded_size(stream->codec->coded_width, stream->codec->coded_height);
-
- // TODO(vrk): This assumes decoded frame data starts at (0, 0), which is true
- // for now, but may not always be true forever. Fix this in the future.
- gfx::Rect visible_rect(stream->codec->width, stream->codec->height);
-
- AVRational aspect_ratio = { 1, 1 };
- if (stream->sample_aspect_ratio.num)
- aspect_ratio = stream->sample_aspect_ratio;
- else if (stream->codec->sample_aspect_ratio.num)
- aspect_ratio = stream->codec->sample_aspect_ratio;
-
- VideoCodec codec = CodecIDToVideoCodec(stream->codec->codec_id);
- VideoCodecProfile profile = (codec == kCodecVP8) ? VP8PROFILE_MAIN :
- ProfileIDToVideoCodecProfile(stream->codec->profile);
- gfx::Size natural_size = GetNaturalSize(
- visible_rect.size(), aspect_ratio.num, aspect_ratio.den);
- config->Initialize(codec,
- profile,
- PixelFormatToVideoFormat(stream->codec->pix_fmt),
- coded_size, visible_rect, natural_size,
- stream->codec->extradata, stream->codec->extradata_size,
- false, // Not encrypted.
- true);
-}
-
-void VideoDecoderConfigToAVCodecContext(
- const VideoDecoderConfig& config,
- AVCodecContext* codec_context) {
- codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
- codec_context->codec_id = VideoCodecToCodecID(config.codec());
- codec_context->profile = VideoCodecProfileToProfileID(config.profile());
- codec_context->coded_width = config.coded_size().width();
- codec_context->coded_height = config.coded_size().height();
- codec_context->pix_fmt = VideoFormatToPixelFormat(config.format());
-
- if (config.extra_data()) {
- codec_context->extradata_size = config.extra_data_size();
- codec_context->extradata = reinterpret_cast<uint8_t*>(
- av_malloc(config.extra_data_size() + FF_INPUT_BUFFER_PADDING_SIZE));
- memcpy(codec_context->extradata, config.extra_data(),
- config.extra_data_size());
- memset(codec_context->extradata + config.extra_data_size(), '\0',
- FF_INPUT_BUFFER_PADDING_SIZE);
- } else {
- codec_context->extradata = NULL;
- codec_context->extradata_size = 0;
- }
-}
-
-ChannelLayout ChannelLayoutToChromeChannelLayout(int64_t layout,
- int channels) {
- switch (layout) {
- case AV_CH_LAYOUT_MONO:
- return CHANNEL_LAYOUT_MONO;
- case AV_CH_LAYOUT_STEREO:
- return CHANNEL_LAYOUT_STEREO;
- case AV_CH_LAYOUT_2_1:
- return CHANNEL_LAYOUT_2_1;
- case AV_CH_LAYOUT_SURROUND:
- return CHANNEL_LAYOUT_SURROUND;
- case AV_CH_LAYOUT_4POINT0:
- return CHANNEL_LAYOUT_4_0;
- case AV_CH_LAYOUT_2_2:
- return CHANNEL_LAYOUT_2_2;
- case AV_CH_LAYOUT_QUAD:
- return CHANNEL_LAYOUT_QUAD;
- case AV_CH_LAYOUT_5POINT0:
- return CHANNEL_LAYOUT_5_0;
- case AV_CH_LAYOUT_5POINT1:
- return CHANNEL_LAYOUT_5_1;
- case AV_CH_LAYOUT_5POINT0_BACK:
- return CHANNEL_LAYOUT_5_0_BACK;
- case AV_CH_LAYOUT_5POINT1_BACK:
- return CHANNEL_LAYOUT_5_1_BACK;
- case AV_CH_LAYOUT_7POINT0:
- return CHANNEL_LAYOUT_7_0;
- case AV_CH_LAYOUT_7POINT1:
- return CHANNEL_LAYOUT_7_1;
- case AV_CH_LAYOUT_7POINT1_WIDE:
- return CHANNEL_LAYOUT_7_1_WIDE;
- case AV_CH_LAYOUT_STEREO_DOWNMIX:
- return CHANNEL_LAYOUT_STEREO_DOWNMIX;
- case AV_CH_LAYOUT_2POINT1:
- return CHANNEL_LAYOUT_2POINT1;
- case AV_CH_LAYOUT_3POINT1:
- return CHANNEL_LAYOUT_3_1;
- case AV_CH_LAYOUT_4POINT1:
- return CHANNEL_LAYOUT_4_1;
- case AV_CH_LAYOUT_6POINT0:
- return CHANNEL_LAYOUT_6_0;
- case AV_CH_LAYOUT_6POINT0_FRONT:
- return CHANNEL_LAYOUT_6_0_FRONT;
- case AV_CH_LAYOUT_HEXAGONAL:
- return CHANNEL_LAYOUT_HEXAGONAL;
- case AV_CH_LAYOUT_6POINT1:
- return CHANNEL_LAYOUT_6_1;
- case AV_CH_LAYOUT_6POINT1_BACK:
- return CHANNEL_LAYOUT_6_1_BACK;
- case AV_CH_LAYOUT_6POINT1_FRONT:
- return CHANNEL_LAYOUT_6_1_FRONT;
- case AV_CH_LAYOUT_7POINT0_FRONT:
- return CHANNEL_LAYOUT_7_0_FRONT;
- case AV_CH_LAYOUT_7POINT1_WIDE_BACK:
- return CHANNEL_LAYOUT_7_1_WIDE_BACK;
- case AV_CH_LAYOUT_OCTAGONAL:
- return CHANNEL_LAYOUT_OCTAGONAL;
- default:
- // FFmpeg channel_layout is 0 for .wav and .mp3. We know mono and stereo
- // from the number of channels, otherwise report errors.
- if (channels == 1)
- return CHANNEL_LAYOUT_MONO;
- if (channels == 2)
- return CHANNEL_LAYOUT_STEREO;
- LOG(ERROR) << "Unsupported channel layout: " << layout;
- }
- return CHANNEL_LAYOUT_UNSUPPORTED;
-}
-
-VideoFrame::Format PixelFormatToVideoFormat(PixelFormat pixel_format) {
- switch (pixel_format) {
- case PIX_FMT_YUV422P:
- return VideoFrame::YV16;
- case PIX_FMT_YUV420P:
- return VideoFrame::YV12;
- default:
- DVLOG(1) << "Unsupported PixelFormat: " << pixel_format;
- }
- return VideoFrame::INVALID;
-}
-
-PixelFormat VideoFormatToPixelFormat(VideoFrame::Format video_format) {
- switch (video_format) {
- case VideoFrame::YV16:
- return PIX_FMT_YUV422P;
- case VideoFrame::YV12:
- return PIX_FMT_YUV420P;
- default:
- DVLOG(1) << "Unsupported VideoFrame::Format: " << video_format;
- }
- return PIX_FMT_NONE;
-}
-
-} // namespace media
diff --git a/src/media/ffmpeg/ffmpeg_common.h b/src/media/ffmpeg/ffmpeg_common.h
deleted file mode 100644
index 38f70d0..0000000
--- a/src/media/ffmpeg/ffmpeg_common.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FFMPEG_FFMPEG_COMMON_H_
-#define MEDIA_FFMPEG_FFMPEG_COMMON_H_
-
-// Used for FFmpeg error codes.
-#include <cerrno>
-
-#include "base/compiler_specific.h"
-#include "base/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_decoder_config.h"
-
-// Include FFmpeg header files.
-extern "C" {
-// Temporarily disable possible loss of data warning.
-// TODO(scherkus): fix and upstream the compiler warnings.
-MSVC_PUSH_DISABLE_WARNING(4244);
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libavformat/avio.h>
-#include <libavutil/audioconvert.h>
-#include <libavutil/avutil.h>
-#include <libavutil/mathematics.h>
-#include <libavutil/log.h>
-#include <libavutil/imgutils.h>
-MSVC_POP_WARNING();
-} // extern "C"
-
-namespace media {
-
-class AudioDecoderConfig;
-class VideoDecoderConfig;
-
-// Wraps FFmpeg's av_free() in a class that can be passed as a template argument
-// to scoped_ptr_malloc.
-class ScopedPtrAVFree {
- public:
- inline void operator()(void* x) const {
- av_free(x);
- }
-};
-
-// This assumes that the AVPacket being captured was allocated outside of
-// FFmpeg via the new operator. Do not use this with AVPacket instances that
-// are allocated via malloc() or av_malloc().
-class ScopedPtrAVFreePacket {
- public:
- inline void operator()(void* x) const {
- AVPacket* packet = static_cast<AVPacket*>(x);
- av_free_packet(packet);
- delete packet;
- }
-};
-
-// Converts an int64 timestamp in |time_base| units to a base::TimeDelta.
-// For example if |timestamp| equals 11025 and |time_base| equals {1, 44100}
-// then the return value will be a base::TimeDelta for 0.25 seconds since that
-// is how much time 11025/44100ths of a second represents.
-MEDIA_EXPORT base::TimeDelta ConvertFromTimeBase(const AVRational& time_base,
- int64 timestamp);
-
-// Converts a base::TimeDelta into an int64 timestamp in |time_base| units.
-// For example if |timestamp| is 0.5 seconds and |time_base| is {1, 44100}, then
-// the return value will be 22050 since that is how many 1/44100ths of a second
-// represent 0.5 seconds.
-MEDIA_EXPORT int64 ConvertToTimeBase(const AVRational& time_base,
- const base::TimeDelta& timestamp);
-
-void AVCodecContextToAudioDecoderConfig(
- const AVCodecContext* codec_context,
- AudioDecoderConfig* config);
-void AudioDecoderConfigToAVCodecContext(
- const AudioDecoderConfig& config,
- AVCodecContext* codec_context);
-
-void AVStreamToVideoDecoderConfig(
- const AVStream* stream,
- VideoDecoderConfig* config);
-void VideoDecoderConfigToAVCodecContext(
- const VideoDecoderConfig& config,
- AVCodecContext* codec_context);
-
-// Converts FFmpeg's channel layout to chrome's ChannelLayout. |channels| can
-// be used when FFmpeg's channel layout is not informative in order to make a
-// good guess about the plausible channel layout based on number of channels.
-ChannelLayout ChannelLayoutToChromeChannelLayout(int64_t layout,
- int channels);
-
-// Converts FFmpeg's pixel formats to its corresponding supported video format.
-VideoFrame::Format PixelFormatToVideoFormat(PixelFormat pixel_format);
-
-// Converts video formats to its corresponding FFmpeg's pixel formats.
-PixelFormat VideoFormatToPixelFormat(VideoFrame::Format video_format);
-
-// Converts an FFmpeg video codec ID into its corresponding supported codec id.
-VideoCodec CodecIDToVideoCodec(CodecID codec_id);
-
-// Converts an FFmpeg audio codec ID into its corresponding supported codec id.
-AudioCodec CodecIDToAudioCodec(CodecID codec_id);
-
-} // namespace media
-
-#endif // MEDIA_FFMPEG_FFMPEG_COMMON_H_
diff --git a/src/media/ffmpeg/ffmpeg_common_unittest.cc b/src/media/ffmpeg/ffmpeg_common_unittest.cc
deleted file mode 100644
index d67e1fe..0000000
--- a/src/media/ffmpeg/ffmpeg_common_unittest.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "base/file_path.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "media/base/media.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::TimeDelta;
-
-namespace media {
-
-static AVIndexEntry kIndexEntries[] = {
- // pos, timestamp, flags, size, min_distance
- { 0, 0, AVINDEX_KEYFRAME, 0, 0 },
- { 2000, 1000, AVINDEX_KEYFRAME, 0, 0 },
- { 3000, 2000, 0, 0, 0 },
- { 5000, 3000, AVINDEX_KEYFRAME, 0, 0 },
- { 6000, 4000, 0, 0, 0 },
- { 8000, 5000, AVINDEX_KEYFRAME, 0, 0 },
- { 9000, 6000, AVINDEX_KEYFRAME, 0, 0 },
- { 11500, 7000, AVINDEX_KEYFRAME, 0, 0 },
-};
-
-static const AVRational kTimeBase = { 1, 1000 };
-
-class FFmpegCommonTest : public testing::Test {
- public:
- FFmpegCommonTest();
- virtual ~FFmpegCommonTest();
-
- protected:
- AVStream stream_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegCommonTest);
-};
-
-static bool InitFFmpeg() {
- static bool initialized = false;
- if (initialized) {
- return true;
- }
- FilePath path;
- PathService::Get(base::DIR_MODULE, &path);
- return media::InitializeMediaLibrary(path);
-}
-
-FFmpegCommonTest::FFmpegCommonTest() {
- CHECK(InitFFmpeg());
- stream_.time_base = kTimeBase;
- stream_.index_entries = kIndexEntries;
- stream_.index_entries_allocated_size = sizeof(kIndexEntries);
- stream_.nb_index_entries = arraysize(kIndexEntries);
-}
-
-FFmpegCommonTest::~FFmpegCommonTest() {}
-
-TEST_F(FFmpegCommonTest, TestTimeBaseConversions) {
- int64 test_data[][5] = {
- {1, 2, 1, 500000, 1 },
- {1, 3, 1, 333333, 1 },
- {1, 3, 2, 666667, 2 },
- };
-
- for (size_t i = 0; i < arraysize(test_data); ++i) {
- SCOPED_TRACE(i);
-
- AVRational time_base;
- time_base.num = static_cast<int>(test_data[i][0]);
- time_base.den = static_cast<int>(test_data[i][1]);
-
- TimeDelta time_delta = ConvertFromTimeBase(time_base, test_data[i][2]);
-
- EXPECT_EQ(time_delta.InMicroseconds(), test_data[i][3]);
- EXPECT_EQ(ConvertToTimeBase(time_base, time_delta), test_data[i][4]);
- }
-}
-
-} // namespace media
diff --git a/src/media/ffmpeg/ffmpeg_regression_tests.cc b/src/media/ffmpeg/ffmpeg_regression_tests.cc
deleted file mode 100644
index ba0d084..0000000
--- a/src/media/ffmpeg/ffmpeg_regression_tests.cc
+++ /dev/null
@@ -1,367 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// Regression tests for FFmpeg. Security test files can be found in the
-// internal media test data directory:
-//
-// svn://svn.chromium.org/chrome-internal/trunk/data/media/security/
-//
-// Simply add the custom_dep below to your gclient and sync:
-//
-// "src/media/test/data/security":
-// "svn://chrome-svn/chrome-internal/trunk/data/media/security"
-//
-// Many of the files here do not cause issues outside of tooling, so you'll need
-// to run this test under ASAN, TSAN, and Valgrind to ensure that all issues are
-// caught.
-//
-// Test cases labeled FLAKY may not always pass, but they should never crash or
-// cause any kind of warnings or errors under tooling.
-//
-// Frame hashes must be generated with --video-threads=1 for correctness.
-//
-// Known issues:
-// Cr47325 will generate an UninitValue error under Valgrind inside of the
-// MD5 hashing code. The error occurs due to some problematic error
-// resilence code for H264 inside of FFmpeg. See http://crbug.com/119020
-//
-// Some OGG files leak ~30 bytes of memory, upstream tracking bug:
-// https://ffmpeg.org/trac/ffmpeg/ticket/1244
-//
-// Some OGG files leak hundreds of kilobytes of memory, upstream bug:
-// https://ffmpeg.org/trac/ffmpeg/ticket/1931
-
-#include "media/filters/pipeline_integration_test_base.h"
-
-#include "base/bind.h"
-#include "media/base/test_data_util.h"
-
-namespace media {
-
-struct RegressionTestData {
- RegressionTestData(const char* filename, PipelineStatus init_status,
- PipelineStatus end_status, const char* video_md5,
- const char* audio_md5)
- : video_md5(video_md5),
- audio_md5(audio_md5),
- filename(filename),
- init_status(init_status),
- end_status(end_status) {
- }
-
- const char* video_md5;
- const char* audio_md5;
- const char* filename;
- PipelineStatus init_status;
- PipelineStatus end_status;
-};
-
-// Used for tests which just need to run without crashing or tooling errors, but
-// which may have undefined behavior for hashing, etc.
-struct FlakyRegressionTestData {
- FlakyRegressionTestData(const char* filename)
- : filename(filename) {
- }
-
- const char* filename;
-};
-
-class FFmpegRegressionTest
- : public testing::TestWithParam<RegressionTestData>,
- public PipelineIntegrationTestBase {
-};
-
-class FlakyFFmpegRegressionTest
- : public testing::TestWithParam<FlakyRegressionTestData>,
- public PipelineIntegrationTestBase {
-};
-
-#define FFMPEG_TEST_CASE(name, fn, init_status, end_status, video_md5, \
- audio_md5) \
- INSTANTIATE_TEST_CASE_P(name, FFmpegRegressionTest, \
- testing::Values(RegressionTestData(fn, \
- init_status, \
- end_status, \
- video_md5, \
- audio_md5)));
-
-#define FLAKY_FFMPEG_TEST_CASE(name, fn) \
- INSTANTIATE_TEST_CASE_P(FLAKY_##name, FlakyFFmpegRegressionTest, \
- testing::Values(FlakyRegressionTestData(fn)));
-
-// Test cases from issues.
-FFMPEG_TEST_CASE(Cr47325, "security/47325.mp4", PIPELINE_OK, PIPELINE_OK,
- "2a7a938c6b5979621cec998f02d9bbb6",
- "efbc63a850c9f8f51942f6a6029eb00f");
-FFMPEG_TEST_CASE(Cr47761, "content/crbug47761.ogg", PIPELINE_OK, PIPELINE_OK,
- kNullHash,
- "f45b9d7556f39dd811700ec72cb71483");
-FFMPEG_TEST_CASE(Cr50045, "content/crbug50045.mp4", PIPELINE_OK, PIPELINE_OK,
- "c345e9ef9ebfc6bfbcbe3f0ddc3125ba",
- "73d65d9cc6ce25060b7510bd74678c26");
-FFMPEG_TEST_CASE(Cr62127, "content/crbug62127.webm", PIPELINE_ERROR_DECODE,
- PIPELINE_ERROR_DECODE, "d41d8cd98f00b204e9800998ecf8427e",
- kNullHash);
-FFMPEG_TEST_CASE(Cr93620, "security/93620.ogg", PIPELINE_OK, PIPELINE_OK,
- kNullHash,
- "0cff252cd46867d26c42a96e6a2e2376");
-FFMPEG_TEST_CASE(Cr100492, "security/100492.webm", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr100543, "security/100543.webm", PIPELINE_OK, PIPELINE_OK,
- "c16691cc9178db3adbf7e562cadcd6e6",
- "816d9a772a449bc29f65f58244ee04c9");
-FFMPEG_TEST_CASE(Cr101458, "security/101458.webm", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr108416, "security/108416.webm", PIPELINE_OK, PIPELINE_OK,
- "5cb3a934795cd552753dec7687928291",
- "3e576c21f83f3c00719dbe62998d71cb");
-FFMPEG_TEST_CASE(Cr110849, "security/110849.mkv",
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr112384, "security/112384.webm",
- DEMUXER_ERROR_COULD_NOT_PARSE, DEMUXER_ERROR_COULD_NOT_PARSE,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr112670, "security/112670.mp4", PIPELINE_ERROR_DECODE,
- PIPELINE_ERROR_DECODE, kNullHash,
- "59adb24ef3cdbe0297f05b395827453f");
-FFMPEG_TEST_CASE(Cr112976, "security/112976.ogg", PIPELINE_OK,
- PIPELINE_ERROR_DECODE, kNullHash,
- "ef79f7c5805561908805eb0bb7097bb4");
-FFMPEG_TEST_CASE(Cr116927, "security/116927.ogv", DEMUXER_ERROR_COULD_NOT_OPEN,
- DEMUXER_ERROR_COULD_NOT_OPEN, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr117912, "security/117912.webm", DEMUXER_ERROR_COULD_NOT_OPEN,
- DEMUXER_ERROR_COULD_NOT_OPEN, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr123481, "security/123481.ogv", PIPELINE_OK,
- PIPELINE_OK, "e6dd853fcbd746c8bb2ab2b8fc376fc7",
- "c96a166a09061ca94202903d7824cf04");
-FFMPEG_TEST_CASE(Cr132779, "security/132779.webm",
- DEMUXER_ERROR_COULD_NOT_PARSE, DEMUXER_ERROR_COULD_NOT_PARSE,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr140165, "security/140165.ogg", PIPELINE_ERROR_DECODE,
- PIPELINE_ERROR_DECODE, kNullHash,
- "bd42757e42bdada18cb9441ee4ef8313");
-FFMPEG_TEST_CASE(Cr140647, "security/140647.ogv", DEMUXER_ERROR_COULD_NOT_OPEN,
- DEMUXER_ERROR_COULD_NOT_OPEN, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(Cr142738, "content/crbug142738.ogg", PIPELINE_OK, PIPELINE_OK,
- kNullHash,
- "03a9591e5b596eb848feeafd7693f371");
-FFMPEG_TEST_CASE(Cr152691, "security/152691.mp3", PIPELINE_ERROR_DECODE,
- PIPELINE_ERROR_DECODE, kNullHash,
- "59adb24ef3cdbe0297f05b395827453f");
-FFMPEG_TEST_CASE(Cr161639, "security/161639.m4a", PIPELINE_OK, PIPELINE_OK,
- kNullHash, "97ae2fa2a2e9ff3c2cf17be96b08bbe8");
-
-// General MKV test cases.
-FFMPEG_TEST_CASE(MKV_0, "security/nested_tags_lang.mka.627.628", PIPELINE_OK,
- PIPELINE_ERROR_DECODE, kNullHash,
- "3fc4e8ef212df08c61acce3db34b2d09");
-FFMPEG_TEST_CASE(MKV_1, "security/nested_tags_lang.mka.667.628", PIPELINE_OK,
- PIPELINE_ERROR_DECODE, kNullHash,
- "2f5ad3e7dd25fa5c0e8f26879953ef0f");
-
-// General MP4 test cases.
-FFMPEG_TEST_CASE(MP4_0, "security/aac.10419.mp4", DEMUXER_ERROR_COULD_NOT_OPEN,
- DEMUXER_ERROR_COULD_NOT_OPEN, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_1, "security/clockh264aac_200021889.mp4",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_2, "security/clockh264aac_200701257.mp4", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "d41d8cd98f00b204e9800998ecf8427e");
-FFMPEG_TEST_CASE(MP4_5, "security/clockh264aac_3022500.mp4",
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_6, "security/clockh264aac_344289.mp4", PIPELINE_OK,
- PIPELINE_OK, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_7, "security/clockh264mp3_187697.mp4",
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
- DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_8, "security/h264.705767.mp4",
- DEMUXER_ERROR_COULD_NOT_PARSE, DEMUXER_ERROR_COULD_NOT_PARSE,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_9, "security/smclockmp4aac_1_0.mp4",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(MP4_11, "security/null1.mp4", PIPELINE_OK, PIPELINE_OK,
- kNullHash, "7397188f229211987268f39ef5a45b3c");
-FFMPEG_TEST_CASE(MP4_16, "security/looping2.mov",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-
-// General OGV test cases.
-FFMPEG_TEST_CASE(OGV_1, "security/out.163.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_2, "security/out.391.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_5, "security/smclocktheora_1_0.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_7, "security/smclocktheora_1_102.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_8, "security/smclocktheora_1_104.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_9, "security/smclocktheora_1_110.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_10, "security/smclocktheora_1_179.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_11, "security/smclocktheora_1_20.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_12, "security/smclocktheora_1_723.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_14, "security/smclocktheora_2_10405.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_15, "security/smclocktheora_2_10619.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_16, "security/smclocktheora_2_1075.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_17, "security/vorbis.482086.ogv",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_18, "security/wav.711.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_19, "security/null1.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_20, "security/null2.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_21, "security/assert1.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(OGV_22, "security/assert2.ogv", DECODER_ERROR_NOT_SUPPORTED,
- DECODER_ERROR_NOT_SUPPORTED, kNullHash, kNullHash);
-
-// General WebM test cases.
-FFMPEG_TEST_CASE(WEBM_1, "security/no-bug.webm", PIPELINE_OK, PIPELINE_OK,
- "39e92700cbb77478fd63f49db855e7e5", kNullHash);
-FFMPEG_TEST_CASE(WEBM_2, "security/uninitialize.webm", PIPELINE_ERROR_DECODE,
- PIPELINE_ERROR_DECODE, kNullHash, kNullHash);
-FFMPEG_TEST_CASE(WEBM_3, "security/out.webm.139771.2965",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(WEBM_4, "security/out.webm.68798.1929",
- DECODER_ERROR_NOT_SUPPORTED, DECODER_ERROR_NOT_SUPPORTED,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(WEBM_5, "content/frame_size_change.webm", PIPELINE_OK,
- PIPELINE_OK, "d8fcf2896b7400a2261bac9e9ea930f8", kNullHash);
-
-// Audio Functional Tests
-FFMPEG_TEST_CASE(AUDIO_GAMING_0, "content/gaming/a_220_00.mp3", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "3c2e03569e2af83415a8f32065425f8c");
-FFMPEG_TEST_CASE(AUDIO_GAMING_1, "content/gaming/a_220_00_v2.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "2fa0e9fca48759a7de1c22418fba7ea0");
-FFMPEG_TEST_CASE(AUDIO_GAMING_2, "content/gaming/ai_laser1.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "d4f331b0f7f04e94cd70f037a1091c2b");
-FFMPEG_TEST_CASE(AUDIO_GAMING_3, "content/gaming/ai_laser2.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "7b0eccb651e5572711f9c8826cc14c3c");
-FFMPEG_TEST_CASE(AUDIO_GAMING_4, "content/gaming/ai_laser3.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "cd977a2dd4fa570f1a7392fc9948f184");
-FFMPEG_TEST_CASE(AUDIO_GAMING_5, "content/gaming/ai_laser4.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "155caa85c878abae43428f424cdc8848");
-FFMPEG_TEST_CASE(AUDIO_GAMING_6, "content/gaming/ai_laser5.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "c0f7768ac3c72aaf26ac7b6070d2392a");
-FFMPEG_TEST_CASE(AUDIO_GAMING_7, "content/gaming/footstep1.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "46fab3db625f0f9b655b9affbb1fff25");
-FFMPEG_TEST_CASE(AUDIO_GAMING_8, "content/gaming/footstep3.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "38b84b04eb3f1993eb97b5d46fa2a444");
-FFMPEG_TEST_CASE(AUDIO_GAMING_9, "content/gaming/footstep4.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "7a3927c3026fa96562b6c19950df0be0");
-FFMPEG_TEST_CASE(AUDIO_GAMING_10, "content/gaming/laser1.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "d2750f18ffce52f3763daba52117b66b");
-FFMPEG_TEST_CASE(AUDIO_GAMING_11, "content/gaming/laser2.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "bb398db9b2873e03a06d486d0a6f6d3a");
-FFMPEG_TEST_CASE(AUDIO_GAMING_12, "content/gaming/laser3.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "deb996d817e155ecd56766749d856e74");
-FFMPEG_TEST_CASE(AUDIO_GAMING_13, "content/gaming/leg1.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "556e339fd0d1bdcb2d98f69063614067");
-FFMPEG_TEST_CASE(AUDIO_GAMING_14, "content/gaming/leg2.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "313344cc2c02db5b23e336a9523b0c4a");
-FFMPEG_TEST_CASE(AUDIO_GAMING_15, "content/gaming/leg3.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "25730f36ed51ba07eacca9c2b6235e6c");
-FFMPEG_TEST_CASE(AUDIO_GAMING_16, "content/gaming/lock_on.ogg", PIPELINE_OK,
- PIPELINE_OK, kNullHash, "92a3af2fc3597e7aaf5b06748daf5d6a");
-FFMPEG_TEST_CASE(AUDIO_GAMING_17, "content/gaming/enemy_lock_on.ogg",
- PIPELINE_OK, PIPELINE_OK, kNullHash,
- "9670d8f5a668cf85f8ae8d6f8e0fdcdc");
-FFMPEG_TEST_CASE(AUDIO_GAMING_18, "content/gaming/rocket_launcher.mp3",
- PIPELINE_OK, PIPELINE_OK, kNullHash,
- "91354320606584f4404514d914d01ee0");
-
-// Allocate gigabytes of memory, likely can't be run on 32bit machines.
-FFMPEG_TEST_CASE(BIG_MEM_1, "security/bigmem1.mov",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(BIG_MEM_2, "security/looping1.mov",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-FFMPEG_TEST_CASE(BIG_MEM_5, "security/looping5.mov",
- DEMUXER_ERROR_COULD_NOT_OPEN, DEMUXER_ERROR_COULD_NOT_OPEN,
- kNullHash, kNullHash);
-FLAKY_FFMPEG_TEST_CASE(BIG_MEM_3, "security/looping3.mov");
-FLAKY_FFMPEG_TEST_CASE(BIG_MEM_4, "security/looping4.mov");
-
-// Flaky under threading or for other reasons. Per rbultje, most of these will
-// never be reliable since FFmpeg does not guarantee consistency in error cases.
-// We only really care that these don't cause crashes or errors under tooling.
-FLAKY_FFMPEG_TEST_CASE(Cr99652, "security/99652.webm");
-FLAKY_FFMPEG_TEST_CASE(Cr100464, "security/100464.webm");
-FLAKY_FFMPEG_TEST_CASE(Cr111342, "security/111342.ogm");
-FLAKY_FFMPEG_TEST_CASE(OGV_0, "security/big_dims.ogv");
-FLAKY_FFMPEG_TEST_CASE(OGV_3, "security/smclock_1_0.ogv");
-FLAKY_FFMPEG_TEST_CASE(OGV_4, "security/smclock.ogv.1.0.ogv");
-FLAKY_FFMPEG_TEST_CASE(OGV_6, "security/smclocktheora_1_10000.ogv");
-FLAKY_FFMPEG_TEST_CASE(OGV_13, "security/smclocktheora_1_790.ogv");
-FLAKY_FFMPEG_TEST_CASE(MP4_3, "security/clockh264aac_300413969.mp4");
-FLAKY_FFMPEG_TEST_CASE(MP4_4, "security/clockh264aac_301350139.mp4");
-FLAKY_FFMPEG_TEST_CASE(MP4_12, "security/assert1.mov");
-// Not really flaky, but can't pass the seek test.
-FLAKY_FFMPEG_TEST_CASE(MP4_10, "security/null1.m4a");
-
-// Videos with massive gaps between frame timestamps that result in long hangs
-// with our pipeline. Should be uncommented when we support clockless playback.
-// FFMPEG_TEST_CASE(WEBM_0, "security/memcpy.webm", PIPELINE_OK, PIPELINE_OK,
-// kNullHash, kNullHash);
-// FFMPEG_TEST_CASE(MP4_17, "security/assert2.mov", PIPELINE_OK, PIPELINE_OK,
-// kNullHash, kNullHash);
-// FFMPEG_TEST_CASE(OGV_23, "security/assert2.ogv", PIPELINE_OK, PIPELINE_OK,
-// kNullHash, kNullHash);
-
-TEST_P(FFmpegRegressionTest, BasicPlayback) {
- if (GetParam().init_status == PIPELINE_OK) {
- ASSERT_TRUE(Start(GetTestDataFilePath(GetParam().filename),
- GetParam().init_status, true));
- Play();
- ASSERT_EQ(WaitUntilEndedOrError(), GetParam().end_status);
- EXPECT_EQ(GetParam().video_md5, GetVideoHash());
- EXPECT_EQ(GetParam().audio_md5, GetAudioHash());
-
- // Check for ended if the pipeline is expected to finish okay.
- if (GetParam().end_status == PIPELINE_OK) {
- ASSERT_TRUE(ended_);
-
- // Tack a seek on the end to catch any seeking issues.
- Seek(base::TimeDelta::FromMilliseconds(0));
- }
- } else {
- ASSERT_FALSE(Start(GetTestDataFilePath(GetParam().filename),
- GetParam().init_status, true));
- EXPECT_EQ(GetParam().video_md5, GetVideoHash());
- EXPECT_EQ(GetParam().audio_md5, GetAudioHash());
- }
-}
-
-TEST_P(FlakyFFmpegRegressionTest, BasicPlayback) {
- if (Start(GetTestDataFilePath(GetParam().filename))) {
- Play();
- WaitUntilEndedOrError();
- }
-}
-
-} // namespace media
diff --git a/src/media/ffmpeg/ffmpeg_unittest.cc b/src/media/ffmpeg/ffmpeg_unittest.cc
deleted file mode 100644
index 4de80ce..0000000
--- a/src/media/ffmpeg/ffmpeg_unittest.cc
+++ /dev/null
@@ -1,590 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ffmpeg_unittests verify that the parts of the FFmpeg API that Chromium uses
-// function as advertised for each media format that Chromium supports. This
-// mostly includes stuff like reporting proper timestamps, seeking to
-// keyframes, and supporting certain features like reordered_opaque.
-//
-
-#include <limits>
-#include <queue>
-
-#include "base/base_paths.h"
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/path_service.h"
-#include "base/perftimer.h"
-#include "base/string_util.h"
-#include "base/test/perf_test_suite.h"
-#include "media/base/media.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/filters/in_memory_url_protocol.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-int main(int argc, char** argv) {
- return base::PerfTestSuite(argc, argv).Run();
-}
-
-namespace media {
-
-// Mirror setting in ffmpeg_video_decoder.
-static const int kDecodeThreads = 2;
-
-class AVPacketQueue {
- public:
- AVPacketQueue() {
- }
-
- ~AVPacketQueue() {
- flush();
- }
-
- bool empty() {
- return packets_.empty();
- }
-
- AVPacket* peek() {
- return packets_.front();
- }
-
- void pop() {
- AVPacket* packet = packets_.front();
- packets_.pop();
- av_free_packet(packet);
- delete packet;
- }
-
- void push(AVPacket* packet) {
- av_dup_packet(packet);
- packets_.push(packet);
- }
-
- void flush() {
- while (!empty()) {
- pop();
- }
- }
-
- private:
- std::queue<AVPacket*> packets_;
-
- DISALLOW_COPY_AND_ASSIGN(AVPacketQueue);
-};
-
-// TODO(dalecurtis): We should really just use PipelineIntegrationTests instead
-// of a one-off step decoder so we're exercising the real pipeline.
-class FFmpegTest : public testing::TestWithParam<const char*> {
- protected:
- FFmpegTest()
- : av_format_context_(NULL),
- audio_stream_index_(-1),
- video_stream_index_(-1),
- audio_buffer_(NULL),
- video_buffer_(NULL),
- decoded_audio_time_(AV_NOPTS_VALUE),
- decoded_audio_duration_(AV_NOPTS_VALUE),
- decoded_video_time_(AV_NOPTS_VALUE),
- decoded_video_duration_(AV_NOPTS_VALUE),
- duration_(AV_NOPTS_VALUE) {
- InitializeFFmpeg();
-
- audio_buffer_.reset(avcodec_alloc_frame());
- video_buffer_.reset(avcodec_alloc_frame());
- }
-
- virtual ~FFmpegTest() {
- }
-
- void OpenAndReadFile(const std::string& name) {
- OpenFile(name);
- OpenCodecs();
- ReadRemainingFile();
- }
-
- void OpenFile(const std::string& name) {
- FilePath path;
- PathService::Get(base::DIR_SOURCE_ROOT, &path);
- path = path.AppendASCII("media")
- .AppendASCII("test")
- .AppendASCII("data")
- .AppendASCII("content")
- .AppendASCII(name.c_str());
- EXPECT_TRUE(file_util::PathExists(path));
-
- CHECK(file_data_.Initialize(path));
- protocol_.reset(new InMemoryUrlProtocol(
- file_data_.data(), file_data_.length(), false));
- glue_.reset(new FFmpegGlue(protocol_.get()));
-
- ASSERT_TRUE(glue_->OpenContext()) << "Could not open " << path.value();
- av_format_context_ = glue_->format_context();
- ASSERT_LE(0, avformat_find_stream_info(av_format_context_, NULL))
- << "Could not find stream information for " << path.value();
-
- // Determine duration by picking max stream duration.
- for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
- AVStream* av_stream = av_format_context_->streams[i];
- int64 duration = ConvertFromTimeBase(
- av_stream->time_base, av_stream->duration).InMicroseconds();
- duration_ = std::max(duration_, duration);
- }
-
- // Final check to see if the container itself specifies a duration.
- AVRational av_time_base = {1, AV_TIME_BASE};
- int64 duration =
- ConvertFromTimeBase(av_time_base,
- av_format_context_->duration).InMicroseconds();
- duration_ = std::max(duration_, duration);
- }
-
- void OpenCodecs() {
- for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
- AVStream* av_stream = av_format_context_->streams[i];
- AVCodecContext* av_codec_context = av_stream->codec;
- AVCodec* av_codec = avcodec_find_decoder(av_codec_context->codec_id);
-
- EXPECT_TRUE(av_codec)
- << "Could not find AVCodec with CodecID "
- << av_codec_context->codec_id;
-
- av_codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
- av_codec_context->thread_count = kDecodeThreads;
-
- EXPECT_EQ(0, avcodec_open2(av_codec_context, av_codec, NULL))
- << "Could not open AVCodecContext with CodecID "
- << av_codec_context->codec_id;
-
- if (av_codec->type == AVMEDIA_TYPE_AUDIO) {
- EXPECT_EQ(-1, audio_stream_index_) << "Found multiple audio streams.";
- audio_stream_index_ = static_cast<int>(i);
- } else if (av_codec->type == AVMEDIA_TYPE_VIDEO) {
- EXPECT_EQ(-1, video_stream_index_) << "Found multiple video streams.";
- video_stream_index_ = static_cast<int>(i);
- } else {
- ADD_FAILURE() << "Found unknown stream type.";
- }
- }
- }
-
- void Flush() {
- if (has_audio()) {
- audio_packets_.flush();
- avcodec_flush_buffers(av_audio_context());
- }
- if (has_video()) {
- video_packets_.flush();
- avcodec_flush_buffers(av_video_context());
- }
- }
-
- void ReadUntil(int64 time) {
- while (true) {
- scoped_ptr<AVPacket> packet(new AVPacket());
- if (av_read_frame(av_format_context_, packet.get()) < 0) {
- break;
- }
-
- int stream_index = static_cast<int>(packet->stream_index);
- int64 packet_time = AV_NOPTS_VALUE;
- if (stream_index == audio_stream_index_) {
- packet_time =
- ConvertFromTimeBase(av_audio_stream()->time_base, packet->pts)
- .InMicroseconds();
- audio_packets_.push(packet.release());
- } else if (stream_index == video_stream_index_) {
- packet_time =
- ConvertFromTimeBase(av_video_stream()->time_base, packet->pts)
- .InMicroseconds();
- video_packets_.push(packet.release());
- } else {
- ADD_FAILURE() << "Found packet that belongs to unknown stream.";
- }
-
- if (packet_time > time) {
- break;
- }
- }
- }
-
- void ReadRemainingFile() {
- ReadUntil(std::numeric_limits<int64>::max());
- }
-
- bool StepDecodeAudio() {
- EXPECT_TRUE(has_audio());
- if (!has_audio() || audio_packets_.empty()) {
- return false;
- }
-
- // Decode until output is produced, end of stream, or error.
- while (true) {
- int result = 0;
- int got_audio = 0;
- bool end_of_stream = false;
-
- AVPacket packet;
- if (audio_packets_.empty()) {
- av_init_packet(&packet);
- end_of_stream = true;
- } else {
- memcpy(&packet, audio_packets_.peek(), sizeof(packet));
- }
-
- avcodec_get_frame_defaults(audio_buffer_.get());
- result = avcodec_decode_audio4(av_audio_context(), audio_buffer_.get(),
- &got_audio, &packet);
- if (!audio_packets_.empty()) {
- audio_packets_.pop();
- }
-
- EXPECT_GE(result, 0) << "Audio decode error.";
- if (result < 0 || (got_audio == 0 && end_of_stream)) {
- return false;
- }
-
- if (result > 0) {
- double microseconds = 1.0L * audio_buffer_->nb_samples /
- av_audio_context()->sample_rate *
- base::Time::kMicrosecondsPerSecond;
- decoded_audio_duration_ = static_cast<int64>(microseconds);
-
- if (packet.pts == static_cast<int64>(AV_NOPTS_VALUE)) {
- EXPECT_NE(decoded_audio_time_, static_cast<int64>(AV_NOPTS_VALUE))
- << "We never received an initial timestamped audio packet! "
- << "Looks like there's a seeking/parsing bug in FFmpeg.";
- decoded_audio_time_ += decoded_audio_duration_;
- } else {
- decoded_audio_time_ =
- ConvertFromTimeBase(av_audio_stream()->time_base, packet.pts)
- .InMicroseconds();
- }
- return true;
- }
- }
- return true;
- }
-
- bool StepDecodeVideo() {
- EXPECT_TRUE(has_video());
- if (!has_video() || video_packets_.empty()) {
- return false;
- }
-
- // Decode until output is produced, end of stream, or error.
- while (true) {
- int result = 0;
- int got_picture = 0;
- bool end_of_stream = false;
-
- AVPacket packet;
- if (video_packets_.empty()) {
- av_init_packet(&packet);
- end_of_stream = true;
- } else {
- memcpy(&packet, video_packets_.peek(), sizeof(packet));
- }
-
- avcodec_get_frame_defaults(video_buffer_.get());
- av_video_context()->reordered_opaque = packet.pts;
- result = avcodec_decode_video2(av_video_context(), video_buffer_.get(),
- &got_picture, &packet);
- if (!video_packets_.empty()) {
- video_packets_.pop();
- }
-
- EXPECT_GE(result, 0) << "Video decode error.";
- if (result < 0 || (got_picture == 0 && end_of_stream)) {
- return false;
- }
-
- if (got_picture) {
- AVRational doubled_time_base;
- doubled_time_base.den = av_video_stream()->r_frame_rate.num;
- doubled_time_base.num = av_video_stream()->r_frame_rate.den;
- doubled_time_base.den *= 2;
-
- decoded_video_time_ =
- ConvertFromTimeBase(av_video_stream()->time_base,
- video_buffer_->reordered_opaque)
- .InMicroseconds();
- decoded_video_duration_ =
- ConvertFromTimeBase(doubled_time_base,
- 2 + video_buffer_->repeat_pict)
- .InMicroseconds();
- return true;
- }
- }
- }
-
- void DecodeRemainingAudio() {
- while (StepDecodeAudio()) {}
- }
-
- void DecodeRemainingVideo() {
- while (StepDecodeVideo()) {}
- }
-
- void SeekTo(double position) {
- int64 seek_time =
- static_cast<int64>(position * base::Time::kMicrosecondsPerSecond);
- int flags = AVSEEK_FLAG_BACKWARD;
-
- // Passing -1 as our stream index lets FFmpeg pick a default stream.
- // FFmpeg will attempt to use the lowest-index video stream, if present,
- // followed by the lowest-index audio stream.
- EXPECT_GE(0, av_seek_frame(av_format_context_, -1, seek_time, flags))
- << "Failed to seek to position " << position;
- Flush();
- }
-
- bool has_audio() { return audio_stream_index_ >= 0; }
- bool has_video() { return video_stream_index_ >= 0; }
- int64 decoded_audio_time() { return decoded_audio_time_; }
- int64 decoded_audio_duration() { return decoded_audio_duration_; }
- int64 decoded_video_time() { return decoded_video_time_; }
- int64 decoded_video_duration() { return decoded_video_duration_; }
- int64 duration() { return duration_; }
-
- AVStream* av_audio_stream() {
- return av_format_context_->streams[audio_stream_index_];
- }
- AVStream* av_video_stream() {
- return av_format_context_->streams[video_stream_index_];
- }
- AVCodecContext* av_audio_context() {
- return av_audio_stream()->codec;
- }
- AVCodecContext* av_video_context() {
- return av_video_stream()->codec;
- }
-
- private:
- void InitializeFFmpeg() {
- static bool initialized = false;
- if (initialized) {
- return;
- }
-
- FilePath path;
- PathService::Get(base::DIR_MODULE, &path);
- EXPECT_TRUE(InitializeMediaLibrary(path))
- << "Could not initialize media library.";
-
- initialized = true;
- }
-
- AVFormatContext* av_format_context_;
- int audio_stream_index_;
- int video_stream_index_;
- AVPacketQueue audio_packets_;
- AVPacketQueue video_packets_;
-
- scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_buffer_;
- scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_buffer_;
-
- int64 decoded_audio_time_;
- int64 decoded_audio_duration_;
- int64 decoded_video_time_;
- int64 decoded_video_duration_;
- int64 duration_;
-
- file_util::MemoryMappedFile file_data_;
- scoped_ptr<InMemoryUrlProtocol> protocol_;
- scoped_ptr<FFmpegGlue> glue_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegTest);
-};
-
-#define FFMPEG_TEST_CASE(name, extension) \
- INSTANTIATE_TEST_CASE_P(name##_##extension, FFmpegTest, \
- testing::Values(#name "." #extension));
-
-// Covers all our basic formats.
-FFMPEG_TEST_CASE(sync0, mp4);
-FFMPEG_TEST_CASE(sync0, ogv);
-FFMPEG_TEST_CASE(sync0, webm);
-FFMPEG_TEST_CASE(sync1, m4a);
-FFMPEG_TEST_CASE(sync1, mp3);
-FFMPEG_TEST_CASE(sync1, mp4);
-FFMPEG_TEST_CASE(sync1, ogg);
-FFMPEG_TEST_CASE(sync1, ogv);
-FFMPEG_TEST_CASE(sync1, webm);
-FFMPEG_TEST_CASE(sync2, m4a);
-FFMPEG_TEST_CASE(sync2, mp3);
-FFMPEG_TEST_CASE(sync2, mp4);
-FFMPEG_TEST_CASE(sync2, ogg);
-FFMPEG_TEST_CASE(sync2, ogv);
-FFMPEG_TEST_CASE(sync2, webm);
-
-// Covers our LayoutTest file.
-FFMPEG_TEST_CASE(counting, ogv);
-
-TEST_P(FFmpegTest, Perf) {
- {
- PerfTimeLogger timer("Opening file");
- OpenFile(GetParam());
- }
- {
- PerfTimeLogger timer("Opening codecs");
- OpenCodecs();
- }
- {
- PerfTimeLogger timer("Reading file");
- ReadRemainingFile();
- }
- if (has_audio()) {
- PerfTimeLogger timer("Decoding audio");
- DecodeRemainingAudio();
- }
- if (has_video()) {
- PerfTimeLogger timer("Decoding video");
- DecodeRemainingVideo();
- }
- {
- PerfTimeLogger timer("Seeking to zero");
- SeekTo(0);
- }
-}
-
-TEST_P(FFmpegTest, Loop_Audio) {
- OpenAndReadFile(GetParam());
- if (!has_audio()) {
- return;
- }
-
- const int kSteps = 4;
- std::vector<int64> expected_timestamps_;
- for (int i = 0; i < kSteps; ++i) {
- EXPECT_TRUE(StepDecodeAudio());
- expected_timestamps_.push_back(decoded_audio_time());
- }
-
- SeekTo(0);
- ReadRemainingFile();
-
- for (int i = 0; i < kSteps; ++i) {
- EXPECT_TRUE(StepDecodeAudio());
- EXPECT_EQ(expected_timestamps_[i], decoded_audio_time())
- << "Frame " << i << " had a mismatched timestamp.";
- }
-}
-
-TEST_P(FFmpegTest, Loop_Video) {
- OpenAndReadFile(GetParam());
- if (!has_video()) {
- return;
- }
-
- const int kSteps = 4;
- std::vector<int64> expected_timestamps_;
- for (int i = 0; i < kSteps; ++i) {
- EXPECT_TRUE(StepDecodeVideo());
- expected_timestamps_.push_back(decoded_video_time());
- }
-
- SeekTo(0);
- ReadRemainingFile();
-
- for (int i = 0; i < kSteps; ++i) {
- EXPECT_TRUE(StepDecodeVideo());
- EXPECT_EQ(expected_timestamps_[i], decoded_video_time())
- << "Frame " << i << " had a mismatched timestamp.";
- }
-}
-
-TEST_P(FFmpegTest, Seek_Audio) {
- OpenAndReadFile(GetParam());
- if (!has_audio() && duration() >= 0.5) {
- return;
- }
-
- SeekTo(duration() - 0.5);
- ReadRemainingFile();
-
- EXPECT_TRUE(StepDecodeAudio());
- EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_audio_time());
-}
-
-TEST_P(FFmpegTest, Seek_Video) {
- OpenAndReadFile(GetParam());
- if (!has_video() && duration() >= 0.5) {
- return;
- }
-
- SeekTo(duration() - 0.5);
- ReadRemainingFile();
-
- EXPECT_TRUE(StepDecodeVideo());
- EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_video_time());
-}
-
-TEST_P(FFmpegTest, Decode_Audio) {
- OpenAndReadFile(GetParam());
- if (!has_audio()) {
- return;
- }
-
- int64 last_audio_time = AV_NOPTS_VALUE;
- while (StepDecodeAudio()) {
- ASSERT_GT(decoded_audio_time(), last_audio_time);
- last_audio_time = decoded_audio_time();
- }
-}
-
-TEST_P(FFmpegTest, Decode_Video) {
- OpenAndReadFile(GetParam());
- if (!has_video()) {
- return;
- }
-
- int64 last_video_time = AV_NOPTS_VALUE;
- while (StepDecodeVideo()) {
- ASSERT_GT(decoded_video_time(), last_video_time);
- last_video_time = decoded_video_time();
- }
-}
-
-TEST_P(FFmpegTest, Duration) {
- OpenAndReadFile(GetParam());
-
- if (has_audio()) {
- DecodeRemainingAudio();
- }
-
- if (has_video()) {
- DecodeRemainingVideo();
- }
-
- double expected = static_cast<double>(duration());
- double actual = static_cast<double>(
- std::max(decoded_audio_time() + decoded_audio_duration(),
- decoded_video_time() + decoded_video_duration()));
- EXPECT_NEAR(expected, actual, 500000)
- << "Duration is off by more than 0.5 seconds.";
-}
-
-TEST_F(FFmpegTest, VideoPlayedCollapse) {
- OpenFile("test.ogv");
- OpenCodecs();
-
- SeekTo(0.5);
- ReadRemainingFile();
- EXPECT_TRUE(StepDecodeVideo());
- VLOG(1) << decoded_video_time();
-
- SeekTo(2.83);
- ReadRemainingFile();
- EXPECT_TRUE(StepDecodeVideo());
- VLOG(1) << decoded_video_time();
-
- SeekTo(0.4);
- ReadRemainingFile();
- EXPECT_TRUE(StepDecodeVideo());
- VLOG(1) << decoded_video_time();
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_decoder_selector.cc b/src/media/filters/audio_decoder_selector.cc
deleted file mode 100644
index 9774d0c..0000000
--- a/src/media/filters/audio_decoder_selector.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/audio_decoder_selector.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/filters/decrypting_audio_decoder.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-
-namespace media {
-
-AudioDecoderSelector::AudioDecoderSelector(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const AudioDecoderList& decoders,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : message_loop_(message_loop),
- decoders_(decoders),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
-}
-
-AudioDecoderSelector::~AudioDecoderSelector() {}
-
-void AudioDecoderSelector::SelectAudioDecoder(
- const scoped_refptr<DemuxerStream>& stream,
- const StatisticsCB& statistics_cb,
- const SelectDecoderCB& select_decoder_cb) {
- DVLOG(2) << "SelectAudioDecoder()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(stream);
-
- // Make sure |select_decoder_cb| runs on a different execution stack.
- select_decoder_cb_ = BindToCurrentLoop(select_decoder_cb);
-
- const AudioDecoderConfig& config = stream->audio_decoder_config();
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid audio stream config.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- input_stream_ = stream;
- statistics_cb_ = statistics_cb;
-
- if (!config.is_encrypted()) {
- if (decoders_.empty()) {
- DLOG(ERROR) << "No audio decoder can be used to decode the input stream.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- InitializeNextDecoder();
- return;
- }
-
- // This could happen if Encrypted Media Extension (EME) is not enabled.
- if (set_decryptor_ready_cb_.is_null()) {
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
-#if defined(COBALT) || defined(__LB_SHELL__)
- DecryptingAudioDecoderInitDone(DECODER_ERROR_NOT_SUPPORTED);
-#else // defined(COBALT) || defined(__LB_SHELL__)
- audio_decoder_ = new DecryptingAudioDecoder(message_loop_,
- set_decryptor_ready_cb_);
-
- audio_decoder_->Initialize(
- input_stream_,
- BindToCurrentLoop(base::Bind(
- &AudioDecoderSelector::DecryptingAudioDecoderInitDone,
- weak_ptr_factory_.GetWeakPtr())),
- statistics_cb_);
-#endif // defined(COBALT) || defined(__LB_SHELL__)
-}
-
-void AudioDecoderSelector::DecryptingAudioDecoderInitDone(
- PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status == PIPELINE_OK) {
- decoders_.clear();
- base::ResetAndReturn(&select_decoder_cb_).Run(audio_decoder_, NULL);
- return;
- }
-
- audio_decoder_ = NULL;
-
- if (decoders_.empty()) {
- DLOG(ERROR) << "No audio decoder can be used to decode the input stream.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- decrypted_stream_ = new DecryptingDemuxerStream(
- message_loop_, set_decryptor_ready_cb_);
-
- decrypted_stream_->Initialize(
- input_stream_,
- BindToCurrentLoop(base::Bind(
- &AudioDecoderSelector::DecryptingDemuxerStreamInitDone,
- weak_ptr_factory_.GetWeakPtr())));
-}
-
-void AudioDecoderSelector::DecryptingDemuxerStreamInitDone(
- PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status != PIPELINE_OK) {
- decrypted_stream_ = NULL;
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- DCHECK(!decrypted_stream_->audio_decoder_config().is_encrypted());
- input_stream_ = decrypted_stream_;
- InitializeNextDecoder();
-}
-
-void AudioDecoderSelector::InitializeNextDecoder() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!decoders_.empty());
-
- audio_decoder_ = decoders_.front();
- decoders_.pop_front();
- DCHECK(audio_decoder_);
- audio_decoder_->Initialize(
- input_stream_,
- BindToCurrentLoop(base::Bind(
- &AudioDecoderSelector::DecoderInitDone,
- weak_ptr_factory_.GetWeakPtr())),
- statistics_cb_);
-}
-
-void AudioDecoderSelector::DecoderInitDone(PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status != PIPELINE_OK) {
- if (!decoders_.empty())
- InitializeNextDecoder();
- else
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- decoders_.clear();
- base::ResetAndReturn(&select_decoder_cb_).Run(audio_decoder_,
- decrypted_stream_);
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_decoder_selector.h b/src/media/filters/audio_decoder_selector.h
deleted file mode 100644
index 84a483b..0000000
--- a/src/media/filters/audio_decoder_selector.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_AUDIO_DECODER_SELECTOR_H_
-#define MEDIA_FILTERS_AUDIO_DECODER_SELECTOR_H_
-
-#include <list>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-class DecryptingDemuxerStream;
-class Decryptor;
-
-// AudioDecoderSelector (creates if necessary and) initializes the proper
-// AudioDecoder for a given DemuxerStream. If the given DemuxerStream is
-// encrypted, a DecryptingDemuxerStream may also be created.
-class MEDIA_EXPORT AudioDecoderSelector {
- public:
- typedef std::list<scoped_refptr<AudioDecoder> > AudioDecoderList;
-
- // Indicates completion of AudioDecoder selection.
- // - First parameter: The initialized AudioDecoder. If it's set to NULL, then
- // AudioDecoder initialization failed.
- // - Second parameter: The initialized DecryptingDemuxerStream. If it's not
- // NULL, then a DecryptingDemuxerStream is created and initialized to do
- // decryption for the initialized AudioDecoder.
- // Note: The caller owns selected AudioDecoder and DecryptingDemuxerStream.
- // The caller should call DecryptingDemuxerStream::Reset() before
- // calling AudioDecoder::Reset() to release any pending decryption or read.
- typedef base::Callback<
- void(const scoped_refptr<AudioDecoder>&,
- const scoped_refptr<DecryptingDemuxerStream>&)> SelectDecoderCB;
-
- // |set_decryptor_ready_cb| is optional. If |set_decryptor_ready_cb| is null,
- // no decryptor will be available to perform decryption.
- AudioDecoderSelector(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const AudioDecoderList& decoders,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
- ~AudioDecoderSelector();
-
- // Initializes and selects an AudioDecoder that can decode the |stream|.
- // Selected AudioDecoder (and DecryptingDemuxerStream) is returned via
- // the |select_decoder_cb|.
- void SelectAudioDecoder(const scoped_refptr<DemuxerStream>& stream,
- const StatisticsCB& statistics_cb,
- const SelectDecoderCB& select_decoder_cb);
-
- private:
- void DecryptingAudioDecoderInitDone(PipelineStatus status);
- void DecryptingDemuxerStreamInitDone(PipelineStatus status);
- void InitializeNextDecoder();
- void DecoderInitDone(PipelineStatus status);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- AudioDecoderList decoders_;
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- scoped_refptr<DemuxerStream> input_stream_;
- StatisticsCB statistics_cb_;
- SelectDecoderCB select_decoder_cb_;
-
- scoped_refptr<AudioDecoder> audio_decoder_;
- scoped_refptr<DecryptingDemuxerStream> decrypted_stream_;
-
- base::WeakPtrFactory<AudioDecoderSelector> weak_ptr_factory_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(AudioDecoderSelector);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_AUDIO_DECODER_SELECTOR_H_
diff --git a/src/media/filters/audio_decoder_selector_unittest.cc b/src/media/filters/audio_decoder_selector_unittest.cc
deleted file mode 100644
index 7b0631a..0000000
--- a/src/media/filters/audio_decoder_selector_unittest.cc
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <vector>
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_filters.h"
-#include "media/filters/audio_decoder_selector.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::IsNull;
-using ::testing::NiceMock;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::StrictMock;
-
-namespace media {
-
-class AudioDecoderSelectorTest : public ::testing::Test {
- public:
- enum DecryptorCapability {
- kNoDecryptor,
- kDecryptOnly,
- kDecryptAndDecode
- };
-
- AudioDecoderSelectorTest()
- : clear_audio_config_(
- kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, false),
- encrypted_audio_config_(
- kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, true),
- demuxer_stream_(new StrictMock<MockDemuxerStream>()),
- decryptor_(new NiceMock<MockDecryptor>()),
- decoder_1_(new StrictMock<MockAudioDecoder>()),
- decoder_2_(new StrictMock<MockAudioDecoder>()) {
- all_decoders_.push_back(decoder_1_);
- all_decoders_.push_back(decoder_2_);
-
- EXPECT_CALL(*demuxer_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::AUDIO));
- }
-
- MOCK_METHOD1(OnStatistics, void(const PipelineStatistics&));
- MOCK_METHOD1(SetDecryptorReadyCallback, void(const media::DecryptorReadyCB&));
- MOCK_METHOD2(OnDecoderSelected,
- void(const scoped_refptr<AudioDecoder>&,
- const scoped_refptr<DecryptingDemuxerStream>&));
-
- void UseClearStream() {
- EXPECT_CALL(*demuxer_stream_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(clear_audio_config_));
- }
-
- void UseEncryptedStream() {
- EXPECT_CALL(*demuxer_stream_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(encrypted_audio_config_));
- }
-
- void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
- int num_decoders) {
- SetDecryptorReadyCB set_decryptor_ready_cb;
- if (decryptor_capability == kDecryptOnly ||
- decryptor_capability == kDecryptAndDecode) {
- set_decryptor_ready_cb = base::Bind(
- &AudioDecoderSelectorTest::SetDecryptorReadyCallback,
- base::Unretained(this));
-
- EXPECT_CALL(*this, SetDecryptorReadyCallback(_))
- .WillRepeatedly(RunCallback<0>(decryptor_.get()));
-
- if (decryptor_capability == kDecryptOnly) {
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillRepeatedly(RunCallback<1>(false));
- } else {
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillRepeatedly(RunCallback<1>(true));
- }
- }
-
- DCHECK_GE(all_decoders_.size(), static_cast<size_t>(num_decoders));
- AudioDecoderSelector::AudioDecoderList decoders(
- all_decoders_.begin(), all_decoders_.begin() + num_decoders);
-
- decoder_selector_.reset(new AudioDecoderSelector(
- message_loop_.message_loop_proxy(),
- decoders,
- set_decryptor_ready_cb));
- }
-
- void SelectDecoder() {
- decoder_selector_->SelectAudioDecoder(
- demuxer_stream_,
- base::Bind(&AudioDecoderSelectorTest::OnStatistics,
- base::Unretained(this)),
- base::Bind(&AudioDecoderSelectorTest::OnDecoderSelected,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- // Fixture members.
- scoped_ptr<AudioDecoderSelector> decoder_selector_;
- AudioDecoderConfig clear_audio_config_;
- AudioDecoderConfig encrypted_audio_config_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_stream_;
- // Use NiceMock since we don't care about most of calls on the decryptor, e.g.
- // RegisterKeyAddedCB().
- scoped_ptr<NiceMock<MockDecryptor> > decryptor_;
- scoped_refptr<StrictMock<MockAudioDecoder> > decoder_1_;
- scoped_refptr<StrictMock<MockAudioDecoder> > decoder_2_;
- std::vector<scoped_refptr<AudioDecoder> > all_decoders_;
- MessageLoop message_loop_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AudioDecoderSelectorTest);
-};
-
-// The stream is not encrypted but we have no clear decoder. No decoder can be
-// selected.
-TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_NoClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 0);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// The stream is not encrypted and we have one clear decoder. The decoder
-// will be selected.
-TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_OneClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<AudioDecoder>(decoder_1_),
- IsNull()));
-
- SelectDecoder();
-}
-
-// The stream is not encrypted and we have multiple clear decoders. The first
-// decoder that can decode the input stream will be selected.
-TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_MultipleClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 2);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- EXPECT_CALL(*decoder_2_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<AudioDecoder>(decoder_2_),
- IsNull()));
-
- SelectDecoder();
-}
-
-// There is a decryptor but the stream is not encrypted. The decoder will be
-// selected.
-TEST_F(AudioDecoderSelectorTest, ClearStream_HasDecryptor) {
- UseClearStream();
- InitializeDecoderSelector(kDecryptOnly, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<AudioDecoder>(decoder_1_),
- IsNull()));
-
- SelectDecoder();
-}
-
-// The stream is encrypted and there's no decryptor. No decoder can be selected.
-TEST_F(AudioDecoderSelectorTest, EncryptedStream_NoDecryptor) {
- UseEncryptedStream();
- InitializeDecoderSelector(kNoDecryptor, 1);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can only do decryption and there's no decoder available. No decoder
-// can be selected.
-TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptOnly_NoClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 0);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can do decryption-only and there's a decoder available. The decoder
-// will be selected and a DecryptingDemuxerStream will be created.
-TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptOnly_OneClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<AudioDecoder>(decoder_1_),
- NotNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can only do decryption and there are multiple decoders available.
-// The first decoder that can decode the input stream will be selected and
-// a DecryptingDemuxerStream will be created.
-TEST_F(AudioDecoderSelectorTest,
- EncryptedStream_DecryptOnly_MultipleClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 2);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- EXPECT_CALL(*decoder_2_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<AudioDecoder>(decoder_2_),
- NotNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can do decryption and decoding. A DecryptingAudioDecoder will be
-// created and selected. The clear decoders should not be touched at all.
-// No DecryptingDemuxerStream should to be created.
-TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptAndDecode, 1);
-
- EXPECT_CALL(*this, OnDecoderSelected(NotNull(), IsNull()));
-
- SelectDecoder();
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_file_reader.cc b/src/media/filters/audio_file_reader.cc
deleted file mode 100644
index cf295b6..0000000
--- a/src/media/filters/audio_file_reader.cc
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/audio_file_reader.h"
-
-#include "base/logging.h"
-#include "base/time.h"
-#include "media/base/audio_bus.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-
-namespace media {
-
-AudioFileReader::AudioFileReader(FFmpegURLProtocol* protocol)
- : codec_context_(NULL),
- stream_index_(0),
- protocol_(protocol) {
-}
-
-AudioFileReader::~AudioFileReader() {
- Close();
-}
-
-int AudioFileReader::channels() const {
- return codec_context_->channels;
-}
-
-int AudioFileReader::sample_rate() const {
- return codec_context_->sample_rate;
-}
-
-base::TimeDelta AudioFileReader::duration() const {
- const AVRational av_time_base = {1, AV_TIME_BASE};
-
- // Add one microsecond to avoid rounding-down errors which can occur when
- // |duration| has been calculated from an exact number of sample-frames.
- // One microsecond is much less than the time of a single sample-frame
- // at any real-world sample-rate.
- return ConvertFromTimeBase(
- av_time_base, glue_->format_context()->duration + 1);
-}
-
-int64 AudioFileReader::number_of_frames() const {
- return static_cast<int64>(duration().InSecondsF() * sample_rate());
-}
-
-bool AudioFileReader::Open() {
- glue_.reset(new FFmpegGlue(protocol_));
- AVFormatContext* format_context = glue_->format_context();
-
- // Open FFmpeg AVFormatContext.
- if (!glue_->OpenContext()) {
- DLOG(WARNING) << "AudioFileReader::Open() : error in avformat_open_input()";
- return false;
- }
-
- // Get the codec context.
- codec_context_ = NULL;
- for (size_t i = 0; i < format_context->nb_streams; ++i) {
- AVCodecContext* c = format_context->streams[i]->codec;
- if (c->codec_type == AVMEDIA_TYPE_AUDIO) {
- codec_context_ = c;
- stream_index_ = i;
- break;
- }
- }
-
- // Get the codec.
- if (!codec_context_)
- return false;
-
- int result = avformat_find_stream_info(format_context, NULL);
- if (result < 0) {
- DLOG(WARNING)
- << "AudioFileReader::Open() : error in avformat_find_stream_info()";
- return false;
- }
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- if (codec) {
- if ((result = avcodec_open2(codec_context_, codec, NULL)) < 0) {
- DLOG(WARNING) << "AudioFileReader::Open() : could not open codec -"
- << " result: " << result;
- return false;
- }
- } else {
- DLOG(WARNING) << "AudioFileReader::Open() : could not find codec -"
- << " result: " << result;
- return false;
- }
-
- return true;
-}
-
-void AudioFileReader::Close() {
- if (codec_context_) {
- avcodec_close(codec_context_);
- codec_context_ = NULL;
- }
-}
-
-int AudioFileReader::Read(AudioBus* audio_bus) {
- DCHECK(glue_.get() && codec_context_) <<
- "AudioFileReader::Read() : reader is not opened!";
-
- DCHECK_EQ(audio_bus->channels(), channels());
- if (audio_bus->channels() != channels())
- return 0;
-
- size_t bytes_per_sample = av_get_bytes_per_sample(codec_context_->sample_fmt);
-
- // Holds decoded audio.
- scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> av_frame(avcodec_alloc_frame());
-
- // Read until we hit EOF or we've read the requested number of frames.
- AVPacket packet;
- int current_frame = 0;
- bool continue_decoding = true;
-
- while (current_frame < audio_bus->frames() && continue_decoding &&
- av_read_frame(glue_->format_context(), &packet) >= 0 &&
- av_dup_packet(&packet) >= 0) {
- // Skip packets from other streams.
- if (packet.stream_index != stream_index_) {
- av_free_packet(&packet);
- continue;
- }
-
- // Make a shallow copy of packet so we can slide packet.data as frames are
- // decoded from the packet; otherwise av_free_packet() will corrupt memory.
- AVPacket packet_temp = packet;
- do {
- avcodec_get_frame_defaults(av_frame.get());
- int frame_decoded = 0;
- int result = avcodec_decode_audio4(
- codec_context_, av_frame.get(), &frame_decoded, &packet_temp);
-
- if (result < 0) {
- DLOG(WARNING)
- << "AudioFileReader::Read() : error in avcodec_decode_audio4() -"
- << result;
- continue_decoding = false;
- break;
- }
-
- // Update packet size and data pointer in case we need to call the decoder
- // with the remaining bytes from this packet.
- packet_temp.size -= result;
- packet_temp.data += result;
-
- if (!frame_decoded)
- continue;
-
- // Determine the number of sample-frames we just decoded. Check overflow.
- int frames_read = av_frame->nb_samples;
- if (frames_read < 0) {
- continue_decoding = false;
- break;
- }
-
- // Truncate, if necessary, if the destination isn't big enough.
- if (current_frame + frames_read > audio_bus->frames())
- frames_read = audio_bus->frames() - current_frame;
-
- // Deinterleave each channel and convert to 32bit floating-point
- // with nominal range -1.0 -> +1.0.
- audio_bus->FromInterleavedPartial(
- av_frame->data[0], current_frame, frames_read, bytes_per_sample);
-
- current_frame += frames_read;
- } while (packet_temp.size > 0);
- av_free_packet(&packet);
- }
-
- // Zero any remaining frames.
- audio_bus->ZeroFramesPartial(
- current_frame, audio_bus->frames() - current_frame);
-
- // Returns the actual number of sample-frames decoded.
- // Ideally this represents the "true" exact length of the file.
- return current_frame;
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_file_reader.h b/src/media/filters/audio_file_reader.h
deleted file mode 100644
index c9996f3..0000000
--- a/src/media/filters/audio_file_reader.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_AUDIO_FILE_READER_H_
-#define MEDIA_FILTERS_AUDIO_FILE_READER_H_
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-struct AVCodecContext;
-
-namespace base { class TimeDelta; }
-
-namespace media {
-
-class AudioBus;
-class FFmpegGlue;
-class FFmpegURLProtocol;
-
-class MEDIA_EXPORT AudioFileReader {
- public:
- // Audio file data will be read using the given protocol.
- // The AudioFileReader does not take ownership of |protocol| and
- // simply maintains a weak reference to it.
- explicit AudioFileReader(FFmpegURLProtocol* protocol);
- virtual ~AudioFileReader();
-
- // Open() reads the audio data format so that the sample_rate(),
- // channels(), duration(), and number_of_frames() methods can be called.
- // It returns |true| on success.
- bool Open();
- void Close();
-
- // After a call to Open(), attempts to fully fill |audio_bus| with decoded
- // audio data. Any unfilled frames will be zeroed out.
- // |audio_data| must be of the same size as channels().
- // The audio data will be decoded as floating-point linear PCM with
- // a nominal range of -1.0 -> +1.0.
- // Returns the number of sample-frames actually read which will always be
- // <= audio_bus->frames()
- int Read(AudioBus* audio_bus);
-
- // These methods can be called once Open() has been called.
- int channels() const;
- int sample_rate() const;
-
- // Please note that duration() and number_of_frames() attempt to be accurate,
- // but are only estimates. For some encoded formats, the actual duration
- // of the file can only be determined once all the file data has been read.
- // The Read() method returns the actual number of sample-frames it has read.
- base::TimeDelta duration() const;
- int64 number_of_frames() const;
-
- private:
- scoped_ptr<FFmpegGlue> glue_;
- AVCodecContext* codec_context_;
- int stream_index_;
- FFmpegURLProtocol* protocol_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioFileReader);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_AUDIO_FILE_READER_H_
diff --git a/src/media/filters/audio_file_reader_unittest.cc b/src/media/filters/audio_file_reader_unittest.cc
deleted file mode 100644
index a290131..0000000
--- a/src/media/filters/audio_file_reader_unittest.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "base/md5.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/sys_byteorder.h"
-#include "media/base/audio_bus.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/test_data_util.h"
-#include "media/filters/audio_file_reader.h"
-#include "media/filters/in_memory_url_protocol.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class AudioFileReaderTest : public testing::Test {
- public:
- AudioFileReaderTest() {}
- virtual ~AudioFileReaderTest() {}
-
- void Initialize(const char* filename) {
- data_ = ReadTestDataFile(filename);
- protocol_.reset(new InMemoryUrlProtocol(
- data_->GetData(), data_->GetDataSize(), false));
- reader_.reset(new AudioFileReader(protocol_.get()));
- }
-
- void ReadAndVerify(const char* audio_hash, int expected_frames) {
- scoped_ptr<AudioBus> decoded_audio_data = AudioBus::Create(
- reader_->channels(), reader_->number_of_frames());
- int actual_frames = reader_->Read(decoded_audio_data.get());
- ASSERT_LE(actual_frames, decoded_audio_data->frames());
- ASSERT_EQ(expected_frames, actual_frames);
-
- base::MD5Context md5_context;
- base::MD5Init(&md5_context);
-
- DCHECK_EQ(sizeof(float), sizeof(uint32));
- int channels = decoded_audio_data->channels();
- for (int ch = 0; ch < channels; ++ch) {
- float* channel = decoded_audio_data->channel(ch);
- for (int i = 0; i < actual_frames; ++i) {
- // Convert float to uint32 w/o conversion loss.
- uint32 frame = base::ByteSwapToLE32(bit_cast<uint32>(channel[i]));
- base::MD5Update(&md5_context, base::StringPiece(
- reinterpret_cast<char*>(&frame), sizeof(frame)));
- }
- }
-
- base::MD5Digest digest;
- base::MD5Final(&digest, &md5_context);
-
- EXPECT_EQ(audio_hash, base::MD5DigestToBase16(digest));
- }
-
- void RunTest(const char* fn, const char* hash, int channels, int sample_rate,
- base::TimeDelta duration, int frames, int trimmed_frames) {
- Initialize(fn);
- ASSERT_TRUE(reader_->Open());
- EXPECT_EQ(channels, reader_->channels());
- EXPECT_EQ(sample_rate, reader_->sample_rate());
- EXPECT_EQ(duration.InMicroseconds(), reader_->duration().InMicroseconds());
- EXPECT_EQ(frames, reader_->number_of_frames());
- ReadAndVerify(hash, trimmed_frames);
- }
-
- protected:
- scoped_refptr<DecoderBuffer> data_;
- scoped_ptr<InMemoryUrlProtocol> protocol_;
- scoped_ptr<AudioFileReader> reader_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioFileReaderTest);
-};
-
-TEST_F(AudioFileReaderTest, WithoutOpen) {
- Initialize("bear.ogv");
-}
-
-TEST_F(AudioFileReaderTest, InvalidFile) {
- Initialize("ten_byte_file");
- ASSERT_FALSE(reader_->Open());
-}
-
-TEST_F(AudioFileReaderTest, WithVideo) {
- RunTest("bear.ogv", "302e1773ba2f9a194c35a0f8f0b73f15", 2, 44100,
- base::TimeDelta::FromMicroseconds(1011520), 44608, 44608);
-}
-
-TEST_F(AudioFileReaderTest, Vorbis) {
- RunTest("sfx.ogg", "2b84ad6d605abba1125c0dacc9c8dbdd", 1, 44100,
- base::TimeDelta::FromMicroseconds(350001), 15435, 15435);
-}
-
-TEST_F(AudioFileReaderTest, WaveU8) {
- RunTest("sfx_u8.wav", "d7e255a8e634fffdf9f744c5803632f8", 1, 44100,
- base::TimeDelta::FromMicroseconds(288414), 12719, 12719);
-}
-TEST_F(AudioFileReaderTest, WaveS16LE) {
- RunTest("sfx_s16le.wav", "2a5847207fdcba1c05e52f65ad010f66", 1, 44100,
- base::TimeDelta::FromMicroseconds(288414), 12719, 12719);
-}
-TEST_F(AudioFileReaderTest, WaveS24LE) {
- RunTest("sfx_s24le.wav", "66296b4ec633290581f9abf3c21cd5e7", 1, 44100,
- base::TimeDelta::FromMicroseconds(288414), 12719, 12719);
-}
-
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
-TEST_F(AudioFileReaderTest, DISABLED_MP3) {
- RunTest("sfx.mp3", "2a5847207fdcba1c05e52f65ad010f66", 1, 44100,
- base::TimeDelta::FromMicroseconds(313470), 13824, 12719);
-}
-
-TEST_F(AudioFileReaderTest, DISABLED_AAC) {
- RunTest("sfx.m4a", "d4d3207758d1e8cb0aa176ff77fa6932", 1, 44100,
- base::TimeDelta::FromMicroseconds(312001), 13759, 13312);
-}
-#endif
-
-} // namespace media
diff --git a/src/media/filters/audio_renderer_algorithm.cc b/src/media/filters/audio_renderer_algorithm.cc
deleted file mode 100644
index 9e84b9b..0000000
--- a/src/media/filters/audio_renderer_algorithm.cc
+++ /dev/null
@@ -1,418 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/audio_renderer_algorithm.h"
-
-#include <algorithm>
-#include <cmath>
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/audio/audio_util.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-// The starting size in bytes for |audio_buffer_|.
-// Previous usage maintained a deque of 16 Buffers, each of size 4Kb. This
-// worked well, so we maintain this number of bytes (16 * 4096).
-static const int kStartingBufferSizeInBytes = 65536;
-
-// The maximum size in bytes for the |audio_buffer_|. Arbitrarily determined.
-// This number represents 3 seconds of 96kHz/16 bit 7.1 surround sound.
-static const int kMaxBufferSizeInBytes = 4608000;
-
-// Duration of audio segments used for crossfading (in seconds).
-static const double kWindowDuration = 0.08;
-
-// Duration of crossfade between audio segments (in seconds).
-static const double kCrossfadeDuration = 0.008;
-
-// Max/min supported playback rates for fast/slow audio. Audio outside of these
-// ranges are muted.
-// Audio at these speeds would sound better under a frequency domain algorithm.
-static const float kMinPlaybackRate = 0.5f;
-static const float kMaxPlaybackRate = 4.0f;
-
-AudioRendererAlgorithm::AudioRendererAlgorithm()
- : channels_(0),
- samples_per_second_(0),
- bytes_per_channel_(0),
- playback_rate_(0.0f),
- audio_buffer_(0, kStartingBufferSizeInBytes),
- bytes_in_crossfade_(0),
- bytes_per_frame_(0),
- index_into_window_(0),
- crossfade_frame_number_(0),
- muted_(false),
- needs_more_data_(false),
- window_size_(0) {
-}
-
-AudioRendererAlgorithm::~AudioRendererAlgorithm() {}
-
-void AudioRendererAlgorithm::Initialize(float initial_playback_rate,
- const AudioParameters& params,
- const base::Closure& callback) {
- CHECK(params.IsValid());
- DCHECK(!callback.is_null());
-
- channels_ = params.channels();
- samples_per_second_ = params.sample_rate();
- bytes_per_channel_ = params.bits_per_sample() / 8;
- bytes_per_frame_ = params.GetBytesPerFrame();
- request_read_cb_ = callback;
- SetPlaybackRate(initial_playback_rate);
-
- window_size_ =
- samples_per_second_ * bytes_per_channel_ * channels_ * kWindowDuration;
- AlignToFrameBoundary(&window_size_);
-
- bytes_in_crossfade_ =
- samples_per_second_ * bytes_per_channel_ * channels_ * kCrossfadeDuration;
- AlignToFrameBoundary(&bytes_in_crossfade_);
-
- crossfade_buffer_.reset(new uint8[bytes_in_crossfade_]);
-}
-
-int AudioRendererAlgorithm::FillBuffer(
- uint8* dest, int requested_frames) {
- DCHECK_NE(bytes_per_frame_, 0);
-
- if (playback_rate_ == 0.0f)
- return 0;
-
- int slower_step = ceil(window_size_ * playback_rate_);
- int faster_step = ceil(window_size_ / playback_rate_);
- AlignToFrameBoundary(&slower_step);
- AlignToFrameBoundary(&faster_step);
-
- int total_frames_rendered = 0;
- uint8* output_ptr = dest;
- while (total_frames_rendered < requested_frames) {
- if (index_into_window_ == window_size_)
- ResetWindow();
-
- bool rendered_frame = true;
- if (window_size_ > faster_step) {
- rendered_frame = OutputFasterPlayback(
- output_ptr, window_size_, faster_step);
- } else if (slower_step < window_size_) {
- rendered_frame = OutputSlowerPlayback(
- output_ptr, slower_step, window_size_);
- } else {
- rendered_frame = OutputNormalPlayback(output_ptr);
- }
-
- if (!rendered_frame) {
- needs_more_data_ = true;
- break;
- }
-
- output_ptr += bytes_per_frame_;
- total_frames_rendered++;
- }
- return total_frames_rendered;
-}
-
-void AudioRendererAlgorithm::ResetWindow() {
- DCHECK_LE(index_into_window_, window_size_);
- index_into_window_ = 0;
- crossfade_frame_number_ = 0;
-}
-
-bool AudioRendererAlgorithm::OutputFasterPlayback(uint8* dest,
- int input_step,
- int output_step) {
- // Ensure we don't run into OOB read/write situation.
- CHECK_GT(input_step, output_step);
- DCHECK_LT(index_into_window_, window_size_);
- DCHECK_GT(playback_rate_, 1.0);
-
- if (audio_buffer_.forward_bytes() < bytes_per_frame_)
- return false;
-
- // The audio data is output in a series of windows. For sped-up playback,
- // the window is comprised of the following phases:
- //
- // a) Output raw data.
- // b) Save bytes for crossfade in |crossfade_buffer_|.
- // c) Drop data.
- // d) Output crossfaded audio leading up to the next window.
- //
- // The duration of each phase is computed below based on the |window_size_|
- // and |playback_rate_|.
- int bytes_to_crossfade = bytes_in_crossfade_;
- if (muted_ || bytes_to_crossfade > output_step)
- bytes_to_crossfade = 0;
-
- // This is the index of the end of phase a, beginning of phase b.
- int outtro_crossfade_begin = output_step - bytes_to_crossfade;
-
- // This is the index of the end of phase b, beginning of phase c.
- int outtro_crossfade_end = output_step;
-
- // This is the index of the end of phase c, beginning of phase d.
- // This phase continues until |index_into_window_| reaches |window_size_|, at
- // which point the window restarts.
- int intro_crossfade_begin = input_step - bytes_to_crossfade;
-
- // a) Output a raw frame if we haven't reached the crossfade section.
- if (index_into_window_ < outtro_crossfade_begin) {
- CopyWithAdvance(dest);
- index_into_window_ += bytes_per_frame_;
- return true;
- }
-
- // b) Save outtro crossfade frames into intermediate buffer, but do not output
- // anything to |dest|.
- while (index_into_window_ < outtro_crossfade_end) {
- if (audio_buffer_.forward_bytes() < bytes_per_frame_)
- return false;
-
- // This phase only applies if there are bytes to crossfade.
- DCHECK_GT(bytes_to_crossfade, 0);
- uint8* place_to_copy = crossfade_buffer_.get() +
- (index_into_window_ - outtro_crossfade_begin);
- CopyWithAdvance(place_to_copy);
- index_into_window_ += bytes_per_frame_;
- }
-
- // c) Drop frames until we reach the intro crossfade section.
- while (index_into_window_ < intro_crossfade_begin) {
- if (audio_buffer_.forward_bytes() < bytes_per_frame_)
- return false;
-
- DropFrame();
- index_into_window_ += bytes_per_frame_;
- }
-
- // Return if we have run out of data after Phase c).
- if (audio_buffer_.forward_bytes() < bytes_per_frame_)
- return false;
-
- // Phase d) doesn't apply if there are no bytes to crossfade.
- if (bytes_to_crossfade == 0) {
- DCHECK_EQ(index_into_window_, window_size_);
- return false;
- }
-
- // d) Crossfade and output a frame.
- DCHECK_LT(index_into_window_, window_size_);
- int offset_into_buffer = index_into_window_ - intro_crossfade_begin;
- memcpy(dest, crossfade_buffer_.get() + offset_into_buffer,
- bytes_per_frame_);
- scoped_array<uint8> intro_frame_ptr(new uint8[bytes_per_frame_]);
- audio_buffer_.Read(intro_frame_ptr.get(), bytes_per_frame_);
- OutputCrossfadedFrame(dest, intro_frame_ptr.get());
- index_into_window_ += bytes_per_frame_;
- return true;
-}
-
-bool AudioRendererAlgorithm::OutputSlowerPlayback(uint8* dest,
- int input_step,
- int output_step) {
- // Ensure we don't run into OOB read/write situation.
- CHECK_LT(input_step, output_step);
- DCHECK_LT(index_into_window_, window_size_);
- DCHECK_LT(playback_rate_, 1.0);
- DCHECK_NE(playback_rate_, 0.0);
-
- if (audio_buffer_.forward_bytes() < bytes_per_frame_)
- return false;
-
- // The audio data is output in a series of windows. For slowed down playback,
- // the window is comprised of the following phases:
- //
- // a) Output raw data.
- // b) Output and save bytes for crossfade in |crossfade_buffer_|.
- // c) Output* raw data.
- // d) Output* crossfaded audio leading up to the next window.
- //
- // * Phases c) and d) do not progress |audio_buffer_|'s cursor so that the
- // |audio_buffer_|'s cursor is in the correct place for the next window.
- //
- // The duration of each phase is computed below based on the |window_size_|
- // and |playback_rate_|.
- int bytes_to_crossfade = bytes_in_crossfade_;
- if (muted_ || bytes_to_crossfade > input_step)
- bytes_to_crossfade = 0;
-
- // This is the index of the end of phase a, beginning of phase b.
- int intro_crossfade_begin = input_step - bytes_to_crossfade;
-
- // This is the index of the end of phase b, beginning of phase c.
- int intro_crossfade_end = input_step;
-
- // This is the index of the end of phase c, beginning of phase d.
- // This phase continues until |index_into_window_| reaches |window_size_|, at
- // which point the window restarts.
- int outtro_crossfade_begin = output_step - bytes_to_crossfade;
-
- // a) Output a raw frame.
- if (index_into_window_ < intro_crossfade_begin) {
- CopyWithAdvance(dest);
- index_into_window_ += bytes_per_frame_;
- return true;
- }
-
- // b) Save the raw frame for the intro crossfade section, then output the
- // frame to |dest|.
- if (index_into_window_ < intro_crossfade_end) {
- int offset = index_into_window_ - intro_crossfade_begin;
- uint8* place_to_copy = crossfade_buffer_.get() + offset;
- CopyWithoutAdvance(place_to_copy);
- CopyWithAdvance(dest);
- index_into_window_ += bytes_per_frame_;
- return true;
- }
-
- int audio_buffer_offset = index_into_window_ - intro_crossfade_end;
-
- if (audio_buffer_.forward_bytes() < audio_buffer_offset + bytes_per_frame_)
- return false;
-
- // c) Output a raw frame into |dest| without advancing the |audio_buffer_|
- // cursor. See function-level comment.
- DCHECK_GE(index_into_window_, intro_crossfade_end);
- CopyWithoutAdvance(dest, audio_buffer_offset);
-
- // d) Crossfade the next frame of |crossfade_buffer_| into |dest| if we've
- // reached the outtro crossfade section of the window.
- if (index_into_window_ >= outtro_crossfade_begin) {
- int offset_into_crossfade_buffer =
- index_into_window_ - outtro_crossfade_begin;
- uint8* intro_frame_ptr =
- crossfade_buffer_.get() + offset_into_crossfade_buffer;
- OutputCrossfadedFrame(dest, intro_frame_ptr);
- }
-
- index_into_window_ += bytes_per_frame_;
- return true;
-}
-
-bool AudioRendererAlgorithm::OutputNormalPlayback(uint8* dest) {
- if (audio_buffer_.forward_bytes() >= bytes_per_frame_) {
- CopyWithAdvance(dest);
- index_into_window_ += bytes_per_frame_;
- return true;
- }
- return false;
-}
-
-void AudioRendererAlgorithm::CopyWithAdvance(uint8* dest) {
- CopyWithoutAdvance(dest);
- DropFrame();
-}
-
-void AudioRendererAlgorithm::CopyWithoutAdvance(uint8* dest) {
- CopyWithoutAdvance(dest, 0);
-}
-
-void AudioRendererAlgorithm::CopyWithoutAdvance(
- uint8* dest, int offset) {
- if (muted_) {
- memset(dest, 0, bytes_per_frame_);
- return;
- }
- int copied = audio_buffer_.Peek(dest, bytes_per_frame_, offset);
- DCHECK_EQ(bytes_per_frame_, copied);
-}
-
-void AudioRendererAlgorithm::DropFrame() {
- audio_buffer_.Seek(bytes_per_frame_);
-
- if (!IsQueueFull())
- request_read_cb_.Run();
-}
-
-void AudioRendererAlgorithm::OutputCrossfadedFrame(
- uint8* outtro, const uint8* intro) {
- DCHECK_LE(index_into_window_, window_size_);
- DCHECK(!muted_);
-
- switch (bytes_per_channel_) {
- case 4:
- CrossfadeFrame<int32>(outtro, intro);
- break;
- case 2:
- CrossfadeFrame<int16>(outtro, intro);
- break;
- case 1:
- CrossfadeFrame<uint8>(outtro, intro);
- break;
- default:
- NOTREACHED() << "Unsupported audio bit depth in crossfade.";
- }
-}
-
-template <class Type>
-void AudioRendererAlgorithm::CrossfadeFrame(
- uint8* outtro_bytes, const uint8* intro_bytes) {
- Type* outtro = reinterpret_cast<Type*>(outtro_bytes);
- const Type* intro = reinterpret_cast<const Type*>(intro_bytes);
-
- int frames_in_crossfade = bytes_in_crossfade_ / bytes_per_frame_;
- float crossfade_ratio =
- static_cast<float>(crossfade_frame_number_) / frames_in_crossfade;
- for (int channel = 0; channel < channels_; ++channel) {
- *outtro *= 1.0 - crossfade_ratio;
- *outtro++ += (*intro++) * crossfade_ratio;
- }
- crossfade_frame_number_++;
-}
-
-void AudioRendererAlgorithm::SetPlaybackRate(float new_rate) {
- DCHECK_GE(new_rate, 0.0);
- playback_rate_ = new_rate;
- muted_ =
- playback_rate_ < kMinPlaybackRate || playback_rate_ > kMaxPlaybackRate;
-
- ResetWindow();
-}
-
-void AudioRendererAlgorithm::AlignToFrameBoundary(int* value) {
- (*value) -= ((*value) % bytes_per_frame_);
-}
-
-void AudioRendererAlgorithm::FlushBuffers() {
- ResetWindow();
-
- // Clear the queue of decoded packets (releasing the buffers).
- audio_buffer_.Clear();
- request_read_cb_.Run();
-}
-
-base::TimeDelta AudioRendererAlgorithm::GetTime() {
- return audio_buffer_.current_time();
-}
-
-void AudioRendererAlgorithm::EnqueueBuffer(Buffer* buffer_in) {
- DCHECK(!buffer_in->IsEndOfStream());
- audio_buffer_.Append(buffer_in);
- needs_more_data_ = false;
-
- // If we still don't have enough data, request more.
- if (!IsQueueFull())
- request_read_cb_.Run();
-}
-
-bool AudioRendererAlgorithm::CanFillBuffer() {
- return audio_buffer_.forward_bytes() > 0 && !needs_more_data_;
-}
-
-bool AudioRendererAlgorithm::IsQueueFull() {
- return audio_buffer_.forward_bytes() >= audio_buffer_.forward_capacity();
-}
-
-int AudioRendererAlgorithm::QueueCapacity() {
- return audio_buffer_.forward_capacity();
-}
-
-void AudioRendererAlgorithm::IncreaseQueueCapacity() {
- audio_buffer_.set_forward_capacity(
- std::min(2 * audio_buffer_.forward_capacity(), kMaxBufferSizeInBytes));
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_renderer_algorithm.h b/src/media/filters/audio_renderer_algorithm.h
deleted file mode 100644
index 080a11d..0000000
--- a/src/media/filters/audio_renderer_algorithm.h
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// AudioRendererAlgorithm buffers and transforms audio data. The owner of
-// this object provides audio data to the object through EnqueueBuffer() and
-// requests data from the buffer via FillBuffer(). The owner also sets the
-// playback rate, and the AudioRendererAlgorithm will stretch or compress the
-// buffered audio as necessary to match the playback rate when fulfilling
-// FillBuffer() requests. AudioRendererAlgorithm can request more data to be
-// buffered via a read callback passed in during initialization.
-//
-// This class is *not* thread-safe. Calls to enqueue and retrieve data must be
-// locked if called from multiple threads.
-//
-// AudioRendererAlgorithm uses a simple pitch-preservation algorithm to
-// stretch and compress audio data to meet playback speeds less than and
-// greater than the natural playback of the audio stream.
-//
-// Audio at very low or very high playback rates are muted to preserve quality.
-
-#ifndef MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
-#define MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
-
-#include "base/callback.h"
-#include "media/audio/audio_parameters.h"
-#include "media/base/seekable_buffer.h"
-
-namespace media {
-
-class Buffer;
-
-class MEDIA_EXPORT AudioRendererAlgorithm {
- public:
- AudioRendererAlgorithm();
- ~AudioRendererAlgorithm();
-
- // Initializes this object with information about the audio stream.
- // |request_read_cb| is called to request more data from the client, requests
- // that are fulfilled through calls to EnqueueBuffer().
- void Initialize(float initial_playback_rate,
- const AudioParameters& params,
- const base::Closure& request_read_cb);
-
- // Tries to fill |requested_frames| frames into |dest| with possibly scaled
- // data from our |audio_buffer_|. Data is scaled based on the playback rate,
- // using a variation of the Overlap-Add method to combine sample windows.
- //
- // Data from |audio_buffer_| is consumed in proportion to the playback rate.
- //
- // Returns the number of frames copied into |dest|.
- // May request more reads via |request_read_cb_| before returning.
- int FillBuffer(uint8* dest, int requested_frames);
-
- // Clears |audio_buffer_|.
- void FlushBuffers();
-
- // Returns the time of the next byte in our data or kNoTimestamp() if current
- // time is unknown.
- base::TimeDelta GetTime();
-
- // Enqueues a buffer. It is called from the owner of the algorithm after a
- // read completes.
- void EnqueueBuffer(Buffer* buffer_in);
-
- float playback_rate() const { return playback_rate_; }
- void SetPlaybackRate(float new_rate);
-
- // Returns whether the algorithm has enough data at the current playback rate
- // such that it can write data on the next call to FillBuffer().
- bool CanFillBuffer();
-
- // Returns true if |audio_buffer_| is at or exceeds capacity.
- bool IsQueueFull();
-
- // Returns the capacity of |audio_buffer_|.
- int QueueCapacity();
-
- // Increase the capacity of |audio_buffer_| if possible.
- void IncreaseQueueCapacity();
-
- // Returns the number of bytes left in |audio_buffer_|, which may be larger
- // than QueueCapacity() in the event that a read callback delivered more data
- // than |audio_buffer_| was intending to hold.
- int bytes_buffered() { return audio_buffer_.forward_bytes(); }
-
- int bytes_per_frame() { return bytes_per_frame_; }
-
- int bytes_per_channel() { return bytes_per_channel_; }
-
- int samples_per_second() { return samples_per_second_; }
-
- bool is_muted() { return muted_; }
-
- private:
- // Fills |dest| with one frame of audio data at normal speed. Returns true if
- // a frame was rendered, false otherwise.
- bool OutputNormalPlayback(uint8* dest);
-
- // Fills |dest| with one frame of audio data at faster than normal speed.
- // Returns true if a frame was rendered, false otherwise.
- //
- // When the audio playback is > 1.0, we use a variant of Overlap-Add to squish
- // audio output while preserving pitch. Essentially, we play a bit of audio
- // data at normal speed, then we "fast forward" by dropping the next bit of
- // audio data, and then we stich the pieces together by crossfading from one
- // audio chunk to the next.
- bool OutputFasterPlayback(uint8* dest, int input_step, int output_step);
-
- // Fills |dest| with one frame of audio data at slower than normal speed.
- // Returns true if a frame was rendered, false otherwise.
- //
- // When the audio playback is < 1.0, we use a variant of Overlap-Add to
- // stretch audio output while preserving pitch. This works by outputting a
- // segment of audio data at normal speed. The next audio segment then starts
- // by repeating some of the audio data from the previous audio segment.
- // Segments are stiched together by crossfading from one audio chunk to the
- // next.
- bool OutputSlowerPlayback(uint8* dest, int input_step, int output_step);
-
- // Resets the window state to the start of a new window.
- void ResetWindow();
-
- // Copies a raw frame from |audio_buffer_| into |dest| without progressing
- // |audio_buffer_|'s internal "current" cursor. Optionally peeks at a forward
- // byte |offset|.
- void CopyWithoutAdvance(uint8* dest);
- void CopyWithoutAdvance(uint8* dest, int offset);
-
- // Copies a raw frame from |audio_buffer_| into |dest| and progresses the
- // |audio_buffer_| forward.
- void CopyWithAdvance(uint8* dest);
-
- // Moves the |audio_buffer_| forward by one frame.
- void DropFrame();
-
- // Does a linear crossfade from |intro| into |outtro| for one frame.
- // Assumes pointers are valid and are at least size of |bytes_per_frame_|.
- void OutputCrossfadedFrame(uint8* outtro, const uint8* intro);
- template <class Type>
- void CrossfadeFrame(uint8* outtro, const uint8* intro);
-
- // Rounds |*value| down to the nearest frame boundary.
- void AlignToFrameBoundary(int* value);
-
- // Number of channels in audio stream.
- int channels_;
-
- // Sample rate of audio stream.
- int samples_per_second_;
-
- // Byte depth of audio.
- int bytes_per_channel_;
-
- // Used by algorithm to scale output.
- float playback_rate_;
-
- // Used to request more data.
- base::Closure request_read_cb_;
-
- // Buffered audio data.
- SeekableBuffer audio_buffer_;
-
- // Length for crossfade in bytes.
- int bytes_in_crossfade_;
-
- // Length of frame in bytes.
- int bytes_per_frame_;
-
- // The current location in the audio window, between 0 and |window_size_|.
- // When |index_into_window_| reaches |window_size_|, the window resets.
- // Indexed by byte.
- int index_into_window_;
-
- // The frame number in the crossfade.
- int crossfade_frame_number_;
-
- // True if the audio should be muted.
- bool muted_;
-
- bool needs_more_data_;
-
- // Temporary buffer to hold crossfade data.
- scoped_array<uint8> crossfade_buffer_;
-
- // Window size, in bytes (calculated from audio properties).
- int window_size_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioRendererAlgorithm);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
diff --git a/src/media/filters/audio_renderer_algorithm_unittest.cc b/src/media/filters/audio_renderer_algorithm_unittest.cc
deleted file mode 100644
index 33e2fd0..0000000
--- a/src/media/filters/audio_renderer_algorithm_unittest.cc
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// The format of these tests are to enqueue a known amount of data and then
-// request the exact amount we expect in order to dequeue the known amount of
-// data. This ensures that for any rate we are consuming input data at the
-// correct rate. We always pass in a very large destination buffer with the
-// expectation that FillBuffer() will fill as much as it can but no more.
-
-#include <cmath>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "media/base/channel_layout.h"
-#include "media/base/data_buffer.h"
-#include "media/filters/audio_renderer_algorithm.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const size_t kRawDataSize = 2048;
-static const int kSamplesPerSecond = 3000;
-static const ChannelLayout kDefaultChannelLayout = CHANNEL_LAYOUT_STEREO;
-static const int kDefaultSampleBits = 16;
-
-class AudioRendererAlgorithmTest : public testing::Test {
- public:
- AudioRendererAlgorithmTest()
- : bytes_enqueued_(0) {
- }
-
- ~AudioRendererAlgorithmTest() {}
-
- void Initialize() {
- Initialize(kDefaultChannelLayout, kDefaultSampleBits, kSamplesPerSecond);
- }
-
- void Initialize(ChannelLayout channel_layout, int bits_per_channel,
- int samples_per_second) {
- static const int kFrames = kRawDataSize / ((kDefaultSampleBits / 8) *
- ChannelLayoutToChannelCount(kDefaultChannelLayout));
- AudioParameters params(
- media::AudioParameters::AUDIO_PCM_LINEAR, channel_layout,
- samples_per_second, bits_per_channel, kFrames);
-
- algorithm_.Initialize(1, params, base::Bind(
- &AudioRendererAlgorithmTest::EnqueueData, base::Unretained(this)));
- EnqueueData();
- }
-
- void EnqueueData() {
- scoped_array<uint8> audio_data(new uint8[kRawDataSize]);
- CHECK_EQ(kRawDataSize % algorithm_.bytes_per_channel(), 0u);
- CHECK_EQ(kRawDataSize % algorithm_.bytes_per_frame(), 0u);
- // The value of the data is meaningless; we just want non-zero data to
- // differentiate it from muted data.
- memset(audio_data.get(), 1, kRawDataSize);
- algorithm_.EnqueueBuffer(new DataBuffer(audio_data.Pass(), kRawDataSize));
- bytes_enqueued_ += kRawDataSize;
- }
-
- void CheckFakeData(uint8* audio_data, int frames_written) {
- int sum = 0;
- for (int i = 0; i < frames_written * algorithm_.bytes_per_frame(); ++i)
- sum |= audio_data[i];
-
- if (algorithm_.is_muted())
- ASSERT_EQ(sum, 0);
- else
- ASSERT_NE(sum, 0);
- }
-
- int ComputeConsumedBytes(int initial_bytes_enqueued,
- int initial_bytes_buffered) {
- int byte_delta = bytes_enqueued_ - initial_bytes_enqueued;
- int buffered_delta = algorithm_.bytes_buffered() - initial_bytes_buffered;
- int consumed = byte_delta - buffered_delta;
- CHECK_GE(consumed, 0);
- return consumed;
- }
-
- void TestPlaybackRate(double playback_rate) {
- const int kDefaultBufferSize = algorithm_.samples_per_second() / 10;
- const int kDefaultFramesRequested = 2 * algorithm_.samples_per_second();
-
- TestPlaybackRate(playback_rate, kDefaultBufferSize,
- kDefaultFramesRequested);
- }
-
- void TestPlaybackRate(double playback_rate,
- int buffer_size_in_frames,
- int total_frames_requested) {
- int initial_bytes_enqueued = bytes_enqueued_;
- int initial_bytes_buffered = algorithm_.bytes_buffered();
-
- algorithm_.SetPlaybackRate(static_cast<float>(playback_rate));
-
- scoped_array<uint8> buffer(
- new uint8[buffer_size_in_frames * algorithm_.bytes_per_frame()]);
-
- if (playback_rate == 0.0) {
- int frames_written =
- algorithm_.FillBuffer(buffer.get(), buffer_size_in_frames);
- EXPECT_EQ(0, frames_written);
- return;
- }
-
- int frames_remaining = total_frames_requested;
- while (frames_remaining > 0) {
- int frames_requested = std::min(buffer_size_in_frames, frames_remaining);
- int frames_written =
- algorithm_.FillBuffer(buffer.get(), frames_requested);
- ASSERT_GT(frames_written, 0);
- CheckFakeData(buffer.get(), frames_written);
- frames_remaining -= frames_written;
- }
-
- int bytes_requested = total_frames_requested * algorithm_.bytes_per_frame();
- int bytes_consumed = ComputeConsumedBytes(initial_bytes_enqueued,
- initial_bytes_buffered);
-
- // If playing back at normal speed, we should always get back the same
- // number of bytes requested.
- if (playback_rate == 1.0) {
- EXPECT_EQ(bytes_requested, bytes_consumed);
- return;
- }
-
- // Otherwise, allow |kMaxAcceptableDelta| difference between the target and
- // actual playback rate.
- // When |kSamplesPerSecond| and |total_frames_requested| are reasonably
- // large, one can expect less than a 1% difference in most cases. In our
- // current implementation, sped up playback is less accurate than slowed
- // down playback, and for playback_rate > 1, playback rate generally gets
- // less and less accurate the farther it drifts from 1 (though this is
- // nonlinear).
- static const double kMaxAcceptableDelta = 0.01;
- double actual_playback_rate = 1.0 * bytes_consumed / bytes_requested;
-
- // Calculate the percentage difference from the target |playback_rate| as a
- // fraction from 0.0 to 1.0.
- double delta = std::abs(1.0 - (actual_playback_rate / playback_rate));
-
- EXPECT_LE(delta, kMaxAcceptableDelta);
- }
-
- protected:
- AudioRendererAlgorithm algorithm_;
- int bytes_enqueued_;
-};
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_NormalRate) {
- Initialize();
- TestPlaybackRate(1.0);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_NearlyNormalFasterRate) {
- Initialize();
- TestPlaybackRate(1.0001);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_NearlyNormalSlowerRate) {
- Initialize();
- TestPlaybackRate(0.9999);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_OneAndAQuarterRate) {
- Initialize();
- TestPlaybackRate(1.25);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_OneAndAHalfRate) {
- Initialize();
- TestPlaybackRate(1.5);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_DoubleRate) {
- Initialize();
- TestPlaybackRate(2.0);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_EightTimesRate) {
- Initialize();
- TestPlaybackRate(8.0);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_ThreeQuartersRate) {
- Initialize();
- TestPlaybackRate(0.75);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_HalfRate) {
- Initialize();
- TestPlaybackRate(0.5);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_QuarterRate) {
- Initialize();
- TestPlaybackRate(0.25);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_Pause) {
- Initialize();
- TestPlaybackRate(0.0);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_SlowDown) {
- Initialize();
- TestPlaybackRate(4.5);
- TestPlaybackRate(3.0);
- TestPlaybackRate(2.0);
- TestPlaybackRate(1.0);
- TestPlaybackRate(0.5);
- TestPlaybackRate(0.25);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_SpeedUp) {
- Initialize();
- TestPlaybackRate(0.25);
- TestPlaybackRate(0.5);
- TestPlaybackRate(1.0);
- TestPlaybackRate(2.0);
- TestPlaybackRate(3.0);
- TestPlaybackRate(4.5);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_JumpAroundSpeeds) {
- Initialize();
- TestPlaybackRate(2.1);
- TestPlaybackRate(0.9);
- TestPlaybackRate(0.6);
- TestPlaybackRate(1.4);
- TestPlaybackRate(0.3);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_SmallBufferSize) {
- Initialize();
- static const int kBufferSizeInFrames = 1;
- static const int kFramesRequested = 2 * kSamplesPerSecond;
- TestPlaybackRate(1.0, kBufferSizeInFrames, kFramesRequested);
- TestPlaybackRate(0.5, kBufferSizeInFrames, kFramesRequested);
- TestPlaybackRate(1.5, kBufferSizeInFrames, kFramesRequested);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_LargeBufferSize) {
- Initialize(kDefaultChannelLayout, kDefaultSampleBits, 44100);
- TestPlaybackRate(1.0);
- TestPlaybackRate(0.5);
- TestPlaybackRate(1.5);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_LowerQualityAudio) {
- static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_MONO;
- static const int kSampleBits = 8;
- Initialize(kChannelLayout, kSampleBits, kSamplesPerSecond);
- TestPlaybackRate(1.0);
- TestPlaybackRate(0.5);
- TestPlaybackRate(1.5);
-}
-
-TEST_F(AudioRendererAlgorithmTest, FillBuffer_HigherQualityAudio) {
- static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
- static const int kSampleBits = 32;
- Initialize(kChannelLayout, kSampleBits, kSamplesPerSecond);
- TestPlaybackRate(1.0);
- TestPlaybackRate(0.5);
- TestPlaybackRate(1.5);
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_renderer_impl.cc b/src/media/filters/audio_renderer_impl.cc
deleted file mode 100644
index 2e78218..0000000
--- a/src/media/filters/audio_renderer_impl.cc
+++ /dev/null
@@ -1,647 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/audio_renderer_impl.h"
-
-#include <math.h>
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/audio/audio_util.h"
-#include "media/base/audio_splicer.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/media_switches.h"
-#include "media/filters/audio_decoder_selector.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-
-namespace media {
-
-AudioRendererImpl::AudioRendererImpl(
- media::AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : sink_(sink),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- state_(kUninitialized),
- pending_read_(false),
- received_end_of_stream_(false),
- rendered_end_of_stream_(false),
- audio_time_buffered_(kNoTimestamp()),
- current_time_(kNoTimestamp()),
- underflow_disabled_(false),
- preroll_aborted_(false),
- actual_frames_per_buffer_(0) {
- // We're created on the render thread, but this thread checker is for another.
- pipeline_thread_checker_.DetachFromThread();
-}
-
-void AudioRendererImpl::Play(const base::Closure& callback) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
-
- float playback_rate = 0;
- {
- base::AutoLock auto_lock(lock_);
- DCHECK_EQ(kPaused, state_);
- state_ = kPlaying;
- callback.Run();
- playback_rate = algorithm_->playback_rate();
- }
-
- if (playback_rate != 0.0f) {
- DoPlay();
- } else {
- DoPause();
- }
-}
-
-void AudioRendererImpl::DoPlay() {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(sink_);
- {
- base::AutoLock auto_lock(lock_);
- earliest_end_time_ = base::Time::Now();
- }
- sink_->Play();
-}
-
-void AudioRendererImpl::Pause(const base::Closure& callback) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
-
- {
- base::AutoLock auto_lock(lock_);
- DCHECK(state_ == kPlaying || state_ == kUnderflow ||
- state_ == kRebuffering);
- pause_cb_ = callback;
- state_ = kPaused;
-
- // Pause only when we've completed our pending read.
- if (!pending_read_)
- base::ResetAndReturn(&pause_cb_).Run();
- }
-
- DoPause();
-}
-
-void AudioRendererImpl::DoPause() {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(sink_);
- sink_->Pause(false);
-}
-
-void AudioRendererImpl::Flush(const base::Closure& callback) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
-
- if (decrypting_demuxer_stream_) {
- decrypting_demuxer_stream_->Reset(base::Bind(
- &AudioRendererImpl::ResetDecoder, this, callback));
- return;
- }
-
- decoder_->Reset(callback);
-}
-
-void AudioRendererImpl::ResetDecoder(const base::Closure& callback) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- decoder_->Reset(callback);
-}
-
-void AudioRendererImpl::Stop(const base::Closure& callback) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(!callback.is_null());
-
- if (sink_) {
- sink_->Stop();
- sink_ = NULL;
- }
-
- {
- base::AutoLock auto_lock(lock_);
- state_ = kStopped;
- algorithm_.reset(NULL);
- init_cb_.Reset();
- underflow_cb_.Reset();
- time_cb_.Reset();
- }
-
- callback.Run();
-}
-
-void AudioRendererImpl::Preroll(base::TimeDelta time,
- const PipelineStatusCB& cb) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(sink_);
-
- {
- base::AutoLock auto_lock(lock_);
- DCHECK_EQ(kPaused, state_);
- DCHECK(!pending_read_) << "Pending read must complete before seeking";
- DCHECK(pause_cb_.is_null());
- DCHECK(preroll_cb_.is_null());
- state_ = kPrerolling;
- preroll_cb_ = cb;
- preroll_timestamp_ = time;
-
- // Throw away everything and schedule our reads.
- audio_time_buffered_ = kNoTimestamp();
- current_time_ = kNoTimestamp();
- received_end_of_stream_ = false;
- rendered_end_of_stream_ = false;
- preroll_aborted_ = false;
-
- splicer_->Reset();
-
- // |algorithm_| will request more reads.
- algorithm_->FlushBuffers();
- earliest_end_time_ = base::Time::Now();
- }
-
- // Pause and flush the stream when we preroll to a new location.
- sink_->Pause(true);
-}
-
-void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(stream);
- DCHECK(!decoders.empty());
- DCHECK_EQ(stream->type(), DemuxerStream::AUDIO);
- DCHECK(!init_cb.is_null());
- DCHECK(!statistics_cb.is_null());
- DCHECK(!underflow_cb.is_null());
- DCHECK(!time_cb.is_null());
- DCHECK(!ended_cb.is_null());
- DCHECK(!disabled_cb.is_null());
- DCHECK(!error_cb.is_null());
- DCHECK_EQ(kUninitialized, state_);
- DCHECK(sink_);
-
- init_cb_ = init_cb;
- statistics_cb_ = statistics_cb;
- underflow_cb_ = underflow_cb;
- time_cb_ = time_cb;
- ended_cb_ = ended_cb;
- disabled_cb_ = disabled_cb;
- error_cb_ = error_cb;
-
- scoped_ptr<AudioDecoderSelector> decoder_selector(
- new AudioDecoderSelector(base::MessageLoopProxy::current(),
- decoders,
- set_decryptor_ready_cb_));
-
- // To avoid calling |decoder_selector| methods and passing ownership of
- // |decoder_selector| in the same line.
- AudioDecoderSelector* decoder_selector_ptr = decoder_selector.get();
-
- decoder_selector_ptr->SelectAudioDecoder(
- stream,
- statistics_cb,
- base::Bind(&AudioRendererImpl::OnDecoderSelected, this,
- base::Passed(&decoder_selector)));
-}
-
-void AudioRendererImpl::OnDecoderSelected(
- scoped_ptr<AudioDecoderSelector> decoder_selector,
- const scoped_refptr<AudioDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
-
- if (state_ == kStopped) {
- DCHECK(!sink_);
- return;
- }
-
- if (!selected_decoder) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- decoder_ = selected_decoder;
- decrypting_demuxer_stream_ = decrypting_demuxer_stream;
-
- int sample_rate = decoder_->samples_per_second();
- int buffer_size = GetHighLatencyOutputBufferSize(sample_rate);
- AudioParameters::Format format = AudioParameters::AUDIO_PCM_LINEAR;
-
- // On Windows and Mac we can use the low latency pipeline because they provide
- // accurate and smooth delay information. On other platforms like Linux there
- // are jitter issues.
- // TODO(dalecurtis): Fix bugs: http://crbug.com/138098 http://crbug.com/32757
-#if defined(OS_WIN) || defined(OS_MACOSX)
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- // Either AudioOutputResampler or renderer side mixing must be enabled to use
- // the low latency pipeline.
- if (!cmd_line->HasSwitch(switches::kDisableRendererSideMixing) ||
- !cmd_line->HasSwitch(switches::kDisableAudioOutputResampler)) {
- // There are two cases here:
- //
- // 1. Renderer side mixing is enabled and the buffer size is actually
- // controlled by the size of the AudioBus provided to Render(). In this
- // case the buffer size below is ignored.
- //
- // 2. Renderer side mixing is disabled and AudioOutputResampler on the
- // browser side is rebuffering to the hardware size on the fly.
- //
- // In the second case we need to choose a a buffer size small enough that
- // the decoder can fulfill the high frequency low latency audio callbacks,
- // but not so small that it's less than the hardware buffer size (or we'll
- // run into issues since the shared memory sync is non-blocking).
- //
- // The buffer size below is arbitrarily the same size used by Pepper Flash
- // for consistency. Since renderer side mixing is only disabled for debug
- // purposes it's okay that this buffer size might lead to jitter since it's
- // not a multiple of the hardware buffer size.
- format = AudioParameters::AUDIO_PCM_LOW_LATENCY;
- buffer_size = 2048;
- }
-#endif
-
- audio_parameters_ = AudioParameters(
- format, decoder_->channel_layout(), sample_rate,
- decoder_->bits_per_channel(), buffer_size);
- if (!audio_parameters_.IsValid()) {
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED);
- return;
- }
-
- int channels = ChannelLayoutToChannelCount(decoder_->channel_layout());
- int bytes_per_frame = channels * decoder_->bits_per_channel() / 8;
- splicer_.reset(new AudioSplicer(bytes_per_frame, sample_rate));
-
- // We're all good! Continue initializing the rest of the audio renderer based
- // on the decoder format.
- algorithm_.reset(new AudioRendererAlgorithm());
- algorithm_->Initialize(0, audio_parameters_, base::Bind(
- &AudioRendererImpl::ScheduleRead_Locked, this));
-
- state_ = kPaused;
-
- sink_->Initialize(audio_parameters_, this);
- sink_->Start();
-
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-void AudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- base::AutoLock auto_lock(lock_);
- if (state_ == kUnderflow) {
- // The "&& preroll_aborted_" is a hack. If preroll is aborted, then we
- // shouldn't even reach the kUnderflow state to begin with. But for now
- // we're just making sure that the audio buffer capacity (i.e. the
- // number of bytes that need to be buffered for preroll to complete)
- // does not increase due to an aborted preroll.
- // TODO(vrk): Fix this bug correctly! (crbug.com/151352)
- if (buffer_more_audio && !preroll_aborted_)
- algorithm_->IncreaseQueueCapacity();
-
- state_ = kRebuffering;
- }
-}
-
-void AudioRendererImpl::SetVolume(float volume) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK(sink_);
- sink_->SetVolume(volume);
-}
-
-AudioRendererImpl::~AudioRendererImpl() {
- // Stop() should have been called and |algorithm_| should have been destroyed.
- DCHECK(state_ == kUninitialized || state_ == kStopped);
- DCHECK(!algorithm_.get());
-}
-
-void AudioRendererImpl::DecodedAudioReady(AudioDecoder::Status status,
- const scoped_refptr<Buffer>& buffer) {
- base::AutoLock auto_lock(lock_);
- DCHECK(state_ == kPaused || state_ == kPrerolling || state_ == kPlaying ||
- state_ == kUnderflow || state_ == kRebuffering || state_ == kStopped);
-
- CHECK(pending_read_);
- pending_read_ = false;
-
- if (status == AudioDecoder::kAborted) {
- HandleAbortedReadOrDecodeError(false);
- return;
- }
-
- if (status == AudioDecoder::kDecodeError) {
- HandleAbortedReadOrDecodeError(true);
- return;
- }
-
- DCHECK_EQ(status, AudioDecoder::kOk);
- DCHECK(buffer);
-
- if (!splicer_->AddInput(buffer)) {
- HandleAbortedReadOrDecodeError(true);
- return;
- }
-
- if (!splicer_->HasNextBuffer()) {
- ScheduleRead_Locked();
- return;
- }
-
- bool need_another_buffer = false;
- while (splicer_->HasNextBuffer())
- need_another_buffer = HandleSplicerBuffer(splicer_->GetNextBuffer());
-
- if (!need_another_buffer)
- return;
-
- ScheduleRead_Locked();
-}
-
-bool AudioRendererImpl::HandleSplicerBuffer(
- const scoped_refptr<Buffer>& buffer) {
- if (buffer->IsEndOfStream()) {
- received_end_of_stream_ = true;
-
- // Transition to kPlaying if we are currently handling an underflow since
- // no more data will be arriving.
- if (state_ == kUnderflow || state_ == kRebuffering)
- state_ = kPlaying;
- }
-
- switch (state_) {
- case kUninitialized:
- NOTREACHED();
- return false;
- case kPaused:
- if (!buffer->IsEndOfStream())
- algorithm_->EnqueueBuffer(buffer);
- DCHECK(!pending_read_);
- base::ResetAndReturn(&pause_cb_).Run();
- return false;
- case kPrerolling:
- if (IsBeforePrerollTime(buffer))
- return true;
-
- if (!buffer->IsEndOfStream()) {
- algorithm_->EnqueueBuffer(buffer);
- if (!algorithm_->IsQueueFull())
- return false;
- }
- state_ = kPaused;
- base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
- return false;
- case kPlaying:
- case kUnderflow:
- case kRebuffering:
- if (!buffer->IsEndOfStream())
- algorithm_->EnqueueBuffer(buffer);
- return false;
- case kStopped:
- return false;
- }
- return false;
-}
-
-void AudioRendererImpl::ScheduleRead_Locked() {
- lock_.AssertAcquired();
- if (pending_read_ || state_ == kPaused)
- return;
- pending_read_ = true;
- decoder_->Read(base::Bind(&AudioRendererImpl::DecodedAudioReady, this));
-}
-
-void AudioRendererImpl::SetPlaybackRate(float playback_rate) {
- DCHECK(pipeline_thread_checker_.CalledOnValidThread());
- DCHECK_LE(0.0f, playback_rate);
- DCHECK(sink_);
-
- // We have two cases here:
- // Play: current_playback_rate == 0.0 && playback_rate != 0.0
- // Pause: current_playback_rate != 0.0 && playback_rate == 0.0
- float current_playback_rate = algorithm_->playback_rate();
- if (current_playback_rate == 0.0f && playback_rate != 0.0f) {
- DoPlay();
- } else if (current_playback_rate != 0.0f && playback_rate == 0.0f) {
- // Pause is easy, we can always pause.
- DoPause();
- }
-
- base::AutoLock auto_lock(lock_);
- algorithm_->SetPlaybackRate(playback_rate);
-}
-
-bool AudioRendererImpl::IsBeforePrerollTime(
- const scoped_refptr<Buffer>& buffer) {
- return (state_ == kPrerolling) && buffer && !buffer->IsEndOfStream() &&
- (buffer->GetTimestamp() + buffer->GetDuration()) < preroll_timestamp_;
-}
-
-int AudioRendererImpl::Render(AudioBus* audio_bus,
- int audio_delay_milliseconds) {
- if (actual_frames_per_buffer_ != audio_bus->frames()) {
- audio_buffer_.reset(
- new uint8[audio_bus->frames() * audio_parameters_.GetBytesPerFrame()]);
- actual_frames_per_buffer_ = audio_bus->frames();
- }
-
- int frames_filled = FillBuffer(
- audio_buffer_.get(), audio_bus->frames(), audio_delay_milliseconds);
- DCHECK_LE(frames_filled, actual_frames_per_buffer_);
-
- // Deinterleave audio data into the output bus.
- audio_bus->FromInterleaved(
- audio_buffer_.get(), frames_filled,
- audio_parameters_.bits_per_sample() / 8);
-
- return frames_filled;
-}
-
-uint32 AudioRendererImpl::FillBuffer(uint8* dest,
- uint32 requested_frames,
- int audio_delay_milliseconds) {
- base::TimeDelta current_time = kNoTimestamp();
- base::TimeDelta max_time = kNoTimestamp();
-
- size_t frames_written = 0;
- base::Closure underflow_cb;
- {
- base::AutoLock auto_lock(lock_);
-
- // Ensure Stop() hasn't destroyed our |algorithm_| on the pipeline thread.
- if (!algorithm_)
- return 0;
-
- float playback_rate = algorithm_->playback_rate();
- if (playback_rate == 0.0f)
- return 0;
-
- // Adjust the delay according to playback rate.
- base::TimeDelta playback_delay =
- base::TimeDelta::FromMilliseconds(audio_delay_milliseconds);
- if (playback_rate != 1.0f) {
- playback_delay = base::TimeDelta::FromMicroseconds(static_cast<int64>(
- ceil(playback_delay.InMicroseconds() * playback_rate)));
- }
-
- if (state_ == kRebuffering && algorithm_->IsQueueFull())
- state_ = kPlaying;
-
- // Mute audio by returning 0 when not playing.
- if (state_ != kPlaying) {
- // TODO(scherkus): To keep the audio hardware busy we write at most 8k of
- // zeros. This gets around the tricky situation of pausing and resuming
- // the audio IPC layer in Chrome. Ideally, we should return zero and then
- // the subclass can restart the conversation.
- //
- // This should get handled by the subclass http://crbug.com/106600
- const uint32 kZeroLength = 8192;
- size_t zeros_to_write = std::min(
- kZeroLength, requested_frames * audio_parameters_.GetBytesPerFrame());
- memset(dest, 0, zeros_to_write);
- return zeros_to_write / audio_parameters_.GetBytesPerFrame();
- }
-
- // We use the following conditions to determine end of playback:
- // 1) Algorithm can not fill the audio callback buffer
- // 2) We received an end of stream buffer
- // 3) We haven't already signalled that we've ended
- // 4) Our estimated earliest end time has expired
- //
- // TODO(enal): we should replace (4) with a check that the browser has no
- // more audio data or at least use a delayed callback.
- //
- // We use the following conditions to determine underflow:
- // 1) Algorithm can not fill the audio callback buffer
- // 2) We have NOT received an end of stream buffer
- // 3) We are in the kPlaying state
- //
- // Otherwise fill the buffer with whatever data we can send to the device.
- if (!algorithm_->CanFillBuffer() && received_end_of_stream_ &&
- !rendered_end_of_stream_ && base::Time::Now() >= earliest_end_time_) {
- rendered_end_of_stream_ = true;
- ended_cb_.Run();
- } else if (!algorithm_->CanFillBuffer() && !received_end_of_stream_ &&
- state_ == kPlaying && !underflow_disabled_) {
- state_ = kUnderflow;
- underflow_cb = underflow_cb_;
- } else if (algorithm_->CanFillBuffer()) {
- frames_written = algorithm_->FillBuffer(dest, requested_frames);
- DCHECK_GT(frames_written, 0u);
- } else {
- // We can't write any data this cycle. For example, we may have
- // sent all available data to the audio device while not reaching
- // |earliest_end_time_|.
- }
-
- // The |audio_time_buffered_| is the ending timestamp of the last frame
- // buffered at the audio device. |playback_delay| is the amount of time
- // buffered at the audio device. The current time can be computed by their
- // difference.
- if (audio_time_buffered_ != kNoTimestamp()) {
- base::TimeDelta previous_time = current_time_;
- current_time_ = audio_time_buffered_ - playback_delay;
-
- // Time can change in one of two ways:
- // 1) The time of the audio data at the audio device changed, or
- // 2) The playback delay value has changed
- //
- // We only want to set |current_time| (and thus execute |time_cb_|) if
- // time has progressed and we haven't signaled end of stream yet.
- //
- // Why? The current latency of the system results in getting the last call
- // to FillBuffer() later than we'd like, which delays firing the 'ended'
- // event, which delays the looping/trigging performance of short sound
- // effects.
- //
- // TODO(scherkus): revisit this and switch back to relying on playback
- // delay after we've revamped our audio IPC subsystem.
- if (current_time_ > previous_time && !rendered_end_of_stream_) {
- current_time = current_time_;
- }
- }
-
- // The call to FillBuffer() on |algorithm_| has increased the amount of
- // buffered audio data. Update the new amount of time buffered.
- max_time = algorithm_->GetTime();
- audio_time_buffered_ = max_time;
-
- UpdateEarliestEndTime_Locked(
- frames_written, playback_rate, playback_delay, base::Time::Now());
- }
-
- if (current_time != kNoTimestamp() && max_time != kNoTimestamp()) {
- time_cb_.Run(current_time, max_time);
- }
-
- if (!underflow_cb.is_null())
- underflow_cb.Run();
-
- return frames_written;
-}
-
-void AudioRendererImpl::UpdateEarliestEndTime_Locked(
- int frames_filled, float playback_rate, base::TimeDelta playback_delay,
- base::Time time_now) {
- if (frames_filled <= 0)
- return;
-
- base::TimeDelta predicted_play_time = base::TimeDelta::FromMicroseconds(
- static_cast<float>(frames_filled) * base::Time::kMicrosecondsPerSecond /
- audio_parameters_.sample_rate());
-
- if (playback_rate != 1.0f) {
- predicted_play_time = base::TimeDelta::FromMicroseconds(
- static_cast<int64>(ceil(predicted_play_time.InMicroseconds() *
- playback_rate)));
- }
-
- lock_.AssertAcquired();
- earliest_end_time_ = std::max(
- earliest_end_time_, time_now + playback_delay + predicted_play_time);
-}
-
-void AudioRendererImpl::OnRenderError() {
- disabled_cb_.Run();
-}
-
-void AudioRendererImpl::DisableUnderflowForTesting() {
- underflow_disabled_ = true;
-}
-
-void AudioRendererImpl::HandleAbortedReadOrDecodeError(bool is_decode_error) {
- PipelineStatus status = is_decode_error ? PIPELINE_ERROR_DECODE : PIPELINE_OK;
- switch (state_) {
- case kUninitialized:
- NOTREACHED();
- return;
- case kPaused:
- if (status != PIPELINE_OK)
- error_cb_.Run(status);
- base::ResetAndReturn(&pause_cb_).Run();
- return;
- case kPrerolling:
- // This is a signal for abort if it's not an error.
- preroll_aborted_ = !is_decode_error;
- state_ = kPaused;
- base::ResetAndReturn(&preroll_cb_).Run(status);
- return;
- case kPlaying:
- case kUnderflow:
- case kRebuffering:
- case kStopped:
- if (status != PIPELINE_OK)
- error_cb_.Run(status);
- return;
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/audio_renderer_impl.h b/src/media/filters/audio_renderer_impl.h
deleted file mode 100644
index 9158aa1..0000000
--- a/src/media/filters/audio_renderer_impl.h
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Audio rendering unit utilizing an AudioRendererSink to output data.
-//
-// This class lives inside three threads during it's lifetime, namely:
-// 1. Render thread.
-// This object is created on the render thread.
-// 2. Pipeline thread
-// Initialize() is called here with the audio format.
-// Play/Pause/Preroll() also happens here.
-// 3. Audio thread created by the AudioRendererSink.
-// Render() is called here where audio data is decoded into raw PCM data.
-//
-// AudioRendererImpl talks to an AudioRendererAlgorithm that takes care of
-// queueing audio data and stretching/shrinking audio data when playback rate !=
-// 1.0 or 0.0.
-
-#ifndef MEDIA_FILTERS_AUDIO_RENDERER_IMPL_H_
-#define MEDIA_FILTERS_AUDIO_RENDERER_IMPL_H_
-
-#include <deque>
-
-#include "base/gtest_prod_util.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_checker.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/audio_renderer_sink.h"
-#include "media/base/buffers.h"
-#include "media/base/decryptor.h"
-#include "media/filters/audio_renderer_algorithm.h"
-
-namespace media {
-
-class AudioDecoderSelector;
-class AudioSplicer;
-class DecryptingDemuxerStream;
-
-class MEDIA_EXPORT AudioRendererImpl
- : public AudioRenderer,
- NON_EXPORTED_BASE(public AudioRendererSink::RenderCallback) {
- public:
- // Methods called on Render thread ------------------------------------------
- // An AudioRendererSink is used as the destination for the rendered audio.
- AudioRendererImpl(AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
-
- // Methods called on pipeline thread ----------------------------------------
- // AudioRenderer implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb) OVERRIDE;
- virtual void Play(const base::Closure& callback) OVERRIDE;
- virtual void Pause(const base::Closure& callback) OVERRIDE;
- virtual void Flush(const base::Closure& callback) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void SetPlaybackRate(float rate) OVERRIDE;
- virtual void Preroll(base::TimeDelta time,
- const PipelineStatusCB& cb) OVERRIDE;
- virtual void ResumeAfterUnderflow(bool buffer_more_audio) OVERRIDE;
- virtual void SetVolume(float volume) OVERRIDE;
-
- // Disables underflow support. When used, |state_| will never transition to
- // kUnderflow resulting in Render calls that underflow returning 0 frames
- // instead of some number of silence frames. Must be called prior to
- // Initialize().
- void DisableUnderflowForTesting();
-
- protected:
- virtual ~AudioRendererImpl();
-
- private:
- friend class AudioRendererImplTest;
- FRIEND_TEST_ALL_PREFIXES(AudioRendererImplTest, EndOfStream);
- FRIEND_TEST_ALL_PREFIXES(AudioRendererImplTest, Underflow_EndOfStream);
-
- // Callback from the audio decoder delivering decoded audio samples.
- void DecodedAudioReady(AudioDecoder::Status status,
- const scoped_refptr<Buffer>& buffer);
-
- // Handles buffers that come out of |splicer_|.
- // Returns true if more buffers are needed.
- bool HandleSplicerBuffer(const scoped_refptr<Buffer>& buffer);
-
- // Helper functions for AudioDecoder::Status values passed to
- // DecodedAudioReady().
- void HandleAbortedReadOrDecodeError(bool is_decode_error);
-
- // Fills the given buffer with audio data by delegating to its |algorithm_|.
- // FillBuffer() also takes care of updating the clock. Returns the number of
- // frames copied into |dest|, which may be less than or equal to
- // |requested_frames|.
- //
- // If this method returns fewer frames than |requested_frames|, it could
- // be a sign that the pipeline is stalled or unable to stream the data fast
- // enough. In such scenarios, the callee should zero out unused portions
- // of their buffer to playback silence.
- //
- // FillBuffer() updates the pipeline's playback timestamp. If FillBuffer() is
- // not called at the same rate as audio samples are played, then the reported
- // timestamp in the pipeline will be ahead of the actual audio playback. In
- // this case |playback_delay| should be used to indicate when in the future
- // should the filled buffer be played.
- //
- // Safe to call on any thread.
- uint32 FillBuffer(uint8* dest,
- uint32 requested_frames,
- int audio_delay_milliseconds);
-
- // Estimate earliest time when current buffer can stop playing.
- void UpdateEarliestEndTime_Locked(int frames_filled,
- float playback_rate,
- base::TimeDelta playback_delay,
- base::Time time_now);
-
- // Methods called on pipeline thread ----------------------------------------
- void DoPlay();
- void DoPause();
-
- // media::AudioRendererSink::RenderCallback implementation. Called on the
- // AudioDevice thread.
- virtual int Render(AudioBus* audio_bus,
- int audio_delay_milliseconds) OVERRIDE;
- virtual void OnRenderError() OVERRIDE;
-
- // Helper method that schedules an asynchronous read from the decoder and
- // increments |pending_reads_|.
- //
- // Safe to call from any thread.
- void ScheduleRead_Locked();
-
- // Returns true if the data in the buffer is all before
- // |preroll_timestamp_|. This can only return true while
- // in the kPrerolling state.
- bool IsBeforePrerollTime(const scoped_refptr<Buffer>& buffer);
-
- // Called when |decoder_selector_| selected the |selected_decoder|.
- // |decrypting_demuxer_stream| was also populated if a DecryptingDemuxerStream
- // created to help decrypt the encrypted stream.
- // Note: |decoder_selector| is passed here to keep the AudioDecoderSelector
- // alive until OnDecoderSelected() finishes.
- void OnDecoderSelected(
- scoped_ptr<AudioDecoderSelector> decoder_selector,
- const scoped_refptr<AudioDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream);
-
- void ResetDecoder(const base::Closure& callback);
-
- scoped_ptr<AudioSplicer> splicer_;
-
- // The sink (destination) for rendered audio. |sink_| must only be accessed
- // on the pipeline thread (verify with |pipeline_thread_checker_|). |sink_|
- // must never be called under |lock_| or the 3-way thread bridge between the
- // audio, pipeline, and decoder threads may deadlock.
- scoped_refptr<media::AudioRendererSink> sink_;
-
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- // These two will be set by AudioDecoderSelector::SelectAudioDecoder().
- scoped_refptr<AudioDecoder> decoder_;
- scoped_refptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
-
- // Ensures certain methods are always called on the pipeline thread.
- base::ThreadChecker pipeline_thread_checker_;
-
- // AudioParameters constructed during Initialize() based on |decoder_|.
- AudioParameters audio_parameters_;
-
- // Callbacks provided during Initialize().
- PipelineStatusCB init_cb_;
- StatisticsCB statistics_cb_;
- base::Closure underflow_cb_;
- TimeCB time_cb_;
- base::Closure ended_cb_;
- base::Closure disabled_cb_;
- PipelineStatusCB error_cb_;
-
- // Callback provided to Pause().
- base::Closure pause_cb_;
-
- // Callback provided to Preroll().
- PipelineStatusCB preroll_cb_;
-
- // After Initialize() has completed, all variables below must be accessed
- // under |lock_|. ------------------------------------------------------------
- base::Lock lock_;
-
- // Algorithm for scaling audio.
- scoped_ptr<AudioRendererAlgorithm> algorithm_;
-
- // Simple state tracking variable.
- enum State {
- kUninitialized,
- kPaused,
- kPrerolling,
- kPlaying,
- kStopped,
- kUnderflow,
- kRebuffering,
- };
- State state_;
-
- // Keep track of our outstanding read to |decoder_|.
- bool pending_read_;
-
- // Keeps track of whether we received and rendered the end of stream buffer.
- bool received_end_of_stream_;
- bool rendered_end_of_stream_;
-
- // The timestamp of the last frame (i.e. furthest in the future) buffered as
- // well as the current time that takes current playback delay into account.
- base::TimeDelta audio_time_buffered_;
- base::TimeDelta current_time_;
-
- base::TimeDelta preroll_timestamp_;
-
- // We're supposed to know amount of audio data OS or hardware buffered, but
- // that is not always so -- on my Linux box
- // AudioBuffersState::hardware_delay_bytes never reaches 0.
- //
- // As a result we cannot use it to find when stream ends. If we just ignore
- // buffered data we will notify host that stream ended before it is actually
- // did so, I've seen it done ~140ms too early when playing ~150ms file.
- //
- // Instead of trying to invent OS-specific solution for each and every OS we
- // are supporting, use simple workaround: every time we fill the buffer we
- // remember when it should stop playing, and do not assume that buffer is
- // empty till that time. Workaround is not bulletproof, as we don't exactly
- // know when that particular data would start playing, but it is much better
- // than nothing.
- base::Time earliest_end_time_;
-
- bool underflow_disabled_;
-
- // True if the renderer receives a buffer with kAborted status during preroll,
- // false otherwise. This flag is cleared on the next Preroll() call.
- bool preroll_aborted_;
-
- // End variables which must be accessed under |lock_|. ----------------------
-
- // Variables used only on the audio thread. ---------------------------------
- int actual_frames_per_buffer_;
- scoped_array<uint8> audio_buffer_;
-
- DISALLOW_COPY_AND_ASSIGN(AudioRendererImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_AUDIO_RENDERER_IMPL_H_
diff --git a/src/media/filters/audio_renderer_impl_unittest.cc b/src/media/filters/audio_renderer_impl_unittest.cc
deleted file mode 100644
index a17c6b0..0000000
--- a/src/media/filters/audio_renderer_impl_unittest.cc
+++ /dev/null
@@ -1,491 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/gtest_prod_util.h"
-#include "base/message_loop.h"
-#include "base/stl_util.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/data_buffer.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_audio_renderer_sink.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/audio_renderer_impl.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::NiceMock;
-using ::testing::StrictMock;
-
-namespace media {
-
-// Constants for distinguishing between muted audio and playing audio when using
-// ConsumeBufferedData().
-static uint8 kMutedAudio = 0x00;
-static uint8 kPlayingAudio = 0x99;
-
-class AudioRendererImplTest : public ::testing::Test {
- public:
- // Give the decoder some non-garbage media properties.
- AudioRendererImplTest()
- : renderer_(new AudioRendererImpl(new NiceMock<MockAudioRendererSink>(),
- SetDecryptorReadyCB())),
- demuxer_stream_(new MockDemuxerStream()),
- decoder_(new MockAudioDecoder()),
- audio_config_(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO,
- 44100, NULL, 0, false) {
- EXPECT_CALL(*demuxer_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::AUDIO));
- EXPECT_CALL(*demuxer_stream_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(audio_config_));
-
- // Queue all reads from the decoder by default.
- ON_CALL(*decoder_, Read(_))
- .WillByDefault(Invoke(this, &AudioRendererImplTest::SaveReadCallback));
-
- // Set up audio properties.
- SetSupportedAudioDecoderProperties();
- EXPECT_CALL(*decoder_, bits_per_channel())
- .Times(AnyNumber());
- EXPECT_CALL(*decoder_, channel_layout())
- .Times(AnyNumber());
- EXPECT_CALL(*decoder_, samples_per_second())
- .Times(AnyNumber());
-
- decoders_.push_back(decoder_);
- }
-
- virtual ~AudioRendererImplTest() {
- message_loop_.RunUntilIdle();
- renderer_->Stop(NewExpectedClosure());
- }
-
- void SetSupportedAudioDecoderProperties() {
- ON_CALL(*decoder_, bits_per_channel())
- .WillByDefault(Return(16));
- ON_CALL(*decoder_, channel_layout())
- .WillByDefault(Return(CHANNEL_LAYOUT_MONO));
- ON_CALL(*decoder_, samples_per_second())
- .WillByDefault(Return(44100));
- }
-
- void SetUnsupportedAudioDecoderProperties() {
- ON_CALL(*decoder_, bits_per_channel())
- .WillByDefault(Return(3));
- ON_CALL(*decoder_, channel_layout())
- .WillByDefault(Return(CHANNEL_LAYOUT_UNSUPPORTED));
- ON_CALL(*decoder_, samples_per_second())
- .WillByDefault(Return(0));
- }
-
- MOCK_METHOD1(OnPrerollComplete, void(PipelineStatus));
- PipelineStatusCB NewPrerollCB() {
- return base::Bind(&AudioRendererImplTest::OnPrerollComplete,
- base::Unretained(this));
- }
-
- MOCK_METHOD1(OnStatistics, void(const PipelineStatistics&));
- MOCK_METHOD0(OnUnderflow, void());
- MOCK_METHOD0(OnEnded, void());
- MOCK_METHOD0(OnDisabled, void());
- MOCK_METHOD1(OnError, void(PipelineStatus));
-
- void OnAudioTimeCallback(
- base::TimeDelta current_time, base::TimeDelta max_time) {
- CHECK(current_time <= max_time);
- }
-
- void Initialize() {
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
-
- InitializeWithStatus(PIPELINE_OK);
- message_loop_.RunUntilIdle();
- int channels = ChannelLayoutToChannelCount(decoder_->channel_layout());
- int bytes_per_frame = decoder_->bits_per_channel() * channels / 8;
- next_timestamp_.reset(new AudioTimestampHelper(
- bytes_per_frame, decoder_->samples_per_second()));
- }
-
- void InitializeWithStatus(PipelineStatus expected) {
- renderer_->Initialize(
- demuxer_stream_,
- decoders_,
- NewExpectedStatusCB(expected),
- base::Bind(&AudioRendererImplTest::OnStatistics,
- base::Unretained(this)),
- base::Bind(&AudioRendererImplTest::OnUnderflow,
- base::Unretained(this)),
- base::Bind(&AudioRendererImplTest::OnAudioTimeCallback,
- base::Unretained(this)),
- base::Bind(&AudioRendererImplTest::OnEnded,
- base::Unretained(this)),
- base::Bind(&AudioRendererImplTest::OnDisabled,
- base::Unretained(this)),
- base::Bind(&AudioRendererImplTest::OnError,
- base::Unretained(this)));
- }
-
- void Preroll() {
- next_timestamp_->SetBaseTimestamp(base::TimeDelta());
-
- // Fill entire buffer to complete prerolling.
- EXPECT_CALL(*decoder_, Read(_));
- renderer_->Preroll(base::TimeDelta(), NewPrerollCB());
- EXPECT_CALL(*this, OnPrerollComplete(PIPELINE_OK));
- message_loop_.RunUntilIdle();
- DeliverRemainingAudio();
- }
-
- void Play() {
- renderer_->Play(NewExpectedClosure());
- renderer_->SetPlaybackRate(1.0f);
- }
-
- void Preroll(base::TimeDelta preroll_time) {
- next_timestamp_->SetBaseTimestamp(preroll_time);
-
- // Fill entire buffer to complete prerolling.
- EXPECT_CALL(*decoder_, Read(_));
- renderer_->Preroll(preroll_time, NewPrerollCB());
- EXPECT_CALL(*this, OnPrerollComplete(PIPELINE_OK));
- DeliverRemainingAudio();
- }
-
- // Delivers |size| bytes with value kPlayingAudio to |renderer_|.
- //
- // There must be a pending read callback.
- void FulfillPendingRead(size_t size) {
- CHECK(!read_cb_.is_null());
- scoped_refptr<DataBuffer> buffer(new DataBuffer(size));
- buffer->SetDataSize(size);
- memset(buffer->GetWritableData(), kPlayingAudio, buffer->GetDataSize());
-
- buffer->SetTimestamp(next_timestamp_->GetTimestamp());
- buffer->SetDuration(next_timestamp_->GetDuration(buffer->GetDataSize()));
- next_timestamp_->AddBytes(buffer->GetDataSize());
-
- base::ResetAndReturn(&read_cb_).Run(AudioDecoder::kOk, buffer);
- }
-
- void AbortPendingRead() {
- base::ResetAndReturn(&read_cb_).Run(AudioDecoder::kAborted, NULL);
- }
-
- // Delivers an end of stream buffer to |renderer_|.
- //
- // There must be a pending read callback.
- void DeliverEndOfStream() {
- FulfillPendingRead(0);
- }
-
- // Delivers bytes until |renderer_|'s internal buffer is full and no longer
- // has pending reads.
- void DeliverRemainingAudio() {
- CHECK(!read_cb_.is_null());
- FulfillPendingRead(bytes_remaining_in_buffer());
- CHECK(read_cb_.is_null());
- }
-
- // Attempts to consume |size| bytes from |renderer_|'s internal buffer,
- // returning true if all |size| bytes were consumed, false if less than
- // |size| bytes were consumed.
- //
- // |muted| is optional and if passed will get set if the byte value of
- // the consumed data is muted audio.
- bool ConsumeBufferedData(uint32 size, bool* muted) {
- scoped_array<uint8> buffer(new uint8[size]);
- uint32 bytes_per_frame = (decoder_->bits_per_channel() / 8) *
- ChannelLayoutToChannelCount(decoder_->channel_layout());
- uint32 requested_frames = size / bytes_per_frame;
- uint32 frames_read = renderer_->FillBuffer(
- buffer.get(), requested_frames, 0);
-
- if (frames_read > 0 && muted) {
- *muted = (buffer[0] == kMutedAudio);
- }
- return (frames_read == requested_frames);
- }
-
- uint32 bytes_buffered() {
- return renderer_->algorithm_->bytes_buffered();
- }
-
- uint32 buffer_capacity() {
- return renderer_->algorithm_->QueueCapacity();
- }
-
- uint32 bytes_remaining_in_buffer() {
- // This can happen if too much data was delivered, in which case the buffer
- // will accept the data but not increase capacity.
- if (bytes_buffered() > buffer_capacity()) {
- return 0;
- }
- return buffer_capacity() - bytes_buffered();
- }
-
- void CallResumeAfterUnderflow() {
- renderer_->ResumeAfterUnderflow(false);
- }
-
- // Fixture members.
- scoped_refptr<AudioRendererImpl> renderer_;
- scoped_refptr<MockDemuxerStream> demuxer_stream_;
- scoped_refptr<MockAudioDecoder> decoder_;
- AudioRendererImpl::AudioDecoderList decoders_;
- AudioDecoder::ReadCB read_cb_;
- scoped_ptr<AudioTimestampHelper> next_timestamp_;
- AudioDecoderConfig audio_config_;
- MessageLoop message_loop_;
-
- private:
- void SaveReadCallback(const AudioDecoder::ReadCB& callback) {
- CHECK(read_cb_.is_null()) << "Overlapping reads are not permitted";
- read_cb_ = callback;
- }
-
- DISALLOW_COPY_AND_ASSIGN(AudioRendererImplTest);
-};
-
-TEST_F(AudioRendererImplTest, Initialize_Failed) {
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- SetUnsupportedAudioDecoderProperties();
-
- InitializeWithStatus(PIPELINE_ERROR_INITIALIZATION_FAILED);
-
- // We should have no reads.
- EXPECT_TRUE(read_cb_.is_null());
-}
-
-TEST_F(AudioRendererImplTest, Initialize_Successful) {
- Initialize();
-
- // We should have no reads.
- EXPECT_TRUE(read_cb_.is_null());
-}
-
-TEST_F(AudioRendererImplTest, Initialize_DecoderInitFailure) {
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- InitializeWithStatus(DECODER_ERROR_NOT_SUPPORTED);
-
- // We should have no reads.
- EXPECT_TRUE(read_cb_.is_null());
-}
-
-TEST_F(AudioRendererImplTest, Initialize_MultipleDecoders) {
- scoped_refptr<MockAudioDecoder> decoder1 = new MockAudioDecoder();
- // Insert |decoder1| as the first decoder in the list.
- decoders_.push_front(decoder1);
- EXPECT_CALL(*decoder1, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- InitializeWithStatus(PIPELINE_OK);
-
- // We should have no reads.
- EXPECT_TRUE(read_cb_.is_null());
-}
-
-TEST_F(AudioRendererImplTest, Preroll) {
- Initialize();
- Preroll();
-}
-
-TEST_F(AudioRendererImplTest, Play) {
- Initialize();
- Preroll();
- Play();
-
- // Drain internal buffer, we should have a pending read.
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL));
-}
-
-TEST_F(AudioRendererImplTest, EndOfStream) {
- Initialize();
- Preroll();
- Play();
-
- // Drain internal buffer, we should have a pending read.
- int audio_bytes_filled = bytes_buffered();
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(audio_bytes_filled, NULL));
-
- // Check and clear |earliest_end_time_| so the ended event fires on the next
- // ConsumeBufferedData() call.
- base::TimeDelta audio_play_time = base::TimeDelta::FromMicroseconds(
- audio_bytes_filled * base::Time::kMicrosecondsPerSecond /
- static_cast<float>(renderer_->audio_parameters_.GetBytesPerSecond()));
- base::TimeDelta time_until_ended =
- renderer_->earliest_end_time_ - base::Time::Now();
- EXPECT_TRUE(time_until_ended > base::TimeDelta());
- EXPECT_TRUE(time_until_ended <= audio_play_time);
- renderer_->earliest_end_time_ = base::Time();
-
- // Fulfill the read with an end-of-stream packet, we shouldn't report ended
- // nor have a read until we drain the internal buffer.
- DeliverEndOfStream();
-
- // Drain internal buffer, now we should report ended.
- EXPECT_CALL(*this, OnEnded());
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL));
-}
-
-TEST_F(AudioRendererImplTest, Underflow) {
- Initialize();
- Preroll();
- Play();
-
- // Drain internal buffer, we should have a pending read.
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL));
-
- // Verify the next FillBuffer() call triggers the underflow callback
- // since the decoder hasn't delivered any data after it was drained.
- const size_t kDataSize = 1024;
- EXPECT_CALL(*this, OnUnderflow());
- EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL));
-
- renderer_->ResumeAfterUnderflow(false);
-
- // Verify after resuming that we're still not getting data.
- //
- // NOTE: FillBuffer() satisfies the read but returns muted audio, which
- // is crazy http://crbug.com/106600
- bool muted = false;
- EXPECT_EQ(0u, bytes_buffered());
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_TRUE(muted);
-
- // Deliver data, we should get non-muted audio.
- DeliverRemainingAudio();
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_FALSE(muted);
-}
-
-TEST_F(AudioRendererImplTest, Underflow_EndOfStream) {
- Initialize();
- Preroll();
- Play();
-
- // Drain internal buffer, we should have a pending read.
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL));
-
- // Verify the next FillBuffer() call triggers the underflow callback
- // since the decoder hasn't delivered any data after it was drained.
- const size_t kDataSize = 1024;
- EXPECT_CALL(*this, OnUnderflow());
- EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL));
-
- // Deliver a little bit of data.
- EXPECT_CALL(*decoder_, Read(_));
- FulfillPendingRead(kDataSize);
-
- // Verify we're getting muted audio during underflow.
- //
- // NOTE: FillBuffer() satisfies the read but returns muted audio, which
- // is crazy http://crbug.com/106600
- bool muted = false;
- EXPECT_EQ(kDataSize, bytes_buffered());
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_TRUE(muted);
-
- // Now deliver end of stream, we should get our little bit of data back.
- DeliverEndOfStream();
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_EQ(kDataSize, bytes_buffered());
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_FALSE(muted);
-
- // Deliver another end of stream buffer and attempt to read to make sure
- // we're truly at the end of stream.
- //
- // TODO(scherkus): fix AudioRendererImpl and AudioRendererAlgorithmBase to
- // stop reading after receiving an end of stream buffer. It should have also
- // fired the ended callback http://crbug.com/106641
- DeliverEndOfStream();
- EXPECT_CALL(*this, OnEnded());
-
- // Clear |earliest_end_time_| so ended fires on the next ConsumeBufferedData()
- // call.
- renderer_->earliest_end_time_ = base::Time();
-
- EXPECT_FALSE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_FALSE(muted);
-}
-
-TEST_F(AudioRendererImplTest, Underflow_ResumeFromCallback) {
- Initialize();
- Preroll();
- Play();
-
- // Drain internal buffer, we should have a pending read.
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL));
-
- // Verify the next FillBuffer() call triggers the underflow callback
- // since the decoder hasn't delivered any data after it was drained.
- const size_t kDataSize = 1024;
- EXPECT_CALL(*this, OnUnderflow())
- .WillOnce(Invoke(this, &AudioRendererImplTest::CallResumeAfterUnderflow));
- EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL));
-
- // Verify after resuming that we're still not getting data.
- bool muted = false;
- EXPECT_EQ(0u, bytes_buffered());
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_TRUE(muted);
-
- // Deliver data, we should get non-muted audio.
- DeliverRemainingAudio();
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted));
- EXPECT_FALSE(muted);
-}
-
-TEST_F(AudioRendererImplTest, AbortPendingRead_Preroll) {
- Initialize();
-
- // Start prerolling.
- EXPECT_CALL(*decoder_, Read(_));
- renderer_->Preroll(base::TimeDelta(), NewPrerollCB());
-
- // Simulate the decoder aborting the pending read.
- EXPECT_CALL(*this, OnPrerollComplete(PIPELINE_OK));
- AbortPendingRead();
-
- // Preroll again to verify it completed normally.
- Preroll(base::TimeDelta::FromSeconds(1));
-
- ASSERT_TRUE(read_cb_.is_null());
-}
-
-TEST_F(AudioRendererImplTest, AbortPendingRead_Pause) {
- Initialize();
-
- Preroll();
- Play();
-
- // Partially drain internal buffer so we get a pending read.
- EXPECT_CALL(*decoder_, Read(_));
- EXPECT_TRUE(ConsumeBufferedData(bytes_buffered() / 2, NULL));
-
- renderer_->Pause(NewExpectedClosure());
-
- AbortPendingRead();
-
- Preroll(base::TimeDelta::FromSeconds(1));
-}
-
-} // namespace media
diff --git a/src/media/filters/blocking_url_protocol.cc b/src/media/filters/blocking_url_protocol.cc
deleted file mode 100644
index edb7e96..0000000
--- a/src/media/filters/blocking_url_protocol.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/blocking_url_protocol.h"
-
-#include "base/bind.h"
-#include "media/base/data_source.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-
-namespace media {
-
-BlockingUrlProtocol::BlockingUrlProtocol(
- const scoped_refptr<DataSource>& data_source,
- const base::Closure& error_cb)
- : data_source_(data_source),
- error_cb_(error_cb),
- aborted_(true, false), // We never want to reset |aborted_|.
- read_complete_(false, false),
- last_read_bytes_(0),
- read_position_(0) {
-}
-
-BlockingUrlProtocol::~BlockingUrlProtocol() {}
-
-void BlockingUrlProtocol::Abort() {
- aborted_.Signal();
-}
-
-int BlockingUrlProtocol::Read(int size, uint8* data) {
- // Read errors are unrecoverable.
- if (aborted_.IsSignaled())
- return AVERROR(EIO);
-
- // Even though FFmpeg defines AVERROR_EOF, it's not to be used with I/O
- // routines. Instead return 0 for any read at or past EOF.
- int64 file_size;
- if (data_source_->GetSize(&file_size) && read_position_ >= file_size)
- return 0;
-
- // Blocking read from data source until either:
- // 1) |last_read_bytes_| is set and |read_complete_| is signalled
- // 2) |aborted_| is signalled
- data_source_->Read(read_position_, size, data, base::Bind(
- &BlockingUrlProtocol::SignalReadCompleted, base::Unretained(this)));
-
- base::WaitableEvent* events[] = { &aborted_, &read_complete_ };
- size_t index = base::WaitableEvent::WaitMany(events, arraysize(events));
-
- if (events[index] == &aborted_)
- return AVERROR(EIO);
-
- if (last_read_bytes_ == DataSource::kReadError) {
- aborted_.Signal();
- error_cb_.Run();
- return AVERROR(EIO);
- }
-
- read_position_ += last_read_bytes_;
- return last_read_bytes_;
-}
-
-bool BlockingUrlProtocol::GetPosition(int64* position_out) {
- *position_out = read_position_;
- return true;
-}
-
-bool BlockingUrlProtocol::SetPosition(int64 position) {
- int64 file_size;
- if ((data_source_->GetSize(&file_size) && position >= file_size) ||
- position < 0) {
- return false;
- }
-
- read_position_ = position;
- return true;
-}
-
-bool BlockingUrlProtocol::GetSize(int64* size_out) {
- return data_source_->GetSize(size_out);
-}
-
-bool BlockingUrlProtocol::IsStreaming() {
- return data_source_->IsStreaming();
-}
-
-void BlockingUrlProtocol::SignalReadCompleted(int size) {
- last_read_bytes_ = size;
- read_complete_.Signal();
-}
-
-} // namespace media
diff --git a/src/media/filters/blocking_url_protocol.h b/src/media/filters/blocking_url_protocol.h
deleted file mode 100644
index 8eb8ebc..0000000
--- a/src/media/filters/blocking_url_protocol.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_BLOCKING_URL_PROTOCOL_H_
-#define MEDIA_FILTERS_BLOCKING_URL_PROTOCOL_H_
-
-#include "base/basictypes.h"
-#include "base/callback.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/filters/ffmpeg_glue.h"
-
-namespace media {
-
-class DataSource;
-
-// An implementation of FFmpegURLProtocol that blocks until the underlying
-// asynchronous DataSource::Read() operation completes.
-class MEDIA_EXPORT BlockingUrlProtocol : public FFmpegURLProtocol {
- public:
- // Implements FFmpegURLProtocol using the given |data_source|. |error_cb| is
- // fired any time DataSource::Read() returns an error.
- //
- // TODO(scherkus): After all blocking operations are isolated on a separate
- // thread we should be able to eliminate |error_cb|.
- BlockingUrlProtocol(const scoped_refptr<DataSource>& data_source,
- const base::Closure& error_cb);
- virtual ~BlockingUrlProtocol();
-
- // Aborts any pending reads by returning a read error. After this method
- // returns all subsequent calls to Read() will immediately fail.
- void Abort();
-
- // FFmpegURLProtocol implementation.
- virtual int Read(int size, uint8* data) OVERRIDE;
- virtual bool GetPosition(int64* position_out) OVERRIDE;
- virtual bool SetPosition(int64 position) OVERRIDE;
- virtual bool GetSize(int64* size_out) OVERRIDE;
- virtual bool IsStreaming() OVERRIDE;
-
- private:
- // Sets |last_read_bytes_| and signals the blocked thread that the read
- // has completed.
- void SignalReadCompleted(int size);
-
- scoped_refptr<DataSource> data_source_;
- base::Closure error_cb_;
-
- // Used to unblock the thread during shutdown and when reads complete.
- base::WaitableEvent aborted_;
- base::WaitableEvent read_complete_;
-
- // Cached number of bytes last read from the data source.
- int last_read_bytes_;
-
- // Cached position within the data source.
- int64 read_position_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(BlockingUrlProtocol);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_BLOCKING_URL_PROTOCOL_H_
diff --git a/src/media/filters/blocking_url_protocol_unittest.cc b/src/media/filters/blocking_url_protocol_unittest.cc
deleted file mode 100644
index 3b86e72..0000000
--- a/src/media/filters/blocking_url_protocol_unittest.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/path_service.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/base/test_data_util.h"
-#include "media/filters/blocking_url_protocol.h"
-#include "media/filters/file_data_source.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media {
-
-class BlockingUrlProtocolTest : public testing::Test {
- public:
- BlockingUrlProtocolTest() {
- data_source_ = new FileDataSource();
- CHECK(data_source_->Initialize(GetTestDataFilePath("bear-320x240.webm")));
-
- url_protocol_.reset(new BlockingUrlProtocol(data_source_, base::Bind(
- &BlockingUrlProtocolTest::OnDataSourceError, base::Unretained(this))));
- }
-
- virtual ~BlockingUrlProtocolTest() {
- base::WaitableEvent stop_event(false, false);
- data_source_->Stop(base::Bind(
- &base::WaitableEvent::Signal, base::Unretained(&stop_event)));
- stop_event.Wait();
- }
-
- MOCK_METHOD0(OnDataSourceError, void());
-
- scoped_refptr<FileDataSource> data_source_;
- scoped_ptr<BlockingUrlProtocol> url_protocol_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(BlockingUrlProtocolTest);
-};
-
-
-TEST_F(BlockingUrlProtocolTest, Read) {
- // Set read head to zero as Initialize() will have parsed a bit of the file.
- int64 position = 0;
- EXPECT_TRUE(url_protocol_->SetPosition(0));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(0, position);
-
- // Read 32 bytes from offset zero and verify position.
- uint8 buffer[32];
- EXPECT_EQ(32, url_protocol_->Read(32, buffer));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(32, position);
-
- // Read an additional 32 bytes and verify position.
- EXPECT_EQ(32, url_protocol_->Read(32, buffer));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(64, position);
-
- // Seek to end and read until EOF.
- int64 size = 0;
- EXPECT_TRUE(url_protocol_->GetSize(&size));
- EXPECT_TRUE(url_protocol_->SetPosition(size - 48));
- EXPECT_EQ(32, url_protocol_->Read(32, buffer));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(size - 16, position);
-
- EXPECT_EQ(16, url_protocol_->Read(32, buffer));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(size, position);
-
- EXPECT_EQ(0, url_protocol_->Read(32, buffer));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(size, position);
-}
-
-TEST_F(BlockingUrlProtocolTest, ReadError) {
- data_source_->force_read_errors_for_testing();
-
- uint8 buffer[32];
- EXPECT_CALL(*this, OnDataSourceError());
- EXPECT_EQ(AVERROR(EIO), url_protocol_->Read(32, buffer));
-}
-
-TEST_F(BlockingUrlProtocolTest, GetSetPosition) {
- int64 size;
- int64 position;
- EXPECT_TRUE(url_protocol_->GetSize(&size));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
-
- EXPECT_TRUE(url_protocol_->SetPosition(512));
- EXPECT_FALSE(url_protocol_->SetPosition(size));
- EXPECT_FALSE(url_protocol_->SetPosition(size + 1));
- EXPECT_FALSE(url_protocol_->SetPosition(-1));
- EXPECT_TRUE(url_protocol_->GetPosition(&position));
- EXPECT_EQ(512, position);
-}
-
-TEST_F(BlockingUrlProtocolTest, GetSize) {
- int64 data_source_size = 0;
- int64 url_protocol_size = 0;
- EXPECT_TRUE(data_source_->GetSize(&data_source_size));
- EXPECT_TRUE(url_protocol_->GetSize(&url_protocol_size));
- EXPECT_NE(0, data_source_size);
- EXPECT_EQ(data_source_size, url_protocol_size);
-}
-
-TEST_F(BlockingUrlProtocolTest, IsStreaming) {
- EXPECT_FALSE(data_source_->IsStreaming());
- EXPECT_FALSE(url_protocol_->IsStreaming());
-
- data_source_->force_streaming_for_testing();
- EXPECT_TRUE(data_source_->IsStreaming());
- EXPECT_TRUE(url_protocol_->IsStreaming());
-}
-
-} // namespace media
diff --git a/src/media/filters/chunk_demuxer.cc b/src/media/filters/chunk_demuxer.cc
deleted file mode 100644
index 64760b8..0000000
--- a/src/media/filters/chunk_demuxer.cc
+++ /dev/null
@@ -1,1436 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/chunk_demuxer.h"
-
-#include <algorithm>
-#include <deque>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "base/string_util.h"
-#if LOG_MEDIA_SOURCE_ACTIVITIES
-#include "base/stringprintf.h"
-#endif // LOG_MEDIA_SOURCE_ACTIVITIES
-#include "media/base/audio_decoder_config.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/video_decoder_config.h"
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS) || \
- defined(__LB_SHELL__) || defined(COBALT)
-#include "media/mp4/mp4_stream_parser.h"
-#endif
-#include "media/webm/webm_stream_parser.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_media_platform.h"
-#endif
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#if SB_HAS_QUIRK(SEEK_TO_KEYFRAME)
-#define CHUNK_DEMUXER_SEEK_TO_KEYFRAME
-#endif // SB_HAS_QUIRK(SEEK_TO_KEYFRAME)
-#endif // defined(OS_STARBOARD)
-
-using base::TimeDelta;
-
-namespace media {
-
-namespace {
-
-std::string GetMediaSourceLogDesc(const char* desc_with_format,
- DemuxerStream::Type type) {
-#if LOG_MEDIA_SOURCE_ACTIVITIES
- return base::StringPrintf(desc_with_format,
- type == DemuxerStream::AUDIO ? "audio" : "video");
-#else // LOG_MEDIA_SOURCE_ACTIVITIES
- return "";
-#endif // LOG_MEDIA_SOURCE_ACTIVITIES
-}
-
-} // namespace
-
-struct CodecInfo {
- const char* pattern;
- DemuxerStream::Type type;
-};
-
-typedef StreamParser* (*ParserFactoryFunction)(
- const std::vector<std::string>& codecs);
-
-struct SupportedTypeInfo {
- const char* type;
- const ParserFactoryFunction factory_function;
- const CodecInfo** codecs;
-};
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-#if defined(OS_STARBOARD)
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-
-static const CodecInfo kVP9CodecInfo = {"vp9*", DemuxerStream::VIDEO};
-
-static const CodecInfo* kVideoWebMCodecs[] = {
- &kVP9CodecInfo,
- NULL
-};
-
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-#endif // defined(OS_STARBOARD)
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-static const CodecInfo kVP8CodecInfo = { "vp8", DemuxerStream::VIDEO };
-static const CodecInfo kVorbisCodecInfo = { "vorbis", DemuxerStream::AUDIO };
-
-static const CodecInfo* kVideoWebMCodecs[] = {
- &kVP8CodecInfo,
- &kVorbisCodecInfo,
- NULL
-};
-
-static const CodecInfo* kAudioWebMCodecs[] = {
- &kVorbisCodecInfo,
- NULL
-};
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-static StreamParser* BuildWebMParser(const std::vector<std::string>& codecs) {
- return new WebMStreamParser();
-}
-
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS) || \
- defined(__LB_SHELL__) || defined(COBALT)
-static const CodecInfo kH264CodecInfo = { "avc1.*", DemuxerStream::VIDEO };
-static const CodecInfo kAACCodecInfo = { "mp4a.40.*", DemuxerStream::AUDIO };
-
-static const CodecInfo* kVideoMP4Codecs[] = {
- &kH264CodecInfo,
- &kAACCodecInfo,
- NULL
-};
-
-static const CodecInfo* kAudioMP4Codecs[] = {
- &kAACCodecInfo,
- NULL
-};
-
-// Mimetype codec string that indicates the content contains AAC SBR frames.
-static const char* kSBRCodecId = "mp4a.40.5";
-
-static StreamParser* BuildMP4Parser(const std::vector<std::string>& codecs) {
- bool has_sbr = false;
- for (size_t i = 0; i < codecs.size(); ++i) {
- if (codecs[i] == kSBRCodecId) {
- has_sbr = true;
- break;
- }
- }
-
- return new mp4::MP4StreamParser(has_sbr);
-}
-#endif
-
-static const SupportedTypeInfo kSupportedTypeInfo[] = {
-#if defined(OS_STARBOARD)
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- {"video/webm", &BuildWebMParser, kVideoWebMCodecs},
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-#endif // defined(OS_STARBOARD)
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- {"audio/webm", &BuildWebMParser, kAudioWebMCodecs},
-#endif
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS) || \
- defined(__LB_SHELL__) || defined(COBALT)
- {"video/mp4", &BuildMP4Parser, kVideoMP4Codecs},
- {"audio/mp4", &BuildMP4Parser, kAudioMP4Codecs},
-#endif
-};
-
-// Checks to see if the specified |type| and |codecs| list are supported.
-// Returns true if |type| and all codecs listed in |codecs| are supported.
-// |factory_function| contains a function that can build a StreamParser
-// for this type.
-// |has_audio| is true if an audio codec was specified.
-// |has_video| is true if a video codec was specified.
-// Returns false otherwise. The values of |factory_function|, |has_audio|,
-// and |has_video| are undefined.
-static bool IsSupported(const std::string& type,
- std::vector<std::string>& codecs,
- const LogCB& log_cb,
- ParserFactoryFunction* factory_function,
- bool* has_audio,
- bool* has_video) {
- *factory_function = NULL;
- *has_audio = false;
- *has_video = false;
-
- // Search for the SupportedTypeInfo for |type|.
- for (size_t i = 0; i < arraysize(kSupportedTypeInfo); ++i) {
- const SupportedTypeInfo& type_info = kSupportedTypeInfo[i];
- if (type == type_info.type) {
- // Make sure all the codecs specified in |codecs| are
- // in the supported type info.
- for (size_t j = 0; j < codecs.size(); ++j) {
- // Search the type info for a match.
- bool found_codec = false;
- DemuxerStream::Type codec_type = DemuxerStream::UNKNOWN;
-
- for (int k = 0; type_info.codecs[k]; ++k) {
- if (MatchPattern(codecs[j], type_info.codecs[k]->pattern)) {
- found_codec = true;
- codec_type = type_info.codecs[k]->type;
- break;
- }
- }
-
- if (!found_codec) {
- MEDIA_LOG(log_cb) << "Codec '" << codecs[j]
- <<"' is not supported for '" << type << "'";
- return false;
- }
-
- switch (codec_type) {
- case DemuxerStream::AUDIO:
- *has_audio = true;
- break;
- case DemuxerStream::VIDEO:
- *has_video = true;
- break;
- default:
- MEDIA_LOG(log_cb) << "Unsupported codec type '"<< codec_type
- << "' for " << codecs[j];
- return false;
- }
- }
-
- *factory_function = type_info.factory_function;
-
- // All codecs were supported by this |type|.
- return true;
- }
- }
-
- // |type| didn't match any of the supported types.
- return false;
-}
-
-class ChunkDemuxerStream : public DemuxerStream {
- public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
- typedef std::deque<ReadCB> ReadCBQueue;
- typedef std::deque<base::Closure> ClosureQueue;
-
- ChunkDemuxerStream(const AudioDecoderConfig& audio_config,
- const LogCB& log_cb);
- ChunkDemuxerStream(const VideoDecoderConfig& video_config,
- const LogCB& log_cb);
-
- void StartWaitingForSeek();
- void Seek(TimeDelta time);
- void CancelPendingSeek();
- bool IsSeekPending() const;
- base::TimeDelta GetSeekKeyframeTimestamp() const;
-
- // Add buffers to this stream. Buffers are stored in SourceBufferStreams,
- // which handle ordering and overlap resolution.
- // Returns true if buffers were successfully added.
- bool Append(const StreamParser::BufferQueue& buffers);
-
- // Signal to the stream that duration has changed to |duration|.
- void OnSetDuration(base::TimeDelta duration);
-
- // Returns the range of buffered data in this stream, capped at |duration|.
- Ranges<TimeDelta> GetBufferedRanges(base::TimeDelta duration) const;
-
- // Signal to the stream that buffers handed in through subsequent calls to
- // Append() belong to a media segment that starts at |start_timestamp|.
- void OnNewMediaSegment(TimeDelta start_timestamp);
-
- // Called when mid-stream config updates occur.
- // Returns true if the new config is accepted.
- // Returns false if the new config should trigger an error.
- bool UpdateAudioConfig(const AudioDecoderConfig& config);
- bool UpdateVideoConfig(const VideoDecoderConfig& config);
-
- void EndOfStream();
- void CancelEndOfStream();
- bool CanEndOfStream() const;
-
- void Shutdown();
-
- // DemuxerStream methods.
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual Type type() OVERRIDE;
- virtual void EnableBitstreamConverter() OVERRIDE;
- virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
- virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- bool StreamWasEncrypted() const OVERRIDE;
-#endif
-
- protected:
- virtual ~ChunkDemuxerStream();
-
- private:
- enum State {
- RETURNING_DATA_FOR_READS,
- WAITING_FOR_SEEK,
- CANCELED,
- SHUTDOWN,
- };
-
- // Assigns |state_| to |state|
- void ChangeState_Locked(State state);
-
- // Adds the callback to |read_cbs_| so it can be called later when we
- // have data.
- void DeferRead_Locked(const ReadCB& read_cb);
-
- // Creates closures that bind ReadCBs in |read_cbs_| to data in
- // |buffers_| and pops the callbacks & buffers from the respective queues.
- void CreateReadDoneClosures_Locked(ClosureQueue* closures);
-
- // Gets the value to pass to the next Read() callback. Returns true if
- // |status| and |buffer| should be passed to the callback. False indicates
- // that |status| and |buffer| were not set and more data is needed.
- bool GetNextBuffer_Locked(DemuxerStream::Status* status,
- scoped_refptr<StreamParserBuffer>* buffer);
-
- // Specifies the type of the stream (must be AUDIO or VIDEO for now).
- Type type_;
-
- scoped_ptr<SourceBufferStream> stream_;
-
- mutable base::Lock lock_;
- State state_;
- ReadCBQueue read_cbs_;
- bool end_of_stream_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
-};
-
-
-ChunkDemuxerStream::ChunkDemuxerStream(const AudioDecoderConfig& audio_config,
- const LogCB& log_cb)
- : type_(AUDIO),
- state_(RETURNING_DATA_FOR_READS),
- end_of_stream_(false) {
- stream_.reset(new SourceBufferStream(audio_config, log_cb));
-}
-
-ChunkDemuxerStream::ChunkDemuxerStream(const VideoDecoderConfig& video_config,
- const LogCB& log_cb)
- : type_(VIDEO),
- state_(RETURNING_DATA_FOR_READS),
- end_of_stream_(false) {
- stream_.reset(new SourceBufferStream(video_config, log_cb));
-}
-
-void ChunkDemuxerStream::StartWaitingForSeek() {
- DVLOG(1) << "ChunkDemuxerStream::StartWaitingForSeek()";
- ReadCBQueue read_cbs;
- {
- base::AutoLock auto_lock(lock_);
- ChangeState_Locked(WAITING_FOR_SEEK);
- std::swap(read_cbs_, read_cbs);
- }
-
- for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
- it->Run(kAborted, NULL);
-}
-
-void ChunkDemuxerStream::Seek(TimeDelta time) {
- base::AutoLock auto_lock(lock_);
-
- DCHECK(read_cbs_.empty());
-
- // Ignore seek requests when canceled.
- if (state_ == CANCELED)
- return;
-
- stream_->Seek(time);
-
- if (state_ == WAITING_FOR_SEEK)
- ChangeState_Locked(RETURNING_DATA_FOR_READS);
-}
-
-void ChunkDemuxerStream::CancelPendingSeek() {
- DVLOG(1) << "ChunkDemuxerStream::CancelPendingSeek()";
- ReadCBQueue read_cbs;
- {
- base::AutoLock auto_lock(lock_);
- ChangeState_Locked(CANCELED);
- std::swap(read_cbs_, read_cbs);
- }
-
- for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
- it->Run(kAborted, NULL);
-}
-
-bool ChunkDemuxerStream::IsSeekPending() const {
- base::AutoLock auto_lock(lock_);
- return stream_->IsSeekPending();
-}
-
-base::TimeDelta ChunkDemuxerStream::GetSeekKeyframeTimestamp() const {
- base::AutoLock auto_lock(lock_);
- return stream_->GetSeekKeyframeTimestamp();
-}
-
-void ChunkDemuxerStream::OnNewMediaSegment(TimeDelta start_timestamp) {
- base::AutoLock auto_lock(lock_);
- DCHECK(!end_of_stream_);
- stream_->OnNewMediaSegment(start_timestamp);
-}
-
-bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
- if (buffers.empty())
- return false;
-
- LogMediaSourceTimeRanges(
- GetMediaSourceLogDesc("before append buffers to %s stream", type()),
- GetBufferedRanges(kInfiniteDuration()));
-
- ClosureQueue closures;
- {
- base::AutoLock auto_lock(lock_);
- DCHECK(!end_of_stream_);
- DCHECK_NE(state_, SHUTDOWN);
- if (!stream_->Append(buffers)) {
- DVLOG(1) << "ChunkDemuxerStream::Append() : stream append failed";
- return false;
- }
- CreateReadDoneClosures_Locked(&closures);
- }
-
- LogMediaSourceTimeRanges(
- GetMediaSourceLogDesc("after append buffers to %s stream", type()),
- GetBufferedRanges(kInfiniteDuration()));
-
-#if LOG_MEDIA_SOURCE_ACTIVITIES
- LOG(INFO) << ""; // Extra empty line to indicate the end of append in log.
-#endif // LOG_MEDIA_SOURCE_ACTIVITIES
-
- for (ClosureQueue::iterator it = closures.begin(); it != closures.end(); ++it)
- it->Run();
-
- return true;
-}
-
-void ChunkDemuxerStream::OnSetDuration(base::TimeDelta duration) {
- base::AutoLock auto_lock(lock_);
- stream_->OnSetDuration(duration);
-}
-
-Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges(
- base::TimeDelta duration) const {
- base::AutoLock auto_lock(lock_);
- Ranges<TimeDelta> range = stream_->GetBufferedTime();
-
- if (range.size() == 0u)
- return range;
-
- // Clamp the end of the stream's buffered ranges to fit within the duration.
- // This can be done by intersecting the stream's range with the valid time
- // range.
- Ranges<TimeDelta> valid_time_range;
- valid_time_range.Add(range.start(0), duration);
- return range.IntersectionWith(valid_time_range);
-}
-
-bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config) {
- DCHECK(config.IsValidConfig());
- DCHECK_EQ(type_, AUDIO);
- base::AutoLock auto_lock(lock_);
- return stream_->UpdateAudioConfig(config);
-}
-
-bool ChunkDemuxerStream::UpdateVideoConfig(const VideoDecoderConfig& config) {
- DCHECK(config.IsValidConfig());
- DCHECK_EQ(type_, VIDEO);
- base::AutoLock auto_lock(lock_);
- return stream_->UpdateVideoConfig(config);
-}
-
-void ChunkDemuxerStream::EndOfStream() {
- ClosureQueue closures;
- {
- base::AutoLock auto_lock(lock_);
- DCHECK(!end_of_stream_);
- DCHECK(stream_->IsEndSelected());
- end_of_stream_ = true;
- CreateReadDoneClosures_Locked(&closures);
- }
-
- for (ClosureQueue::iterator it = closures.begin(); it != closures.end(); ++it)
- it->Run();
-}
-
-void ChunkDemuxerStream::CancelEndOfStream() {
- base::AutoLock auto_lock(lock_);
- DCHECK(end_of_stream_);
- end_of_stream_ = false;
-}
-
-bool ChunkDemuxerStream::CanEndOfStream() const {
- base::AutoLock auto_lock(lock_);
- return stream_->IsEndSelected();
-}
-
-void ChunkDemuxerStream::Shutdown() {
- ReadCBQueue read_cbs;
- {
- base::AutoLock auto_lock(lock_);
- ChangeState_Locked(SHUTDOWN);
- std::swap(read_cbs_, read_cbs);
- }
-
- // Pass end of stream buffers to all callbacks to signal that no more data
- // will be sent.
- for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
- it->Run(DemuxerStream::kOk, StreamParserBuffer::CreateEOSBuffer());
-}
-
-// Helper function that makes sure |read_cb| runs on |message_loop_proxy|.
-static void RunOnMessageLoop(
- const DemuxerStream::ReadCB& read_cb,
- const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- if (!message_loop_proxy->BelongsToCurrentThread()) {
- message_loop_proxy->PostTask(FROM_HERE, base::Bind(
- &RunOnMessageLoop, read_cb, message_loop_proxy, status, buffer));
- return;
- }
-
- read_cb.Run(status, buffer);
-}
-
-// DemuxerStream methods.
-void ChunkDemuxerStream::Read(const ReadCB& read_cb) {
- DemuxerStream::Status status = kOk;
- scoped_refptr<StreamParserBuffer> buffer;
- {
- base::AutoLock auto_lock(lock_);
- if (!read_cbs_.empty() || !GetNextBuffer_Locked(&status, &buffer)) {
- DeferRead_Locked(read_cb);
- return;
- }
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- read_cb.Run(
- status,
- ShellMediaPlatform::Instance()->ProcessBeforeLeavingDemuxer(buffer));
-#else // defined(__LB_SHELL__) || defined(COBALT)
- read_cb.Run(status, buffer);
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-}
-
-DemuxerStream::Type ChunkDemuxerStream::type() { return type_; }
-
-void ChunkDemuxerStream::EnableBitstreamConverter() {}
-
-const AudioDecoderConfig& ChunkDemuxerStream::audio_decoder_config() {
- CHECK_EQ(type_, AUDIO);
- base::AutoLock auto_lock(lock_);
- return stream_->GetCurrentAudioDecoderConfig();
-}
-
-const VideoDecoderConfig& ChunkDemuxerStream::video_decoder_config() {
- CHECK_EQ(type_, VIDEO);
- base::AutoLock auto_lock(lock_);
- return stream_->GetCurrentVideoDecoderConfig();
-}
-
-void ChunkDemuxerStream::ChangeState_Locked(State state) {
- lock_.AssertAcquired();
- DVLOG(1) << "ChunkDemuxerStream::ChangeState_Locked() : "
- << "type " << type_
- << " - " << state_ << " -> " << state;
- state_ = state;
-}
-
-ChunkDemuxerStream::~ChunkDemuxerStream() {}
-
-void ChunkDemuxerStream::DeferRead_Locked(const ReadCB& read_cb) {
- lock_.AssertAcquired();
- // Wrap & store |read_cb| so that it will
- // get called on the current MessageLoop.
- read_cbs_.push_back(base::Bind(&RunOnMessageLoop, read_cb,
- base::MessageLoopProxy::current()));
-}
-
-void ChunkDemuxerStream::CreateReadDoneClosures_Locked(ClosureQueue* closures) {
- lock_.AssertAcquired();
-
- if (state_ != RETURNING_DATA_FOR_READS)
- return;
-
- DemuxerStream::Status status = kOk;
- scoped_refptr<StreamParserBuffer> buffer;
- // When the status is kConfigChanged, we should stop the loop.
- while (!read_cbs_.empty() && status != kConfigChanged) {
- if (!GetNextBuffer_Locked(&status, &buffer))
- return;
-#if defined(__LB_SHELL__) || defined(COBALT)
- closures->push_back(base::Bind(
- read_cbs_.front(), status,
- ShellMediaPlatform::Instance()->ProcessBeforeLeavingDemuxer(buffer)));
-#else // defined(__LB_SHELL__) || defined(COBALT)
- closures->push_back(base::Bind(read_cbs_.front(),
- status, buffer));
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- read_cbs_.pop_front();
- }
-}
-
-bool ChunkDemuxerStream::GetNextBuffer_Locked(
- DemuxerStream::Status* status,
- scoped_refptr<StreamParserBuffer>* buffer) {
- lock_.AssertAcquired();
-
- switch (state_) {
- case RETURNING_DATA_FOR_READS:
- switch (stream_->GetNextBuffer(buffer)) {
- case SourceBufferStream::kSuccess:
- *status = DemuxerStream::kOk;
- return true;
- case SourceBufferStream::kNeedBuffer:
- if (end_of_stream_) {
- *status = DemuxerStream::kOk;
- *buffer = StreamParserBuffer::CreateEOSBuffer();
- return true;
- }
- return false;
- case SourceBufferStream::kConfigChange:
- DVLOG(2) << "Config change reported to ChunkDemuxerStream.";
- *status = kConfigChanged;
- *buffer = NULL;
- return true;
- }
- break;
- case CANCELED:
- case WAITING_FOR_SEEK:
- // Null buffers should be returned in this state since we are waiting
- // for a seek. Any buffers in the SourceBuffer should NOT be returned
- // because they are associated with the seek.
- DCHECK(read_cbs_.empty());
- *status = DemuxerStream::kAborted;
- *buffer = NULL;
- return true;
- case SHUTDOWN:
- DCHECK(read_cbs_.empty());
- *status = DemuxerStream::kOk;
- *buffer = StreamParserBuffer::CreateEOSBuffer();
- return true;
- }
-
- NOTREACHED();
- return false;
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-bool ChunkDemuxerStream::StreamWasEncrypted() const {
- base::AutoLock auto_lock(lock_);
- if (type_ == VIDEO)
- return stream_->GetCurrentVideoDecoderConfig().is_encrypted();
- else if (type_ == AUDIO)
- return stream_->GetCurrentAudioDecoderConfig().is_encrypted();
-
- NOTREACHED();
- return false;
-}
-
-#endif
-
-ChunkDemuxer::ChunkDemuxer(const base::Closure& open_cb,
- const NeedKeyCB& need_key_cb,
- const LogCB& log_cb)
- : state_(WAITING_FOR_INIT),
- delayed_audio_seek_(false),
- host_(NULL),
- open_cb_(open_cb),
- need_key_cb_(need_key_cb),
- log_cb_(log_cb),
- duration_(kNoTimestamp()),
- user_specified_duration_(-1) {
- DCHECK(!open_cb_.is_null());
- DCHECK(!need_key_cb_.is_null());
-}
-
-void ChunkDemuxer::Initialize(DemuxerHost* host, const PipelineStatusCB& cb) {
- DVLOG(1) << "Init()";
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- DLOG(INFO) << "this is a MEDIA SOURCE playback.";
-#endif
-
- base::AutoLock auto_lock(lock_);
-
- if (state_ == SHUTDOWN) {
- base::MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
- cb, DEMUXER_ERROR_COULD_NOT_OPEN));
- return;
- }
- DCHECK_EQ(state_, WAITING_FOR_INIT);
- host_ = host;
-
- ChangeState_Locked(INITIALIZING);
- init_cb_ = cb;
-
- base::ResetAndReturn(&open_cb_).Run();
-}
-
-void ChunkDemuxer::Stop(const base::Closure& callback) {
- DVLOG(1) << "Stop()";
- Shutdown();
- callback.Run();
-}
-
-void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) {
- DVLOG(1) << "Seek(" << time.InSecondsF() << ")";
- DCHECK(time >= TimeDelta());
- DCHECK(seek_cb_.is_null());
-
- PipelineStatus status = PIPELINE_ERROR_INVALID_STATE;
- {
- base::AutoLock auto_lock(lock_);
-
- if (state_ == INITIALIZED || state_ == ENDED) {
- if (video_)
- video_->Seek(time);
-#if defined(CHUNK_DEMUXER_SEEK_TO_KEYFRAME)
- // We only need to do a delayed audio seek when there are both audio and
- // video streams and the seek on the video stream is pending.
- delayed_audio_seek_ = audio_ && video_ && video_->IsSeekPending();
- if (audio_ && !delayed_audio_seek_) {
- audio_->Seek(video_->GetSeekKeyframeTimestamp());
- }
-#else // defined(CHUNK_DEMUXER_SEEK_TO_KEYFRAME)
- if (audio_)
- audio_->Seek(time);
-#endif // defined(CHUNK_DEMUXER_SEEK_TO_KEYFRAME)
-
- if (IsSeekPending_Locked()) {
- DVLOG(1) << "Seek() : waiting for more data to arrive.";
- seek_cb_ = cb;
- return;
- }
-
- status = PIPELINE_OK;
- }
- }
-
- cb.Run(status);
-}
-
-void ChunkDemuxer::OnAudioRendererDisabled() {
- base::AutoLock auto_lock(lock_);
- audio_ = NULL;
-}
-
-// Demuxer implementation.
-scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream(
- DemuxerStream::Type type) {
- base::AutoLock auto_lock(lock_);
- if (type == DemuxerStream::VIDEO)
- return video_;
-
- if (type == DemuxerStream::AUDIO)
- return audio_;
-
- return NULL;
-}
-
-TimeDelta ChunkDemuxer::GetStartTime() const {
- return TimeDelta();
-}
-
-void ChunkDemuxer::StartWaitingForSeek() {
- DVLOG(1) << "StartWaitingForSeek()";
- base::AutoLock auto_lock(lock_);
- DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN);
-
- if (state_ == SHUTDOWN)
- return;
-
- if (audio_)
- audio_->StartWaitingForSeek();
-
- if (video_)
- video_->StartWaitingForSeek();
-}
-
-void ChunkDemuxer::CancelPendingSeek() {
- PipelineStatusCB cb;
- {
- base::AutoLock auto_lock(lock_);
- if (IsSeekPending_Locked() && !seek_cb_.is_null()) {
- std::swap(cb, seek_cb_);
- }
- if (audio_)
- audio_->CancelPendingSeek();
-
- if (video_)
- video_->CancelPendingSeek();
- }
-
- if (!cb.is_null())
- cb.Run(PIPELINE_OK);
-}
-
-ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
- const std::string& type,
- std::vector<std::string>& codecs) {
- DCHECK_GT(codecs.size(), 0u);
- base::AutoLock auto_lock(lock_);
-
- if ((state_ != WAITING_FOR_INIT && state_ != INITIALIZING) ||
- stream_parser_map_.count(id) > 0u)
- return kReachedIdLimit;
-
- bool has_audio = false;
- bool has_video = false;
- ParserFactoryFunction factory_function = NULL;
- std::string error;
- if (!IsSupported(type, codecs, log_cb_, &factory_function, &has_audio,
- &has_video)) {
- return kNotSupported;
- }
-
- if ((has_audio && !source_id_audio_.empty()) ||
- (has_video && !source_id_video_.empty()))
- return kReachedIdLimit;
-
- StreamParser::NewBuffersCB audio_cb;
- StreamParser::NewBuffersCB video_cb;
-
- if (has_audio) {
- source_id_audio_ = id;
- audio_cb = base::Bind(&ChunkDemuxer::OnAudioBuffers,
- base::Unretained(this));
- }
-
- if (has_video) {
- source_id_video_ = id;
- video_cb = base::Bind(&ChunkDemuxer::OnVideoBuffers,
- base::Unretained(this));
- }
-
- scoped_ptr<StreamParser> stream_parser(factory_function(codecs));
- CHECK(stream_parser.get());
-
- stream_parser->Init(
- base::Bind(&ChunkDemuxer::OnStreamParserInitDone, base::Unretained(this)),
- base::Bind(&ChunkDemuxer::OnNewConfigs, base::Unretained(this),
- has_audio, has_video),
- audio_cb,
- video_cb,
- base::Bind(&ChunkDemuxer::OnNeedKey, base::Unretained(this)),
- base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id),
- base::Bind(&ChunkDemuxer::OnEndOfMediaSegment,
- base::Unretained(this), id),
- log_cb_);
-
- stream_parser_map_[id] = stream_parser.release();
- SourceInfo info = { base::TimeDelta(), true };
- source_info_map_[id] = info;
-
- return kOk;
-}
-
-void ChunkDemuxer::RemoveId(const std::string& id) {
- base::AutoLock auto_lock(lock_);
- CHECK(IsValidId(id));
-
- delete stream_parser_map_[id];
- stream_parser_map_.erase(id);
- source_info_map_.erase(id);
-
- if (source_id_audio_ == id) {
- if (audio_)
- audio_->Shutdown();
- source_id_audio_.clear();
- }
-
- if (source_id_video_ == id) {
- if (video_)
- video_->Shutdown();
- source_id_video_.clear();
- }
-}
-
-Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const {
- base::AutoLock auto_lock(lock_);
- DCHECK(!id.empty());
- DCHECK(IsValidId(id));
- DCHECK(id == source_id_audio_ || id == source_id_video_);
-
- if (id == source_id_audio_ && id != source_id_video_) {
- // Only include ranges that have been buffered in |audio_|
- return audio_ ? audio_->GetBufferedRanges(duration_) : Ranges<TimeDelta>();
- }
-
- if (id != source_id_audio_ && id == source_id_video_) {
- // Only include ranges that have been buffered in |video_|
- return video_ ? video_->GetBufferedRanges(duration_) : Ranges<TimeDelta>();
- }
-
- return ComputeIntersection();
-}
-
-Ranges<TimeDelta> ChunkDemuxer::ComputeIntersection() const {
- lock_.AssertAcquired();
-
- if (!audio_ || !video_)
- return Ranges<TimeDelta>();
-
- // Include ranges that have been buffered in both |audio_| and |video_|.
- Ranges<TimeDelta> audio_ranges = audio_->GetBufferedRanges(duration_);
- Ranges<TimeDelta> video_ranges = video_->GetBufferedRanges(duration_);
- Ranges<TimeDelta> result = audio_ranges.IntersectionWith(video_ranges);
-
- if (state_ == ENDED && result.size() > 0) {
- // If appending has ended, extend the last intersection range to include the
- // max end time of the last audio/video range. This allows the buffered
- // information to match the actual time range that will get played out if
- // the streams have slightly different lengths.
- TimeDelta audio_start = audio_ranges.start(audio_ranges.size() - 1);
- TimeDelta audio_end = audio_ranges.end(audio_ranges.size() - 1);
- TimeDelta video_start = video_ranges.start(video_ranges.size() - 1);
- TimeDelta video_end = video_ranges.end(video_ranges.size() - 1);
-
- // Verify the last audio range overlaps with the last video range.
- // This is enforced by the logic that controls the transition to ENDED.
- DCHECK((audio_start <= video_start && video_start <= audio_end) ||
- (video_start <= audio_start && audio_start <= video_end));
- result.Add(result.end(result.size()-1), std::max(audio_end, video_end));
- }
-
- return result;
-}
-
-bool ChunkDemuxer::AppendData(const std::string& id,
- const uint8* data,
- size_t length) {
- DVLOG(1) << "AppendData(" << id << ", " << length << ")";
-
-#if LOG_MEDIA_SOURCE_ACTIVITIES
- LOG(INFO) << "======== append " << length << " bytes to stream " << id
- << " ========";
-#endif // LOG_MEDIA_SOURCE_ACTIVITIES
-
- DCHECK(!id.empty());
-
- Ranges<TimeDelta> ranges;
-
- PipelineStatusCB cb;
- {
- base::AutoLock auto_lock(lock_);
-
- // Capture if the SourceBuffer has a pending seek before we start parsing.
- bool old_seek_pending = IsSeekPending_Locked();
-
- if (state_ == ENDED) {
- ChangeState_Locked(INITIALIZED);
-
- if (audio_)
- audio_->CancelEndOfStream();
-
- if (video_)
- video_->CancelEndOfStream();
- }
-
- if (length == 0u)
- return true;
-
- DCHECK(data);
-
- switch (state_) {
- case INITIALIZING:
- DCHECK(IsValidId(id));
- if (!stream_parser_map_[id]->Parse(data, length)) {
- ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
- return true;
- }
- break;
-
- case INITIALIZED: {
- DCHECK(IsValidId(id));
- if (!stream_parser_map_[id]->Parse(data, length)) {
- ReportError_Locked(PIPELINE_ERROR_DECODE);
- return true;
- }
- } break;
-
- case WAITING_FOR_INIT:
- case ENDED:
- case PARSE_ERROR:
- case SHUTDOWN:
- DVLOG(1) << "AppendData(): called in unexpected state " << state_;
- return false;
- }
-
- if (delayed_audio_seek_ && !video_->IsSeekPending()) {
- DCHECK(audio_);
- audio_->Seek(video_->GetSeekKeyframeTimestamp());
- delayed_audio_seek_ = false;
- }
-
- // Check to see if data was appended at the pending seek point. This
- // indicates we have parsed enough data to complete the seek.
- if (old_seek_pending && !IsSeekPending_Locked() && !seek_cb_.is_null()) {
- std::swap(cb, seek_cb_);
- }
-
- ranges = GetBufferedRanges();
- }
-
- for (size_t i = 0; i < ranges.size(); ++i)
- host_->AddBufferedTimeRange(ranges.start(i), ranges.end(i));
-
- if (!cb.is_null())
- cb.Run(PIPELINE_OK);
-
- return true;
-}
-
-void ChunkDemuxer::Abort(const std::string& id) {
- DVLOG(1) << "Abort(" << id << ")";
- base::AutoLock auto_lock(lock_);
- DCHECK(!id.empty());
- CHECK(IsValidId(id));
- stream_parser_map_[id]->Flush();
- source_info_map_[id].can_update_offset = true;
-}
-
-double ChunkDemuxer::GetDuration() const {
- base::AutoLock auto_lock(lock_);
- return GetDuration_Locked();
-}
-
-void ChunkDemuxer::SetDuration(double duration) {
- base::AutoLock auto_lock(lock_);
- DVLOG(1) << "SetDuration(" << duration << ")";
- DCHECK_GE(duration, 0);
-
- if (duration == GetDuration_Locked())
- return;
-
- // Compute & bounds check the TimeDelta representation of duration.
- // This can be different if the value of |duration| doesn't fit the range or
- // precision of TimeDelta.
- TimeDelta min_duration = TimeDelta::FromInternalValue(1);
- // Don't use TimeDelta::Max() here, as we want the largest finite time delta.
- TimeDelta max_duration = TimeDelta::FromInternalValue(kint64max - 1);
- double min_duration_in_seconds = min_duration.InSecondsF();
- double max_duration_in_seconds = max_duration.InSecondsF();
-
- TimeDelta duration_td;
- if (duration == std::numeric_limits<double>::infinity()) {
- duration_td = media::kInfiniteDuration();
- } else if (duration < min_duration_in_seconds) {
- duration_td = min_duration;
- } else if (duration > max_duration_in_seconds) {
- duration_td = max_duration;
- } else {
- duration_td = TimeDelta::FromMicroseconds(
- duration * base::Time::kMicrosecondsPerSecond);
- }
-
- DCHECK(duration_td > TimeDelta());
-
- user_specified_duration_ = duration;
- duration_ = duration_td;
- host_->SetDuration(duration_);
-
- if (audio_)
- audio_->OnSetDuration(duration_);
-
- if (video_)
- video_->OnSetDuration(duration_);
-}
-
-bool ChunkDemuxer::SetTimestampOffset(const std::string& id, TimeDelta offset) {
- base::AutoLock auto_lock(lock_);
- DVLOG(1) << "SetTimestampOffset(" << id << ", " << offset.InSecondsF() << ")";
- CHECK(IsValidId(id));
-
- if (!source_info_map_[id].can_update_offset)
- return false;
-
- source_info_map_[id].timestamp_offset = offset;
- return true;
-}
-
-bool ChunkDemuxer::EndOfStream(PipelineStatus status) {
- DVLOG(1) << "EndOfStream(" << status << ")";
- base::AutoLock auto_lock(lock_);
- DCHECK_NE(state_, WAITING_FOR_INIT);
- DCHECK_NE(state_, ENDED);
-
- if (state_ == SHUTDOWN || state_ == PARSE_ERROR)
- return true;
-
- if (state_ == INITIALIZING) {
- ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
- return true;
- }
-
- if (!CanEndOfStream_Locked() && status == PIPELINE_OK)
- return false;
-
- if (audio_)
- audio_->EndOfStream();
-
- if (video_)
- video_->EndOfStream();
-
- if (status != PIPELINE_OK) {
- ReportError_Locked(status);
- } else {
- ChangeState_Locked(ENDED);
- DecreaseDurationIfNecessary();
- }
-
- return true;
-}
-
-void ChunkDemuxer::Shutdown() {
- DVLOG(1) << "Shutdown()";
- PipelineStatusCB cb;
- {
- base::AutoLock auto_lock(lock_);
-
- if (state_ == SHUTDOWN)
- return;
-
- std::swap(cb, seek_cb_);
-
- if (audio_)
- audio_->Shutdown();
-
- if (video_)
- video_->Shutdown();
-
- ChangeState_Locked(SHUTDOWN);
- }
-
- if (!cb.is_null())
- cb.Run(PIPELINE_ERROR_ABORT);
-}
-
-void ChunkDemuxer::ChangeState_Locked(State new_state) {
- lock_.AssertAcquired();
- DVLOG(1) << "ChunkDemuxer::ChangeState_Locked() : "
- << state_ << " -> " << new_state;
- state_ = new_state;
-}
-
-ChunkDemuxer::~ChunkDemuxer() {
- DCHECK_NE(state_, INITIALIZED);
- for (StreamParserMap::iterator it = stream_parser_map_.begin();
- it != stream_parser_map_.end(); ++it) {
- delete it->second;
- }
- stream_parser_map_.clear();
-}
-
-void ChunkDemuxer::ReportError_Locked(PipelineStatus error) {
- DVLOG(1) << "ReportError_Locked(" << error << ")";
- lock_.AssertAcquired();
- DCHECK_NE(error, PIPELINE_OK);
-
- ChangeState_Locked(PARSE_ERROR);
-
- PipelineStatusCB cb;
-
- if (!init_cb_.is_null()) {
- std::swap(cb, init_cb_);
- } else {
- if (!seek_cb_.is_null())
- std::swap(cb, seek_cb_);
-
- if (audio_)
- audio_->Shutdown();
-
- if (video_)
- video_->Shutdown();
- }
-
- if (!cb.is_null()) {
- base::AutoUnlock auto_unlock(lock_);
- cb.Run(error);
- return;
- }
-
- base::AutoUnlock auto_unlock(lock_);
- host_->OnDemuxerError(error);
-}
-
-bool ChunkDemuxer::IsSeekPending_Locked() const {
- lock_.AssertAcquired();
- bool seek_pending = false;
-
- if (audio_)
- seek_pending = audio_->IsSeekPending();
-
- if (!seek_pending && video_)
- seek_pending = video_->IsSeekPending();
-
- return seek_pending;
-}
-
-bool ChunkDemuxer::CanEndOfStream_Locked() const {
- lock_.AssertAcquired();
- return (!audio_ || audio_->CanEndOfStream()) &&
- (!video_ || video_->CanEndOfStream());
-}
-
-double ChunkDemuxer::GetDuration_Locked() const {
- lock_.AssertAcquired();
-
- if (duration_ == kNoTimestamp())
- return std::numeric_limits<double>::quiet_NaN();
-
- // Return positive infinity if the resource is unbounded.
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
- if (duration_ == kInfiniteDuration())
- return std::numeric_limits<double>::infinity();
-
- if (user_specified_duration_ >= 0)
- return user_specified_duration_;
-
- return duration_.InSecondsF();
-}
-
-void ChunkDemuxer::OnStreamParserInitDone(bool success, TimeDelta duration) {
- DVLOG(1) << "OnStreamParserInitDone(" << success << ", "
- << duration.InSecondsF() << ")";
- lock_.AssertAcquired();
- DCHECK_EQ(state_, INITIALIZING);
- if (!success || (!audio_ && !video_)) {
- ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
- return;
- }
-
- if (duration != base::TimeDelta() && duration_ == base::TimeDelta())
- UpdateDuration(duration);
-
- // Wait until all streams have initialized.
- if ((!source_id_audio_.empty() && !audio_) ||
- (!source_id_video_.empty() && !video_))
- return;
-
- if (audio_)
- audio_->Seek(TimeDelta());
-
- if (video_)
- video_->Seek(TimeDelta());
-
- if (duration_ == kNoTimestamp())
- duration_ = kInfiniteDuration();
-
- // The demuxer is now initialized after the |start_timestamp_| was set.
- ChangeState_Locked(INITIALIZED);
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video,
- const AudioDecoderConfig& audio_config,
- const VideoDecoderConfig& video_config) {
- DVLOG(1) << "OnNewConfigs(" << has_audio << ", " << has_video
- << ", " << audio_config.IsValidConfig()
- << ", " << video_config.IsValidConfig() << ")";
- lock_.AssertAcquired();
-
- if (!audio_config.IsValidConfig() && !video_config.IsValidConfig()) {
- DVLOG(1) << "OnNewConfigs() : Audio & video config are not valid!";
- return false;
- }
-
- // Signal an error if we get configuration info for stream types that weren't
- // specified in AddId() or more configs after a stream is initialized.
- // Only allow a single audio config for now.
- if (has_audio != audio_config.IsValidConfig()) {
- MEDIA_LOG(log_cb_)
- << "Initialization segment"
- << (audio_config.IsValidConfig() ? " has" : " does not have")
- << " an audio track, but the mimetype"
- << (has_audio ? " specifies" : " does not specify")
- << " an audio codec.";
- return false;
- }
-
- // Only allow a single video config for now.
- if (has_video != video_config.IsValidConfig()) {
- MEDIA_LOG(log_cb_)
- << "Initialization segment"
- << (video_config.IsValidConfig() ? " has" : " does not have")
- << " a video track, but the mimetype"
- << (has_video ? " specifies" : " does not specify")
- << " a video codec.";
- return false;
- }
-
- bool success = true;
- if (audio_config.IsValidConfig()) {
- if (audio_) {
- success &= audio_->UpdateAudioConfig(audio_config);
- } else {
- audio_ = new ChunkDemuxerStream(audio_config, log_cb_);
- }
- }
-
- if (video_config.IsValidConfig()) {
- if (video_) {
- success &= video_->UpdateVideoConfig(video_config);
- } else {
- video_ = new ChunkDemuxerStream(video_config, log_cb_);
- }
- }
-
- DVLOG(1) << "OnNewConfigs() : " << (success ? "success" : "failed");
- return success;
-}
-
-bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) {
- lock_.AssertAcquired();
- DCHECK_NE(state_, SHUTDOWN);
-
- if (!audio_)
- return false;
-
- CHECK(IsValidId(source_id_audio_));
- AdjustBufferTimestamps(
- buffers, source_info_map_[source_id_audio_].timestamp_offset);
-
- if (!audio_->Append(buffers))
- return false;
-
- IncreaseDurationIfNecessary(buffers, audio_);
- return true;
-}
-
-bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) {
- lock_.AssertAcquired();
- DCHECK_NE(state_, SHUTDOWN);
-
- if (!video_)
- return false;
-
- CHECK(IsValidId(source_id_video_));
- AdjustBufferTimestamps(
- buffers, source_info_map_[source_id_video_].timestamp_offset);
-
- if (!video_->Append(buffers))
- return false;
-
- IncreaseDurationIfNecessary(buffers, video_);
- return true;
-}
-
-// TODO(acolwell): Remove bool from StreamParser::NeedKeyCB so that
-// this method can be removed and need_key_cb_ can be passed directly
-// to the parser.
-bool ChunkDemuxer::OnNeedKey(const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size) {
- lock_.AssertAcquired();
- need_key_cb_.Run(type, init_data.Pass(), init_data_size);
- return true;
-}
-
-void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
- TimeDelta timestamp) {
- DCHECK(timestamp != kNoTimestamp());
- DVLOG(2) << "OnNewMediaSegment(" << source_id << ", "
- << timestamp.InSecondsF() << ")";
- lock_.AssertAcquired();
-
- CHECK(IsValidId(source_id));
- source_info_map_[source_id].can_update_offset = false;
- base::TimeDelta start_timestamp =
- timestamp + source_info_map_[source_id].timestamp_offset;
-
- if (audio_ && source_id == source_id_audio_)
- audio_->OnNewMediaSegment(start_timestamp);
- if (video_ && source_id == source_id_video_)
- video_->OnNewMediaSegment(start_timestamp);
-}
-
-void ChunkDemuxer::OnEndOfMediaSegment(const std::string& source_id) {
- DVLOG(2) << "OnEndOfMediaSegment(" << source_id << ")";
- CHECK(IsValidId(source_id));
- source_info_map_[source_id].can_update_offset = true;
-}
-
-void ChunkDemuxer::AdjustBufferTimestamps(
- const StreamParser::BufferQueue& buffers,
- base::TimeDelta timestamp_offset) {
- if (timestamp_offset == base::TimeDelta())
- return;
-
- for (StreamParser::BufferQueue::const_iterator itr = buffers.begin();
- itr != buffers.end(); ++itr) {
- (*itr)->SetDecodeTimestamp(
- (*itr)->GetDecodeTimestamp() + timestamp_offset);
- (*itr)->SetTimestamp((*itr)->GetTimestamp() + timestamp_offset);
- }
-}
-
-bool ChunkDemuxer::IsValidId(const std::string& source_id) const {
- lock_.AssertAcquired();
- return source_info_map_.count(source_id) > 0u &&
- stream_parser_map_.count(source_id) > 0u;
-}
-
-void ChunkDemuxer::UpdateDuration(base::TimeDelta new_duration) {
- DCHECK(duration_ != new_duration);
- user_specified_duration_ = -1;
- duration_ = new_duration;
- host_->SetDuration(new_duration);
-}
-
-void ChunkDemuxer::IncreaseDurationIfNecessary(
- const StreamParser::BufferQueue& buffers,
- const scoped_refptr<ChunkDemuxerStream>& stream) {
- DCHECK(!buffers.empty());
- if (buffers.back()->GetTimestamp() <= duration_)
- return;
-
- Ranges<TimeDelta> ranges = stream->GetBufferedRanges(kInfiniteDuration());
- DCHECK_GT(ranges.size(), 0u);
-
- base::TimeDelta last_timestamp_buffered = ranges.end(ranges.size() - 1);
- if (last_timestamp_buffered > duration_)
- UpdateDuration(last_timestamp_buffered);
-}
-
-void ChunkDemuxer::DecreaseDurationIfNecessary() {
- Ranges<TimeDelta> ranges = GetBufferedRanges();
- if (ranges.size() == 0u)
- return;
-
- base::TimeDelta last_timestamp_buffered = ranges.end(ranges.size() - 1);
- if (last_timestamp_buffered < duration_)
- UpdateDuration(last_timestamp_buffered);
-}
-
-Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges() const {
- if (audio_ && !video_)
- return audio_->GetBufferedRanges(duration_);
- else if (!audio_ && video_)
- return video_->GetBufferedRanges(duration_);
- return ComputeIntersection();
-}
-
-} // namespace media
diff --git a/src/media/filters/chunk_demuxer.h b/src/media/filters/chunk_demuxer.h
deleted file mode 100644
index 6e09181..0000000
--- a/src/media/filters/chunk_demuxer.h
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_CHUNK_DEMUXER_H_
-#define MEDIA_FILTERS_CHUNK_DEMUXER_H_
-
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/synchronization/lock.h"
-#include "media/base/byte_queue.h"
-#include "media/base/demuxer.h"
-#include "media/base/ranges.h"
-#include "media/base/stream_parser.h"
-#include "media/filters/source_buffer_stream.h"
-
-namespace media {
-
-class ChunkDemuxerStream;
-class FFmpegURLProtocol;
-
-// Demuxer implementation that allows chunks of media data to be passed
-// from JavaScript to the media stack.
-class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
- public:
- enum Status {
- kOk, // ID added w/o error.
- kNotSupported, // Type specified is not supported.
- kReachedIdLimit, // Reached ID limit. We can't handle any more IDs.
- };
-
- typedef base::Callback<void(const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size)> NeedKeyCB;
-
- // |open_cb| Run when Initialize() is called to signal that the demuxer
- // is ready to receive media data via AppenData().
- // |need_key_cb| Run when the demuxer determines that an encryption key is
- // needed to decrypt the content.
- // |log_cb| Run when parsing error messages need to be logged to the error
- // console.
- ChunkDemuxer(const base::Closure& open_cb, const NeedKeyCB& need_key_cb,
- const LogCB& log_cb);
-
- // Demuxer implementation.
- virtual void Initialize(DemuxerHost* host,
- const PipelineStatusCB& cb) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE;
- virtual void OnAudioRendererDisabled() OVERRIDE;
- virtual scoped_refptr<DemuxerStream> GetStream(
- DemuxerStream::Type type) OVERRIDE;
- virtual base::TimeDelta GetStartTime() const OVERRIDE;
-
- // Methods used by an external object to control this demuxer.
- void StartWaitingForSeek();
- void CancelPendingSeek();
-
- // Registers a new |id| to use for AppendData() calls. |type| indicates
- // the MIME type for the data that we intend to append for this ID.
- // kOk is returned if the demuxer has enough resources to support another ID
- // and supports the format indicated by |type|.
- // kNotSupported is returned if |type| is not a supported format.
- // kReachedIdLimit is returned if the demuxer cannot handle another ID right
- // now.
- Status AddId(const std::string& id, const std::string& type,
- std::vector<std::string>& codecs);
-
- // Removed an ID & associated resources that were previously added with
- // AddId().
- void RemoveId(const std::string& id);
-
- // Gets the currently buffered ranges for the specified ID.
- Ranges<base::TimeDelta> GetBufferedRanges(const std::string& id) const;
-
- // Appends media data to the source buffer associated with |id|. Returns
- // false if this method is called in an invalid state.
- bool AppendData(const std::string& id, const uint8* data, size_t length);
-
- // Aborts parsing the current segment and reset the parser to a state where
- // it can accept a new segment.
- void Abort(const std::string& id);
-
- // Get the duration of the media. It can be an inferred duration set by the
- // class or can be a duration explicitly set by script.
- double GetDuration() const;
-
- // Notifies the demuxer that the duration of the media has changed to
- // |duration|.
- void SetDuration(double duration);
-
- // Sets a time |offset| to be applied to subsequent buffers appended to the
- // source buffer assicated with |id|. Returns true if the offset is set
- // properly, false if the offset cannot be applied because we're in the
- // middle of parsing a media segment.
- bool SetTimestampOffset(const std::string& id, base::TimeDelta offset);
-
- // Signals an EndOfStream request.
- // Returns false if called in an unexpected state or if there is a gap between
- // the current position and the end of the buffered data.
- bool EndOfStream(PipelineStatus status);
- void Shutdown();
-
- protected:
- virtual ~ChunkDemuxer();
-
- private:
- enum State {
- WAITING_FOR_INIT,
- INITIALIZING,
- INITIALIZED,
- ENDED,
- PARSE_ERROR,
- SHUTDOWN,
- };
-
- void ChangeState_Locked(State new_state);
-
- // Reports an error and puts the demuxer in a state where it won't accept more
- // data.
- void ReportError_Locked(PipelineStatus error);
-
- // Returns true if any stream has seeked to a time without buffered data.
- bool IsSeekPending_Locked() const;
-
- // Returns true if all streams can successfully call EndOfStream,
- // false if any can not.
- bool CanEndOfStream_Locked() const;
-
- double GetDuration_Locked() const;
-
- // StreamParser callbacks.
- void OnStreamParserInitDone(bool success, base::TimeDelta duration);
- bool OnNewConfigs(bool has_audio, bool has_video,
- const AudioDecoderConfig& audio_config,
- const VideoDecoderConfig& video_config);
- bool OnAudioBuffers(const StreamParser::BufferQueue& buffers);
- bool OnVideoBuffers(const StreamParser::BufferQueue& buffers);
- bool OnNeedKey(const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size);
- void OnNewMediaSegment(const std::string& source_id,
- base::TimeDelta start_timestamp);
- void OnEndOfMediaSegment(const std::string& source_id);
-
- // Computes the intersection between the video & audio
- // buffered ranges.
- Ranges<base::TimeDelta> ComputeIntersection() const;
-
- // Applies |time_offset| to the timestamps of |buffers|.
- void AdjustBufferTimestamps(const StreamParser::BufferQueue& buffers,
- base::TimeDelta timestamp_offset);
-
- // Returns true if |source_id| is valid, false otherwise.
- bool IsValidId(const std::string& source_id) const;
-
- // Increases |duration_| if the newly appended |buffers| exceed the current
- // |duration_|. The |duration_| is set to the end buffered timestamp of
- // |stream|.
- void IncreaseDurationIfNecessary(
- const StreamParser::BufferQueue& buffers,
- const scoped_refptr<ChunkDemuxerStream>& stream);
-
- // Decreases |duration_| if the buffered region is less than |duration_| when
- // EndOfStream() is called.
- void DecreaseDurationIfNecessary();
-
- // Sets |duration_| to |new_duration| and notifies |host_|.
- void UpdateDuration(base::TimeDelta new_duration);
-
- // Returns the ranges representing the buffered data in the demuxer.
- Ranges<base::TimeDelta> GetBufferedRanges() const;
-
- mutable base::Lock lock_;
- State state_;
-
- DemuxerHost* host_;
- base::Closure open_cb_;
- NeedKeyCB need_key_cb_;
- // Callback used to report error strings that can help the web developer
- // figure out what is wrong with the content.
- LogCB log_cb_;
-
- PipelineStatusCB init_cb_;
- PipelineStatusCB seek_cb_;
- bool delayed_audio_seek_;
-
- scoped_refptr<ChunkDemuxerStream> audio_;
- scoped_refptr<ChunkDemuxerStream> video_;
-
- base::TimeDelta duration_;
-
- // The duration passed to the last SetDuration(). If
- // SetDuration() is never called or an AppendData() call or
- // a EndOfStream() call changes |duration_|, then this
- // variable is set to < 0 to indicate that the |duration_| represents
- // the actual duration instead of a user specified value.
- double user_specified_duration_;
-
- typedef std::map<std::string, StreamParser*> StreamParserMap;
- StreamParserMap stream_parser_map_;
-
- // Contains state belonging to a source id.
- struct SourceInfo {
- base::TimeDelta timestamp_offset;
- bool can_update_offset;
- };
- typedef std::map<std::string, SourceInfo> SourceInfoMap;
- SourceInfoMap source_info_map_;
-
- // Used to ensure that (1) config data matches the type and codec provided in
- // AddId(), (2) only 1 audio and 1 video sources are added, and (3) ids may be
- // removed with RemoveID() but can not be re-added (yet).
- std::string source_id_audio_;
- std::string source_id_video_;
-
- DISALLOW_COPY_AND_ASSIGN(ChunkDemuxer);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_CHUNK_DEMUXER_H_
diff --git a/src/media/filters/chunk_demuxer_unittest.cc b/src/media/filters/chunk_demuxer_unittest.cc
deleted file mode 100644
index 6dfe7a8..0000000
--- a/src/media/filters/chunk_demuxer_unittest.cc
+++ /dev/null
@@ -1,2506 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/message_loop.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/mock_demuxer_host.h"
-#include "media/base/test_data_util.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/chunk_demuxer.h"
-#include "media/webm/cluster_builder.h"
-#include "media/webm/webm_constants.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::AnyNumber;
-using ::testing::Exactly;
-using ::testing::InSequence;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::SaveArg;
-using ::testing::SetArgumentPointee;
-using ::testing::_;
-
-namespace media {
-
-static const uint8 kTracksHeader[] = {
- 0x16, 0x54, 0xAE, 0x6B, // Tracks ID
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0)
-};
-
-static const int kTracksHeaderSize = sizeof(kTracksHeader);
-static const int kTracksSizeOffset = 4;
-
-// The size of TrackEntry element in test file "webm_vorbis_track_entry" starts
-// at index 1 and spans 8 bytes.
-static const int kAudioTrackSizeOffset = 1;
-static const int kAudioTrackSizeWidth = 8;
-static const int kAudioTrackEntryHeaderSize = kAudioTrackSizeOffset +
- kAudioTrackSizeWidth;
-
-// The size of TrackEntry element in test file "webm_vp8_track_entry" starts at
-// index 1 and spans 8 bytes.
-static const int kVideoTrackSizeOffset = 1;
-static const int kVideoTrackSizeWidth = 8;
-static const int kVideoTrackEntryHeaderSize = kVideoTrackSizeOffset +
- kVideoTrackSizeWidth;
-
-static const int kVideoTrackNum = 1;
-static const int kAudioTrackNum = 2;
-
-static const int kAudioBlockDuration = 23;
-static const int kVideoBlockDuration = 33;
-
-static const char* kSourceId = "SourceId";
-static const char* kDefaultFirstClusterRange = "{ [0,46) }";
-static const int kDefaultFirstClusterEndTimestamp = 66;
-static const int kDefaultSecondClusterEndTimestamp = 132;
-
-static const char kWebMInitDataType[] = "video/webm";
-
-base::TimeDelta kDefaultDuration() {
- return base::TimeDelta::FromMilliseconds(201224);
-}
-
-// Write an integer into buffer in the form of vint that spans 8 bytes.
-// The data pointed by |buffer| should be at least 8 bytes long.
-// |number| should be in the range 0 <= number < 0x00FFFFFFFFFFFFFF.
-static void WriteInt64(uint8* buffer, int64 number) {
- DCHECK(number >= 0 && number < GG_LONGLONG(0x00FFFFFFFFFFFFFF));
- buffer[0] = 0x01;
- int64 tmp = number;
- for (int i = 7; i > 0; i--) {
- buffer[i] = tmp & 0xff;
- tmp >>= 8;
- }
-}
-
-MATCHER_P(HasTimestamp, timestamp_in_ms, "") {
- return arg && !arg->IsEndOfStream() &&
- arg->GetTimestamp().InMilliseconds() == timestamp_in_ms;
-}
-
-MATCHER(IsEndOfStream, "") {
- return arg && arg->IsEndOfStream();
-}
-
-static void OnReadDone(const base::TimeDelta& expected_time,
- bool* called,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- EXPECT_EQ(status, DemuxerStream::kOk);
- EXPECT_EQ(expected_time, buffer->GetTimestamp());
- *called = true;
-}
-
-static void OnReadDone_AbortExpected(
- bool* called, DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- EXPECT_EQ(status, DemuxerStream::kAborted);
- EXPECT_EQ(NULL, buffer.get());
- *called = true;
-}
-
-static void OnReadDone_EOSExpected(bool* called,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- EXPECT_EQ(status, DemuxerStream::kOk);
- EXPECT_TRUE(buffer->IsEndOfStream());
- *called = true;
-}
-
-static void StoreStatusAndBuffer(DemuxerStream::Status* status_out,
- scoped_refptr<DecoderBuffer>* buffer_out,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- *status_out = status;
- *buffer_out = buffer;
-}
-
-static void ReadUntilNotOkOrEndOfStream(
- const scoped_refptr<DemuxerStream>& stream,
- DemuxerStream::Status* status,
- base::TimeDelta* last_timestamp) {
- scoped_refptr<DecoderBuffer> buffer;
-
- *last_timestamp = kNoTimestamp();
- do {
- stream->Read(base::Bind(&StoreStatusAndBuffer, status, &buffer));
- if (*status == DemuxerStream::kOk && !buffer->IsEndOfStream())
- *last_timestamp = buffer->GetTimestamp();
- } while (*status == DemuxerStream::kOk && !buffer->IsEndOfStream());
-}
-
-class ChunkDemuxerTest : public testing::Test {
- protected:
- enum CodecsIndex {
- AUDIO,
- VIDEO,
- MAX_CODECS_INDEX
- };
-
- // Default cluster to append first for simple tests.
- scoped_ptr<Cluster> kDefaultFirstCluster() {
- return GenerateCluster(0, 4);
- }
-
- // Default cluster to append after kDefaultFirstCluster()
- // has been appended. This cluster starts with blocks that
- // have timestamps consistent with the end times of the blocks
- // in kDefaultFirstCluster() so that these two clusters represent
- // a continuous region.
- scoped_ptr<Cluster> kDefaultSecondCluster() {
- return GenerateCluster(46, 66, 5);
- }
-
- ChunkDemuxerTest() {
- CreateNewDemuxer();
- }
-
- void CreateNewDemuxer() {
- base::Closure open_cb =
- base::Bind(&ChunkDemuxerTest::DemuxerOpened, base::Unretained(this));
- ChunkDemuxer::NeedKeyCB need_key_cb =
- base::Bind(&ChunkDemuxerTest::DemuxerNeedKey, base::Unretained(this));
- demuxer_ = new ChunkDemuxer(open_cb, need_key_cb, LogCB());
- }
-
- virtual ~ChunkDemuxerTest() {
- ShutdownDemuxer();
- }
-
- void CreateInitSegment(bool has_audio, bool has_video,
- bool is_audio_encrypted, bool is_video_encrypted,
- scoped_array<uint8>* buffer,
- int* size) {
- scoped_refptr<DecoderBuffer> ebml_header;
- scoped_refptr<DecoderBuffer> info;
- scoped_refptr<DecoderBuffer> audio_track_entry;
- scoped_refptr<DecoderBuffer> video_track_entry;
- scoped_refptr<DecoderBuffer> audio_content_encodings;
- scoped_refptr<DecoderBuffer> video_content_encodings;
-
- ebml_header = ReadTestDataFile("webm_ebml_element");
-
- info = ReadTestDataFile("webm_info_element");
-
- int tracks_element_size = 0;
-
- if (has_audio) {
- audio_track_entry = ReadTestDataFile("webm_vorbis_track_entry");
- tracks_element_size += audio_track_entry->GetDataSize();
- if (is_audio_encrypted) {
- audio_content_encodings = ReadTestDataFile("webm_content_encodings");
- tracks_element_size += audio_content_encodings->GetDataSize();
- }
- }
-
- if (has_video) {
- video_track_entry = ReadTestDataFile("webm_vp8_track_entry");
- tracks_element_size += video_track_entry->GetDataSize();
- if (is_video_encrypted) {
- video_content_encodings = ReadTestDataFile("webm_content_encodings");
- tracks_element_size += video_content_encodings->GetDataSize();
- }
- }
-
- *size = ebml_header->GetDataSize() + info->GetDataSize() +
- kTracksHeaderSize + tracks_element_size;
-
- buffer->reset(new uint8[*size]);
-
- uint8* buf = buffer->get();
- memcpy(buf, ebml_header->GetData(), ebml_header->GetDataSize());
- buf += ebml_header->GetDataSize();
-
- memcpy(buf, info->GetData(), info->GetDataSize());
- buf += info->GetDataSize();
-
- memcpy(buf, kTracksHeader, kTracksHeaderSize);
- WriteInt64(buf + kTracksSizeOffset, tracks_element_size);
- buf += kTracksHeaderSize;
-
- // TODO(xhwang): Simplify this! Probably have test data files that contain
- // ContentEncodings directly instead of trying to create one at run-time.
- if (has_audio) {
- memcpy(buf, audio_track_entry->GetData(),
- audio_track_entry->GetDataSize());
- if (is_audio_encrypted) {
- memcpy(buf + audio_track_entry->GetDataSize(),
- audio_content_encodings->GetData(),
- audio_content_encodings->GetDataSize());
- WriteInt64(buf + kAudioTrackSizeOffset,
- audio_track_entry->GetDataSize() +
- audio_content_encodings->GetDataSize() -
- kAudioTrackEntryHeaderSize);
- buf += audio_content_encodings->GetDataSize();
- }
- buf += audio_track_entry->GetDataSize();
- }
-
- if (has_video) {
- memcpy(buf, video_track_entry->GetData(),
- video_track_entry->GetDataSize());
- if (is_video_encrypted) {
- memcpy(buf + video_track_entry->GetDataSize(),
- video_content_encodings->GetData(),
- video_content_encodings->GetDataSize());
- WriteInt64(buf + kVideoTrackSizeOffset,
- video_track_entry->GetDataSize() +
- video_content_encodings->GetDataSize() -
- kVideoTrackEntryHeaderSize);
- buf += video_content_encodings->GetDataSize();
- }
- buf += video_track_entry->GetDataSize();
- }
- }
-
- ChunkDemuxer::Status AddId() {
- return AddId(kSourceId, true, true);
- }
-
- ChunkDemuxer::Status AddId(const std::string& source_id,
- bool has_audio, bool has_video) {
- std::vector<std::string> codecs;
- std::string type;
-
- if (has_audio) {
- codecs.push_back("vorbis");
- type = "audio/webm";
- }
-
- if (has_video) {
- codecs.push_back("vp8");
- type = "video/webm";
- }
-
- if (!has_audio && !has_video) {
- return AddId(kSourceId, true, true);
- }
-
- return demuxer_->AddId(source_id, type, codecs);
- }
-
- bool AppendData(const uint8* data, size_t length) {
- return AppendData(kSourceId, data, length);
- }
-
- bool AppendCluster(int timecode, int block_count) {
- scoped_ptr<Cluster> cluster(GenerateCluster(timecode, block_count));
- return AppendData(kSourceId, cluster->data(), cluster->size());
- }
-
- bool AppendData(const std::string& source_id,
- const uint8* data, size_t length) {
- EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
- return demuxer_->AppendData(source_id, data, length);
- }
-
- bool AppendDataInPieces(const uint8* data, size_t length) {
- return AppendDataInPieces(data, length, 7);
- }
-
- bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
- const uint8* start = data;
- const uint8* end = data + length;
- while (start < end) {
- size_t append_size = std::min(piece_size,
- static_cast<size_t>(end - start));
- if (!AppendData(start, append_size))
- return false;
- start += append_size;
- }
- return true;
- }
-
- bool AppendInitSegment(bool has_audio, bool has_video) {
- return AppendInitSegmentWithSourceId(kSourceId, has_audio, has_video);
- }
-
- bool AppendInitSegmentWithSourceId(const std::string& source_id,
- bool has_audio, bool has_video) {
- return AppendInitSegmentWithEncryptedInfo(
- source_id, has_audio, has_video, false, false);
- }
-
- bool AppendInitSegmentWithEncryptedInfo(const std::string& source_id,
- bool has_audio, bool has_video,
- bool is_audio_encrypted,
- bool is_video_encrypted) {
- scoped_array<uint8> info_tracks;
- int info_tracks_size = 0;
- CreateInitSegment(has_audio, has_video,
- is_audio_encrypted, is_video_encrypted,
- &info_tracks, &info_tracks_size);
- return AppendData(source_id, info_tracks.get(), info_tracks_size);
- }
-
- bool AppendGarbage() {
- // Fill up an array with gibberish.
- int garbage_cluster_size = 10;
- scoped_array<uint8> garbage_cluster(new uint8[garbage_cluster_size]);
- for (int i = 0; i < garbage_cluster_size; ++i)
- garbage_cluster[i] = i;
- return AppendData(garbage_cluster.get(), garbage_cluster_size);
- }
-
- void InitDoneCalled(PipelineStatus expected_status,
- PipelineStatus status) {
- EXPECT_EQ(status, expected_status);
- }
-
- bool AppendEmptyCluster(int timecode) {
- scoped_ptr<Cluster> empty_cluster = GenerateEmptyCluster(timecode);
- return AppendData(empty_cluster->data(), empty_cluster->size());
- }
-
- PipelineStatusCB CreateInitDoneCB(const base::TimeDelta& expected_duration,
- PipelineStatus expected_status) {
- if (expected_duration != kNoTimestamp())
- EXPECT_CALL(host_, SetDuration(expected_duration));
- return CreateInitDoneCB(expected_status);
- }
-
- PipelineStatusCB CreateInitDoneCB(PipelineStatus expected_status) {
- return base::Bind(&ChunkDemuxerTest::InitDoneCalled,
- base::Unretained(this),
- expected_status);
- }
-
- bool InitDemuxer(bool has_audio, bool has_video) {
- return InitDemuxerWithEncryptionInfo(has_audio, has_video, false, false);
- }
-
- bool InitDemuxerWithEncryptionInfo(
- bool has_audio, bool has_video,
- bool is_audio_encrypted, bool is_video_encrypted) {
- PipelineStatus expected_status =
- (has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN;
-
- base::TimeDelta expected_duration = kNoTimestamp();
- if (expected_status == PIPELINE_OK)
- expected_duration = kDefaultDuration();
-
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(expected_duration, expected_status));
-
- if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk)
- return false;
-
- return AppendInitSegmentWithEncryptedInfo(
- kSourceId, has_audio, has_video,
- is_audio_encrypted, is_video_encrypted);
- }
-
- bool InitDemuxerAudioAndVideoSources(const std::string& audio_id,
- const std::string& video_id) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
-
- if (AddId(audio_id, true, false) != ChunkDemuxer::kOk)
- return false;
- if (AddId(video_id, false, true) != ChunkDemuxer::kOk)
- return false;
-
- bool success = AppendInitSegmentWithSourceId(audio_id, true, false);
- success &= AppendInitSegmentWithSourceId(video_id, false, true);
- return success;
- }
-
- // Initializes the demuxer with data from 2 files with different
- // decoder configurations. This is used to test the decoder config change
- // logic.
- //
- // bear-320x240.webm VideoDecoderConfig returns 320x240 for its natural_size()
- // bear-640x360.webm VideoDecoderConfig returns 640x360 for its natural_size()
- // The resulting video stream returns data from each file for the following
- // time ranges.
- // bear-320x240.webm : [0-501) [801-2737)
- // bear-640x360.webm : [527-793)
- //
- // bear-320x240.webm AudioDecoderConfig returns 3863 for its extra_data_size()
- // bear-640x360.webm AudioDecoderConfig returns 3935 for its extra_data_size()
- // The resulting audio stream returns data from each file for the following
- // time ranges.
- // bear-320x240.webm : [0-524) [779-2737)
- // bear-640x360.webm : [527-759)
- bool InitDemuxerWithConfigChangeData() {
- scoped_refptr<DecoderBuffer> bear1 = ReadTestDataFile("bear-320x240.webm");
- scoped_refptr<DecoderBuffer> bear2 = ReadTestDataFile("bear-640x360.webm");
-
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(base::TimeDelta::FromMilliseconds(2744),
- PIPELINE_OK));
-
- if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk)
- return false;
-
- // Append the whole bear1 file.
- if (!AppendData(bear1->GetData(), bear1->GetDataSize()))
- return false;
- CheckExpectedRanges(kSourceId, "{ [0,2737) }");
-
- // Append initialization segment for bear2.
- // Note: Offsets here and below are derived from
- // media/test/data/bear-640x360-manifest.js and
- // media/test/data/bear-320x240-manifest.js which were
- // generated from media/test/data/bear-640x360.webm and
- // media/test/data/bear-320x240.webm respectively.
- if (!AppendData(bear2->GetData(), 4340))
- return false;
-
- // Append a media segment that goes from [0.527000, 1.014000).
- if (!AppendData(bear2->GetData() + 55290, 18785))
- return false;
- CheckExpectedRanges(kSourceId, "{ [0,1028) [1201,2737) }");
-
- // Append initialization segment for bear1 & fill gap with [779-1197)
- // segment.
- if (!AppendData(bear1->GetData(), 4370) ||
- !AppendData(bear1->GetData() + 72737, 28183)) {
- return false;
- }
- CheckExpectedRanges(kSourceId, "{ [0,2737) }");
-
- return demuxer_->EndOfStream(PIPELINE_OK);
- }
-
- void ShutdownDemuxer() {
- if (demuxer_)
- demuxer_->Shutdown();
- }
-
- void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) {
- uint8 data[] = { 0x00 };
- cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data));
- }
-
- scoped_ptr<Cluster> GenerateCluster(int timecode, int block_count) {
- return GenerateCluster(timecode, timecode, block_count);
- }
-
- scoped_ptr<Cluster> GenerateCluster(int first_audio_timecode,
- int first_video_timecode,
- int block_count) {
- CHECK_GT(block_count, 0);
-
- int size = 10;
- scoped_array<uint8> data(new uint8[size]);
-
- ClusterBuilder cb;
- cb.SetClusterTimecode(std::min(first_audio_timecode, first_video_timecode));
-
- if (block_count == 1) {
- cb.AddBlockGroup(kAudioTrackNum, first_audio_timecode,
- kAudioBlockDuration, kWebMFlagKeyframe,
- data.get(), size);
- return cb.Finish();
- }
-
- int audio_timecode = first_audio_timecode;
- int video_timecode = first_video_timecode;
-
- // Create simple blocks for everything except the last 2 blocks.
- // The first video frame must be a keyframe.
- uint8 video_flag = kWebMFlagKeyframe;
- for (int i = 0; i < block_count - 2; i++) {
- if (audio_timecode <= video_timecode) {
- cb.AddSimpleBlock(kAudioTrackNum, audio_timecode, kWebMFlagKeyframe,
- data.get(), size);
- audio_timecode += kAudioBlockDuration;
- continue;
- }
-
- cb.AddSimpleBlock(kVideoTrackNum, video_timecode, video_flag, data.get(),
- size);
- video_timecode += kVideoBlockDuration;
- video_flag = 0;
- }
-
- // Make the last 2 blocks BlockGroups so that they don't get delayed by the
- // block duration calculation logic.
- if (audio_timecode <= video_timecode) {
- cb.AddBlockGroup(kAudioTrackNum, audio_timecode, kAudioBlockDuration,
- kWebMFlagKeyframe, data.get(), size);
- cb.AddBlockGroup(kVideoTrackNum, video_timecode, kVideoBlockDuration,
- video_flag, data.get(), size);
- } else {
- cb.AddBlockGroup(kVideoTrackNum, video_timecode, kVideoBlockDuration,
- video_flag, data.get(), size);
- cb.AddBlockGroup(kAudioTrackNum, audio_timecode, kAudioBlockDuration,
- kWebMFlagKeyframe, data.get(), size);
- }
-
- return cb.Finish();
- }
-
- scoped_ptr<Cluster> GenerateSingleStreamCluster(int timecode,
- int end_timecode,
- int track_number,
- int block_duration) {
- CHECK_GT(end_timecode, timecode);
-
- int size = 10;
- scoped_array<uint8> data(new uint8[size]);
-
- ClusterBuilder cb;
- cb.SetClusterTimecode(timecode);
-
- // Create simple blocks for everything except the last block.
- for (int i = 0; timecode < (end_timecode - block_duration); i++) {
- cb.AddSimpleBlock(track_number, timecode, kWebMFlagKeyframe,
- data.get(), size);
- timecode += block_duration;
- }
-
- // Make the last block a BlockGroup so that it doesn't get delayed by the
- // block duration calculation logic.
- cb.AddBlockGroup(track_number, timecode, block_duration,
- kWebMFlagKeyframe, data.get(), size);
- return cb.Finish();
- }
-
- void GenerateExpectedReads(int timecode, int block_count,
- DemuxerStream* audio,
- DemuxerStream* video) {
- GenerateExpectedReads(timecode, timecode, block_count, audio, video);
- }
-
- void GenerateExpectedReads(int start_audio_timecode,
- int start_video_timecode,
- int block_count, DemuxerStream* audio,
- DemuxerStream* video) {
- CHECK_GT(block_count, 0);
-
- if (block_count == 1) {
- ExpectRead(audio, start_audio_timecode);
- return;
- }
-
- int audio_timecode = start_audio_timecode;
- int video_timecode = start_video_timecode;
-
- for (int i = 0; i < block_count; i++) {
- if (audio_timecode <= video_timecode) {
- ExpectRead(audio, audio_timecode);
- audio_timecode += kAudioBlockDuration;
- continue;
- }
-
- ExpectRead(video, video_timecode);
- video_timecode += kVideoBlockDuration;
- }
- }
-
- void GenerateSingleStreamExpectedReads(
- int timecode, int block_count, DemuxerStream* stream,
- int block_duration) {
- CHECK_GT(block_count, 0);
- int stream_timecode = timecode;
-
- for (int i = 0; i < block_count; i++) {
- ExpectRead(stream, stream_timecode);
- stream_timecode += block_duration;
- }
- }
-
- scoped_ptr<Cluster> GenerateEmptyCluster(int timecode) {
- ClusterBuilder cb;
- cb.SetClusterTimecode(timecode);
- return cb.Finish();
- }
-
- void CheckExpectedRanges(const std::string& expected) {
- CheckExpectedRanges(kSourceId, expected);
- }
-
- void CheckExpectedRanges(const std::string& id,
- const std::string& expected) {
- Ranges<base::TimeDelta> r = demuxer_->GetBufferedRanges(id);
-
- std::stringstream ss;
- ss << "{ ";
- for (size_t i = 0; i < r.size(); ++i) {
- ss << "[" << r.start(i).InMilliseconds() << ","
- << r.end(i).InMilliseconds() << ") ";
- }
- ss << "}";
- EXPECT_EQ(ss.str(), expected);
- }
-
- MOCK_METHOD2(ReadDone, void(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>&));
-
- void ExpectEndOfStream(DemuxerStream* stream) {
- EXPECT_CALL(*this, ReadDone(DemuxerStream::kOk, IsEndOfStream()));
- stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
- base::Unretained(this)));
- }
-
- void ExpectRead(DemuxerStream* stream, int64 timestamp_in_ms) {
- EXPECT_CALL(*this, ReadDone(DemuxerStream::kOk,
- HasTimestamp(timestamp_in_ms)));
- stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
- base::Unretained(this)));
- }
-
- void ExpectConfigChanged(DemuxerStream* stream) {
- EXPECT_CALL(*this, ReadDone(DemuxerStream::kConfigChanged, _));
- stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
- base::Unretained(this)));
- }
-
- MOCK_METHOD1(Checkpoint, void(int id));
-
- struct BufferTimestamps {
- int video_time_ms;
- int audio_time_ms;
- };
- static const int kSkip = -1;
-
- // Test parsing a WebM file.
- // |filename| - The name of the file in media/test/data to parse.
- // |timestamps| - The expected timestamps on the parsed buffers.
- // a timestamp of kSkip indicates that a Read() call for that stream
- // shouldn't be made on that iteration of the loop. If both streams have
- // a kSkip then the loop will terminate.
- bool ParseWebMFile(const std::string& filename,
- const BufferTimestamps* timestamps,
- const base::TimeDelta& duration) {
- return ParseWebMFile(filename, timestamps, duration, true, true);
- }
-
- bool ParseWebMFile(const std::string& filename,
- const BufferTimestamps* timestamps,
- const base::TimeDelta& duration,
- bool has_audio, bool has_video) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(duration, PIPELINE_OK));
-
- if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk)
- return false;
-
- // Read a WebM file into memory and send the data to the demuxer.
- scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
- if (!AppendDataInPieces(buffer->GetData(), buffer->GetDataSize(), 512))
- return false;
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Verify that the timestamps on the first few packets match what we
- // expect.
- for (size_t i = 0;
- (timestamps[i].audio_time_ms != kSkip ||
- timestamps[i].video_time_ms != kSkip);
- i++) {
- bool audio_read_done = false;
- bool video_read_done = false;
-
- if (timestamps[i].audio_time_ms != kSkip) {
- DCHECK(audio);
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(
- timestamps[i].audio_time_ms),
- &audio_read_done));
- EXPECT_TRUE(audio_read_done);
- }
-
- if (timestamps[i].video_time_ms != kSkip) {
- DCHECK(video);
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(
- timestamps[i].video_time_ms),
- &video_read_done));
-
- EXPECT_TRUE(video_read_done);
- }
- }
-
- return true;
- }
-
- MOCK_METHOD0(DemuxerOpened, void());
- // TODO(xhwang): This is a workaround of the issue that move-only parameters
- // are not supported in mocked methods. Remove this when the issue is fixed
- // (http://code.google.com/p/googletest/issues/detail?id=395) or when we use
- // std::string instead of scoped_array<uint8> (http://crbug.com/130689).
- MOCK_METHOD3(NeedKeyMock, void(const std::string& type,
- const uint8* init_data, int init_data_size));
- void DemuxerNeedKey(const std::string& type,
- scoped_array<uint8> init_data, int init_data_size) {
- NeedKeyMock(type, init_data.get(), init_data_size);
- }
-
- MessageLoop message_loop_;
- MockDemuxerHost host_;
-
- scoped_refptr<ChunkDemuxer> demuxer_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest);
-};
-
-TEST_F(ChunkDemuxerTest, TestInit) {
- // Test no streams, audio-only, video-only, and audio & video scenarios.
- // Audio and video streams can be encrypted or not encrypted.
- for (int i = 0; i < 16; i++) {
- bool has_audio = (i & 0x1) != 0;
- bool has_video = (i & 0x2) != 0;
- bool is_audio_encrypted = (i & 0x4) != 0;
- bool is_video_encrypted = (i & 0x8) != 0;
-
- // No test on invalid combination.
- if ((!has_audio && is_audio_encrypted) ||
- (!has_video && is_video_encrypted)) {
- continue;
- }
-
- CreateNewDemuxer();
-
- if (is_audio_encrypted || is_video_encrypted) {
- int need_key_count = (is_audio_encrypted ? 1 : 0) +
- (is_video_encrypted ? 1 : 0);
- EXPECT_CALL(*this, NeedKeyMock(kWebMInitDataType, NotNull(), 16))
- .Times(Exactly(need_key_count));
- }
-
- ASSERT_TRUE(InitDemuxerWithEncryptionInfo(
- has_audio, has_video, is_audio_encrypted, is_video_encrypted));
-
- scoped_refptr<DemuxerStream> audio_stream =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- if (has_audio) {
- ASSERT_TRUE(audio_stream);
-
- const AudioDecoderConfig& config = audio_stream->audio_decoder_config();
- EXPECT_EQ(kCodecVorbis, config.codec());
- EXPECT_EQ(16, config.bits_per_channel());
- EXPECT_EQ(CHANNEL_LAYOUT_STEREO, config.channel_layout());
- EXPECT_EQ(44100, config.samples_per_second());
- EXPECT_TRUE(config.extra_data());
- EXPECT_GT(config.extra_data_size(), 0u);
- EXPECT_EQ(is_audio_encrypted,
- audio_stream->audio_decoder_config().is_encrypted());
- } else {
- EXPECT_FALSE(audio_stream);
- }
-
- scoped_refptr<DemuxerStream> video_stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- if (has_video) {
- EXPECT_TRUE(video_stream);
- EXPECT_EQ(is_video_encrypted,
- video_stream->video_decoder_config().is_encrypted());
- } else {
- EXPECT_FALSE(video_stream);
- }
-
- ShutdownDemuxer();
- demuxer_ = NULL;
- }
-}
-
-// Make sure that the demuxer reports an error if Shutdown()
-// is called before all the initialization segments are appended.
-TEST_F(ChunkDemuxerTest, TestShutdownBeforeAllInitSegmentsAppended) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(
- kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN));
-
- EXPECT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk);
- EXPECT_EQ(AddId("video", false, true), ChunkDemuxer::kOk);
-
- EXPECT_TRUE(AppendInitSegmentWithSourceId("audio", true, false));
-}
-
-// Test that Seek() completes successfully when the first cluster
-// arrives.
-TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- InSequence s;
-
- EXPECT_CALL(*this, Checkpoint(1));
-
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(46),
- NewExpectedStatusCB(PIPELINE_OK));
-
- EXPECT_CALL(*this, Checkpoint(2));
-
- scoped_ptr<Cluster> cluster(kDefaultSecondCluster());
-
- Checkpoint(1);
-
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- Checkpoint(2);
-}
-
-// Test that parsing errors are handled for clusters appended after init.
-TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterAfterInit) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
- ASSERT_TRUE(AppendGarbage());
-}
-
-// Test the case where a Seek() is requested while the parser
-// is in the middle of cluster. This is to verify that the parser
-// does not reset itself on a seek.
-TEST_F(ChunkDemuxerTest, TestSeekWhileParsingCluster) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- InSequence s;
-
- scoped_ptr<Cluster> cluster_a(GenerateCluster(0, 6));
-
- // Split the cluster into two appends at an arbitrary point near the end.
- int first_append_size = cluster_a->size() - 11;
- int second_append_size = cluster_a->size() - first_append_size;
-
- // Append the first part of the cluster.
- ASSERT_TRUE(AppendData(cluster_a->data(), first_append_size));
-
- ExpectRead(audio, 0);
- ExpectRead(video, 0);
- ExpectRead(audio, kAudioBlockDuration);
- // Note: We skip trying to read a video buffer here because computing
- // the duration for this block relies on successfully parsing the last block
- // in the cluster the cluster.
- ExpectRead(audio, 2 * kAudioBlockDuration);
-
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(5),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Append the rest of the cluster.
- ASSERT_TRUE(AppendData(cluster_a->data() + first_append_size,
- second_append_size));
-
- // Append the new cluster and verify that only the blocks
- // in the new cluster are returned.
- scoped_ptr<Cluster> cluster_b(GenerateCluster(5000, 6));
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
- GenerateExpectedReads(5000, 6, audio, video);
-}
-
-// Test the case where AppendData() is called before Init().
-TEST_F(ChunkDemuxerTest, TestAppendDataBeforeInit) {
- scoped_array<uint8> info_tracks;
- int info_tracks_size = 0;
- CreateInitSegment(true, true, false, false, &info_tracks, &info_tracks_size);
-
- EXPECT_FALSE(demuxer_->AppendData(kSourceId, info_tracks.get(),
- info_tracks_size));
-}
-
-// Make sure Read() callbacks are dispatched with the proper data.
-TEST_F(ChunkDemuxerTest, TestRead) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- bool audio_read_done = false;
- bool video_read_done = false;
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &audio_read_done));
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &video_read_done));
-
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-}
-
-TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- scoped_ptr<Cluster> cluster_a(GenerateCluster(10, 4));
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- // Cluster B starts before cluster_a and has data
- // that overlaps.
- scoped_ptr<Cluster> cluster_b(GenerateCluster(5, 4));
-
- // Make sure that AppendData() does not fail.
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
-
- // Verify that AppendData() can still accept more data.
- scoped_ptr<Cluster> cluster_c(GenerateCluster(45, 2));
- ASSERT_TRUE(demuxer_->AppendData(kSourceId, cluster_c->data(),
- cluster_c->size()));
-}
-
-TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- ClusterBuilder cb;
-
- // Test the case where block timecodes are not monotonically
- // increasing but stay above the cluster timecode.
- cb.SetClusterTimecode(5);
- AddSimpleBlock(&cb, kAudioTrackNum, 5);
- AddSimpleBlock(&cb, kVideoTrackNum, 10);
- AddSimpleBlock(&cb, kAudioTrackNum, 7);
- AddSimpleBlock(&cb, kVideoTrackNum, 15);
- scoped_ptr<Cluster> cluster_a(cb.Finish());
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- // Verify that AppendData() doesn't accept more data now.
- scoped_ptr<Cluster> cluster_b(GenerateCluster(20, 2));
- EXPECT_FALSE(demuxer_->AppendData(kSourceId, cluster_b->data(),
- cluster_b->size()));
-}
-
-TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- ClusterBuilder cb;
-
- // Test timecodes going backwards and including values less than the cluster
- // timecode.
- cb.SetClusterTimecode(5);
- AddSimpleBlock(&cb, kAudioTrackNum, 5);
- AddSimpleBlock(&cb, kVideoTrackNum, 5);
- AddSimpleBlock(&cb, kAudioTrackNum, 3);
- AddSimpleBlock(&cb, kVideoTrackNum, 3);
- scoped_ptr<Cluster> cluster_a(cb.Finish());
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- // Verify that AppendData() doesn't accept more data now.
- scoped_ptr<Cluster> cluster_b(GenerateCluster(6, 2));
- EXPECT_FALSE(demuxer_->AppendData(kSourceId, cluster_b->data(),
- cluster_b->size()));
-}
-
-
-TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- ClusterBuilder cb;
-
- // Test monotonic increasing timestamps on a per stream
- // basis.
- cb.SetClusterTimecode(5);
- AddSimpleBlock(&cb, kAudioTrackNum, 5);
- AddSimpleBlock(&cb, kVideoTrackNum, 5);
- AddSimpleBlock(&cb, kAudioTrackNum, 4);
- AddSimpleBlock(&cb, kVideoTrackNum, 7);
- scoped_ptr<Cluster> cluster(cb.Finish());
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-}
-
-// Test the case where a cluster is passed to AppendData() before
-// INFO & TRACKS data.
-TEST_F(ChunkDemuxerTest, TestClusterBeforeInitSegment) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
-
- ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
-
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 1));
-
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-}
-
-// Test cases where we get an EndOfStream() call during initialization.
-TEST_F(ChunkDemuxerTest, TestEOSDuringInit) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
- demuxer_->EndOfStream(PIPELINE_OK);
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamWithNoAppend) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
-
- ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
-
- CheckExpectedRanges("{ }");
- demuxer_->EndOfStream(PIPELINE_OK);
- ShutdownDemuxer();
- CheckExpectedRanges("{ }");
- demuxer_->RemoveId(kSourceId);
- demuxer_ = NULL;
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamWithNoMediaAppend) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- CheckExpectedRanges("{ }");
- demuxer_->EndOfStream(PIPELINE_OK);
- CheckExpectedRanges("{ }");
-}
-
-TEST_F(ChunkDemuxerTest, TestDecodeErrorEndOfStream) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
- CheckExpectedRanges(kDefaultFirstClusterRange);
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
- demuxer_->EndOfStream(PIPELINE_ERROR_DECODE);
- CheckExpectedRanges(kDefaultFirstClusterRange);
-}
-
-TEST_F(ChunkDemuxerTest, TestNetworkErrorEndOfStream) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
- CheckExpectedRanges(kDefaultFirstClusterRange);
-
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_NETWORK));
- demuxer_->EndOfStream(PIPELINE_ERROR_NETWORK);
-}
-
-// Helper class to reduce duplicate code when testing end of stream
-// Read() behavior.
-class EndOfStreamHelper {
- public:
- explicit EndOfStreamHelper(const scoped_refptr<Demuxer> demuxer)
- : demuxer_(demuxer),
- audio_read_done_(false),
- video_read_done_(false) {
- }
-
- // Request a read on the audio and video streams.
- void RequestReads() {
- EXPECT_FALSE(audio_read_done_);
- EXPECT_FALSE(video_read_done_);
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- audio->Read(base::Bind(&OnEndOfStreamReadDone,
- &audio_read_done_));
-
- video->Read(base::Bind(&OnEndOfStreamReadDone,
- &video_read_done_));
- }
-
- // Check to see if |audio_read_done_| and |video_read_done_| variables
- // match |expected|.
- void CheckIfReadDonesWereCalled(bool expected) {
- EXPECT_EQ(expected, audio_read_done_);
- EXPECT_EQ(expected, video_read_done_);
- }
-
- private:
- static void OnEndOfStreamReadDone(
- bool* called,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- EXPECT_EQ(status, DemuxerStream::kOk);
- EXPECT_TRUE(buffer->IsEndOfStream());
- *called = true;
- }
-
- scoped_refptr<Demuxer> demuxer_;
- bool audio_read_done_;
- bool video_read_done_;
-
- DISALLOW_COPY_AND_ASSIGN(EndOfStreamHelper);
-};
-
-// Make sure that all pending reads that we don't have media data for get an
-// "end of stream" buffer when EndOfStream() is called.
-TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- bool audio_read_done_1 = false;
- bool video_read_done_1 = false;
- EndOfStreamHelper end_of_stream_helper_1(demuxer_);
- EndOfStreamHelper end_of_stream_helper_2(demuxer_);
-
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &audio_read_done_1));
-
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &video_read_done_1));
-
- end_of_stream_helper_1.RequestReads();
- end_of_stream_helper_2.RequestReads();
-
- EXPECT_TRUE(audio_read_done_1);
- EXPECT_TRUE(video_read_done_1);
- end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
- end_of_stream_helper_2.CheckIfReadDonesWereCalled(false);
-
- EXPECT_CALL(host_, SetDuration(
- base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
- demuxer_->EndOfStream(PIPELINE_OK);
-
- end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
- end_of_stream_helper_2.CheckIfReadDonesWereCalled(true);
-}
-
-// Make sure that all Read() calls after we get an EndOfStream()
-// call return an "end of stream" buffer.
-TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- bool audio_read_done_1 = false;
- bool video_read_done_1 = false;
- EndOfStreamHelper end_of_stream_helper_1(demuxer_);
- EndOfStreamHelper end_of_stream_helper_2(demuxer_);
- EndOfStreamHelper end_of_stream_helper_3(demuxer_);
-
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &audio_read_done_1));
-
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &video_read_done_1));
-
- end_of_stream_helper_1.RequestReads();
-
- EXPECT_TRUE(audio_read_done_1);
- EXPECT_TRUE(video_read_done_1);
- end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
-
- EXPECT_CALL(host_, SetDuration(
- base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
- EXPECT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
-
- end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
-
- // Request a few more reads and make sure we immediately get
- // end of stream buffers.
- end_of_stream_helper_2.RequestReads();
- end_of_stream_helper_2.CheckIfReadDonesWereCalled(true);
-
- end_of_stream_helper_3.RequestReads();
- end_of_stream_helper_3.CheckIfReadDonesWereCalled(true);
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringCanceledSeek) {
- ASSERT_TRUE(InitDemuxer(true, true));
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- ASSERT_TRUE(AppendCluster(0, 10));
- EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(138)));
- EXPECT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
-
- // Start the first seek.
- demuxer_->StartWaitingForSeek();
-
- // Simulate the pipeline finally calling Seek().
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(20),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Simulate another seek being requested before the first
- // seek has finished prerolling.
- demuxer_->CancelPendingSeek();
-
- // Finish second seek.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(30),
- NewExpectedStatusCB(PIPELINE_OK));
-
- DemuxerStream::Status status;
- base::TimeDelta last_timestamp;
-
- // Make sure audio can reach end of stream.
- ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kOk);
-
- // Make sure video can reach end of stream.
- ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kOk);
-}
-
-// Make sure AppendData() will accept elements that span multiple calls.
-TEST_F(ChunkDemuxerTest, TestAppendingInPieces) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
-
- ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
-
- scoped_array<uint8> info_tracks;
- int info_tracks_size = 0;
- CreateInitSegment(true, true, false, false, &info_tracks, &info_tracks_size);
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
-
- size_t buffer_size = info_tracks_size + cluster_a->size() + cluster_b->size();
- scoped_array<uint8> buffer(new uint8[buffer_size]);
- uint8* dst = buffer.get();
- memcpy(dst, info_tracks.get(), info_tracks_size);
- dst += info_tracks_size;
-
- memcpy(dst, cluster_a->data(), cluster_a->size());
- dst += cluster_a->size();
-
- memcpy(dst, cluster_b->data(), cluster_b->size());
- dst += cluster_b->size();
-
- ASSERT_TRUE(AppendDataInPieces(buffer.get(), buffer_size));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- ASSERT_TRUE(audio);
- ASSERT_TRUE(video);
-
- GenerateExpectedReads(0, 9, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestWebMFile_AudioAndVideo) {
- struct BufferTimestamps buffer_timestamps[] = {
- {0, 0},
- {33, 3},
- {67, 6},
- {100, 9},
- {133, 12},
- {kSkip, kSkip},
- };
-
- ASSERT_TRUE(ParseWebMFile("bear-320x240.webm", buffer_timestamps,
- base::TimeDelta::FromMilliseconds(2744)));
-}
-
-TEST_F(ChunkDemuxerTest, TestWebMFile_LiveAudioAndVideo) {
- struct BufferTimestamps buffer_timestamps[] = {
- {0, 0},
- {33, 3},
- {67, 6},
- {100, 9},
- {133, 12},
- {kSkip, kSkip},
- };
-
- ASSERT_TRUE(ParseWebMFile("bear-320x240-live.webm", buffer_timestamps,
- kInfiniteDuration()));
-}
-
-TEST_F(ChunkDemuxerTest, TestWebMFile_AudioOnly) {
- struct BufferTimestamps buffer_timestamps[] = {
- {kSkip, 0},
- {kSkip, 3},
- {kSkip, 6},
- {kSkip, 9},
- {kSkip, 12},
- {kSkip, kSkip},
- };
-
- ASSERT_TRUE(ParseWebMFile("bear-320x240-audio-only.webm", buffer_timestamps,
- base::TimeDelta::FromMilliseconds(2744),
- true, false));
-}
-
-TEST_F(ChunkDemuxerTest, TestWebMFile_VideoOnly) {
- struct BufferTimestamps buffer_timestamps[] = {
- {0, kSkip},
- {33, kSkip},
- {67, kSkip},
- {100, kSkip},
- {133, kSkip},
- {kSkip, kSkip},
- };
-
- ASSERT_TRUE(ParseWebMFile("bear-320x240-video-only.webm", buffer_timestamps,
- base::TimeDelta::FromMilliseconds(2703),
- false, true));
-}
-
-TEST_F(ChunkDemuxerTest, TestWebMFile_AltRefFrames) {
- struct BufferTimestamps buffer_timestamps[] = {
- {0, 0},
- {33, 3},
- {33, 6},
- {67, 9},
- {100, 12},
- {kSkip, kSkip},
- };
-
- ASSERT_TRUE(ParseWebMFile("bear-320x240-altref.webm", buffer_timestamps,
- base::TimeDelta::FromMilliseconds(2767)));
-}
-
-// Verify that we output buffers before the entire cluster has been parsed.
-TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) {
- ASSERT_TRUE(InitDemuxer(true, true));
- ASSERT_TRUE(AppendEmptyCluster(0));
-
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 6));
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- bool audio_read_done = false;
- bool video_read_done = false;
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &audio_read_done));
-
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &video_read_done));
-
- // Make sure the reads haven't completed yet.
- EXPECT_FALSE(audio_read_done);
- EXPECT_FALSE(video_read_done);
-
- // Append data one byte at a time until the audio read completes.
- int i = 0;
- for (; i < cluster->size() && !audio_read_done; ++i) {
- ASSERT_TRUE(AppendData(cluster->data() + i, 1));
- }
-
- EXPECT_TRUE(audio_read_done);
- EXPECT_FALSE(video_read_done);
- EXPECT_GT(i, 0);
- EXPECT_LT(i, cluster->size());
-
- // Append data one byte at a time until the video read completes.
- for (; i < cluster->size() && !video_read_done; ++i) {
- ASSERT_TRUE(AppendData(cluster->data() + i, 1));
- }
-
- EXPECT_TRUE(video_read_done);
- EXPECT_LT(i, cluster->size());
-
- audio_read_done = false;
- video_read_done = false;
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(23),
- &audio_read_done));
-
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(33),
- &video_read_done));
-
- // Make sure the reads haven't completed yet.
- EXPECT_FALSE(audio_read_done);
- EXPECT_FALSE(video_read_done);
-
- // Append the remaining data.
- ASSERT_LT(i, cluster->size());
- ASSERT_TRUE(AppendData(cluster->data() + i, cluster->size() - i));
-
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-}
-
-TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(
- kNoTimestamp(), DEMUXER_ERROR_COULD_NOT_OPEN));
-
- ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
-
- uint8 tmp = 0;
- ASSERT_TRUE(demuxer_->AppendData(kSourceId, &tmp, 1));
-}
-
-TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kNoTimestamp(),
- DEMUXER_ERROR_COULD_NOT_OPEN));
-
- std::vector<std::string> codecs(1);
- codecs[0] = "vorbis";
- ASSERT_EQ(demuxer_->AddId(kSourceId, "audio/webm", codecs),
- ChunkDemuxer::kOk);
-
- ASSERT_TRUE(AppendInitSegment(true, true));
-}
-
-TEST_F(ChunkDemuxerTest, TestAVHeadersWithVideoOnlyType) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kNoTimestamp(),
- DEMUXER_ERROR_COULD_NOT_OPEN));
-
- std::vector<std::string> codecs(1);
- codecs[0] = "vp8";
- ASSERT_EQ(demuxer_->AddId(kSourceId, "video/webm", codecs),
- ChunkDemuxer::kOk);
-
- ASSERT_TRUE(AppendInitSegment(true, true));
-}
-
-TEST_F(ChunkDemuxerTest, TestMultipleHeaders) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- // Append another identical initialization segment.
- ASSERT_TRUE(AppendInitSegment(true, true));
-
- scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
-
- GenerateExpectedReads(0, 9, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestAddSeparateSourcesForAudioAndVideo) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- scoped_ptr<Cluster> cluster_a(
- GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v(
- GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
-
- // Append audio and video data into separate source ids.
- ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
- GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
- ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
- GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
-}
-
-TEST_F(ChunkDemuxerTest, TestAddIdFailures) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
-
- std::string audio_id = "audio1";
- std::string video_id = "video1";
-
- ASSERT_EQ(AddId(audio_id, true, false), ChunkDemuxer::kOk);
-
- // Adding an id with audio/video should fail because we already added audio.
- ASSERT_EQ(AddId(), ChunkDemuxer::kReachedIdLimit);
-
- ASSERT_TRUE(AppendInitSegmentWithSourceId(audio_id, true, false));
-
- // Adding an id after append should fail.
- ASSERT_EQ(AddId(video_id, false, true), ChunkDemuxer::kReachedIdLimit);
-}
-
-// Test that Read() calls after a RemoveId() return "end of stream" buffers.
-TEST_F(ChunkDemuxerTest, TestRemoveId) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_ptr<Cluster> cluster_a(
- GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v(
- GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
-
- // Append audio and video data into separate source ids.
- ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
-
- // Read() from audio should return normal buffers.
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
-
- // Remove the audio id.
- demuxer_->RemoveId(audio_id);
-
- // Read() from audio should return "end of stream" buffers.
- bool audio_read_done = false;
- audio->Read(base::Bind(&OnReadDone_EOSExpected,
- &audio_read_done));
- EXPECT_TRUE(audio_read_done);
-
- // Read() from video should still return normal buffers.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
-}
-
-// Test that removing an ID immediately after adding it does not interfere with
-// quota for new IDs in the future.
-TEST_F(ChunkDemuxerTest, TestRemoveAndAddId) {
- std::string audio_id_1 = "audio1";
- ASSERT_TRUE(AddId(audio_id_1, true, false) == ChunkDemuxer::kOk);
- demuxer_->RemoveId(audio_id_1);
-
- std::string audio_id_2 = "audio2";
- ASSERT_TRUE(AddId(audio_id_2, true, false) == ChunkDemuxer::kOk);
-}
-
-TEST_F(ChunkDemuxerTest, TestSeekCanceled) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Append cluster at the beginning of the stream.
- scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
- ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
-
- // Seek to an unbuffered region.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(50),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Attempt to read in unbuffered area; should not fulfill the read.
- bool audio_read_done = false;
- bool video_read_done = false;
- audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
- video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
- EXPECT_FALSE(audio_read_done);
- EXPECT_FALSE(video_read_done);
-
- // Now cancel the pending seek, which should flush the reads with empty
- // buffers.
- demuxer_->CancelPendingSeek();
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-
- // A seek back to the buffered region should succeed.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
- GenerateExpectedReads(0, 4, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestSeekCanceledWhileWaitingForSeek) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Append cluster at the beginning of the stream.
- scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
- ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
-
- // Start waiting for a seek.
- demuxer_->StartWaitingForSeek();
-
- // Now cancel the upcoming seek to an unbuffered region.
- demuxer_->CancelPendingSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(50),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Read requests should be fulfilled with empty buffers.
- bool audio_read_done = false;
- bool video_read_done = false;
- audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
- video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-
- // A seek back to the buffered region should succeed.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
- GenerateExpectedReads(0, 4, audio, video);
-}
-
-// Test that Seek() successfully seeks to all source IDs.
-TEST_F(ChunkDemuxerTest, TestSeekAudioAndVideoSources) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_ptr<Cluster> cluster_a1(
- GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v1(
- GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
-
- ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
-
- // Read() should return buffers at 0.
- bool audio_read_done = false;
- bool video_read_done = false;
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &audio_read_done));
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromMilliseconds(0),
- &video_read_done));
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-
- // Seek to 3 (an unbuffered region).
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(3),
- NewExpectedStatusCB(PIPELINE_OK));
-
- audio_read_done = false;
- video_read_done = false;
- audio->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromSeconds(3),
- &audio_read_done));
- video->Read(base::Bind(&OnReadDone,
- base::TimeDelta::FromSeconds(3),
- &video_read_done));
-
- // Read()s should not return until after data is appended at the Seek point.
- EXPECT_FALSE(audio_read_done);
- EXPECT_FALSE(video_read_done);
-
- scoped_ptr<Cluster> cluster_a2(
- GenerateSingleStreamCluster(3000, 3092, kAudioTrackNum,
- kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v2(
- GenerateSingleStreamCluster(3000, 3132, kVideoTrackNum,
- kVideoBlockDuration));
-
- ASSERT_TRUE(AppendData(audio_id, cluster_a2->data(), cluster_a2->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
-
- // Read() should return buffers at 3.
- EXPECT_TRUE(audio_read_done);
- EXPECT_TRUE(video_read_done);
-}
-
-// Test ranges in an audio-only stream.
-TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioIdOnly) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
-
- ASSERT_EQ(AddId(kSourceId, true, false), ChunkDemuxer::kOk);
- ASSERT_TRUE(AppendInitSegment(true, false));
-
- // Test a simple cluster.
- scoped_ptr<Cluster> cluster_1(GenerateSingleStreamCluster(0, 92,
- kAudioTrackNum, kAudioBlockDuration));
- ASSERT_TRUE(AppendData(cluster_1->data(), cluster_1->size()));
-
- CheckExpectedRanges("{ [0,92) }");
-
- // Append a disjoint cluster to check for two separate ranges.
- scoped_ptr<Cluster> cluster_2(GenerateSingleStreamCluster(150, 219,
- kAudioTrackNum, kAudioBlockDuration));
-
- ASSERT_TRUE(AppendData(cluster_2->data(), cluster_2->size()));
-
- CheckExpectedRanges("{ [0,92) [150,219) }");
-}
-
-// Test ranges in a video-only stream.
-TEST_F(ChunkDemuxerTest, GetBufferedRanges_VideoIdOnly) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
-
- ASSERT_EQ(AddId(kSourceId, false, true), ChunkDemuxer::kOk);
- ASSERT_TRUE(AppendInitSegment(false, true));
-
- // Test a simple cluster.
- scoped_ptr<Cluster> cluster_1(GenerateSingleStreamCluster(0, 132,
- kVideoTrackNum, kVideoBlockDuration));
-
- ASSERT_TRUE(AppendData(cluster_1->data(), cluster_1->size()));
-
- CheckExpectedRanges("{ [0,132) }");
-
- // Append a disjoint cluster to check for two separate ranges.
- scoped_ptr<Cluster> cluster_2(GenerateSingleStreamCluster(200, 299,
- kVideoTrackNum, kVideoBlockDuration));
-
- ASSERT_TRUE(AppendData(cluster_2->data(), cluster_2->size()));
-
- CheckExpectedRanges("{ [0,132) [200,299) }");
-}
-
-TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioVideo) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- // Audio: 0 -> 23
- // Video: 0 -> 33
- // Buffered Range: 0 -> 23
- // Audio block duration is smaller than video block duration,
- // so the buffered ranges should correspond to the audio blocks.
- scoped_ptr<Cluster> cluster_a0(
- GenerateSingleStreamCluster(0, kAudioBlockDuration, kAudioTrackNum,
- kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v0(
- GenerateSingleStreamCluster(0, kVideoBlockDuration, kVideoTrackNum,
- kVideoBlockDuration));
-
- ASSERT_TRUE(AppendData(cluster_a0->data(), cluster_a0->size()));
- ASSERT_TRUE(AppendData(cluster_v0->data(), cluster_v0->size()));
-
- CheckExpectedRanges("{ [0,23) }");
-
- // Audio: 300 -> 400
- // Video: 320 -> 420
- // Buffered Range: 320 -> 400 (end overlap)
- scoped_ptr<Cluster> cluster_a1(
- GenerateSingleStreamCluster(300, 400, kAudioTrackNum, 50));
-
- scoped_ptr<Cluster> cluster_v1(
- GenerateSingleStreamCluster(320, 420, kVideoTrackNum, 50));
-
- ASSERT_TRUE(AppendData(cluster_a1->data(), cluster_a1->size()));
- ASSERT_TRUE(AppendData(cluster_v1->data(), cluster_v1->size()));
-
- CheckExpectedRanges("{ [0,23) [320,400) }");
-
- // Audio: 520 -> 590
- // Video: 500 -> 570
- // Buffered Range: 520 -> 570 (front overlap)
- scoped_ptr<Cluster> cluster_a2(
- GenerateSingleStreamCluster(520, 590, kAudioTrackNum, 70));
-
- scoped_ptr<Cluster> cluster_v2(
- GenerateSingleStreamCluster(500, 570, kVideoTrackNum, 70));
-
- ASSERT_TRUE(AppendData(cluster_a2->data(), cluster_a2->size()));
- ASSERT_TRUE(AppendData(cluster_v2->data(), cluster_v2->size()));
-
- CheckExpectedRanges("{ [0,23) [320,400) [520,570) }");
-
- // Audio: 720 -> 750
- // Video: 700 -> 770
- // Buffered Range: 720 -> 750 (complete overlap, audio)
- scoped_ptr<Cluster> cluster_a3(
- GenerateSingleStreamCluster(720, 750, kAudioTrackNum, 30));
-
- scoped_ptr<Cluster> cluster_v3(
- GenerateSingleStreamCluster(700, 770, kVideoTrackNum, 70));
-
- ASSERT_TRUE(AppendData(cluster_a3->data(), cluster_a3->size()));
- ASSERT_TRUE(AppendData(cluster_v3->data(), cluster_v3->size()));
-
- CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) }");
-
- // Audio: 900 -> 970
- // Video: 920 -> 950
- // Buffered Range: 920 -> 950 (complete overlap, video)
- scoped_ptr<Cluster> cluster_a4(
- GenerateSingleStreamCluster(900, 970, kAudioTrackNum, 70));
-
- scoped_ptr<Cluster> cluster_v4(
- GenerateSingleStreamCluster(920, 950, kVideoTrackNum, 30));
-
- ASSERT_TRUE(AppendData(cluster_a4->data(), cluster_a4->size()));
- ASSERT_TRUE(AppendData(cluster_v4->data(), cluster_v4->size()));
-
- CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
-
- // Appending within buffered range should not affect buffered ranges.
- scoped_ptr<Cluster> cluster_a5(
- GenerateSingleStreamCluster(930, 950, kAudioTrackNum, 20));
- ASSERT_TRUE(AppendData(cluster_a5->data(), cluster_a5->size()));
- CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
-
- // Appending to single stream outside buffered ranges should not affect
- // buffered ranges.
- scoped_ptr<Cluster> cluster_v5(
- GenerateSingleStreamCluster(1230, 1240, kVideoTrackNum, 10));
- ASSERT_TRUE(AppendData(cluster_v5->data(), cluster_v5->size()));
- CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
-}
-
-// Once EndOfStream() is called, GetBufferedRanges should not cut off any
-// over-hanging tails at the end of the ranges as this is likely due to block
-// duration differences.
-TEST_F(ChunkDemuxerTest, GetBufferedRanges_EndOfStream) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster_a(
- GenerateSingleStreamCluster(0, 90, kAudioTrackNum, 90));
- scoped_ptr<Cluster> cluster_v(
- GenerateSingleStreamCluster(0, 100, kVideoTrackNum, 100));
-
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
- ASSERT_TRUE(AppendData(cluster_v->data(), cluster_v->size()));
-
- CheckExpectedRanges("{ [0,90) }");
-
- EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(100)));
- demuxer_->EndOfStream(PIPELINE_OK);
-
- CheckExpectedRanges("{ [0,100) }");
-}
-
-TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Create a cluster where the video timecode begins 25ms after the audio.
- scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 25, 8));
- ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
-
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
- GenerateExpectedReads(0, 25, 8, audio, video);
-
- // Seek to 5 seconds.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromSeconds(5),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Generate a cluster to fulfill this seek, where audio timecode begins 25ms
- // after the video.
- scoped_ptr<Cluster> middle_cluster(GenerateCluster(5025, 5000, 8));
- ASSERT_TRUE(AppendData(middle_cluster->data(), middle_cluster->size()));
- GenerateExpectedReads(5025, 5000, 8, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesSeparateSources) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Generate two streams where the video stream starts 5ms after the audio
- // stream and append them.
- scoped_ptr<Cluster> cluster_v(
- GenerateSingleStreamCluster(30, 4 * kVideoBlockDuration + 30,
- kVideoTrackNum, kVideoBlockDuration));
- scoped_ptr<Cluster> cluster_a(
- GenerateSingleStreamCluster(25, 4 * kAudioBlockDuration + 25,
- kAudioTrackNum, kAudioBlockDuration));
- ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
-
- // Both streams should be able to fulfill a seek to 25.
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(25),
- NewExpectedStatusCB(PIPELINE_OK));
- GenerateSingleStreamExpectedReads(25, 4, audio, kAudioBlockDuration);
- GenerateSingleStreamExpectedReads(30, 4, video, kVideoBlockDuration);
-}
-
-TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesOutOfRange) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Generate two streams where the video stream starts 10s after the audio
- // stream and append them.
- scoped_ptr<Cluster> cluster_v(
- GenerateSingleStreamCluster(10000, 4 * kVideoBlockDuration + 10000,
- kVideoTrackNum, kVideoBlockDuration));
- scoped_ptr<Cluster> cluster_a(
- GenerateSingleStreamCluster(0, 4 * kAudioBlockDuration + 0,
- kAudioTrackNum, kAudioBlockDuration));
- ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
-
- // Should not be able to fulfill a seek to 0.
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
- NewExpectedStatusCB(PIPELINE_ERROR_ABORT));
- ExpectRead(audio, 0);
- ExpectEndOfStream(video);
-}
-
-TEST_F(ChunkDemuxerTest, TestClusterWithNoBuffers) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- // Generate and append an empty cluster beginning at 0.
- ASSERT_TRUE(AppendEmptyCluster(0));
-
- // Sanity check that data can be appended after this cluster correctly.
- scoped_ptr<Cluster> media_data(GenerateCluster(0, 2));
- ASSERT_TRUE(AppendData(media_data->data(), media_data->size()));
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- ExpectRead(audio, 0);
- ExpectRead(video, 0);
-}
-
-TEST_F(ChunkDemuxerTest, TestCodecPrefixMatching) {
- ChunkDemuxer::Status expected = ChunkDemuxer::kNotSupported;
-
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
- expected = ChunkDemuxer::kOk;
-#endif
-
- std::vector<std::string> codecs;
- codecs.push_back("avc1.4D4041");
- codecs.push_back("mp4a.40.2");
-
- EXPECT_EQ(demuxer_->AddId("source_id", "video/mp4", codecs), expected);
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamFailures) {
- std::string audio_id = "audio";
- std::string video_id = "video";
-
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_ptr<Cluster> cluster_a1(
- GenerateSingleStreamCluster(0, 35, kAudioTrackNum, 35));
- scoped_ptr<Cluster> cluster_v1(
- GenerateSingleStreamCluster(0, 10, kVideoTrackNum, 5));
- scoped_ptr<Cluster> cluster_v2(
- GenerateSingleStreamCluster(10, 25, kVideoTrackNum, 5));
- scoped_ptr<Cluster> cluster_v3(
- GenerateSingleStreamCluster(30, 50, kVideoTrackNum, 10));
-
- ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v3->data(), cluster_v3->size()));
-
- CheckExpectedRanges(audio_id, "{ [0,35) }");
- CheckExpectedRanges(video_id, "{ [0,10) [30,50) }");
-
- // Make sure that end of stream fails because there is a gap between
- // the current position(0) and the end of the appended data.
- EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(50)));
- ASSERT_FALSE(demuxer_->EndOfStream(PIPELINE_OK));
-
- // Seek to an time that is inside the last ranges for both streams
- // and verify that the EndOfStream() is successful.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(30),
- NewExpectedStatusCB(PIPELINE_OK));
-
- ASSERT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
-
- // Append an zero length buffer to transition out of the end of stream state.
- ASSERT_TRUE(AppendData(NULL, 0));
-
- // Seek back to 0 and verify that EndOfStream() fails again.
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
-
- ASSERT_FALSE(demuxer_->EndOfStream(PIPELINE_OK));
-
- // Append the missing range and verify that EndOfStream() succeeds now.
- ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
-
- CheckExpectedRanges(audio_id, "{ [0,35) }");
- CheckExpectedRanges(video_id, "{ [0,50) }");
-
- ASSERT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamStillSetAfterSeek) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- EXPECT_CALL(host_, SetDuration(_))
- .Times(AnyNumber());
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
- base::TimeDelta kLastAudioTimestamp = base::TimeDelta::FromMilliseconds(92);
- base::TimeDelta kLastVideoTimestamp = base::TimeDelta::FromMilliseconds(99);
-
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
- demuxer_->EndOfStream(PIPELINE_OK);
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- DemuxerStream::Status status;
- base::TimeDelta last_timestamp;
-
- // Verify that we can read audio & video to the end w/o problems.
- ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
- EXPECT_EQ(DemuxerStream::kOk, status);
- EXPECT_EQ(kLastAudioTimestamp, last_timestamp);
-
- ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
- EXPECT_EQ(DemuxerStream::kOk, status);
- EXPECT_EQ(kLastVideoTimestamp, last_timestamp);
-
- // Seek back to 0 and verify that we can read to the end again..
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
-
- ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
- EXPECT_EQ(DemuxerStream::kOk, status);
- EXPECT_EQ(kLastAudioTimestamp, last_timestamp);
-
- ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
- EXPECT_EQ(DemuxerStream::kOk, status);
- EXPECT_EQ(kLastVideoTimestamp, last_timestamp);
-}
-
-TEST_F(ChunkDemuxerTest, TestGetBufferedRangesBeforeInitSegment) {
- EXPECT_CALL(*this, DemuxerOpened());
- demuxer_->Initialize(&host_, CreateInitDoneCB(PIPELINE_OK));
- ASSERT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk);
- ASSERT_EQ(AddId("video", false, true), ChunkDemuxer::kOk);
-
- CheckExpectedRanges("audio", "{ }");
- CheckExpectedRanges("video", "{ }");
-}
-
-// Test that Seek() completes successfully when the first cluster
-// arrives.
-TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringSeek) {
- InSequence s;
-
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- demuxer_->StartWaitingForSeek();
-
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
- EXPECT_CALL(host_, SetDuration(
- base::TimeDelta::FromMilliseconds(kDefaultSecondClusterEndTimestamp)));
- demuxer_->EndOfStream(PIPELINE_OK);
-
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- GenerateExpectedReads(0, 4, audio, video);
- GenerateExpectedReads(46, 66, 5, audio, video);
-
- EndOfStreamHelper end_of_stream_helper(demuxer_);
- end_of_stream_helper.RequestReads();
- end_of_stream_helper.CheckIfReadDonesWereCalled(true);
-}
-
-TEST_F(ChunkDemuxerTest, TestConfigChange_Video) {
- InSequence s;
-
- ASSERT_TRUE(InitDemuxerWithConfigChangeData());
-
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- DemuxerStream::Status status;
- base::TimeDelta last_timestamp;
-
- // Fetch initial video config and verify it matches what we expect.
- const VideoDecoderConfig& video_config_1 = stream->video_decoder_config();
- ASSERT_TRUE(video_config_1.IsValidConfig());
- EXPECT_EQ(video_config_1.natural_size().width(), 320);
- EXPECT_EQ(video_config_1.natural_size().height(), 240);
-
- ExpectRead(stream, 0);
-
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
-
- ASSERT_EQ(status, DemuxerStream::kConfigChanged);
- EXPECT_EQ(last_timestamp.InMilliseconds(), 501);
-
- // Fetch the new decoder config.
- const VideoDecoderConfig& video_config_2 = stream->video_decoder_config();
- ASSERT_TRUE(video_config_2.IsValidConfig());
- EXPECT_EQ(video_config_2.natural_size().width(), 640);
- EXPECT_EQ(video_config_2.natural_size().height(), 360);
-
- ExpectRead(stream, 527);
-
- // Read until the next config change.
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kConfigChanged);
- EXPECT_EQ(last_timestamp.InMilliseconds(), 793);
-
- // Get the new config and verify that it matches the first one.
- ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
-
- ExpectRead(stream, 801);
-
- // Read until the end of the stream just to make sure there aren't any other
- // config changes.
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kOk);
-}
-
-TEST_F(ChunkDemuxerTest, TestConfigChange_Audio) {
- InSequence s;
-
- ASSERT_TRUE(InitDemuxerWithConfigChangeData());
-
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- DemuxerStream::Status status;
- base::TimeDelta last_timestamp;
-
- // Fetch initial audio config and verify it matches what we expect.
- const AudioDecoderConfig& audio_config_1 = stream->audio_decoder_config();
- ASSERT_TRUE(audio_config_1.IsValidConfig());
- EXPECT_EQ(audio_config_1.samples_per_second(), 44100);
- EXPECT_EQ(audio_config_1.extra_data_size(), 3863u);
-
- ExpectRead(stream, 0);
-
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
-
- ASSERT_EQ(status, DemuxerStream::kConfigChanged);
- EXPECT_EQ(last_timestamp.InMilliseconds(), 524);
-
- // Fetch the new decoder config.
- const AudioDecoderConfig& audio_config_2 = stream->audio_decoder_config();
- ASSERT_TRUE(audio_config_2.IsValidConfig());
- EXPECT_EQ(audio_config_2.samples_per_second(), 44100);
- EXPECT_EQ(audio_config_2.extra_data_size(), 3935u);
-
- ExpectRead(stream, 527);
-
- // Read until the next config change.
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kConfigChanged);
- EXPECT_EQ(last_timestamp.InMilliseconds(), 759);
-
- // Get the new config and verify that it matches the first one.
- ASSERT_TRUE(audio_config_1.Matches(stream->audio_decoder_config()));
-
- ExpectRead(stream, 779);
-
- // Read until the end of the stream just to make sure there aren't any other
- // config changes.
- ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
- ASSERT_EQ(status, DemuxerStream::kOk);
-}
-
-TEST_F(ChunkDemuxerTest, TestConfigChange_Seek) {
- InSequence s;
-
- ASSERT_TRUE(InitDemuxerWithConfigChangeData());
-
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- // Fetch initial video config and verify it matches what we expect.
- const VideoDecoderConfig& video_config_1 = stream->video_decoder_config();
- ASSERT_TRUE(video_config_1.IsValidConfig());
- EXPECT_EQ(video_config_1.natural_size().width(), 320);
- EXPECT_EQ(video_config_1.natural_size().height(), 240);
-
- ExpectRead(stream, 0);
-
- // Seek to a location with a different config.
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(527),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Verify that the config change is signalled.
- ExpectConfigChanged(stream);
-
- // Fetch the new decoder config and verify it is what we expect.
- const VideoDecoderConfig& video_config_2 = stream->video_decoder_config();
- ASSERT_TRUE(video_config_2.IsValidConfig());
- EXPECT_EQ(video_config_2.natural_size().width(), 640);
- EXPECT_EQ(video_config_2.natural_size().height(), 360);
-
- // Verify that Read() will return a buffer now.
- ExpectRead(stream, 527);
-
- // Seek back to the beginning and verify we get another config change.
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
- ExpectConfigChanged(stream);
- ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
- ExpectRead(stream, 0);
-
- // Seek to a location that requires a config change and then
- // seek to a new location that has the same configuration as
- // the start of the file without a Read() in the middle.
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(527),
- NewExpectedStatusCB(PIPELINE_OK));
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(801),
- NewExpectedStatusCB(PIPELINE_OK));
-
- // Verify that no config change is signalled.
- ExpectRead(stream, 801);
- ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
-}
-
-TEST_F(ChunkDemuxerTest, TestTimestampPositiveOffset) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- kSourceId, base::TimeDelta::FromSeconds(30)));
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(30000),
- NewExpectedStatusCB(PIPELINE_OK));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- GenerateExpectedReads(30000, 2, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestTimestampNegativeOffset) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- kSourceId, base::TimeDelta::FromSeconds(-1)));
- scoped_ptr<Cluster> cluster = GenerateCluster(1000, 2);
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- GenerateExpectedReads(0, 2, audio, video);
-}
-
-TEST_F(ChunkDemuxerTest, TestTimestampOffsetSeparateStreams) {
- std::string audio_id = "audio1";
- std::string video_id = "video1";
- ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
-
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- scoped_ptr<Cluster> cluster_a1(
- GenerateSingleStreamCluster(
- 2500, 2500 + kAudioBlockDuration * 4, kAudioTrackNum,
- kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v1(
- GenerateSingleStreamCluster(
- 2500, 2500 + kVideoBlockDuration * 4, kVideoTrackNum,
- kVideoBlockDuration));
-
- scoped_ptr<Cluster> cluster_a2(
- GenerateSingleStreamCluster(
- 0, kAudioBlockDuration * 4, kAudioTrackNum, kAudioBlockDuration));
-
- scoped_ptr<Cluster> cluster_v2(
- GenerateSingleStreamCluster(
- 0, kVideoBlockDuration * 4, kVideoTrackNum, kVideoBlockDuration));
-
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- audio_id, base::TimeDelta::FromMilliseconds(-2500)));
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- video_id, base::TimeDelta::FromMilliseconds(-2500)));
- ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
- GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
- GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
-
- demuxer_->StartWaitingForSeek();
- demuxer_->Seek(base::TimeDelta::FromMilliseconds(27300),
- NewExpectedStatusCB(PIPELINE_OK));
-
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- audio_id, base::TimeDelta::FromMilliseconds(27300)));
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- video_id, base::TimeDelta::FromMilliseconds(27300)));
- ASSERT_TRUE(AppendData(audio_id, cluster_a2->data(), cluster_a2->size()));
- ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
- GenerateSingleStreamExpectedReads(27300, 4, video, kVideoBlockDuration);
- GenerateSingleStreamExpectedReads(27300, 4, audio, kAudioBlockDuration);
-}
-
-TEST_F(ChunkDemuxerTest, TestTimestampOffsetMidParse) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster = GenerateCluster(0, 2);
- // Append only part of the cluster data.
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size() - 13));
-
- // Setting a timestamp should fail because we're in the middle of a cluster.
- ASSERT_FALSE(demuxer_->SetTimestampOffset(
- kSourceId, base::TimeDelta::FromSeconds(25)));
-
- demuxer_->Abort(kSourceId);
- // After Abort(), setting a timestamp should succeed since we're no longer
- // in the middle of a cluster
- ASSERT_TRUE(demuxer_->SetTimestampOffset(
- kSourceId, base::TimeDelta::FromSeconds(25)));
-}
-
-TEST_F(ChunkDemuxerTest, TestDurationChange) {
- ASSERT_TRUE(InitDemuxer(true, true));
- static const int kStreamDuration = kDefaultDuration().InMilliseconds();
-
- // Add data leading up to the currently set duration.
- scoped_ptr<Cluster> first_cluster = GenerateCluster(
- kStreamDuration - kAudioBlockDuration,
- kStreamDuration - kVideoBlockDuration, 2);
- ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
-
- CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
-
- // Add data at the currently set duration. The duration should not increase.
- scoped_ptr<Cluster> second_cluster = GenerateCluster(
- kDefaultDuration().InMilliseconds(), 2);
- ASSERT_TRUE(AppendData(second_cluster->data(), second_cluster->size()));
-
- // Range should not be affected.
- CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
-
- // Now add data past the duration and expect a new duration to be signalled.
- static const int kNewStreamDuration =
- kStreamDuration + kAudioBlockDuration * 2;
- scoped_ptr<Cluster> third_cluster = GenerateCluster(
- kStreamDuration + kAudioBlockDuration,
- kStreamDuration + kVideoBlockDuration, 2);
- EXPECT_CALL(host_, SetDuration(
- base::TimeDelta::FromMilliseconds(kNewStreamDuration)));
- ASSERT_TRUE(AppendData(third_cluster->data(), third_cluster->size()));
-
- // See that the range has increased appropriately.
- CheckExpectedRanges(kSourceId, "{ [201191,201270) }");
-}
-
-TEST_F(ChunkDemuxerTest, TestDurationChangeTimestampOffset) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- ASSERT_TRUE(demuxer_->SetTimestampOffset(kSourceId, kDefaultDuration()));
- scoped_ptr<Cluster> cluster = GenerateCluster(0, 4);
-
- EXPECT_CALL(host_, SetDuration(
- kDefaultDuration() + base::TimeDelta::FromMilliseconds(
- kAudioBlockDuration * 2)));
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-}
-
-TEST_F(ChunkDemuxerTest, TestEndOfStreamTruncateDuration) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
-
- EXPECT_CALL(host_, SetDuration(
- base::TimeDelta::FromMilliseconds(kDefaultFirstClusterEndTimestamp)));
- demuxer_->EndOfStream(PIPELINE_OK);
-}
-
-
-TEST_F(ChunkDemuxerTest, TestZeroLengthAppend) {
- ASSERT_TRUE(InitDemuxer(true, true));
- ASSERT_TRUE(AppendData(NULL, 0));
-}
-
-TEST_F(ChunkDemuxerTest, TestAppendAfterEndOfStream) {
- ASSERT_TRUE(InitDemuxer(true, true));
-
- EXPECT_CALL(host_, SetDuration(_))
- .Times(AnyNumber());
-
- scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
- ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
- demuxer_->EndOfStream(PIPELINE_OK);
-
- scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
- ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
- demuxer_->EndOfStream(PIPELINE_OK);
-}
-
-// Test receiving a Shutdown() call before we get an Initialize()
-// call. This can happen if video element gets destroyed before
-// the pipeline has a chance to initialize the demuxer.
-TEST_F(ChunkDemuxerTest, TestShutdownBeforeInitialize) {
- demuxer_->Shutdown();
- demuxer_->Initialize(
- &host_, CreateInitDoneCB(DEMUXER_ERROR_COULD_NOT_OPEN));
- message_loop_.RunUntilIdle();
-}
-
-} // namespace media
diff --git a/src/media/filters/decrypting_audio_decoder.cc b/src/media/filters/decrypting_audio_decoder.cc
deleted file mode 100644
index 85964c2..0000000
--- a/src/media/filters/decrypting_audio_decoder.cc
+++ /dev/null
@@ -1,502 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/decrypting_audio_decoder.h"
-
-#include <cstdlib>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/buffers.h"
-#include "media/base/data_buffer.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/base/decoder_buffer.h"
-
-namespace media {
-
-#define BIND_TO_LOOP(function) \
- media::BindToLoop(message_loop_, base::Bind(function, this))
-
-static inline bool IsOutOfSync(const base::TimeDelta& timestamp_1,
- const base::TimeDelta& timestamp_2) {
- // Out of sync of 100ms would be pretty noticeable and we should keep any
- // drift below that.
- const int64 kOutOfSyncThresholdInMicroseconds = 100000;
- return std::abs(timestamp_1.InMicroseconds() - timestamp_2.InMicroseconds()) >
- kOutOfSyncThresholdInMicroseconds;
-}
-
-DecryptingAudioDecoder::DecryptingAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : message_loop_(message_loop),
- state_(kUninitialized),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- decryptor_(NULL),
- key_added_while_decode_pending_(false),
- bits_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- samples_per_second_(0),
- bytes_per_sample_(0),
- output_timestamp_base_(kNoTimestamp()),
- total_samples_decoded_(0) {
-}
-
-void DecryptingAudioDecoder::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingAudioDecoder::DoInitialize, this,
- stream, status_cb, statistics_cb));
- return;
- }
- DoInitialize(stream, status_cb, statistics_cb);
-}
-
-void DecryptingAudioDecoder::Read(const ReadCB& read_cb) {
- // Complete operation asynchronously on different stack of execution as per
- // the API contract of AudioDecoder::Read()
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingAudioDecoder::DoRead, this, read_cb));
-}
-
-void DecryptingAudioDecoder::Reset(const base::Closure& closure) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingAudioDecoder::Reset, this, closure));
- return;
- }
-
- DVLOG(2) << "Reset() - state: " << state_;
- DCHECK(state_ == kIdle ||
- state_ == kPendingConfigChange ||
- state_ == kPendingDemuxerRead ||
- state_ == kPendingDecode ||
- state_ == kWaitingForKey ||
- state_ == kDecodeFinished) << state_;
- DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
- DCHECK(reset_cb_.is_null());
-
- reset_cb_ = closure;
-
- decryptor_->ResetDecoder(Decryptor::kAudio);
-
- // Reset() cannot complete if the read callback is still pending.
- // Defer the resetting process in this case. The |reset_cb_| will be fired
- // after the read callback is fired - see DoDecryptAndDecodeBuffer() and
- // DoDeliverFrame().
- if (state_ == kPendingConfigChange ||
- state_ == kPendingDemuxerRead ||
- state_ == kPendingDecode) {
- DCHECK(!read_cb_.is_null());
- return;
- }
-
- if (state_ == kWaitingForKey) {
- DCHECK(!read_cb_.is_null());
- pending_buffer_to_decode_ = NULL;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- }
-
- DCHECK(read_cb_.is_null());
- DoReset();
-}
-
-int DecryptingAudioDecoder::bits_per_channel() {
- return bits_per_channel_;
-}
-
-ChannelLayout DecryptingAudioDecoder::channel_layout() {
- return channel_layout_;
-}
-
-int DecryptingAudioDecoder::samples_per_second() {
- return samples_per_second_;
-}
-
-DecryptingAudioDecoder::~DecryptingAudioDecoder() {
-}
-
-void DecryptingAudioDecoder::DoInitialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- DVLOG(2) << "DoInitialize()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kUninitialized) << state_;
- DCHECK(stream);
-
- const AudioDecoderConfig& config = stream->audio_decoder_config();
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid audio stream config.";
- status_cb.Run(PIPELINE_ERROR_DECODE);
- return;
- }
-
- // DecryptingAudioDecoder only accepts potentially encrypted stream.
- if (!config.is_encrypted()) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- DCHECK(!demuxer_stream_);
- demuxer_stream_ = stream;
- statistics_cb_ = statistics_cb;
-
- init_cb_ = status_cb;
-
- state_ = kDecryptorRequested;
- set_decryptor_ready_cb_.Run(
- BIND_TO_LOOP(&DecryptingAudioDecoder::SetDecryptor));
-}
-
-void DecryptingAudioDecoder::SetDecryptor(Decryptor* decryptor) {
- DVLOG(2) << "SetDecryptor()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kDecryptorRequested) << state_;
- DCHECK(!init_cb_.is_null());
- DCHECK(!set_decryptor_ready_cb_.is_null());
-
- set_decryptor_ready_cb_.Reset();
-
- if (!decryptor) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- // TODO(xhwang): Add kError state. See http://crbug.com/251503
- state_ = kDecodeFinished;
- return;
- }
-
- decryptor_ = decryptor;
-
- scoped_ptr<AudioDecoderConfig> scoped_config(new AudioDecoderConfig());
- scoped_config->CopyFrom(demuxer_stream_->audio_decoder_config());
-
- state_ = kPendingDecoderInit;
- decryptor_->InitializeAudioDecoder(
- scoped_config.Pass(),
- BIND_TO_LOOP(&DecryptingAudioDecoder::FinishInitialization));
-}
-
-void DecryptingAudioDecoder::FinishInitialization(bool success) {
- DVLOG(2) << "FinishInitialization()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecoderInit) << state_;
- DCHECK(!init_cb_.is_null());
- DCHECK(reset_cb_.is_null()); // No Reset() before initialization finished.
- DCHECK(read_cb_.is_null()); // No Read() before initialization finished.
-
- if (!success) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- state_ = kDecodeFinished;
- return;
- }
-
- // Success!
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
- bits_per_channel_ = config.bits_per_channel();
- channel_layout_ = config.channel_layout();
- samples_per_second_ = config.samples_per_second();
- const int kBitsPerByte = 8;
- bytes_per_sample_ = ChannelLayoutToChannelCount(channel_layout_) *
- bits_per_channel_ / kBitsPerByte;
-
- decryptor_->RegisterKeyAddedCB(
- Decryptor::kAudio, BIND_TO_LOOP(&DecryptingAudioDecoder::OnKeyAdded));
-
- state_ = kIdle;
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-void DecryptingAudioDecoder::FinishConfigChange(bool success) {
- DVLOG(2) << "FinishConfigChange()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingConfigChange) << state_;
- DCHECK(!read_cb_.is_null());
-
- if (!success) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- state_ = kDecodeFinished;
- if (!reset_cb_.is_null())
- base::ResetAndReturn(&reset_cb_).Run();
- return;
- }
-
- // Config change succeeded.
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
- return;
- }
-
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
-}
-
-void DecryptingAudioDecoder::DoRead(const ReadCB& read_cb) {
- DVLOG(3) << "DoRead()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(state_ == kIdle || state_ == kDecodeFinished) << state_;
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
-
- // Return empty (end-of-stream) frames if decoding has finished.
- if (state_ == kDecodeFinished) {
- read_cb.Run(kOk, scoped_refptr<Buffer>(new DataBuffer(0)));
- return;
- }
-
- if (!queued_audio_frames_.empty()) {
- read_cb.Run(kOk, queued_audio_frames_.front());
- queued_audio_frames_.pop_front();
- return;
- }
-
- read_cb_ = read_cb;
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
-}
-
-void DecryptingAudioDecoder::ReadFromDemuxerStream() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
- DCHECK(!read_cb_.is_null());
-
- demuxer_stream_->Read(
- base::Bind(&DecryptingAudioDecoder::DoDecryptAndDecodeBuffer, this));
-}
-
-void DecryptingAudioDecoder::DoDecryptAndDecodeBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingAudioDecoder::DoDecryptAndDecodeBuffer, this,
- status, buffer));
- return;
- }
-
- DVLOG(3) << "DoDecryptAndDecodeBuffer()";
- DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
- DCHECK(!read_cb_.is_null());
- DCHECK_EQ(buffer != NULL, status == DemuxerStream::kOk) << status;
-
- if (status == DemuxerStream::kConfigChanged) {
- DVLOG(2) << "DoDecryptAndDecodeBuffer() - kConfigChanged";
-
- scoped_ptr<AudioDecoderConfig> scoped_config(new AudioDecoderConfig());
- scoped_config->CopyFrom(demuxer_stream_->audio_decoder_config());
-
- state_ = kPendingConfigChange;
- decryptor_->DeinitializeDecoder(Decryptor::kAudio);
- decryptor_->InitializeAudioDecoder(
- scoped_config.Pass(), BindToCurrentLoop(base::Bind(
- &DecryptingAudioDecoder::FinishConfigChange, this)));
- return;
- }
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
- return;
- }
-
- if (status == DemuxerStream::kAborted) {
- DVLOG(2) << "DoDecryptAndDecodeBuffer() - kAborted";
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- return;
- }
-
- DCHECK_EQ(status, DemuxerStream::kOk);
-
- // Initialize the |next_output_timestamp_| to be the timestamp of the first
- // non-EOS buffer.
- if (output_timestamp_base_ == kNoTimestamp() && !buffer->IsEndOfStream()) {
- DCHECK_EQ(total_samples_decoded_, 0);
- output_timestamp_base_ = buffer->GetTimestamp();
- }
-
- pending_buffer_to_decode_ = buffer;
- state_ = kPendingDecode;
- DecodePendingBuffer();
-}
-
-void DecryptingAudioDecoder::DecodePendingBuffer() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecode) << state_;
- decryptor_->DecryptAndDecodeAudio(
- pending_buffer_to_decode_,
- base::Bind(&DecryptingAudioDecoder::DeliverFrame, this,
- pending_buffer_to_decode_->GetDataSize()));
-}
-
-void DecryptingAudioDecoder::DeliverFrame(
- int buffer_size,
- Decryptor::Status status,
- const Decryptor::AudioBuffers& frames) {
- // We need to force task post here because the AudioDecodeCB can be executed
- // synchronously in Reset(). Instead of using more complicated logic in
- // those function to fix it, why not force task post here to make everything
- // simple and clear?
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingAudioDecoder::DoDeliverFrame, this,
- buffer_size, status, frames));
-}
-
-void DecryptingAudioDecoder::DoDeliverFrame(
- int buffer_size,
- Decryptor::Status status,
- const Decryptor::AudioBuffers& frames) {
- DVLOG(3) << "DoDeliverFrame() - status: " << status;
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecode) << state_;
- DCHECK(!read_cb_.is_null());
- DCHECK(pending_buffer_to_decode_);
- DCHECK(queued_audio_frames_.empty());
-
- bool need_to_try_again_if_nokey_is_returned = key_added_while_decode_pending_;
- key_added_while_decode_pending_ = false;
-
- scoped_refptr<DecoderBuffer> scoped_pending_buffer_to_decode =
- pending_buffer_to_decode_;
-
- pending_buffer_to_decode_ = NULL;
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
- return;
- }
-
- DCHECK_EQ(status == Decryptor::kSuccess, !frames.empty());
-
- if (status == Decryptor::kError) {
- DVLOG(2) << "DoDeliverFrame() - kError";
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- if (status == Decryptor::kNoKey) {
- DVLOG(2) << "DoDeliverFrame() - kNoKey";
- // Set |pending_buffer_to_decode_| back as we need to try decoding the
- // pending buffer again when new key is added to the decryptor.
- pending_buffer_to_decode_ = scoped_pending_buffer_to_decode;
-
- if (need_to_try_again_if_nokey_is_returned) {
- // The |state_| is still kPendingDecode.
- DecodePendingBuffer();
- return;
- }
-
- state_ = kWaitingForKey;
- return;
- }
-
- // The buffer has been accepted by the decoder, let's report statistics.
- if (buffer_size) {
- PipelineStatistics statistics;
- statistics.audio_bytes_decoded = buffer_size;
- statistics_cb_.Run(statistics);
- }
-
- if (status == Decryptor::kNeedMoreData) {
- DVLOG(2) << "DoDeliverFrame() - kNeedMoreData";
- if (scoped_pending_buffer_to_decode->IsEndOfStream()) {
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(
- kOk, scoped_refptr<Buffer>(new DataBuffer(0)));
- return;
- }
-
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
- return;
- }
-
- DCHECK_EQ(status, Decryptor::kSuccess);
- DCHECK(!frames.empty());
- EnqueueFrames(frames);
-
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kOk, queued_audio_frames_.front());
- queued_audio_frames_.pop_front();
-}
-
-void DecryptingAudioDecoder::OnKeyAdded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kPendingDecode) {
- key_added_while_decode_pending_ = true;
- return;
- }
-
- if (state_ == kWaitingForKey) {
- state_ = kPendingDecode;
- DecodePendingBuffer();
- }
-}
-
-void DecryptingAudioDecoder::DoReset() {
- DCHECK(init_cb_.is_null());
- DCHECK(read_cb_.is_null());
- output_timestamp_base_ = kNoTimestamp();
- total_samples_decoded_ = 0;
- state_ = kIdle;
- base::ResetAndReturn(&reset_cb_).Run();
-}
-
-void DecryptingAudioDecoder::EnqueueFrames(
- const Decryptor::AudioBuffers& frames) {
- queued_audio_frames_ = frames;
-
- for (Decryptor::AudioBuffers::iterator iter = queued_audio_frames_.begin();
- iter != queued_audio_frames_.end();
- ++iter) {
- scoped_refptr<Buffer>& frame = *iter;
-
- DCHECK(!frame->IsEndOfStream()) << "EOS frame returned.";
- DCHECK_GT(frame->GetDataSize(), 0) << "Empty frame returned.";
-
- base::TimeDelta cur_timestamp = output_timestamp_base_ +
- NumberOfSamplesToDuration(total_samples_decoded_);
- if (IsOutOfSync(cur_timestamp, frame->GetTimestamp())) {
- DVLOG(1) << "Timestamp returned by the decoder ("
- << frame->GetTimestamp().InMilliseconds() << " ms)"
- << " does not match the input timestamp and number of samples"
- << " decoded (" << cur_timestamp.InMilliseconds() << " ms).";
- }
- frame->SetTimestamp(cur_timestamp);
-
- int frame_size = frame->GetDataSize();
- DCHECK_EQ(frame_size % bytes_per_sample_, 0) <<
- "Decoder didn't output full samples";
- int samples_decoded = frame_size / bytes_per_sample_;
- total_samples_decoded_ += samples_decoded;
-
- base::TimeDelta next_timestamp = output_timestamp_base_ +
- NumberOfSamplesToDuration(total_samples_decoded_);
- base::TimeDelta duration = next_timestamp - cur_timestamp;
- frame->SetDuration(duration);
- }
-}
-
-base::TimeDelta DecryptingAudioDecoder::NumberOfSamplesToDuration(
- int number_of_samples) const {
- DCHECK(samples_per_second_);
- return base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond *
- number_of_samples /
- samples_per_second_);
-}
-
-} // namespace media
diff --git a/src/media/filters/decrypting_audio_decoder.h b/src/media/filters/decrypting_audio_decoder.h
deleted file mode 100644
index 7890d22..0000000
--- a/src/media/filters/decrypting_audio_decoder.h
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_DECRYPTING_AUDIO_DECODER_H_
-#define MEDIA_FILTERS_DECRYPTING_AUDIO_DECODER_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-class Decryptor;
-
-// Decryptor-based AudioDecoder implementation that can decrypt and decode
-// encrypted audio buffers and return decrypted and decompressed audio frames.
-// All public APIs and callbacks are trampolined to the |message_loop_| so
-// that no locks are required for thread safety.
-class MEDIA_EXPORT DecryptingAudioDecoder : public AudioDecoder {
- public:
- DecryptingAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
-
- // AudioDecoder implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
- virtual int bits_per_channel() OVERRIDE;
- virtual ChannelLayout channel_layout() OVERRIDE;
- virtual int samples_per_second() OVERRIDE;
-
- protected:
- virtual ~DecryptingAudioDecoder();
-
- private:
- // For a detailed state diagram please see this link: http://goo.gl/8jAok
- // TODO(xhwang): Add a ASCII state diagram in this file after this class
- // stabilizes.
- // TODO(xhwang): Update this diagram for DecryptingAudioDecoder.
- enum State {
- kUninitialized = 0,
- kDecryptorRequested,
- kPendingDecoderInit,
- kIdle,
- kPendingConfigChange,
- kPendingDemuxerRead,
- kPendingDecode,
- kWaitingForKey,
- kDecodeFinished,
- };
-
- // Carries out the initialization operation scheduled by Initialize().
- void DoInitialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb);
-
- // Callback for DecryptorHost::RequestDecryptor().
- void SetDecryptor(Decryptor* decryptor);
-
- // Callback for Decryptor::InitializeAudioDecoder() during initialization.
- void FinishInitialization(bool success);
-
- // Callback for Decryptor::InitializeAudioDecoder() during config change.
- void FinishConfigChange(bool success);
-
- // Carries out the buffer reading operation scheduled by Read().
- void DoRead(const ReadCB& read_cb);
-
- void ReadFromDemuxerStream();
-
- // Callback for DemuxerStream::Read().
- void DoDecryptAndDecodeBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- void DecodePendingBuffer();
-
- // Callback for Decryptor::DecryptAndDecodeAudio().
- void DeliverFrame(int buffer_size,
- Decryptor::Status status,
- const Decryptor::AudioBuffers& frames);
-
- // Carries out the frame delivery operation scheduled by DeliverFrame().
- void DoDeliverFrame(int buffer_size,
- Decryptor::Status status,
- const Decryptor::AudioBuffers& frames);
-
- // Callback for the |decryptor_| to notify this object that a new key has been
- // added.
- void OnKeyAdded();
-
- // Resets decoder and calls |reset_cb_|.
- void DoReset();
-
- // Sets timestamp and duration for |queued_audio_frames_| to make sure the
- // renderer always receives continuous frames without gaps and overlaps.
- void EnqueueFrames(const Decryptor::AudioBuffers& frames);
-
- // Converts number of samples to duration.
- base::TimeDelta NumberOfSamplesToDuration(int number_of_samples) const;
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- State state_;
-
- PipelineStatusCB init_cb_;
- StatisticsCB statistics_cb_;
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- // Pointer to the demuxer stream that will feed us compressed buffers.
- scoped_refptr<DemuxerStream> demuxer_stream_;
-
- // Callback to request/cancel decryptor creation notification.
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- Decryptor* decryptor_;
-
- // The buffer returned by the demuxer that needs decrypting/decoding.
- scoped_refptr<media::DecoderBuffer> pending_buffer_to_decode_;
-
- // Indicates the situation where new key is added during pending decode
- // (in other words, this variable can only be set in state kPendingDecode).
- // If this variable is true and kNoKey is returned then we need to try
- // decrypting/decoding again in case the newly added key is the correct
- // decryption key.
- bool key_added_while_decode_pending_;
-
- Decryptor::AudioBuffers queued_audio_frames_;
-
- // Decoded audio format.
- int bits_per_channel_;
- ChannelLayout channel_layout_;
- int samples_per_second_;
-
- int bytes_per_sample_;
-
- base::TimeDelta output_timestamp_base_;
- int total_samples_decoded_;
-
- DISALLOW_COPY_AND_ASSIGN(DecryptingAudioDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_DECRYPTING_AUDIO_DECODER_H_
diff --git a/src/media/filters/decrypting_audio_decoder_unittest.cc b/src/media/filters/decrypting_audio_decoder_unittest.cc
deleted file mode 100644
index 90d65c6..0000000
--- a/src/media/filters/decrypting_audio_decoder_unittest.cc
+++ /dev/null
@@ -1,570 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "media/base/buffers.h"
-#include "media/base/data_buffer.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/decrypting_audio_decoder.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-using ::testing::_;
-using ::testing::AtMost;
-using ::testing::IsNull;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const int kFakeAudioFrameSize = 16;
-static const uint8 kFakeKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 };
-static const uint8 kFakeIv[DecryptConfig::kDecryptionKeySize] = { 0 };
-
-// Create a fake non-empty encrypted buffer.
-static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
- const int buffer_size = 16; // Need a non-empty buffer;
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(buffer_size));
- buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(kFakeKeyId),
- arraysize(kFakeKeyId)),
- std::string(reinterpret_cast<const char*>(kFakeIv), arraysize(kFakeIv)),
- 0,
- std::vector<SubsampleEntry>())));
- return buffer;
-}
-
-// Use anonymous namespace here to prevent the actions to be defined multiple
-// times across multiple test files. Sadly we can't use static for them.
-namespace {
-
-ACTION_P(ReturnBuffer, buffer) {
- arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
-}
-
-ACTION_P(RunCallbackIfNotNull, param) {
- if (!arg0.is_null())
- arg0.Run(param);
-}
-
-ACTION_P2(ResetAndRunCallback, callback, param) {
- base::ResetAndReturn(callback).Run(param);
-}
-
-MATCHER(IsEndOfStream, "end of stream") {
- return (arg->IsEndOfStream());
-}
-
-} // namespace
-
-class DecryptingAudioDecoderTest : public testing::Test {
- public:
- DecryptingAudioDecoderTest()
- : decoder_(new DecryptingAudioDecoder(
- message_loop_.message_loop_proxy(),
- base::Bind(
- &DecryptingAudioDecoderTest::RequestDecryptorNotification,
- base::Unretained(this)))),
- decryptor_(new StrictMock<MockDecryptor>()),
- demuxer_(new StrictMock<MockDemuxerStream>()),
- encrypted_buffer_(CreateFakeEncryptedBuffer()),
- decoded_frame_(NULL),
- end_of_stream_frame_(new DataBuffer(0)),
- decoded_frame_list_() {
- scoped_refptr<DataBuffer> data_buffer = new DataBuffer(kFakeAudioFrameSize);
- data_buffer->SetDataSize(kFakeAudioFrameSize);
- // |decoded_frame_| contains random data.
- decoded_frame_ = data_buffer;
- decoded_frame_list_.push_back(decoded_frame_);
- }
-
- void InitializeAndExpectStatus(const AudioDecoderConfig& config,
- PipelineStatus status) {
- EXPECT_CALL(*demuxer_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(config));
-
- decoder_->Initialize(demuxer_, NewExpectedStatusCB(status),
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_)));
- message_loop_.RunUntilIdle();
- }
-
- void Initialize() {
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .Times(AtMost(1))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
- EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kAudio, _))
- .WillOnce(SaveArg<1>(&key_added_cb_));
-
- config_.Initialize(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
- NULL, 0, true, true);
- InitializeAndExpectStatus(config_, PIPELINE_OK);
-
- EXPECT_EQ(config_.bits_per_channel(), decoder_->bits_per_channel());
- EXPECT_EQ(config_.channel_layout(), decoder_->channel_layout());
- EXPECT_EQ(config_.samples_per_second(), decoder_->samples_per_second());
- }
-
- void ReadAndExpectFrameReadyWith(
- AudioDecoder::Status status,
- const scoped_refptr<Buffer>& audio_frame) {
- if (status != AudioDecoder::kOk)
- EXPECT_CALL(*this, FrameReady(status, IsNull()));
- else if (audio_frame->IsEndOfStream())
- EXPECT_CALL(*this, FrameReady(status, IsEndOfStream()));
- else
- EXPECT_CALL(*this, FrameReady(status, audio_frame));
-
- decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- // Sets up expectations and actions to put DecryptingAudioDecoder in an
- // active normal decoding state.
- void EnterNormalDecodingState() {
- Decryptor::AudioBuffers end_of_stream_frames_(1, end_of_stream_frame_);
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(encrypted_buffer_))
- .WillRepeatedly(ReturnBuffer(DecoderBuffer::CreateEOSBuffer()));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillOnce(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_))
- .WillRepeatedly(RunCallback<1>(Decryptor::kNeedMoreData,
- Decryptor::AudioBuffers()));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, decoded_frame_);
- }
-
- // Sets up expectations and actions to put DecryptingAudioDecoder in an end
- // of stream state. This function must be called after
- // EnterNormalDecodingState() to work.
- void EnterEndOfStreamState() {
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, end_of_stream_frame_);
- }
-
- // Make the read callback pending by saving and not firing it.
- void EnterPendingReadState() {
- EXPECT_TRUE(pending_demuxer_read_cb_.is_null());
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&pending_demuxer_read_cb_));
- decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure the Read() on the decoder triggers a Read() on the demuxer.
- EXPECT_FALSE(pending_demuxer_read_cb_.is_null());
- }
-
- // Make the audio decode callback pending by saving and not firing it.
- void EnterPendingDecodeState() {
- EXPECT_TRUE(pending_audio_decode_cb_.is_null());
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(encrypted_buffer_, _))
- .WillOnce(SaveArg<1>(&pending_audio_decode_cb_));
-
- decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure the Read() on the decoder triggers a DecryptAndDecode() on the
- // decryptor.
- EXPECT_FALSE(pending_audio_decode_cb_.is_null());
- }
-
- void EnterWaitingForKeyState() {
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(encrypted_buffer_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kNoKey,
- Decryptor::AudioBuffers()));
- decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- void AbortPendingAudioDecodeCB() {
- if (!pending_audio_decode_cb_.is_null()) {
- base::ResetAndReturn(&pending_audio_decode_cb_).Run(
- Decryptor::kSuccess, Decryptor::AudioBuffers());
- }
- }
-
- void Reset() {
- EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kAudio))
- .WillRepeatedly(InvokeWithoutArgs(
- this, &DecryptingAudioDecoderTest::AbortPendingAudioDecodeCB));
-
- decoder_->Reset(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- MOCK_METHOD1(RequestDecryptorNotification, void(const DecryptorReadyCB&));
-
- MOCK_METHOD2(FrameReady, void(AudioDecoder::Status,
- const scoped_refptr<Buffer>&));
-
- MessageLoop message_loop_;
- scoped_refptr<DecryptingAudioDecoder> decoder_;
- scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
- MockStatisticsCB statistics_cb_;
- AudioDecoderConfig config_;
-
- DemuxerStream::ReadCB pending_demuxer_read_cb_;
- Decryptor::DecoderInitCB pending_init_cb_;
- Decryptor::KeyAddedCB key_added_cb_;
- Decryptor::AudioDecodeCB pending_audio_decode_cb_;
-
- // Constant buffer/frames to be returned by the |demuxer_| and |decryptor_|.
- scoped_refptr<DecoderBuffer> encrypted_buffer_;
- scoped_refptr<Buffer> decoded_frame_;
- scoped_refptr<Buffer> end_of_stream_frame_;
- Decryptor::AudioBuffers decoded_frame_list_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DecryptingAudioDecoderTest);
-};
-
-TEST_F(DecryptingAudioDecoderTest, Initialize_Normal) {
- Initialize();
-}
-
-// Ensure that DecryptingAudioDecoder only accepts encrypted audio.
-TEST_F(DecryptingAudioDecoderTest, Initialize_UnencryptedAudioConfig) {
- AudioDecoderConfig config(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
- NULL, 0, false);
-
- InitializeAndExpectStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Ensure decoder handles invalid audio configs without crashing.
-TEST_F(DecryptingAudioDecoderTest, Initialize_InvalidAudioConfig) {
- AudioDecoderConfig config(kUnknownAudioCodec, 0, CHANNEL_LAYOUT_STEREO, 0,
- NULL, 0, true);
-
- InitializeAndExpectStatus(config, PIPELINE_ERROR_DECODE);
-}
-
-// Ensure decoder handles unsupported audio configs without crashing.
-TEST_F(DecryptingAudioDecoderTest, Initialize_UnsupportedAudioConfig) {
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
-
- AudioDecoderConfig config(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
- NULL, 0, true);
- InitializeAndExpectStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(DecryptingAudioDecoderTest, Initialize_NullDecryptor) {
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillRepeatedly(RunCallbackIfNotNull(static_cast<Decryptor*>(NULL)));
-
- AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
- CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, true);
- InitializeAndExpectStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Test normal decrypt and decode case.
-TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_Normal) {
- Initialize();
- EnterNormalDecodingState();
-}
-
-// Test the case where the decryptor returns error when doing decrypt and
-// decode.
-TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_DecodeError) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kError,
- Decryptor::AudioBuffers()));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kDecodeError, NULL);
-}
-
-// Test the case where the decryptor returns kNeedMoreData to ask for more
-// buffers before it can produce a frame.
-TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_NeedMoreData) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .Times(2)
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillOnce(RunCallback<1>(Decryptor::kNeedMoreData,
- Decryptor::AudioBuffers()))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_))
- .Times(2);
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, decoded_frame_);
-}
-
-// Test the case where the decryptor returns multiple decoded frames.
-TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_MultipleFrames) {
- Initialize();
-
- scoped_refptr<DataBuffer> frame_a = new DataBuffer(kFakeAudioFrameSize);
- frame_a->SetDataSize(kFakeAudioFrameSize);
- scoped_refptr<DataBuffer> frame_b = new DataBuffer(kFakeAudioFrameSize);
- frame_b->SetDataSize(kFakeAudioFrameSize);
- decoded_frame_list_.push_back(frame_a);
- decoded_frame_list_.push_back(frame_b);
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillOnce(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, decoded_frame_);
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, frame_a);
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, frame_b);
-}
-
-// Test the case where the decryptor receives end-of-stream buffer.
-TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_EndOfStream) {
- Initialize();
- EnterNormalDecodingState();
- EnterEndOfStreamState();
-}
-
-// Test aborted read on the demuxer stream.
-TEST_F(DecryptingAudioDecoderTest, DemuxerRead_Aborted) {
- Initialize();
-
- // ReturnBuffer() with NULL triggers aborted demuxer read.
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>()));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kAborted, NULL);
-}
-
-// Test config change on the demuxer stream.
-TEST_F(DecryptingAudioDecoderTest, DemuxerRead_ConfigChange) {
- Initialize();
-
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kOk, decoded_frame_);
-}
-
-// Test config change failure.
-TEST_F(DecryptingAudioDecoderTest, DemuxerRead_ConfigChangeFailed) {
- Initialize();
-
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
-
- ReadAndExpectFrameReadyWith(AudioDecoder::kDecodeError, NULL);
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kWaitingForKey state.
-TEST_F(DecryptingAudioDecoderTest, KeyAdded_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kOk, decoded_frame_));
- key_added_cb_.Run();
- message_loop_.RunUntilIdle();
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kPendingDecode state.
-TEST_F(DecryptingAudioDecoderTest, KeyAdded_DruingPendingDecode) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kOk, decoded_frame_));
- // The audio decode callback is returned after the correct decryption key is
- // added.
- key_added_cb_.Run();
- base::ResetAndReturn(&pending_audio_decode_cb_).Run(
- Decryptor::kNoKey, Decryptor::AudioBuffers());
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kIdle state but has not decoded any
-// frame.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringIdleAfterInitialization) {
- Initialize();
- Reset();
-}
-
-// Test resetting when the decoder is in kIdle state after it has decoded one
-// frame.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringIdleAfterDecodedOneFrame) {
- Initialize();
- EnterNormalDecodingState();
- Reset();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kOk.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringDemuxerRead_Ok) {
- Initialize();
- EnterPendingReadState();
-
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kOk,
- encrypted_buffer_);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kAborted.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringDemuxerRead_Aborted) {
- Initialize();
- EnterPendingReadState();
-
- // Make sure we get a NULL audio frame returned.
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kAborted,
- NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kConfigChanged.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringDemuxerRead_ConfigChange) {
- Initialize();
- EnterPendingReadState();
-
- Reset();
-
- // Even during pending reset, the decoder still needs to be initialized with
- // the new config.
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- base::ResetAndReturn(&pending_demuxer_read_cb_)
- .Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state, the read
-// callback is returned with kConfigChanged and the config change fails.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringDemuxerRead_ConfigChangeFailed) {
- Initialize();
- EnterPendingReadState();
-
- Reset();
-
- // Even during pending reset, the decoder still needs to be initialized with
- // the new config.
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kDecodeError, IsNull()));
-
- base::ResetAndReturn(&pending_demuxer_read_cb_)
- .Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingConfigChange state.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringPendingConfigChange) {
- Initialize();
- EnterNormalDecodingState();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()));
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
- EXPECT_CALL(*decryptor_, InitializeAudioDecoderMock(_, _))
- .WillOnce(SaveArg<1>(&pending_init_cb_));
-
- decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- EXPECT_FALSE(pending_init_cb_.is_null());
-
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_init_cb_).Run(true);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDecode state.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringPendingDecode) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- Reset();
-}
-
-// Test resetting when the decoder is in kWaitingForKey state.
-TEST_F(DecryptingAudioDecoderTest, Reset_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*this, FrameReady(AudioDecoder::kAborted, IsNull()));
-
- Reset();
-}
-
-// Test resetting when the decoder has hit end of stream and is in
-// kDecodeFinished state.
-TEST_F(DecryptingAudioDecoderTest, Reset_AfterDecodeFinished) {
- Initialize();
- EnterNormalDecodingState();
- EnterEndOfStreamState();
- Reset();
-}
-
-// Test resetting after the decoder has been reset.
-TEST_F(DecryptingAudioDecoderTest, Reset_AfterReset) {
- Initialize();
- EnterNormalDecodingState();
- Reset();
- Reset();
-}
-
-} // namespace media
diff --git a/src/media/filters/decrypting_demuxer_stream.cc b/src/media/filters/decrypting_demuxer_stream.cc
deleted file mode 100644
index f9803ca..0000000
--- a/src/media/filters/decrypting_demuxer_stream.cc
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/decrypting_demuxer_stream.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_decoder_config.h"
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/shell_media_statistics.h"
-#endif
-
-namespace media {
-
-#define BIND_TO_LOOP(function) \
- media::BindToLoop(message_loop_, base::Bind(function, this))
-
-static bool IsStreamValidAndEncrypted(
- const scoped_refptr<DemuxerStream>& stream) {
- return ((stream->type() == DemuxerStream::AUDIO &&
- stream->audio_decoder_config().IsValidConfig() &&
- stream->audio_decoder_config().is_encrypted()) ||
- (stream->type() == DemuxerStream::VIDEO &&
- stream->video_decoder_config().IsValidConfig() &&
- stream->video_decoder_config().is_encrypted()));
-}
-
-DecryptingDemuxerStream::DecryptingDemuxerStream(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : message_loop_(message_loop),
- state_(kUninitialized),
- stream_type_(UNKNOWN),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- decryptor_(NULL),
- key_added_while_decrypt_pending_(false) {
-}
-
-void DecryptingDemuxerStream::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingDemuxerStream::DoInitialize, this, stream, status_cb));
- return;
- }
- DoInitialize(stream, status_cb);
-}
-
-void DecryptingDemuxerStream::Read(const ReadCB& read_cb) {
- DVLOG(3) << "Read()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kIdle) << state_;
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping reads are not supported.";
-
- read_cb_ = read_cb;
- state_ = kPendingDemuxerRead;
- demuxer_stream_->Read(
- base::Bind(&DecryptingDemuxerStream::DecryptBuffer, this));
-}
-
-void DecryptingDemuxerStream::Reset(const base::Closure& closure) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingDemuxerStream::Reset, this, closure));
- return;
- }
-
- DVLOG(2) << "Reset() - state: " << state_;
- DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
- DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
- DCHECK(reset_cb_.is_null());
-
- reset_cb_ = BindToCurrentLoop(closure);
-
- decryptor_->CancelDecrypt(GetDecryptorStreamType());
-
- // Reset() cannot complete if the read callback is still pending.
- // Defer the resetting process in this case. The |reset_cb_| will be fired
- // after the read callback is fired - see DoDecryptBuffer() and
- // DoDeliverBuffer().
- if (state_ == kPendingDemuxerRead || state_ == kPendingDecrypt) {
- DCHECK(!read_cb_.is_null());
- return;
- }
-
- if (state_ == kWaitingForKey) {
- DCHECK(!read_cb_.is_null());
- pending_buffer_to_decrypt_ = NULL;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- }
-
- DCHECK(read_cb_.is_null());
- DoReset();
-}
-
-const AudioDecoderConfig& DecryptingDemuxerStream::audio_decoder_config() {
- DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
- CHECK_EQ(stream_type_, AUDIO);
- return *audio_config_;
-}
-
-const VideoDecoderConfig& DecryptingDemuxerStream::video_decoder_config() {
- DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
- CHECK_EQ(stream_type_, VIDEO);
- return *video_config_;
-}
-
-DemuxerStream::Type DecryptingDemuxerStream::type() {
- DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
- return stream_type_;
-}
-
-void DecryptingDemuxerStream::EnableBitstreamConverter() {
- demuxer_stream_->EnableBitstreamConverter();
-}
-
-DecryptingDemuxerStream::~DecryptingDemuxerStream() {}
-
-void DecryptingDemuxerStream::DoInitialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb) {
- DVLOG(2) << "DoInitialize()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kUninitialized) << state_;
-
- DCHECK(!demuxer_stream_);
- demuxer_stream_ = stream;
- stream_type_ = stream->type();
- init_cb_ = status_cb;
-
- SetDecoderConfig(stream);
-
- state_ = kDecryptorRequested;
- set_decryptor_ready_cb_.Run(
- BIND_TO_LOOP(&DecryptingDemuxerStream::SetDecryptor));
-}
-
-void DecryptingDemuxerStream::SetDecryptor(Decryptor* decryptor) {
- DVLOG(2) << "SetDecryptor()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kDecryptorRequested) << state_;
- DCHECK(!init_cb_.is_null());
- DCHECK(!set_decryptor_ready_cb_.is_null());
-
- set_decryptor_ready_cb_.Reset();
-
- if (!decryptor) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- state_ = kUninitialized;
- return;
- }
-
- decryptor_ = decryptor;
-
- decryptor_->RegisterKeyAddedCB(
- GetDecryptorStreamType(),
- BIND_TO_LOOP(&DecryptingDemuxerStream::OnKeyAdded));
-
- state_ = kIdle;
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-void DecryptingDemuxerStream::DecryptBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- // In theory, we don't need to force post the task here, because we do a
- // force task post in DeliverBuffer(). Therefore, even if
- // demuxer_stream_->Read() execute the read callback on the same execution
- // stack we are still fine. But it looks like a force post task makes the
- // logic more understandable and manageable, so why not?
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingDemuxerStream::DoDecryptBuffer, this, status, buffer));
-}
-
-void DecryptingDemuxerStream::DoDecryptBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- DVLOG(3) << "DoDecryptBuffer()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
- DCHECK(!read_cb_.is_null());
- DCHECK_EQ(buffer != NULL, status == kOk) << status;
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
- return;
- }
-
- if (status == kAborted) {
- DVLOG(2) << "DoDecryptBuffer() - kAborted.";
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- return;
- }
-
- if (status == kConfigChanged) {
- DVLOG(2) << "DoDecryptBuffer() - kConfigChanged.";
- DCHECK_EQ(demuxer_stream_->type(), stream_type_);
-
- // Update the decoder config, which the decoder will use when it is notified
- // of kConfigChanged.
- SetDecoderConfig(demuxer_stream_);
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kConfigChanged, NULL);
- return;
- }
-
- if (buffer->IsEndOfStream()) {
- DVLOG(2) << "DoDecryptBuffer() - EOS buffer.";
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(status, buffer);
- return;
- }
-
- pending_buffer_to_decrypt_ = buffer;
- state_ = kPendingDecrypt;
- DecryptPendingBuffer();
-}
-
-void DecryptingDemuxerStream::DecryptPendingBuffer() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecrypt) << state_;
-#if defined(__LB_SHELL__) || defined(COBALT)
- decrypting_start_ = base::Time::Now();
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- decryptor_->Decrypt(
- GetDecryptorStreamType(),
- pending_buffer_to_decrypt_,
- base::Bind(&DecryptingDemuxerStream::DeliverBuffer, this));
-}
-
-void DecryptingDemuxerStream::DeliverBuffer(
- Decryptor::Status status,
- const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_DECRYPT,
- (base::Time::Now() - decrypting_start_).ToInternalValue());
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- // We need to force task post here because the DecryptCB can be executed
- // synchronously in Reset(). Instead of using more complicated logic in
- // those function to fix it, why not force task post here to make everything
- // simple and clear?
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &DecryptingDemuxerStream::DoDeliverBuffer, this,
- status, decrypted_buffer));
-}
-
-void DecryptingDemuxerStream::DoDeliverBuffer(
- Decryptor::Status status,
- const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
- DVLOG(3) << "DoDeliverBuffer() - status: " << status;
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecrypt) << state_;
- DCHECK_NE(status, Decryptor::kNeedMoreData);
- DCHECK(!read_cb_.is_null());
- DCHECK(pending_buffer_to_decrypt_);
-
- bool need_to_try_again_if_nokey = key_added_while_decrypt_pending_;
- key_added_while_decrypt_pending_ = false;
-
- if (!reset_cb_.is_null()) {
- pending_buffer_to_decrypt_ = NULL;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- DoReset();
- return;
- }
-
- DCHECK_EQ(status == Decryptor::kSuccess, decrypted_buffer.get() != NULL);
-
- if (status == Decryptor::kError) {
- DVLOG(2) << "DoDeliverBuffer() - kError";
- pending_buffer_to_decrypt_ = NULL;
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- return;
- }
-
- if (status == Decryptor::kNoKey) {
- DVLOG(2) << "DoDeliverBuffer() - kNoKey";
- if (need_to_try_again_if_nokey) {
- // The |state_| is still kPendingDecrypt.
- DecryptPendingBuffer();
- return;
- }
-
- state_ = kWaitingForKey;
- return;
- }
-
- DCHECK_EQ(status, Decryptor::kSuccess);
- pending_buffer_to_decrypt_ = NULL;
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kOk, decrypted_buffer);
-}
-
-void DecryptingDemuxerStream::OnKeyAdded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kPendingDecrypt) {
- key_added_while_decrypt_pending_ = true;
- return;
- }
-
- if (state_ == kWaitingForKey) {
- state_ = kPendingDecrypt;
- DecryptPendingBuffer();
- }
-}
-
-void DecryptingDemuxerStream::DoReset() {
- DCHECK(init_cb_.is_null());
- DCHECK(read_cb_.is_null());
- state_ = kIdle;
- base::ResetAndReturn(&reset_cb_).Run();
-}
-
-Decryptor::StreamType DecryptingDemuxerStream::GetDecryptorStreamType() const {
- DCHECK(stream_type_ == AUDIO || stream_type_ == VIDEO);
- return stream_type_ == AUDIO ? Decryptor::kAudio : Decryptor::kVideo;
-}
-
-void DecryptingDemuxerStream::SetDecoderConfig(
- const scoped_refptr<DemuxerStream>& stream) {
- // The decoder selector or upstream demuxer make sure the stream is valid and
- // potentially encrypted.
- DCHECK(IsStreamValidAndEncrypted(stream));
-
- switch (stream_type_) {
- case AUDIO: {
- const AudioDecoderConfig& input_audio_config =
- stream->audio_decoder_config();
- audio_config_.reset(new AudioDecoderConfig());
- audio_config_->Initialize(input_audio_config.codec(),
- input_audio_config.bits_per_channel(),
- input_audio_config.channel_layout(),
- input_audio_config.samples_per_second(),
- input_audio_config.extra_data(),
- input_audio_config.extra_data_size(),
- false, // Output audio is not encrypted.
- false);
- break;
- }
-
- case VIDEO: {
- const VideoDecoderConfig& input_video_config =
- stream->video_decoder_config();
- video_config_.reset(new VideoDecoderConfig());
- video_config_->Initialize(
- input_video_config.codec(), input_video_config.profile(),
- input_video_config.format(), input_video_config.color_space(),
- input_video_config.coded_size(), input_video_config.visible_rect(),
- input_video_config.natural_size(), input_video_config.extra_data(),
- input_video_config.extra_data_size(),
- false, // Output video is not encrypted.
- false);
- video_config_->set_webm_color_metadata(
- input_video_config.webm_color_metadata());
- break;
- }
-
- default:
- NOTREACHED();
- return;
- }
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-bool DecryptingDemuxerStream::StreamWasEncrypted() const {
- DCHECK(demuxer_stream_->StreamWasEncrypted());
- return true;
-}
-#endif
-
-} // namespace media
diff --git a/src/media/filters/decrypting_demuxer_stream.h b/src/media/filters/decrypting_demuxer_stream.h
deleted file mode 100644
index 94b019d..0000000
--- a/src/media/filters/decrypting_demuxer_stream.h
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
-#define MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "base/time.h"
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-
-// Decryptor-based DemuxerStream implementation that converts a potentially
-// encrypted demuxer stream to a clear demuxer stream.
-// All public APIs and callbacks are trampolined to the |message_loop_| so
-// that no locks are required for thread safety.
-class MEDIA_EXPORT DecryptingDemuxerStream : public DemuxerStream {
- public:
- DecryptingDemuxerStream(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
-
- void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb);
- void Reset(const base::Closure& closure);
-
- // DemuxerStream implementation.
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
- virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
- virtual Type type() OVERRIDE;
- virtual void EnableBitstreamConverter() OVERRIDE;
-#if defined(__LB_SHELL__) || defined(COBALT)
- virtual bool StreamWasEncrypted() const OVERRIDE;
- virtual Decryptor* GetDecryptor() const OVERRIDE { return decryptor_; }
-#endif
-
- protected:
- virtual ~DecryptingDemuxerStream();
-
- private:
- // For a detailed state diagram please see this link: http://goo.gl/8jAok
- // TODO(xhwang): Add a ASCII state diagram in this file after this class
- // stabilizes.
- // TODO(xhwang): Update this diagram for DecryptingDemuxerStream.
- enum State {
- kUninitialized = 0,
- kDecryptorRequested,
- kIdle,
- kPendingDemuxerRead,
- kPendingDecrypt,
- kWaitingForKey,
- };
-
- // Carries out the initialization operation scheduled by Initialize().
- void DoInitialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb);
-
- // Callback for DecryptorHost::RequestDecryptor().
- void SetDecryptor(Decryptor* decryptor);
-
- // Callback for DemuxerStream::Read().
- void DecryptBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- // Carries out the buffer decryption operation scheduled by DecryptBuffer().
- void DoDecryptBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- void DecryptPendingBuffer();
-
- // Callback for Decryptor::Decrypt().
- void DeliverBuffer(Decryptor::Status status,
- const scoped_refptr<DecoderBuffer>& decrypted_buffer);
-
- // Carries out the frame delivery operation scheduled by DeliverBuffer().
- void DoDeliverBuffer(Decryptor::Status status,
- const scoped_refptr<DecoderBuffer>& decrypted_buffer);
-
- // Callback for the |decryptor_| to notify this object that a new key has been
- // added.
- void OnKeyAdded();
-
- // Resets decoder and calls |reset_cb_|.
- void DoReset();
-
- // Returns Decryptor::StreamType converted from |stream_type_|.
- Decryptor::StreamType GetDecryptorStreamType() const;
-
- // Sets |{audio|video}_config_| from |stream|.
- void SetDecoderConfig(const scoped_refptr<DemuxerStream>& stream);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- State state_;
-
- PipelineStatusCB init_cb_;
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- // Pointer to the input demuxer stream that will feed us encrypted buffers.
- scoped_refptr<DemuxerStream> demuxer_stream_;
-
- Type stream_type_;
- scoped_ptr<AudioDecoderConfig> audio_config_;
- scoped_ptr<VideoDecoderConfig> video_config_;
-
- // Callback to request/cancel decryptor creation notification.
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- Decryptor* decryptor_;
-
- // The buffer returned by the demuxer that needs to be decrypted.
- scoped_refptr<media::DecoderBuffer> pending_buffer_to_decrypt_;
-
- // Indicates the situation where new key is added during pending decryption
- // (in other words, this variable can only be set in state kPendingDecrypt).
- // If this variable is true and kNoKey is returned then we need to try
- // decrypting again in case the newly added key is the correct decryption key.
- bool key_added_while_decrypt_pending_;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- base::Time decrypting_start_;
-#endif // defined(__LB_SHELL__)
-
- DISALLOW_COPY_AND_ASSIGN(DecryptingDemuxerStream);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
diff --git a/src/media/filters/decrypting_demuxer_stream_unittest.cc b/src/media/filters/decrypting_demuxer_stream_unittest.cc
deleted file mode 100644
index e2da55b..0000000
--- a/src/media/filters/decrypting_demuxer_stream_unittest.cc
+++ /dev/null
@@ -1,434 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-using ::testing::_;
-using ::testing::IsNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const int kFakeBufferSize = 16;
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(320, 240);
-static const gfx::Rect kVisibleRect(320, 240);
-static const gfx::Size kNaturalSize(320, 240);
-static const uint8 kFakeKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 };
-static const uint8 kFakeIv[DecryptConfig::kDecryptionKeySize] = { 0 };
-
-// Create a fake non-empty encrypted buffer.
-static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(kFakeBufferSize));
- buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(kFakeKeyId),
- arraysize(kFakeKeyId)),
- std::string(reinterpret_cast<const char*>(kFakeIv), arraysize(kFakeIv)),
- 0,
- std::vector<SubsampleEntry>())));
- return buffer;
-}
-
-// Use anonymous namespace here to prevent the actions to be defined multiple
-// times across multiple test files. Sadly we can't use static for them.
-namespace {
-
-ACTION_P(ReturnBuffer, buffer) {
- arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
-}
-
-ACTION_P(RunCallbackIfNotNull, param) {
- if (!arg0.is_null())
- arg0.Run(param);
-}
-
-ACTION_P2(ResetAndRunCallback, callback, param) {
- base::ResetAndReturn(callback).Run(param);
-}
-
-MATCHER(IsEndOfStream, "end of stream") {
- return (arg->IsEndOfStream());
-}
-
-} // namespace
-
-class DecryptingDemuxerStreamTest : public testing::Test {
- public:
- DecryptingDemuxerStreamTest()
- : demuxer_stream_(new DecryptingDemuxerStream(
- message_loop_.message_loop_proxy(),
- base::Bind(
- &DecryptingDemuxerStreamTest::RequestDecryptorNotification,
- base::Unretained(this)))),
- decryptor_(new StrictMock<MockDecryptor>()),
- input_audio_stream_(new StrictMock<MockDemuxerStream>()),
- input_video_stream_(new StrictMock<MockDemuxerStream>()),
- encrypted_buffer_(CreateFakeEncryptedBuffer()),
- decrypted_buffer_(new DecoderBuffer(kFakeBufferSize)) {
- }
-
- void InitializeAudioAndExpectStatus(const AudioDecoderConfig& config,
- PipelineStatus status) {
- EXPECT_CALL(*input_audio_stream_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(config));
- EXPECT_CALL(*input_audio_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::AUDIO));
-
- demuxer_stream_->Initialize(input_audio_stream_,
- NewExpectedStatusCB(status));
- message_loop_.RunUntilIdle();
- }
-
- void InitializeVideoAndExpectStatus(const VideoDecoderConfig& config,
- PipelineStatus status) {
- EXPECT_CALL(*input_video_stream_, video_decoder_config())
- .WillRepeatedly(ReturnRef(config));
- EXPECT_CALL(*input_video_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::VIDEO));
-
- demuxer_stream_->Initialize(input_video_stream_,
- NewExpectedStatusCB(status));
- message_loop_.RunUntilIdle();
- }
-
- // The following functions are used to test stream-type-neutral logic in
- // DecryptingDemuxerStream. Therefore, we don't specify audio or video in the
- // function names. But for testing purpose, they all use an audio input
- // demuxer stream.
-
- void Initialize() {
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
- EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kAudio, _))
- .WillOnce(SaveArg<1>(&key_added_cb_));
-
- AudioDecoderConfig input_config(
- kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, true);
- InitializeAudioAndExpectStatus(input_config, PIPELINE_OK);
-
- const AudioDecoderConfig& output_config =
- demuxer_stream_->audio_decoder_config();
- EXPECT_EQ(DemuxerStream::AUDIO, demuxer_stream_->type());
- EXPECT_FALSE(output_config.is_encrypted());
- EXPECT_EQ(16, output_config.bits_per_channel());
- EXPECT_EQ(CHANNEL_LAYOUT_STEREO, output_config.channel_layout());
- EXPECT_EQ(44100, output_config.samples_per_second());
- }
-
- void ReadAndExpectBufferReadyWith(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
- if (status != DemuxerStream::kOk)
- EXPECT_CALL(*this, BufferReady(status, IsNull()));
- else if (decrypted_buffer->IsEndOfStream())
- EXPECT_CALL(*this, BufferReady(status, IsEndOfStream()));
- else
- EXPECT_CALL(*this, BufferReady(status, decrypted_buffer));
-
- demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- // Sets up expectations and actions to put DecryptingDemuxerStream in an
- // active normal reading state.
- void EnterNormalReadingState() {
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillOnce(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, Decrypt(_, _, _))
- .WillOnce(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
-
- ReadAndExpectBufferReadyWith(DemuxerStream::kOk, decrypted_buffer_);
- }
-
- // Make the read callback pending by saving and not firing it.
- void EnterPendingReadState() {
- EXPECT_TRUE(pending_demuxer_read_cb_.is_null());
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillOnce(SaveArg<0>(&pending_demuxer_read_cb_));
- demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure the Read() triggers a Read() on the input demuxer stream.
- EXPECT_FALSE(pending_demuxer_read_cb_.is_null());
- }
-
- // Make the decrypt callback pending by saving and not firing it.
- void EnterPendingDecryptState() {
- EXPECT_TRUE(pending_decrypt_cb_.is_null());
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
- .WillOnce(SaveArg<2>(&pending_decrypt_cb_));
-
- demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure Read() triggers a Decrypt() on the decryptor.
- EXPECT_FALSE(pending_decrypt_cb_.is_null());
- }
-
- void EnterWaitingForKeyState() {
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
- .WillRepeatedly(RunCallback<2>(Decryptor::kNoKey,
- scoped_refptr<DecoderBuffer>()));
- demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- void AbortPendingDecryptCB() {
- if (!pending_decrypt_cb_.is_null()) {
- base::ResetAndReturn(&pending_decrypt_cb_).Run(Decryptor::kSuccess, NULL);
- }
- }
-
- void Reset() {
- EXPECT_CALL(*decryptor_, CancelDecrypt(Decryptor::kAudio))
- .WillRepeatedly(InvokeWithoutArgs(
- this, &DecryptingDemuxerStreamTest::AbortPendingDecryptCB));
-
- demuxer_stream_->Reset(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- MOCK_METHOD1(RequestDecryptorNotification, void(const DecryptorReadyCB&));
-
- MOCK_METHOD2(BufferReady, void(DemuxerStream::Status,
- const scoped_refptr<DecoderBuffer>&));
-
- MessageLoop message_loop_;
- scoped_refptr<DecryptingDemuxerStream> demuxer_stream_;
- scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
- scoped_refptr<StrictMock<MockDemuxerStream> > input_audio_stream_;
- scoped_refptr<StrictMock<MockDemuxerStream> > input_video_stream_;
-
- DemuxerStream::ReadCB pending_demuxer_read_cb_;
- Decryptor::KeyAddedCB key_added_cb_;
- Decryptor::DecryptCB pending_decrypt_cb_;
-
- // Constant buffers to be returned by the input demuxer streams and the
- // |decryptor_|.
- scoped_refptr<DecoderBuffer> encrypted_buffer_;
- scoped_refptr<DecoderBuffer> decrypted_buffer_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DecryptingDemuxerStreamTest);
-};
-
-TEST_F(DecryptingDemuxerStreamTest, Initialize_NormalAudio) {
- Initialize();
-}
-
-TEST_F(DecryptingDemuxerStreamTest, Initialize_NormalVideo) {
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
- EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kVideo, _))
- .WillOnce(SaveArg<1>(&key_added_cb_));
-
- VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true);
- InitializeVideoAndExpectStatus(config, PIPELINE_OK);
-
- const VideoDecoderConfig& output_config =
- demuxer_stream_->video_decoder_config();
- EXPECT_EQ(DemuxerStream::VIDEO, demuxer_stream_->type());
- EXPECT_FALSE(output_config.is_encrypted());
- EXPECT_EQ(kCodecVP8, output_config.codec());
- EXPECT_EQ(kVideoFormat, output_config.format());
- EXPECT_EQ(VIDEO_CODEC_PROFILE_UNKNOWN, output_config.profile());
- EXPECT_EQ(kCodedSize, output_config.coded_size());
- EXPECT_EQ(kVisibleRect, output_config.visible_rect());
- EXPECT_EQ(kNaturalSize, output_config.natural_size());
- EXPECT_FALSE(output_config.extra_data());
- EXPECT_EQ(0u, output_config.extra_data_size());
-}
-
-TEST_F(DecryptingDemuxerStreamTest, Initialize_NullDecryptor) {
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillRepeatedly(RunCallbackIfNotNull(static_cast<Decryptor*>(NULL)));
-
- AudioDecoderConfig input_config(kCodecVorbis, kSampleFormatPlanarF32,
- CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, true);
- InitializeAudioAndExpectStatus(input_config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Test normal read case.
-TEST_F(DecryptingDemuxerStreamTest, Read_Normal) {
- Initialize();
- EnterNormalReadingState();
-}
-
-// Test the case where the decryptor returns error during read.
-TEST_F(DecryptingDemuxerStreamTest, Read_DecryptError) {
- Initialize();
-
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
- .WillRepeatedly(RunCallback<2>(Decryptor::kError,
- scoped_refptr<DecoderBuffer>()));
- ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
-}
-
-// Test the case where the input is an end-of-stream buffer.
-TEST_F(DecryptingDemuxerStreamTest, Read_EndOfStream) {
- Initialize();
- EnterNormalReadingState();
-
- // No Decryptor::Decrypt() call is expected for EOS buffer.
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillOnce(ReturnBuffer(DecoderBuffer::CreateEOSBuffer()));
-
- ReadAndExpectBufferReadyWith(DemuxerStream::kOk,
- DecoderBuffer::CreateEOSBuffer());
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kWaitingForKey state.
-TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
- .WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
- key_added_cb_.Run();
- message_loop_.RunUntilIdle();
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kPendingDecrypt state.
-TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DruingPendingDecrypt) {
- Initialize();
- EnterPendingDecryptState();
-
- EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
- .WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
- // The decrypt callback is returned after the correct decryption key is added.
- key_added_cb_.Run();
- base::ResetAndReturn(&pending_decrypt_cb_).Run(Decryptor::kNoKey, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the DecryptingDemuxerStream is in kIdle state but has
-// not returned any buffer.
-TEST_F(DecryptingDemuxerStreamTest, Reset_DuringIdleAfterInitialization) {
- Initialize();
- Reset();
-}
-
-// Test resetting when the DecryptingDemuxerStream is in kIdle state after it
-// has returned one buffer.
-TEST_F(DecryptingDemuxerStreamTest, Reset_DuringIdleAfterReadOneBuffer) {
- Initialize();
- EnterNormalReadingState();
- Reset();
-}
-
-// Test resetting when DecryptingDemuxerStream is in kPendingDemuxerRead state.
-TEST_F(DecryptingDemuxerStreamTest, Reset_DuringPendingDemuxerRead) {
- Initialize();
- EnterPendingReadState();
-
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kOk,
- encrypted_buffer_);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the DecryptingDemuxerStream is in kPendingDecrypt state.
-TEST_F(DecryptingDemuxerStreamTest, Reset_DuringPendingDecrypt) {
- Initialize();
- EnterPendingDecryptState();
-
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
-
- Reset();
-}
-
-// Test resetting when the DecryptingDemuxerStream is in kWaitingForKey state.
-TEST_F(DecryptingDemuxerStreamTest, Reset_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
-
- Reset();
-}
-
-// Test resetting after the DecryptingDemuxerStream has been reset.
-TEST_F(DecryptingDemuxerStreamTest, Reset_AfterReset) {
- Initialize();
- EnterNormalReadingState();
- Reset();
- Reset();
-}
-
-// Test aborted read on the demuxer stream.
-TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_Aborted) {
- Initialize();
-
- // ReturnBuffer() with NULL triggers aborted demuxer read.
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>()));
-
- ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
-}
-
-// Test aborted read on the input demuxer stream when the
-// DecryptingDemuxerStream is being reset.
-TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_AbortedDuringReset) {
- Initialize();
- EnterPendingReadState();
-
- // Make sure we get a NULL audio frame returned.
- EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kAborted,
- NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test config change on the input demuxer stream.
-TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_ConfigChanged) {
- Initialize();
-
- AudioDecoderConfig new_config(
- kCodecVorbis, 32, CHANNEL_LAYOUT_STEREO, 88200, NULL, 0, true);
-
- EXPECT_CALL(*input_audio_stream_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(new_config));
-
- EXPECT_CALL(*input_audio_stream_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()));
-
- ReadAndExpectBufferReadyWith(DemuxerStream::kConfigChanged, NULL);
-}
-
-} // namespace media
diff --git a/src/media/filters/decrypting_video_decoder.cc b/src/media/filters/decrypting_video_decoder.cc
deleted file mode 100644
index 85db1c6..0000000
--- a/src/media/filters/decrypting_video_decoder.cc
+++ /dev/null
@@ -1,407 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/decrypting_video_decoder.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-
-namespace media {
-
-DecryptingVideoDecoder::DecryptingVideoDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : message_loop_(message_loop),
- state_(kUninitialized),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- decryptor_(NULL),
- key_added_while_decode_pending_(false),
- trace_id_(0) {
-}
-
-void DecryptingVideoDecoder::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- DVLOG(2) << "Initialize()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kUninitialized) << state_;
- DCHECK(stream);
- init_cb_ = BindToCurrentLoop(status_cb);
-
- const VideoDecoderConfig& config = stream->video_decoder_config();
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid video stream config: "
- << config.AsHumanReadableString();
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_DECODE);
- return;
- }
-
- // DecryptingVideoDecoder only accepts potentially encrypted stream.
- if (!config.is_encrypted()) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- DCHECK(!demuxer_stream_);
- demuxer_stream_ = stream;
- statistics_cb_ = statistics_cb;
-
- state_ = kDecryptorRequested;
- set_decryptor_ready_cb_.Run(BindToCurrentLoop(base::Bind(
- &DecryptingVideoDecoder::SetDecryptor, this)));
-}
-
-void DecryptingVideoDecoder::Read(const ReadCB& read_cb) {
- DVLOG(3) << "Read()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(state_ == kIdle || state_ == kDecodeFinished) << state_;
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
- read_cb_ = BindToCurrentLoop(read_cb);
-
- // Return empty frames if decoding has finished.
- if (state_ == kDecodeFinished) {
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
-}
-
-void DecryptingVideoDecoder::Reset(const base::Closure& closure) {
- DVLOG(2) << "Reset() - state: " << state_;
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(state_ == kIdle ||
- state_ == kPendingConfigChange ||
- state_ == kPendingDemuxerRead ||
- state_ == kPendingDecode ||
- state_ == kWaitingForKey ||
- state_ == kDecodeFinished) << state_;
- DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
- DCHECK(reset_cb_.is_null());
-
- reset_cb_ = BindToCurrentLoop(closure);
-
- decryptor_->ResetDecoder(Decryptor::kVideo);
-
- // Reset() cannot complete if the read callback is still pending.
- // Defer the resetting process in this case. The |reset_cb_| will be fired
- // after the read callback is fired - see DecryptAndDecodeBuffer() and
- // DeliverFrame().
- if (state_ == kPendingConfigChange ||
- state_ == kPendingDemuxerRead ||
- state_ == kPendingDecode) {
- DCHECK(!read_cb_.is_null());
- return;
- }
-
- if (state_ == kWaitingForKey) {
- DCHECK(!read_cb_.is_null());
- pending_buffer_to_decode_ = NULL;
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- }
-
- DCHECK(read_cb_.is_null());
- DoReset();
-}
-
-void DecryptingVideoDecoder::Stop(const base::Closure& closure) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DVLOG(2) << "Stop() - state: " << state_;
-
- // At this point the render thread is likely paused (in WebMediaPlayerImpl's
- // Destroy()), so running |closure| can't wait for anything that requires the
- // render thread to be processing messages to complete (such as PPAPI
- // callbacks).
- if (decryptor_) {
- decryptor_->RegisterKeyAddedCB(Decryptor::kVideo, Decryptor::KeyAddedCB());
- decryptor_->DeinitializeDecoder(Decryptor::kVideo);
- decryptor_ = NULL;
- }
- if (!set_decryptor_ready_cb_.is_null())
- base::ResetAndReturn(&set_decryptor_ready_cb_).Run(DecryptorReadyCB());
- pending_buffer_to_decode_ = NULL;
- if (!init_cb_.is_null())
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- if (!read_cb_.is_null())
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- if (!reset_cb_.is_null())
- base::ResetAndReturn(&reset_cb_).Run();
- state_ = kStopped;
- BindToCurrentLoop(closure).Run();
-}
-
-DecryptingVideoDecoder::~DecryptingVideoDecoder() {
- DCHECK(state_ == kUninitialized || state_ == kStopped) << state_;
-}
-
-void DecryptingVideoDecoder::SetDecryptor(Decryptor* decryptor) {
- DVLOG(2) << "SetDecryptor()";
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kDecryptorRequested) << state_;
- DCHECK(!init_cb_.is_null());
- DCHECK(!set_decryptor_ready_cb_.is_null());
- set_decryptor_ready_cb_.Reset();
-
- if (!decryptor) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- state_ = kStopped;
- return;
- }
-
- decryptor_ = decryptor;
-
- scoped_ptr<VideoDecoderConfig> scoped_config(new VideoDecoderConfig());
- scoped_config->CopyFrom(demuxer_stream_->video_decoder_config());
-
- state_ = kPendingDecoderInit;
- decryptor_->InitializeVideoDecoder(
- scoped_config.Pass(), BindToCurrentLoop(base::Bind(
- &DecryptingVideoDecoder::FinishInitialization, this)));
-}
-
-void DecryptingVideoDecoder::FinishInitialization(bool success) {
- DVLOG(2) << "FinishInitialization()";
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kPendingDecoderInit) << state_;
- DCHECK(!init_cb_.is_null());
- DCHECK(reset_cb_.is_null()); // No Reset() before initialization finished.
- DCHECK(read_cb_.is_null()); // No Read() before initialization finished.
-
- if (!success) {
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- state_ = kStopped;
- return;
- }
-
- decryptor_->RegisterKeyAddedCB(Decryptor::kVideo, BindToCurrentLoop(
- base::Bind(&DecryptingVideoDecoder::OnKeyAdded, this)));
-
- // Success!
- state_ = kIdle;
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-void DecryptingVideoDecoder::FinishConfigChange(bool success) {
- DVLOG(2) << "FinishConfigChange()";
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kPendingConfigChange) << state_;
- DCHECK(!read_cb_.is_null());
-
- if (!success) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- state_ = kDecodeFinished;
- if (!reset_cb_.is_null())
- base::ResetAndReturn(&reset_cb_).Run();
- return;
- }
-
- // Config change succeeded.
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- DoReset();
- return;
- }
-
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
-}
-
-void DecryptingVideoDecoder::ReadFromDemuxerStream() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
- DCHECK(!read_cb_.is_null());
-
- demuxer_stream_->Read(
- base::Bind(&DecryptingVideoDecoder::DecryptAndDecodeBuffer, this));
-}
-
-void DecryptingVideoDecoder::DecryptAndDecodeBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- DVLOG(3) << "DecryptAndDecodeBuffer()";
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
- DCHECK(!read_cb_.is_null());
- DCHECK_EQ(buffer != NULL, status == DemuxerStream::kOk) << status;
-
- if (status == DemuxerStream::kConfigChanged) {
- DVLOG(2) << "DecryptAndDecodeBuffer() - kConfigChanged";
-
- scoped_ptr<VideoDecoderConfig> scoped_config(new VideoDecoderConfig());
- scoped_config->CopyFrom(demuxer_stream_->video_decoder_config());
-
- state_ = kPendingConfigChange;
- decryptor_->DeinitializeDecoder(Decryptor::kVideo);
- decryptor_->InitializeVideoDecoder(
- scoped_config.Pass(), BindToCurrentLoop(base::Bind(
- &DecryptingVideoDecoder::FinishConfigChange, this)));
- return;
- }
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- DoReset();
- return;
- }
-
- if (status == DemuxerStream::kAborted) {
- DVLOG(2) << "DecryptAndDecodeBuffer() - kAborted";
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- return;
- }
-
- DCHECK_EQ(status, DemuxerStream::kOk);
- pending_buffer_to_decode_ = buffer;
- state_ = kPendingDecode;
- DecodePendingBuffer();
-}
-
-void DecryptingVideoDecoder::DecodePendingBuffer() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPendingDecode) << state_;
- TRACE_EVENT_ASYNC_BEGIN0(
- "eme", "DecryptingVideoDecoder::DecodePendingBuffer", ++trace_id_);
- decryptor_->DecryptAndDecodeVideo(
- pending_buffer_to_decode_, BindToCurrentLoop(base::Bind(
- &DecryptingVideoDecoder::DeliverFrame, this,
- pending_buffer_to_decode_->GetDataSize())));
-}
-
-void DecryptingVideoDecoder::DeliverFrame(
- int buffer_size,
- Decryptor::Status status,
- const scoped_refptr<VideoFrame>& frame) {
- DVLOG(3) << "DeliverFrame() - status: " << status;
- DCHECK(message_loop_->BelongsToCurrentThread());
- TRACE_EVENT_ASYNC_END0(
- "eme", "DecryptingVideoDecoder::DecodePendingBuffer", trace_id_);
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kPendingDecode) << state_;
- DCHECK(!read_cb_.is_null());
- DCHECK(pending_buffer_to_decode_);
-
- bool need_to_try_again_if_nokey_is_returned = key_added_while_decode_pending_;
- key_added_while_decode_pending_ = false;
-
- scoped_refptr<DecoderBuffer> scoped_pending_buffer_to_decode =
- pending_buffer_to_decode_;
-
- pending_buffer_to_decode_ = NULL;
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- DoReset();
- return;
- }
-
- DCHECK_EQ(status == Decryptor::kSuccess, frame != NULL);
-
- if (status == Decryptor::kError) {
- DVLOG(2) << "DeliverFrame() - kError";
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- if (status == Decryptor::kNoKey) {
- DVLOG(2) << "DeliverFrame() - kNoKey";
- // Set |pending_buffer_to_decode_| back as we need to try decoding the
- // pending buffer again when new key is added to the decryptor.
- pending_buffer_to_decode_ = scoped_pending_buffer_to_decode;
-
- if (need_to_try_again_if_nokey_is_returned) {
- // The |state_| is still kPendingDecode.
- DecodePendingBuffer();
- return;
- }
-
- state_ = kWaitingForKey;
- return;
- }
-
- // The buffer has been accepted by the decoder, let's report statistics.
- if (buffer_size) {
- PipelineStatistics statistics;
- statistics.video_bytes_decoded = buffer_size;
- statistics_cb_.Run(statistics);
- }
-
- if (status == Decryptor::kNeedMoreData) {
- DVLOG(2) << "DeliverFrame() - kNeedMoreData";
- if (scoped_pending_buffer_to_decode->IsEndOfStream()) {
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(
- kOk, media::VideoFrame::CreateEmptyFrame());
- return;
- }
-
- state_ = kPendingDemuxerRead;
- ReadFromDemuxerStream();
- return;
- }
-
- DCHECK_EQ(status, Decryptor::kSuccess);
- // No frame returned with kSuccess should be end-of-stream frame.
- DCHECK(!frame->IsEndOfStream());
- state_ = kIdle;
- base::ResetAndReturn(&read_cb_).Run(kOk, frame);
-}
-
-void DecryptingVideoDecoder::OnKeyAdded() {
- DVLOG(2) << "OnKeyAdded()";
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (state_ == kPendingDecode) {
- key_added_while_decode_pending_ = true;
- return;
- }
-
- if (state_ == kWaitingForKey) {
- state_ = kPendingDecode;
- DecodePendingBuffer();
- }
-}
-
-void DecryptingVideoDecoder::DoReset() {
- DCHECK(init_cb_.is_null());
- DCHECK(read_cb_.is_null());
- state_ = kIdle;
- base::ResetAndReturn(&reset_cb_).Run();
-}
-
-} // namespace media
diff --git a/src/media/filters/decrypting_video_decoder.h b/src/media/filters/decrypting_video_decoder.h
deleted file mode 100644
index 9da57a1..0000000
--- a/src/media/filters/decrypting_video_decoder.h
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_DECRYPTING_VIDEO_DECODER_H_
-#define MEDIA_FILTERS_DECRYPTING_VIDEO_DECODER_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/video_decoder.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-class Decryptor;
-
-// Decryptor-based VideoDecoder implementation that can decrypt and decode
-// encrypted video buffers and return decrypted and decompressed video frames.
-// All public APIs and callbacks are trampolined to the |message_loop_| so
-// that no locks are required for thread safety.
-//
-// TODO(xhwang): For now, DecryptingVideoDecoder relies on the decryptor to do
-// both decryption and video decoding. Add the path to use the decryptor for
-// decryption only and use other VideoDecoder implementations within
-// DecryptingVideoDecoder for video decoding.
-class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
- public:
- DecryptingVideoDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
-
- // VideoDecoder implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
- virtual void Stop(const base::Closure& closure) OVERRIDE;
-
- protected:
- virtual ~DecryptingVideoDecoder();
-
- private:
- // For a detailed state diagram please see this link: http://goo.gl/8jAok
- // TODO(xhwang): Add a ASCII state diagram in this file after this class
- // stabilizes.
- enum State {
- kUninitialized = 0,
- kDecryptorRequested,
- kPendingDecoderInit,
- kIdle,
- kPendingConfigChange,
- kPendingDemuxerRead,
- kPendingDecode,
- kWaitingForKey,
- kDecodeFinished,
- kStopped
- };
-
- // Callback for DecryptorHost::RequestDecryptor().
- void SetDecryptor(Decryptor* decryptor);
-
- // Callback for Decryptor::InitializeVideoDecoder() during initialization.
- void FinishInitialization(bool success);
-
- // Callback for Decryptor::InitializeVideoDecoder() during config change.
- void FinishConfigChange(bool success);
-
- void ReadFromDemuxerStream();
-
- // Callback for DemuxerStream::Read().
- void DecryptAndDecodeBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- void DecodePendingBuffer();
-
- // Callback for Decryptor::DecryptAndDecodeVideo().
- void DeliverFrame(int buffer_size,
- Decryptor::Status status,
- const scoped_refptr<VideoFrame>& frame);
-
- // Callback for the |decryptor_| to notify this object that a new key has been
- // added.
- void OnKeyAdded();
-
- // Reset decoder and call |reset_cb_|.
- void DoReset();
-
- // Free decoder resources and call |stop_cb_|.
- void DoStop();
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- State state_;
-
- PipelineStatusCB init_cb_;
- StatisticsCB statistics_cb_;
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- // Pointer to the demuxer stream that will feed us compressed buffers.
- scoped_refptr<DemuxerStream> demuxer_stream_;
-
- // Callback to request/cancel decryptor creation notification.
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- Decryptor* decryptor_;
-
- // The buffer returned by the demuxer that needs decrypting/decoding.
- scoped_refptr<media::DecoderBuffer> pending_buffer_to_decode_;
-
- // Indicates the situation where new key is added during pending decode
- // (in other words, this variable can only be set in state kPendingDecode).
- // If this variable is true and kNoKey is returned then we need to try
- // decrypting/decoding again in case the newly added key is the correct
- // decryption key.
- bool key_added_while_decode_pending_;
-
- // A unique ID to trace Decryptor::DecryptAndDecodeVideo() call and the
- // matching DecryptCB call (in DoDeliverFrame()).
- uint32 trace_id_;
-
- DISALLOW_COPY_AND_ASSIGN(DecryptingVideoDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_DECRYPTING_VIDEO_DECODER_H_
diff --git a/src/media/filters/decrypting_video_decoder_unittest.cc b/src/media/filters/decrypting_video_decoder_unittest.cc
deleted file mode 100644
index d92c79a..0000000
--- a/src/media/filters/decrypting_video_decoder_unittest.cc
+++ /dev/null
@@ -1,722 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/base/video_frame.h"
-#include "media/filters/decrypting_video_decoder.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-using ::testing::_;
-using ::testing::AtMost;
-using ::testing::IsNull;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(320, 240);
-static const gfx::Rect kVisibleRect(320, 240);
-static const gfx::Size kNaturalSize(320, 240);
-static const uint8 kFakeKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 };
-static const uint8 kFakeIv[DecryptConfig::kDecryptionKeySize] = { 0 };
-
-// Create a fake non-empty encrypted buffer.
-static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
- const int buffer_size = 16; // Need a non-empty buffer;
- scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(buffer_size));
- buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(kFakeKeyId),
- arraysize(kFakeKeyId)),
- std::string(reinterpret_cast<const char*>(kFakeIv), arraysize(kFakeIv)),
- 0,
- std::vector<SubsampleEntry>())));
- return buffer;
-}
-
-// Use anonymous namespace here to prevent the actions to be defined multiple
-// times across multiple test files. Sadly we can't use static for them.
-namespace {
-
-ACTION_P(ReturnBuffer, buffer) {
- arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
-}
-
-ACTION_P(RunCallbackIfNotNull, param) {
- if (!arg0.is_null())
- arg0.Run(param);
-}
-
-ACTION_P2(ResetAndRunCallback, callback, param) {
- base::ResetAndReturn(callback).Run(param);
-}
-
-MATCHER(IsEndOfStream, "end of stream") {
- return (arg->IsEndOfStream());
-}
-
-} // namespace
-
-class DecryptingVideoDecoderTest : public testing::Test {
- public:
- DecryptingVideoDecoderTest()
- : decoder_(new DecryptingVideoDecoder(
- message_loop_.message_loop_proxy(),
- base::Bind(
- &DecryptingVideoDecoderTest::RequestDecryptorNotification,
- base::Unretained(this)))),
- decryptor_(new StrictMock<MockDecryptor>()),
- demuxer_(new StrictMock<MockDemuxerStream>()),
- encrypted_buffer_(CreateFakeEncryptedBuffer()),
- decoded_video_frame_(VideoFrame::CreateBlackFrame(kCodedSize)),
- null_video_frame_(scoped_refptr<VideoFrame>()),
- end_of_stream_video_frame_(VideoFrame::CreateEmptyFrame()) {
- }
-
- virtual ~DecryptingVideoDecoderTest() {
- Stop();
- }
-
- void InitializeAndExpectStatus(const VideoDecoderConfig& config,
- PipelineStatus status) {
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .WillRepeatedly(ReturnRef(config));
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
-
- decoder_->Initialize(demuxer_, NewExpectedStatusCB(status),
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_)));
- message_loop_.RunUntilIdle();
- }
-
- void Initialize() {
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .Times(AtMost(1))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kVideo, _))
- .WillOnce(SaveArg<1>(&key_added_cb_));
-
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true, true);
-
- InitializeAndExpectStatus(config_, PIPELINE_OK);
- }
-
- void ReadAndExpectFrameReadyWith(
- VideoDecoder::Status status,
- const scoped_refptr<VideoFrame>& video_frame) {
- if (status != VideoDecoder::kOk)
- EXPECT_CALL(*this, FrameReady(status, IsNull()));
- else if (video_frame && video_frame->IsEndOfStream())
- EXPECT_CALL(*this, FrameReady(status, IsEndOfStream()));
- else
- EXPECT_CALL(*this, FrameReady(status, video_frame));
-
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- // Sets up expectations and actions to put DecryptingVideoDecoder in an
- // active normal decoding state.
- void EnterNormalDecodingState() {
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(encrypted_buffer_))
- .WillRepeatedly(ReturnBuffer(DecoderBuffer::CreateEOSBuffer()));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillOnce(RunCallback<1>(Decryptor::kSuccess, decoded_video_frame_))
- .WillRepeatedly(RunCallback<1>(Decryptor::kNeedMoreData,
- scoped_refptr<VideoFrame>()));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kOk, decoded_video_frame_);
- }
-
- // Sets up expectations and actions to put DecryptingVideoDecoder in an end
- // of stream state. This function must be called after
- // EnterNormalDecodingState() to work.
- void EnterEndOfStreamState() {
- ReadAndExpectFrameReadyWith(VideoDecoder::kOk, end_of_stream_video_frame_);
- }
-
- // Make the read callback pending by saving and not firing it.
- void EnterPendingReadState() {
- EXPECT_TRUE(pending_demuxer_read_cb_.is_null());
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&pending_demuxer_read_cb_));
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure the Read() on the decoder triggers a Read() on the demuxer.
- EXPECT_FALSE(pending_demuxer_read_cb_.is_null());
- }
-
- // Make the video decode callback pending by saving and not firing it.
- void EnterPendingDecodeState() {
- EXPECT_TRUE(pending_video_decode_cb_.is_null());
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(encrypted_buffer_, _))
- .WillOnce(SaveArg<1>(&pending_video_decode_cb_));
-
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- // Make sure the Read() on the decoder triggers a DecryptAndDecode() on the
- // decryptor.
- EXPECT_FALSE(pending_video_decode_cb_.is_null());
- }
-
- void EnterWaitingForKeyState() {
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kNoKey, null_video_frame_));
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- void AbortPendingVideoDecodeCB() {
- if (!pending_video_decode_cb_.is_null()) {
- base::ResetAndReturn(&pending_video_decode_cb_).Run(
- Decryptor::kSuccess, scoped_refptr<VideoFrame>(NULL));
- }
- }
-
- void AbortAllPendingCBs() {
- if (!pending_init_cb_.is_null()) {
- ASSERT_TRUE(pending_video_decode_cb_.is_null());
- base::ResetAndReturn(&pending_init_cb_).Run(false);
- return;
- }
-
- AbortPendingVideoDecodeCB();
- }
-
- void Reset() {
- EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kVideo))
- .WillRepeatedly(InvokeWithoutArgs(
- this, &DecryptingVideoDecoderTest::AbortPendingVideoDecodeCB));
-
- decoder_->Reset(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- void Stop() {
- EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kVideo,
- IsNullCallback()))
- .Times(AtMost(1));
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo))
- .WillRepeatedly(InvokeWithoutArgs(
- this, &DecryptingVideoDecoderTest::AbortAllPendingCBs));
-
- decoder_->Stop(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- MOCK_METHOD1(RequestDecryptorNotification, void(const DecryptorReadyCB&));
-
- MOCK_METHOD2(FrameReady, void(VideoDecoder::Status,
- const scoped_refptr<VideoFrame>&));
-
- MessageLoop message_loop_;
- scoped_refptr<DecryptingVideoDecoder> decoder_;
- scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
- MockStatisticsCB statistics_cb_;
- VideoDecoderConfig config_;
-
- DemuxerStream::ReadCB pending_demuxer_read_cb_;
- Decryptor::DecoderInitCB pending_init_cb_;
- Decryptor::KeyAddedCB key_added_cb_;
- Decryptor::VideoDecodeCB pending_video_decode_cb_;
-
- // Constant buffer/frames to be returned by the |demuxer_| and |decryptor_|.
- scoped_refptr<DecoderBuffer> encrypted_buffer_;
- scoped_refptr<VideoFrame> decoded_video_frame_;
- scoped_refptr<VideoFrame> null_video_frame_;
- scoped_refptr<VideoFrame> end_of_stream_video_frame_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DecryptingVideoDecoderTest);
-};
-
-TEST_F(DecryptingVideoDecoderTest, Initialize_Normal) {
- Initialize();
-}
-
-TEST_F(DecryptingVideoDecoderTest, Initialize_NullDecryptor) {
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillRepeatedly(RunCallbackIfNotNull(static_cast<Decryptor*>(NULL)));
- InitializeAndExpectStatus(TestVideoConfig::NormalEncrypted(),
- DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Ensure that DecryptingVideoDecoder only accepts encrypted video.
-TEST_F(DecryptingVideoDecoderTest, Initialize_UnencryptedVideoConfig) {
- VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, false);
-
- InitializeAndExpectStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Ensure decoder handles invalid video configs without crashing.
-TEST_F(DecryptingVideoDecoderTest, Initialize_InvalidVideoConfig) {
- VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- VideoFrame::INVALID,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true);
-
- InitializeAndExpectStatus(config, PIPELINE_ERROR_DECODE);
-}
-
-// Ensure decoder handles unsupported video configs without crashing.
-TEST_F(DecryptingVideoDecoderTest, Initialize_UnsupportedVideoConfig) {
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
-
- VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true);
-
- InitializeAndExpectStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-// Test normal decrypt and decode case.
-TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_Normal) {
- Initialize();
- EnterNormalDecodingState();
-}
-
-// Test the case where the decryptor returns error when doing decrypt and
-// decode.
-TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_DecodeError) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kError,
- scoped_refptr<VideoFrame>(NULL)));
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kDecodeError, null_video_frame_);
-}
-
-// Test the case where the decryptor returns kNeedMoreData to ask for more
-// buffers before it can produce a frame.
-TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_NeedMoreData) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .Times(2)
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillOnce(RunCallback<1>(Decryptor::kNeedMoreData,
- scoped_refptr<VideoFrame>()))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess,
- decoded_video_frame_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_))
- .Times(2);
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kOk, decoded_video_frame_);
-}
-
-// Test the case where the decryptor receives end-of-stream buffer.
-TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_EndOfStream) {
- Initialize();
- EnterNormalDecodingState();
- EnterEndOfStreamState();
-}
-
-// Test aborted read on the demuxer stream.
-TEST_F(DecryptingVideoDecoderTest, DemuxerRead_Aborted) {
- Initialize();
-
- // ReturnBuffer() with NULL triggers aborted demuxer read.
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>()));
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kOk, null_video_frame_);
-}
-
-// Test config change on the demuxer stream.
-TEST_F(DecryptingVideoDecoderTest, DemuxerRead_ConfigChange) {
- Initialize();
-
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess,
- decoded_video_frame_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kOk, decoded_video_frame_);
-}
-
-// Test config change failure.
-TEST_F(DecryptingVideoDecoderTest, DemuxerRead_ConfigChangeFailed) {
- Initialize();
-
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()))
- .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
-
- ReadAndExpectFrameReadyWith(VideoDecoder::kDecodeError, null_video_frame_);
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kWaitingForKey state.
-TEST_F(DecryptingVideoDecoderTest, KeyAdded_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess,
- decoded_video_frame_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, decoded_video_frame_));
- key_added_cb_.Run();
- message_loop_.RunUntilIdle();
-}
-
-// Test the case where the a key is added when the decryptor is in
-// kPendingDecode state.
-TEST_F(DecryptingVideoDecoderTest, KeyAdded_DruingPendingDecode) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
- .WillRepeatedly(RunCallback<1>(Decryptor::kSuccess,
- decoded_video_frame_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, decoded_video_frame_));
- // The video decode callback is returned after the correct decryption key is
- // added.
- key_added_cb_.Run();
- base::ResetAndReturn(&pending_video_decode_cb_).Run(Decryptor::kNoKey,
- null_video_frame_);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kIdle state but has not decoded any
-// frame.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringIdleAfterInitialization) {
- Initialize();
- Reset();
-}
-
-// Test resetting when the decoder is in kIdle state after it has decoded one
-// frame.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringIdleAfterDecodedOneFrame) {
- Initialize();
- EnterNormalDecodingState();
- Reset();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kOk.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringDemuxerRead_Ok) {
- Initialize();
- EnterPendingReadState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kOk,
- encrypted_buffer_);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kAborted.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringDemuxerRead_Aborted) {
- Initialize();
- EnterPendingReadState();
-
- // Make sure we get a NULL video frame returned.
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kAborted,
- NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state and the read
-// callback is returned with kConfigChanged.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringDemuxerRead_ConfigChange) {
- Initialize();
- EnterPendingReadState();
-
- Reset();
-
- // Even during pending reset, the decoder still needs to be initialized with
- // the new config.
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(RunCallback<1>(true));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, null_video_frame_));
-
- base::ResetAndReturn(&pending_demuxer_read_cb_)
- .Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDemuxerRead state, the read
-// callback is returned with kConfigChanged and the config change fails.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringDemuxerRead_ConfigChangeFailed) {
- Initialize();
- EnterPendingReadState();
-
- Reset();
-
- // Even during pending reset, the decoder still needs to be initialized with
- // the new config.
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(RunCallback<1>(false));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kDecodeError, null_video_frame_));
-
- base::ResetAndReturn(&pending_demuxer_read_cb_)
- .Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingConfigChange state.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingConfigChange) {
- Initialize();
- EnterNormalDecodingState();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()));
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(SaveArg<1>(&pending_init_cb_));
-
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- EXPECT_FALSE(pending_init_cb_.is_null());
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Reset();
- base::ResetAndReturn(&pending_init_cb_).Run(true);
- message_loop_.RunUntilIdle();
-}
-
-// Test resetting when the decoder is in kPendingDecode state.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingDecode) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Reset();
-}
-
-// Test resetting when the decoder is in kWaitingForKey state.
-TEST_F(DecryptingVideoDecoderTest, Reset_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Reset();
-}
-
-// Test resetting when the decoder has hit end of stream and is in
-// kDecodeFinished state.
-TEST_F(DecryptingVideoDecoderTest, Reset_AfterDecodeFinished) {
- Initialize();
- EnterNormalDecodingState();
- EnterEndOfStreamState();
- Reset();
-}
-
-// Test resetting after the decoder has been reset.
-TEST_F(DecryptingVideoDecoderTest, Reset_AfterReset) {
- Initialize();
- EnterNormalDecodingState();
- Reset();
- Reset();
-}
-
-// Test stopping when the decoder is in kDecryptorRequested state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringDecryptorRequested) {
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true, true);
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .WillRepeatedly(ReturnRef(config_));
- DecryptorReadyCB decryptor_ready_cb;
- EXPECT_CALL(*this, RequestDecryptorNotification(_))
- .WillOnce(SaveArg<0>(&decryptor_ready_cb));
- decoder_->Initialize(demuxer_,
- NewExpectedStatusCB(DECODER_ERROR_NOT_SUPPORTED),
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_)));
- message_loop_.RunUntilIdle();
- // |decryptor_ready_cb| is saved but not called here.
- EXPECT_FALSE(decryptor_ready_cb.is_null());
-
- // During stop, RequestDecryptorNotification() should be called with a NULL
- // callback to cancel the |decryptor_ready_cb|.
- EXPECT_CALL(*this, RequestDecryptorNotification(IsNullCallback()))
- .WillOnce(ResetAndRunCallback(&decryptor_ready_cb,
- reinterpret_cast<Decryptor*>(NULL)));
- Stop();
-}
-
-// Test stopping when the decoder is in kPendingDecoderInit state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDecoderInit) {
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(SaveArg<1>(&pending_init_cb_));
-
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, true,
- true);
- InitializeAndExpectStatus(config_, DECODER_ERROR_NOT_SUPPORTED);
- EXPECT_FALSE(pending_init_cb_.is_null());
-
- Stop();
-}
-
-// Test stopping when the decoder is in kIdle state but has not decoded any
-// frame.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringIdleAfterInitialization) {
- Initialize();
- Stop();
-}
-
-// Test stopping when the decoder is in kIdle state after it has decoded one
-// frame.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringIdleAfterDecodedOneFrame) {
- Initialize();
- EnterNormalDecodingState();
- Stop();
-}
-
-// Test stopping when the decoder is in kPendingConfigChange state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingConfigChange) {
- Initialize();
- EnterNormalDecodingState();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()));
- EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kVideo));
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillOnce(SaveArg<1>(&pending_init_cb_));
-
- decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- EXPECT_FALSE(pending_init_cb_.is_null());
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Stop();
-}
-
-// Test stopping when the decoder is in kPendingDemuxerRead state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDemuxerRead) {
- Initialize();
- EnterPendingReadState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Stop();
- base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kOk,
- encrypted_buffer_);
- message_loop_.RunUntilIdle();
-}
-
-// Test stopping when the decoder is in kPendingDecode state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDecode) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Stop();
-}
-
-// Test stopping when the decoder is in kWaitingForKey state.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringWaitingForKey) {
- Initialize();
- EnterWaitingForKeyState();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Stop();
-}
-
-// Test stopping when the decoder has hit end of stream and is in
-// kDecodeFinished state.
-TEST_F(DecryptingVideoDecoderTest, Stop_AfterDecodeFinished) {
- Initialize();
- EnterNormalDecodingState();
- EnterEndOfStreamState();
- Stop();
-}
-
-// Test stopping when there is a pending reset on the decoder.
-// Reset is pending because it cannot complete when the video decode callback
-// is pending.
-TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingReset) {
- Initialize();
- EnterPendingDecodeState();
-
- EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kVideo));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- decoder_->Reset(NewExpectedClosure());
- Stop();
-}
-
-// Test stopping after the decoder has been reset.
-TEST_F(DecryptingVideoDecoderTest, Stop_AfterReset) {
- Initialize();
- EnterNormalDecodingState();
- Reset();
- Stop();
-}
-
-// Test stopping after the decoder has been stopped.
-TEST_F(DecryptingVideoDecoderTest, Stop_AfterStop) {
- Initialize();
- EnterNormalDecodingState();
- Stop();
- Stop();
-}
-
-} // namespace media
diff --git a/src/media/filters/dummy_demuxer.cc b/src/media/filters/dummy_demuxer.cc
deleted file mode 100644
index 8df1373..0000000
--- a/src/media/filters/dummy_demuxer.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/dummy_demuxer.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-DummyDemuxerStream::DummyDemuxerStream(Type type)
- : type_(type) {
-}
-
-DummyDemuxerStream::~DummyDemuxerStream() {}
-
-DemuxerStream::Type DummyDemuxerStream::type() {
- return type_;
-}
-
-const AudioDecoderConfig& DummyDemuxerStream::audio_decoder_config() {
- CHECK_EQ(type_, AUDIO);
- return audio_config_;
-}
-
-const VideoDecoderConfig& DummyDemuxerStream::video_decoder_config() {
- CHECK_EQ(type_, VIDEO);
- return video_config_;
-}
-
-void DummyDemuxerStream::Read(const ReadCB& read_cb) {}
-
-void DummyDemuxerStream::EnableBitstreamConverter() {}
-
-DummyDemuxer::DummyDemuxer(bool has_video, bool has_audio) {
- streams_.resize(DemuxerStream::NUM_TYPES);
- if (has_audio) {
- streams_[DemuxerStream::AUDIO] =
- new DummyDemuxerStream(DemuxerStream::AUDIO);
- }
- if (has_video) {
- streams_[DemuxerStream::VIDEO] =
- new DummyDemuxerStream(DemuxerStream::VIDEO);
- }
-}
-
-void DummyDemuxer::Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) {
- host->SetDuration(media::kInfiniteDuration());
- status_cb.Run(PIPELINE_OK);
-}
-
-scoped_refptr<DemuxerStream> DummyDemuxer::GetStream(DemuxerStream::Type type) {
- return streams_[type];
-}
-
-base::TimeDelta DummyDemuxer::GetStartTime() const {
- return base::TimeDelta();
-}
-
-DummyDemuxer::~DummyDemuxer() {}
-
-} // namespace media
diff --git a/src/media/filters/dummy_demuxer.h b/src/media/filters/dummy_demuxer.h
deleted file mode 100644
index f59bfeb..0000000
--- a/src/media/filters/dummy_demuxer.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implements the Demuxer interface. DummyDemuxer returns corresponding
-// DummyDemuxerStream as signal for media pipeline to construct correct
-// playback channels. Used in WebRTC local video capture pipeline, where
-// demuxing is not needed.
-
-#ifndef MEDIA_FILTERS_DUMMY_DEMUXER_H_
-#define MEDIA_FILTERS_DUMMY_DEMUXER_H_
-
-#include <vector>
-
-#include "media/base/audio_decoder_config.h"
-#include "media/base/demuxer.h"
-#include "media/base/video_decoder_config.h"
-
-namespace media {
-
-class DummyDemuxerStream : public DemuxerStream {
- public:
- explicit DummyDemuxerStream(Type type);
-
- // DemuxerStream implementation.
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual Type type() OVERRIDE;
- virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
- virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
- virtual void EnableBitstreamConverter() OVERRIDE;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- bool StreamWasEncrypted() const OVERRIDE {
- return video_config_.is_encrypted();
- }
-#endif
-
- protected:
- virtual ~DummyDemuxerStream();
-
- private:
- Type type_;
- AudioDecoderConfig audio_config_;
- VideoDecoderConfig video_config_;
-
- DISALLOW_COPY_AND_ASSIGN(DummyDemuxerStream);
-};
-
-class MEDIA_EXPORT DummyDemuxer : public Demuxer {
- public:
- DummyDemuxer(bool has_video, bool has_audio);
-
- // Demuxer implementation.
- virtual void Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) OVERRIDE;
- virtual scoped_refptr<DemuxerStream> GetStream(
- DemuxerStream::Type type) OVERRIDE;
- virtual base::TimeDelta GetStartTime() const OVERRIDE;
-
- protected:
- virtual ~DummyDemuxer();
-
- private:
- std::vector< scoped_refptr<DummyDemuxerStream> > streams_;
-
- DISALLOW_COPY_AND_ASSIGN(DummyDemuxer);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_DUMMY_DEMUXER_H_
diff --git a/src/media/filters/ffmpeg_audio_decoder.cc b/src/media/filters/ffmpeg_audio_decoder.cc
deleted file mode 100644
index 5384d63..0000000
--- a/src/media/filters/ffmpeg_audio_decoder.cc
+++ /dev/null
@@ -1,434 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/ffmpeg_audio_decoder.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/data_buffer.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/demuxer.h"
-#include "media/base/pipeline.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-
-namespace media {
-
-// Helper structure for managing multiple decoded audio frames per packet.
-struct QueuedAudioBuffer {
- AudioDecoder::Status status;
- scoped_refptr<Buffer> buffer;
-};
-
-// Returns true if the decode result was end of stream.
-static inline bool IsEndOfStream(int result, int decoded_size, Buffer* input) {
- // Three conditions to meet to declare end of stream for this decoder:
- // 1. FFmpeg didn't read anything.
- // 2. FFmpeg didn't output anything.
- // 3. An end of stream buffer is received.
- return result == 0 && decoded_size == 0 && input->IsEndOfStream();
-}
-
-FFmpegAudioDecoder::FFmpegAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop)
- : message_loop_(message_loop),
- codec_context_(NULL),
- bits_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- samples_per_second_(0),
- bytes_per_frame_(0),
- last_input_timestamp_(kNoTimestamp()),
- output_bytes_to_drop_(0),
- av_frame_(NULL) {
-}
-
-void FFmpegAudioDecoder::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &FFmpegAudioDecoder::DoInitialize, this,
- stream, status_cb, statistics_cb));
- return;
- }
- DoInitialize(stream, status_cb, statistics_cb);
-}
-
-void FFmpegAudioDecoder::Read(const ReadCB& read_cb) {
- // Complete operation asynchronously on different stack of execution as per
- // the API contract of AudioDecoder::Read()
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &FFmpegAudioDecoder::DoRead, this, read_cb));
-}
-
-int FFmpegAudioDecoder::bits_per_channel() {
- return bits_per_channel_;
-}
-
-ChannelLayout FFmpegAudioDecoder::channel_layout() {
- return channel_layout_;
-}
-
-int FFmpegAudioDecoder::samples_per_second() {
- return samples_per_second_;
-}
-
-void FFmpegAudioDecoder::Reset(const base::Closure& closure) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &FFmpegAudioDecoder::DoReset, this, closure));
-}
-
-FFmpegAudioDecoder::~FFmpegAudioDecoder() {
- // TODO(scherkus): should we require Stop() to be called? this might end up
- // getting called on a random thread due to refcounting.
- ReleaseFFmpegResources();
-}
-
-void FFmpegAudioDecoder::DoInitialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- FFmpegGlue::InitializeFFmpeg();
-
- if (demuxer_stream_) {
- // TODO(scherkus): initialization currently happens more than once in
- // PipelineIntegrationTest.BasicPlayback.
- LOG(ERROR) << "Initialize has already been called.";
- CHECK(false);
- }
-
- demuxer_stream_ = stream;
-
- if (!ConfigureDecoder()) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- statistics_cb_ = statistics_cb;
- status_cb.Run(PIPELINE_OK);
-}
-
-void FFmpegAudioDecoder::DoReset(const base::Closure& closure) {
- avcodec_flush_buffers(codec_context_);
- ResetTimestampState();
- queued_audio_.clear();
- closure.Run();
-}
-
-void FFmpegAudioDecoder::DoRead(const ReadCB& read_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
-
- read_cb_ = read_cb;
-
- // If we don't have any queued audio from the last packet we decoded, ask for
- // more data from the demuxer to satisfy this read.
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
-
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
-}
-
-void FFmpegAudioDecoder::DoDecodeBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& input) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &FFmpegAudioDecoder::DoDecodeBuffer, this, status, input));
- return;
- }
-
- DCHECK(!read_cb_.is_null());
- DCHECK(queued_audio_.empty());
- DCHECK_EQ(status != DemuxerStream::kOk, !input) << status;
-
- if (status == DemuxerStream::kAborted) {
- DCHECK(!input);
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- return;
- }
-
- if (status == DemuxerStream::kConfigChanged) {
- DCHECK(!input);
-
- // Send a "end of stream" buffer to the decode loop
- // to output any remaining data still in the decoder.
- RunDecodeLoop(DecoderBuffer::CreateEOSBuffer(), true);
-
- DVLOG(1) << "Config changed.";
-
- if (!ConfigureDecoder()) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- ResetTimestampState();
-
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
-
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
- return;
- }
-
- DCHECK_EQ(status, DemuxerStream::kOk);
- DCHECK(input);
-
- // Make sure we are notified if http://crbug.com/49709 returns. Issue also
- // occurs with some damaged files.
- if (!input->IsEndOfStream() && input->GetTimestamp() == kNoTimestamp() &&
- output_timestamp_helper_->base_timestamp() == kNoTimestamp()) {
- DVLOG(1) << "Received a buffer without timestamps!";
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- bool is_vorbis = codec_context_->codec_id == CODEC_ID_VORBIS;
- if (!input->IsEndOfStream()) {
- if (last_input_timestamp_ == kNoTimestamp()) {
- if (is_vorbis && (input->GetTimestamp() < base::TimeDelta())) {
- // Dropping frames for negative timestamps as outlined in section A.2
- // in the Vorbis spec. http://xiph.org/vorbis/doc/Vorbis_I_spec.html
- int frames_to_drop = floor(
- 0.5 + -input->GetTimestamp().InSecondsF() * samples_per_second_);
- output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop;
- } else {
- last_input_timestamp_ = input->GetTimestamp();
- }
- } else if (input->GetTimestamp() != kNoTimestamp()) {
- if (input->GetTimestamp() < last_input_timestamp_) {
- base::TimeDelta diff = input->GetTimestamp() - last_input_timestamp_;
- DVLOG(1) << "Input timestamps are not monotonically increasing! "
- << " ts " << input->GetTimestamp().InMicroseconds() << " us"
- << " diff " << diff.InMicroseconds() << " us";
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- last_input_timestamp_ = input->GetTimestamp();
- }
- }
-
- RunDecodeLoop(input, false);
-
- // We exhausted the provided packet, but it wasn't enough for a frame. Ask
- // for more data in order to fulfill this read.
- if (queued_audio_.empty()) {
- ReadFromDemuxerStream();
- return;
- }
-
- // Execute callback to return the first frame we decoded.
- base::ResetAndReturn(&read_cb_).Run(
- queued_audio_.front().status, queued_audio_.front().buffer);
- queued_audio_.pop_front();
-}
-
-void FFmpegAudioDecoder::ReadFromDemuxerStream() {
- DCHECK(!read_cb_.is_null());
-
- demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DoDecodeBuffer, this));
-}
-
-bool FFmpegAudioDecoder::ConfigureDecoder() {
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
-
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid audio stream -"
- << " codec: " << config.codec()
- << " channel layout: " << config.channel_layout()
- << " bits per channel: " << config.bits_per_channel()
- << " samples per second: " << config.samples_per_second();
- return false;
- }
-
- if (config.is_encrypted()) {
- DLOG(ERROR) << "Encrypted audio stream not supported";
- return false;
- }
-
- if (codec_context_ &&
- (bits_per_channel_ != config.bits_per_channel() ||
- channel_layout_ != config.channel_layout() ||
- samples_per_second_ != config.samples_per_second())) {
- DVLOG(1) << "Unsupported config change :";
- DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_
- << " -> " << config.bits_per_channel();
- DVLOG(1) << "\tchannel_layout : " << channel_layout_
- << " -> " << config.channel_layout();
- DVLOG(1) << "\tsample_rate : " << samples_per_second_
- << " -> " << config.samples_per_second();
- return false;
- }
-
- // Release existing decoder resources if necessary.
- ReleaseFFmpegResources();
-
- // Initialize AVCodecContext structure.
- codec_context_ = avcodec_alloc_context3(NULL);
- AudioDecoderConfigToAVCodecContext(config, codec_context_);
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) {
- DLOG(ERROR) << "Could not initialize audio decoder: "
- << codec_context_->codec_id;
- return false;
- }
-
- // Success!
- av_frame_ = avcodec_alloc_frame();
- bits_per_channel_ = config.bits_per_channel();
- channel_layout_ = config.channel_layout();
- samples_per_second_ = config.samples_per_second();
- output_timestamp_helper_.reset(new AudioTimestampHelper(
- config.bytes_per_frame(), config.samples_per_second()));
- return true;
-}
-
-void FFmpegAudioDecoder::ReleaseFFmpegResources() {
- if (codec_context_) {
- av_free(codec_context_->extradata);
- avcodec_close(codec_context_);
- av_free(codec_context_);
- }
-
- if (av_frame_) {
- av_free(av_frame_);
- av_frame_ = NULL;
- }
-}
-
-void FFmpegAudioDecoder::ResetTimestampState() {
- output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
- last_input_timestamp_ = kNoTimestamp();
- output_bytes_to_drop_ = 0;
-}
-
-void FFmpegAudioDecoder::RunDecodeLoop(
- const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append) {
- AVPacket packet;
- av_init_packet(&packet);
- packet.data = const_cast<uint8*>(input->GetData());
- packet.size = input->GetDataSize();
-
- // Each audio packet may contain several frames, so we must call the decoder
- // until we've exhausted the packet. Regardless of the packet size we always
- // want to hand it to the decoder at least once, otherwise we would end up
- // skipping end of stream packets since they have a size of zero.
- do {
- // Reset frame to default values.
- avcodec_get_frame_defaults(av_frame_);
-
- int frame_decoded = 0;
- int result = avcodec_decode_audio4(
- codec_context_, av_frame_, &frame_decoded, &packet);
-
- if (result < 0) {
- DCHECK(!input->IsEndOfStream())
- << "End of stream buffer produced an error! "
- << "This is quite possibly a bug in the audio decoder not handling "
- << "end of stream AVPackets correctly.";
-
- DLOG(ERROR)
- << "Error decoding an audio frame with timestamp: "
- << input->GetTimestamp().InMicroseconds() << " us, duration: "
- << input->GetDuration().InMicroseconds() << " us, packet size: "
- << input->GetDataSize() << " bytes";
-
- // TODO(dalecurtis): We should return a kDecodeError here instead:
- // http://crbug.com/145276
- break;
- }
-
- // Update packet size and data pointer in case we need to call the decoder
- // with the remaining bytes from this packet.
- packet.size -= result;
- packet.data += result;
-
- if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
- !input->IsEndOfStream()) {
- DCHECK(input->GetTimestamp() != kNoTimestamp());
- if (output_bytes_to_drop_ > 0) {
- // Currently Vorbis is the only codec that causes us to drop samples.
- // If we have to drop samples it always means the timeline starts at 0.
- DCHECK_EQ(codec_context_->codec_id, CODEC_ID_VORBIS);
- output_timestamp_helper_->SetBaseTimestamp(base::TimeDelta());
- } else {
- output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp());
- }
- }
-
- const uint8* decoded_audio_data = NULL;
- int decoded_audio_size = 0;
- if (frame_decoded) {
- int output_sample_rate = av_frame_->sample_rate;
- if (output_sample_rate != samples_per_second_) {
- DLOG(ERROR) << "Output sample rate (" << output_sample_rate
- << ") doesn't match expected rate " << samples_per_second_;
-
- // This is an unrecoverable error, so bail out.
- QueuedAudioBuffer queue_entry = { kDecodeError, NULL };
- queued_audio_.push_back(queue_entry);
- break;
- }
-
- decoded_audio_data = av_frame_->data[0];
- decoded_audio_size = av_samples_get_buffer_size(
- NULL, codec_context_->channels, av_frame_->nb_samples,
- codec_context_->sample_fmt, 1);
- }
-
- scoped_refptr<DataBuffer> output;
-
- if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
- int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
- decoded_audio_data += dropped_size;
- decoded_audio_size -= dropped_size;
- output_bytes_to_drop_ -= dropped_size;
- }
-
- if (decoded_audio_size > 0) {
- // Copy the audio samples into an output buffer.
- output = new DataBuffer(decoded_audio_data, decoded_audio_size);
- output->SetTimestamp(output_timestamp_helper_->GetTimestamp());
- output->SetDuration(
- output_timestamp_helper_->GetDuration(decoded_audio_size));
- output_timestamp_helper_->AddBytes(decoded_audio_size);
- } else if (IsEndOfStream(result, decoded_audio_size, input) &&
- !skip_eos_append) {
- DCHECK_EQ(packet.size, 0);
- // Create an end of stream output buffer.
- output = new DataBuffer(0);
- }
-
- if (output) {
- QueuedAudioBuffer queue_entry = { kOk, output };
- queued_audio_.push_back(queue_entry);
- }
-
- // Decoding finished successfully, update statistics.
- if (result > 0) {
- PipelineStatistics statistics;
- statistics.audio_bytes_decoded = result;
- statistics_cb_.Run(statistics);
- }
- } while (packet.size > 0);
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_audio_decoder.h b/src/media/filters/ffmpeg_audio_decoder.h
deleted file mode 100644
index ce096ee..0000000
--- a/src/media/filters/ffmpeg_audio_decoder.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_FFMPEG_AUDIO_DECODER_H_
-#define MEDIA_FILTERS_FFMPEG_AUDIO_DECODER_H_
-
-#include <list>
-
-#include "base/callback.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/demuxer_stream.h"
-
-struct AVCodecContext;
-struct AVFrame;
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class AudioTimestampHelper;
-class DataBuffer;
-class DecoderBuffer;
-struct QueuedAudioBuffer;
-
-class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder {
- public:
- explicit FFmpegAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- // AudioDecoder implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual int bits_per_channel() OVERRIDE;
- virtual ChannelLayout channel_layout() OVERRIDE;
- virtual int samples_per_second() OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
-
- protected:
- virtual ~FFmpegAudioDecoder();
-
- private:
- // Methods running on decoder thread.
- void DoInitialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb);
- void DoReset(const base::Closure& closure);
- void DoRead(const ReadCB& read_cb);
- void DoDecodeBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& input);
-
- // Reads from the demuxer stream with corresponding callback method.
- void ReadFromDemuxerStream();
-
- bool ConfigureDecoder();
- void ReleaseFFmpegResources();
- void ResetTimestampState();
- void RunDecodeLoop(const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- scoped_refptr<DemuxerStream> demuxer_stream_;
- StatisticsCB statistics_cb_;
- AVCodecContext* codec_context_;
-
- // Decoded audio format.
- int bits_per_channel_;
- ChannelLayout channel_layout_;
- int samples_per_second_;
-
- // Used for computing output timestamps.
- scoped_ptr<AudioTimestampHelper> output_timestamp_helper_;
- int bytes_per_frame_;
- base::TimeDelta last_input_timestamp_;
-
- // Number of output sample bytes to drop before generating
- // output buffers.
- int output_bytes_to_drop_;
-
- // Holds decoded audio.
- AVFrame* av_frame_;
-
- ReadCB read_cb_;
-
- // Since multiple frames may be decoded from the same packet we need to queue
- // them up and hand them out as we receive Read() calls.
- std::list<QueuedAudioBuffer> queued_audio_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(FFmpegAudioDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FFMPEG_AUDIO_DECODER_H_
diff --git a/src/media/filters/ffmpeg_audio_decoder_unittest.cc b/src/media/filters/ffmpeg_audio_decoder_unittest.cc
deleted file mode 100644
index ead293d..0000000
--- a/src/media/filters/ffmpeg_audio_decoder_unittest.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <deque>
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/stringprintf.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_data_util.h"
-#include "media/base/test_helpers.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_audio_decoder.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::ReturnRef;
-using ::testing::StrictMock;
-
-namespace media {
-
-ACTION_P(InvokeReadPacket, test) {
- test->ReadPacket(arg0);
-}
-
-class FFmpegAudioDecoderTest : public testing::Test {
- public:
- FFmpegAudioDecoderTest()
- : decoder_(new FFmpegAudioDecoder(message_loop_.message_loop_proxy())),
- demuxer_(new StrictMock<MockDemuxerStream>()) {
- FFmpegGlue::InitializeFFmpeg();
-
- vorbis_extradata_ = ReadTestDataFile("vorbis-extradata");
-
- // Refer to media/test/data/README for details on vorbis test data.
- for (int i = 0; i < 4; ++i) {
- scoped_refptr<DecoderBuffer> buffer =
- ReadTestDataFile(base::StringPrintf("vorbis-packet-%d", i));
-
- if (i < 3) {
- buffer->SetTimestamp(base::TimeDelta());
- } else {
- buffer->SetTimestamp(base::TimeDelta::FromMicroseconds(2902));
- }
-
- buffer->SetDuration(base::TimeDelta());
- encoded_audio_.push_back(buffer);
- }
-
- // Push in an EOS buffer.
- encoded_audio_.push_back(DecoderBuffer::CreateEOSBuffer());
-
- config_.Initialize(kCodecVorbis,
- 16,
- CHANNEL_LAYOUT_STEREO,
- 44100,
- vorbis_extradata_->GetData(),
- vorbis_extradata_->GetDataSize(),
- false, // Not encrypted.
- true);
- }
-
- virtual ~FFmpegAudioDecoderTest() {}
-
- void Initialize() {
- EXPECT_CALL(*demuxer_, audio_decoder_config())
- .WillRepeatedly(ReturnRef(config_));
-
- decoder_->Initialize(demuxer_,
- NewExpectedStatusCB(PIPELINE_OK),
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_)));
-
- message_loop_.RunUntilIdle();
- }
-
- void ReadPacket(const DemuxerStream::ReadCB& read_cb) {
- CHECK(!encoded_audio_.empty()) << "ReadPacket() called too many times";
-
- scoped_refptr<DecoderBuffer> buffer(encoded_audio_.front());
- DemuxerStream::Status status =
- buffer ? DemuxerStream::kOk : DemuxerStream::kAborted;
- encoded_audio_.pop_front();
- read_cb.Run(status, buffer);
- }
-
- void Read() {
- decoder_->Read(base::Bind(
- &FFmpegAudioDecoderTest::DecodeFinished, base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- void DecodeFinished(AudioDecoder::Status status,
- const scoped_refptr<Buffer>& buffer) {
- decoded_audio_.push_back(buffer);
- }
-
- void ExpectDecodedAudio(size_t i, int64 timestamp, int64 duration) {
- EXPECT_LT(i, decoded_audio_.size());
- EXPECT_EQ(timestamp, decoded_audio_[i]->GetTimestamp().InMicroseconds());
- EXPECT_EQ(duration, decoded_audio_[i]->GetDuration().InMicroseconds());
- EXPECT_FALSE(decoded_audio_[i]->IsEndOfStream());
- }
-
- void ExpectEndOfStream(size_t i) {
- EXPECT_LT(i, decoded_audio_.size());
- EXPECT_EQ(0, decoded_audio_[i]->GetTimestamp().InMicroseconds());
- EXPECT_EQ(0, decoded_audio_[i]->GetDuration().InMicroseconds());
- EXPECT_TRUE(decoded_audio_[i]->IsEndOfStream());
- }
-
- MessageLoop message_loop_;
- scoped_refptr<FFmpegAudioDecoder> decoder_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
- MockStatisticsCB statistics_cb_;
-
- scoped_refptr<DecoderBuffer> vorbis_extradata_;
-
- std::deque<scoped_refptr<DecoderBuffer> > encoded_audio_;
- std::deque<scoped_refptr<Buffer> > decoded_audio_;
-
- AudioDecoderConfig config_;
-};
-
-TEST_F(FFmpegAudioDecoderTest, Initialize) {
- Initialize();
-
- EXPECT_EQ(16, decoder_->bits_per_channel());
- EXPECT_EQ(CHANNEL_LAYOUT_STEREO, decoder_->channel_layout());
- EXPECT_EQ(44100, decoder_->samples_per_second());
-}
-
-TEST_F(FFmpegAudioDecoderTest, ProduceAudioSamples) {
- Initialize();
-
- // Vorbis requires N+1 packets to produce audio data for N packets.
- //
- // This will should result in the demuxer receiving three reads for two
- // requests to produce audio samples.
- EXPECT_CALL(*demuxer_, Read(_))
- .Times(5)
- .WillRepeatedly(InvokeReadPacket(this));
- EXPECT_CALL(statistics_cb_, OnStatistics(_))
- .Times(4);
-
- Read();
- Read();
- Read();
-
- ASSERT_EQ(3u, decoded_audio_.size());
- ExpectDecodedAudio(0, 0, 2902);
- ExpectDecodedAudio(1, 2902, 13061);
- ExpectDecodedAudio(2, 15963, 23220);
-
- // Call one more time to trigger EOS.
- Read();
- ASSERT_EQ(4u, decoded_audio_.size());
- ExpectEndOfStream(3);
-}
-
-TEST_F(FFmpegAudioDecoderTest, ReadAbort) {
- Initialize();
-
- encoded_audio_.clear();
- encoded_audio_.push_back(NULL);
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(InvokeReadPacket(this));
- Read();
-
- EXPECT_EQ(decoded_audio_.size(), 1u);
- EXPECT_TRUE(decoded_audio_[0].get() == NULL);
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_demuxer.cc b/src/media/filters/ffmpeg_demuxer.cc
deleted file mode 100644
index 56929bc..0000000
--- a/src/media/filters/ffmpeg_demuxer.cc
+++ /dev/null
@@ -1,687 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/ffmpeg_demuxer.h"
-
-#include <algorithm>
-#include <string>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/command_line.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/stl_util.h"
-#include "base/string_util.h"
-#include "base/task_runner_util.h"
-#include "base/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-#include "media/base/video_decoder_config.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
-
-namespace media {
-
-//
-// FFmpegDemuxerStream
-//
-FFmpegDemuxerStream::FFmpegDemuxerStream(
- FFmpegDemuxer* demuxer,
- AVStream* stream)
- : demuxer_(demuxer),
- message_loop_(base::MessageLoopProxy::current()),
- stream_(stream),
- type_(UNKNOWN),
- stopped_(false),
- end_of_stream_(false),
- last_packet_timestamp_(kNoTimestamp()),
- bitstream_converter_enabled_(false) {
- DCHECK(demuxer_);
-
- // Determine our media format.
- switch (stream->codec->codec_type) {
- case AVMEDIA_TYPE_AUDIO:
- type_ = AUDIO;
- AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_);
- break;
- case AVMEDIA_TYPE_VIDEO:
- type_ = VIDEO;
- AVStreamToVideoDecoderConfig(stream, &video_config_);
- break;
- default:
- NOTREACHED();
- break;
- }
-
- // Calculate the duration.
- duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration);
-
- if (stream_->codec->codec_id == CODEC_ID_H264) {
- bitstream_converter_.reset(
- new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec));
- }
-}
-
-void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (stopped_ || end_of_stream_) {
- NOTREACHED() << "Attempted to enqueue packet on a stopped stream";
- return;
- }
-
- // Convert the packet if there is a bitstream filter.
- if (packet->data && bitstream_converter_enabled_ &&
- !bitstream_converter_->ConvertPacket(packet.get())) {
- LOG(ERROR) << "Format conversion failed.";
- }
-
- // If a packet is returned by FFmpeg's av_parser_parse2() the packet will
- // reference inner memory of FFmpeg. As such we should transfer the packet
- // into memory we control.
- scoped_refptr<DecoderBuffer> buffer;
- buffer = DecoderBuffer::CopyFrom(packet->data, packet->size);
- buffer->SetTimestamp(ConvertStreamTimestamp(
- stream_->time_base, packet->pts));
- buffer->SetDuration(ConvertStreamTimestamp(
- stream_->time_base, packet->duration));
- if (buffer->GetTimestamp() != kNoTimestamp() &&
- last_packet_timestamp_ != kNoTimestamp() &&
- last_packet_timestamp_ < buffer->GetTimestamp()) {
- buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp());
- demuxer_->NotifyBufferingChanged();
- }
- last_packet_timestamp_ = buffer->GetTimestamp();
-
- buffer_queue_.Push(buffer);
- SatisfyPendingRead();
-}
-
-void FFmpegDemuxerStream::SetEndOfStream() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- end_of_stream_ = true;
- SatisfyPendingRead();
-}
-
-void FFmpegDemuxerStream::FlushBuffers() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(read_cb_.is_null()) << "There should be no pending read";
- buffer_queue_.Clear();
- end_of_stream_ = false;
- last_packet_timestamp_ = kNoTimestamp();
-}
-
-void FFmpegDemuxerStream::Stop() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- buffer_queue_.Clear();
- if (!read_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(
- DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
- }
- stopped_ = true;
- end_of_stream_ = true;
-}
-
-base::TimeDelta FFmpegDemuxerStream::duration() {
- return duration_;
-}
-
-DemuxerStream::Type FFmpegDemuxerStream::type() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- return type_;
-}
-
-void FFmpegDemuxerStream::Read(const ReadCB& read_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK(read_cb_.is_null()) << "Overlapping reads are not supported";
- read_cb_ = BindToCurrentLoop(read_cb);
-
- // Don't accept any additional reads if we've been told to stop.
- // The |demuxer_| may have been destroyed in the pipeline thread.
- //
- // TODO(scherkus): it would be cleaner to reply with an error message.
- if (stopped_) {
- base::ResetAndReturn(&read_cb_).Run(
- DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
- return;
- }
-
- SatisfyPendingRead();
-}
-
-void FFmpegDemuxerStream::EnableBitstreamConverter() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK(bitstream_converter_.get());
- bitstream_converter_enabled_ = true;
-}
-
-const AudioDecoderConfig& FFmpegDemuxerStream::audio_decoder_config() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK_EQ(type_, AUDIO);
- return audio_config_;
-}
-
-const VideoDecoderConfig& FFmpegDemuxerStream::video_decoder_config() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK_EQ(type_, VIDEO);
- return video_config_;
-}
-
-FFmpegDemuxerStream::~FFmpegDemuxerStream() {
- DCHECK(stopped_);
- DCHECK(read_cb_.is_null());
- DCHECK(buffer_queue_.IsEmpty());
-}
-
-base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const {
- return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts);
-}
-
-Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
- return buffered_ranges_;
-}
-
-void FFmpegDemuxerStream::SatisfyPendingRead() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (!read_cb_.is_null()) {
- if (!buffer_queue_.IsEmpty()) {
- base::ResetAndReturn(&read_cb_).Run(
- DemuxerStream::kOk, buffer_queue_.Pop());
- } else if (end_of_stream_) {
- base::ResetAndReturn(&read_cb_).Run(
- DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
- }
- }
-
- // Have capacity? Ask for more!
- if (HasAvailableCapacity() && !end_of_stream_) {
- demuxer_->NotifyCapacityAvailable();
- }
-}
-
-bool FFmpegDemuxerStream::HasAvailableCapacity() {
- // TODO(scherkus): Remove early return and reenable time-based capacity
- // after our data sources support canceling/concurrent reads, see
- // http://crbug.com/165762 for details.
- return !read_cb_.is_null();
-
- // Try to have one second's worth of encoded data per stream.
- const base::TimeDelta kCapacity = base::TimeDelta::FromSeconds(1);
- return buffer_queue_.IsEmpty() || buffer_queue_.Duration() < kCapacity;
-}
-
-// static
-base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
- const AVRational& time_base, int64 timestamp) {
- if (timestamp == static_cast<int64>(AV_NOPTS_VALUE))
- return kNoTimestamp();
-
- return ConvertFromTimeBase(time_base, timestamp);
-}
-
-//
-// FFmpegDemuxer
-//
-FFmpegDemuxer::FFmpegDemuxer(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const scoped_refptr<DataSource>& data_source)
- : host_(NULL),
- message_loop_(message_loop),
- blocking_thread_("FFmpegDemuxer"),
- pending_read_(false),
- pending_seek_(false),
- data_source_(data_source),
- bitrate_(0),
- start_time_(kNoTimestamp()),
- audio_disabled_(false),
- duration_known_(false),
- url_protocol_(data_source, BindToLoop(message_loop_, base::Bind(
- &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))) {
- DCHECK(message_loop_);
- DCHECK(data_source_);
-}
-
-FFmpegDemuxer::~FFmpegDemuxer() {}
-
-void FFmpegDemuxer::Stop(const base::Closure& callback) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- url_protocol_.Abort();
- data_source_->Stop(BindToCurrentLoop(base::Bind(
- &FFmpegDemuxer::OnDataSourceStopped, this, BindToCurrentLoop(callback))));
-}
-
-void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK(!pending_seek_);
-
- // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|,
- // otherwise we can end up waiting for a pre-seek read to complete even though
- // we know we're going to drop it on the floor.
-
- // Always seek to a timestamp less than or equal to the desired timestamp.
- int flags = AVSEEK_FLAG_BACKWARD;
-
- // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg
- // will attempt to use the lowest-index video stream, if present, followed by
- // the lowest-index audio stream.
- pending_seek_ = true;
- base::PostTaskAndReplyWithResult(
- blocking_thread_.message_loop_proxy(), FROM_HERE,
- base::Bind(&av_seek_frame, glue_->format_context(), -1,
- time.InMicroseconds(), flags),
- base::Bind(&FFmpegDemuxer::OnSeekFrameDone, this, cb));
-}
-
-void FFmpegDemuxer::SetPlaybackRate(float playback_rate) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- data_source_->SetPlaybackRate(playback_rate);
-}
-
-void FFmpegDemuxer::OnAudioRendererDisabled() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- audio_disabled_ = true;
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
- (*iter)->Stop();
- }
- }
-}
-
-void FFmpegDemuxer::Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- host_ = host;
-
- // TODO(scherkus): DataSource should have a host by this point,
- // see http://crbug.com/122071
- data_source_->set_host(host);
-
- glue_.reset(new FFmpegGlue(&url_protocol_));
- AVFormatContext* format_context = glue_->format_context();
-
- // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we
- // don't use. FFmpeg will only read ID3v1 tags if no other metadata is
- // available, so add a metadata entry to ensure some is always present.
- av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0);
-
- // Open the AVFormatContext using our glue layer.
- CHECK(blocking_thread_.Start());
- base::PostTaskAndReplyWithResult(
- blocking_thread_.message_loop_proxy(), FROM_HERE,
- base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())),
- base::Bind(&FFmpegDemuxer::OnOpenContextDone, this, status_cb));
-}
-
-scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(
- DemuxerStream::Type type) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- return GetFFmpegStream(type);
-}
-
-scoped_refptr<FFmpegDemuxerStream> FFmpegDemuxer::GetFFmpegStream(
- DemuxerStream::Type type) const {
- StreamVector::const_iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter && (*iter)->type() == type) {
- return *iter;
- }
- }
- return NULL;
-}
-
-base::TimeDelta FFmpegDemuxer::GetStartTime() const {
- DCHECK(message_loop_->BelongsToCurrentThread());
- return start_time_;
-}
-
-// Helper for calculating the bitrate of the media based on information stored
-// in |format_context| or failing that the size and duration of the media.
-//
-// Returns 0 if a bitrate could not be determined.
-static int CalculateBitrate(
- AVFormatContext* format_context,
- const base::TimeDelta& duration,
- int64 filesize_in_bytes) {
- // If there is a bitrate set on the container, use it.
- if (format_context->bit_rate > 0)
- return format_context->bit_rate;
-
- // Then try to sum the bitrates individually per stream.
- int bitrate = 0;
- for (size_t i = 0; i < format_context->nb_streams; ++i) {
- AVCodecContext* codec_context = format_context->streams[i]->codec;
- bitrate += codec_context->bit_rate;
- }
- if (bitrate > 0)
- return bitrate;
-
- // See if we can approximate the bitrate as long as we have a filesize and
- // valid duration.
- if (duration.InMicroseconds() <= 0 ||
- duration == kInfiniteDuration() ||
- filesize_in_bytes == 0) {
- return 0;
- }
-
- // Do math in floating point as we'd overflow an int64 if the filesize was
- // larger than ~1073GB.
- double bytes = filesize_in_bytes;
- double duration_us = duration.InMicroseconds();
- return bytes * 8000000.0 / duration_us;
-}
-
-void FFmpegDemuxer::OnOpenContextDone(const PipelineStatusCB& status_cb,
- bool result) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (!blocking_thread_.IsRunning()) {
- status_cb.Run(PIPELINE_ERROR_ABORT);
- return;
- }
-
- if (!result) {
- status_cb.Run(DEMUXER_ERROR_COULD_NOT_OPEN);
- return;
- }
-
- // Fully initialize AVFormatContext by parsing the stream a little.
- base::PostTaskAndReplyWithResult(
- blocking_thread_.message_loop_proxy(), FROM_HERE,
- base::Bind(&avformat_find_stream_info, glue_->format_context(),
- static_cast<AVDictionary**>(NULL)),
- base::Bind(&FFmpegDemuxer::OnFindStreamInfoDone, this, status_cb));
-}
-
-void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
- int result) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (!blocking_thread_.IsRunning()) {
- status_cb.Run(PIPELINE_ERROR_ABORT);
- return;
- }
-
- if (result < 0) {
- status_cb.Run(DEMUXER_ERROR_COULD_NOT_PARSE);
- return;
- }
-
- // Create demuxer stream entries for each possible AVStream.
- AVFormatContext* format_context = glue_->format_context();
- streams_.resize(format_context->nb_streams);
- bool found_audio_stream = false;
- bool found_video_stream = false;
-
- base::TimeDelta max_duration;
- for (size_t i = 0; i < format_context->nb_streams; ++i) {
- AVCodecContext* codec_context = format_context->streams[i]->codec;
- AVMediaType codec_type = codec_context->codec_type;
-
- if (codec_type == AVMEDIA_TYPE_AUDIO) {
- if (found_audio_stream)
- continue;
- // Ensure the codec is supported.
- if (CodecIDToAudioCodec(codec_context->codec_id) == kUnknownAudioCodec)
- continue;
- found_audio_stream = true;
- } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
- if (found_video_stream)
- continue;
- // Ensure the codec is supported.
- if (CodecIDToVideoCodec(codec_context->codec_id) == kUnknownVideoCodec)
- continue;
- found_video_stream = true;
- } else {
- continue;
- }
-
- AVStream* stream = format_context->streams[i];
- scoped_refptr<FFmpegDemuxerStream> demuxer_stream(
- new FFmpegDemuxerStream(this, stream));
-
- streams_[i] = demuxer_stream;
- max_duration = std::max(max_duration, demuxer_stream->duration());
-
- if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) {
- const base::TimeDelta first_dts = ConvertFromTimeBase(
- stream->time_base, stream->first_dts);
- if (start_time_ == kNoTimestamp() || first_dts < start_time_)
- start_time_ = first_dts;
- }
- }
-
- if (!found_audio_stream && !found_video_stream) {
- status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
- return;
- }
-
- if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) {
- // If there is a duration value in the container use that to find the
- // maximum between it and the duration from A/V streams.
- const AVRational av_time_base = {1, AV_TIME_BASE};
- max_duration =
- std::max(max_duration,
- ConvertFromTimeBase(av_time_base, format_context->duration));
- } else {
- // The duration is unknown, in which case this is likely a live stream.
- max_duration = kInfiniteDuration();
- }
-
- // Some demuxers, like WAV, do not put timestamps on their frames. We
- // assume the the start time is 0.
- if (start_time_ == kNoTimestamp())
- start_time_ = base::TimeDelta();
-
- // Good to go: set the duration and bitrate and notify we're done
- // initializing.
- host_->SetDuration(max_duration);
- duration_known_ = (max_duration != kInfiniteDuration());
-
- int64 filesize_in_bytes = 0;
- url_protocol_.GetSize(&filesize_in_bytes);
- bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes);
- if (bitrate_ > 0)
- data_source_->SetBitrate(bitrate_);
-
- status_cb.Run(PIPELINE_OK);
-}
-
-void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- CHECK(pending_seek_);
- pending_seek_ = false;
-
- if (!blocking_thread_.IsRunning()) {
- cb.Run(PIPELINE_ERROR_ABORT);
- return;
- }
-
- if (result < 0) {
- // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being
- // captured from stdout and contaminates testing.
- // TODO(scherkus): Implement this properly and signal error (BUG=23447).
- VLOG(1) << "Not implemented";
- }
-
- // Tell streams to flush buffers due to seeking.
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter)
- (*iter)->FlushBuffers();
- }
-
- // Resume reading until capacity.
- ReadFrameIfNeeded();
-
- // Notify we're finished seeking.
- cb.Run(PIPELINE_OK);
-}
-
-void FFmpegDemuxer::ReadFrameIfNeeded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Make sure we have work to do before reading.
- if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() ||
- pending_read_ || pending_seek_) {
- return;
- }
-
- // Allocate and read an AVPacket from the media. Save |packet_ptr| since
- // evaluation order of packet.get() and base::Passed(&packet) is
- // undefined.
- ScopedAVPacket packet(new AVPacket());
- AVPacket* packet_ptr = packet.get();
-
- pending_read_ = true;
- base::PostTaskAndReplyWithResult(
- blocking_thread_.message_loop_proxy(), FROM_HERE,
- base::Bind(&av_read_frame, glue_->format_context(), packet_ptr),
- base::Bind(&FFmpegDemuxer::OnReadFrameDone, this, base::Passed(&packet)));
-}
-
-void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(pending_read_);
- pending_read_ = false;
-
- if (!blocking_thread_.IsRunning() || pending_seek_) {
- return;
- }
-
- if (result < 0) {
- // Update the duration based on the audio stream if
- // it was previously unknown http://crbug.com/86830
- if (!duration_known_) {
- // Search streams for AUDIO one.
- for (StreamVector::iterator iter = streams_.begin();
- iter != streams_.end();
- ++iter) {
- if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
- base::TimeDelta duration = (*iter)->GetElapsedTime();
- if (duration != kNoTimestamp() && duration > base::TimeDelta()) {
- host_->SetDuration(duration);
- duration_known_ = true;
- }
- break;
- }
- }
- }
- // If we have reached the end of stream, tell the downstream filters about
- // the event.
- StreamHasEnded();
- return;
- }
-
- // Queue the packet with the appropriate stream.
- DCHECK_GE(packet->stream_index, 0);
- DCHECK_LT(packet->stream_index, static_cast<int>(streams_.size()));
-
- // Defend against ffmpeg giving us a bad stream index.
- if (packet->stream_index >= 0 &&
- packet->stream_index < static_cast<int>(streams_.size()) &&
- streams_[packet->stream_index] &&
- (!audio_disabled_ ||
- streams_[packet->stream_index]->type() != DemuxerStream::AUDIO)) {
-
- // TODO(scherkus): Fix demuxing upstream to never return packets w/o data
- // when av_read_frame() returns success code. See bug comment for ideas:
- //
- // https://code.google.com/p/chromium/issues/detail?id=169133#c10
- if (!packet->data) {
- ScopedAVPacket new_packet(new AVPacket());
- av_new_packet(new_packet.get(), 0);
-
- new_packet->pts = packet->pts;
- new_packet->dts = packet->dts;
- new_packet->pos = packet->pos;
- new_packet->duration = packet->duration;
- new_packet->convergence_duration = packet->convergence_duration;
- new_packet->flags = packet->flags;
- new_packet->stream_index = packet->stream_index;
-
- packet.swap(new_packet);
- }
-
- FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index];
- demuxer_stream->EnqueuePacket(packet.Pass());
- }
-
- // Keep reading until we've reached capacity.
- ReadFrameIfNeeded();
-}
-
-void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) {
- // This will block until all tasks complete. Note that after this returns it's
- // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this
- // thread. Each of the reply task methods must check whether we've stopped the
- // thread and drop their results on the floor.
- DCHECK(message_loop_->BelongsToCurrentThread());
- blocking_thread_.Stop();
-
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter)
- (*iter)->Stop();
- }
-
- callback.Run();
-}
-
-bool FFmpegDemuxer::StreamsHaveAvailableCapacity() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (*iter && (*iter)->HasAvailableCapacity()) {
- return true;
- }
- }
- return false;
-}
-
-void FFmpegDemuxer::StreamHasEnded() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- StreamVector::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if (!*iter ||
- (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) {
- continue;
- }
- (*iter)->SetEndOfStream();
- }
-}
-
-void FFmpegDemuxer::NotifyCapacityAvailable() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- ReadFrameIfNeeded();
-}
-
-void FFmpegDemuxer::NotifyBufferingChanged() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- Ranges<base::TimeDelta> buffered;
- scoped_refptr<FFmpegDemuxerStream> audio =
- audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO);
- scoped_refptr<FFmpegDemuxerStream> video =
- GetFFmpegStream(DemuxerStream::VIDEO);
- if (audio && video) {
- buffered = audio->GetBufferedRanges().IntersectionWith(
- video->GetBufferedRanges());
- } else if (audio) {
- buffered = audio->GetBufferedRanges();
- } else if (video) {
- buffered = video->GetBufferedRanges();
- }
- for (size_t i = 0; i < buffered.size(); ++i)
- host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i));
-}
-
-void FFmpegDemuxer::OnDataSourceError() {
- host_->OnDemuxerError(PIPELINE_ERROR_READ);
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_demuxer.h b/src/media/filters/ffmpeg_demuxer.h
deleted file mode 100644
index 1056d07..0000000
--- a/src/media/filters/ffmpeg_demuxer.h
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implements the Demuxer interface using FFmpeg's libavformat. At this time
-// will support demuxing any audio/video format thrown at it. The streams
-// output mime types audio/x-ffmpeg and video/x-ffmpeg and include an integer
-// key FFmpegCodecID which contains the CodecID enumeration value. The CodecIDs
-// can be used to create and initialize the corresponding FFmpeg decoder.
-//
-// FFmpegDemuxer sets the duration of pipeline during initialization by using
-// the duration of the longest audio/video stream.
-//
-// NOTE: since FFmpegDemuxer reads packets sequentially without seeking, media
-// files with very large drift between audio/video streams may result in
-// excessive memory consumption.
-//
-// When stopped, FFmpegDemuxer and FFmpegDemuxerStream release all callbacks
-// and buffered packets. Reads from a stopped FFmpegDemuxerStream will not be
-// replied to.
-
-#ifndef MEDIA_FILTERS_FFMPEG_DEMUXER_H_
-#define MEDIA_FILTERS_FFMPEG_DEMUXER_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/gtest_prod_util.h"
-#include "base/threading/thread.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decoder_buffer_queue.h"
-#include "media/base/demuxer.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_decoder_config.h"
-#include "media/filters/blocking_url_protocol.h"
-
-// FFmpeg forward declarations.
-struct AVPacket;
-struct AVRational;
-struct AVStream;
-
-namespace media {
-
-class FFmpegDemuxer;
-class FFmpegGlue;
-class FFmpegH264ToAnnexBBitstreamConverter;
-class ScopedPtrAVFreePacket;
-
-typedef scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> ScopedAVPacket;
-
-class FFmpegDemuxerStream : public DemuxerStream {
- public:
- // Keeps a copy of |demuxer| and initializes itself using information
- // inside |stream|. Both parameters must outlive |this|.
- FFmpegDemuxerStream(FFmpegDemuxer* demuxer, AVStream* stream);
-
- // Enqueues the given AVPacket. It is invalid to queue a |packet| after
- // SetEndOfStream() has been called.
- void EnqueuePacket(ScopedAVPacket packet);
-
- // Enters the end of stream state. After delivering remaining queued buffers
- // only end of stream buffers will be delivered.
- void SetEndOfStream();
-
- // Drops queued buffers and clears end of stream state.
- void FlushBuffers();
-
- // Empties the queues and ignores any additional calls to Read().
- void Stop();
-
- // Returns the duration of this stream.
- base::TimeDelta duration();
-
- // DemuxerStream implementation.
- virtual Type type() OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual void EnableBitstreamConverter() OVERRIDE;
- virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
- virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
-
- // Returns the range of buffered data in this stream.
- Ranges<base::TimeDelta> GetBufferedRanges() const;
-
- // Returns elapsed time based on the already queued packets.
- // Used to determine stream duration when it's not known ahead of time.
- base::TimeDelta GetElapsedTime() const;
-
- // Returns true if this stream has capacity for additional data.
- bool HasAvailableCapacity();
-
- protected:
- virtual ~FFmpegDemuxerStream();
-
- private:
- friend class FFmpegDemuxerTest;
-
- // Runs |read_cb_| if present with the front of |buffer_queue_|, calling
- // NotifyCapacityAvailable() if capacity is still available.
- void SatisfyPendingRead();
-
- // Converts an FFmpeg stream timestamp into a base::TimeDelta.
- static base::TimeDelta ConvertStreamTimestamp(const AVRational& time_base,
- int64 timestamp);
-
- FFmpegDemuxer* demuxer_;
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- AVStream* stream_;
- AudioDecoderConfig audio_config_;
- VideoDecoderConfig video_config_;
- Type type_;
- base::TimeDelta duration_;
- bool stopped_;
- bool end_of_stream_;
- base::TimeDelta last_packet_timestamp_;
- Ranges<base::TimeDelta> buffered_ranges_;
-
- DecoderBufferQueue buffer_queue_;
- ReadCB read_cb_;
-
- scoped_ptr<FFmpegH264ToAnnexBBitstreamConverter> bitstream_converter_;
- bool bitstream_converter_enabled_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerStream);
-};
-
-class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
- public:
- FFmpegDemuxer(const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const scoped_refptr<DataSource>& data_source);
-
- // Demuxer implementation.
- virtual void Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE;
- virtual void OnAudioRendererDisabled() OVERRIDE;
- virtual void SetPlaybackRate(float playback_rate) OVERRIDE;
- virtual scoped_refptr<DemuxerStream> GetStream(
- DemuxerStream::Type type) OVERRIDE;
- virtual base::TimeDelta GetStartTime() const OVERRIDE;
-
- // Allow FFmpegDemuxerStream to notify us when there is updated information
- // about capacity and what buffered data is available.
- void NotifyCapacityAvailable();
- void NotifyBufferingChanged();
-
- private:
- // To allow tests access to privates.
- friend class FFmpegDemuxerTest;
-
- virtual ~FFmpegDemuxer();
-
- // FFmpeg callbacks during initialization.
- void OnOpenContextDone(const PipelineStatusCB& status_cb, bool result);
- void OnFindStreamInfoDone(const PipelineStatusCB& status_cb, int result);
-
- // FFmpeg callbacks during seeking.
- void OnSeekFrameDone(const PipelineStatusCB& cb, int result);
-
- // FFmpeg callbacks during reading + helper method to initiate reads.
- void ReadFrameIfNeeded();
- void OnReadFrameDone(ScopedAVPacket packet, int result);
-
- // DataSource callbacks during stopping.
- void OnDataSourceStopped(const base::Closure& callback);
-
- // Returns true iff any stream has additional capacity. Note that streams can
- // go over capacity depending on how the file is muxed.
- bool StreamsHaveAvailableCapacity();
-
- // Signal all FFmpegDemuxerStreams that the stream has ended.
- void StreamHasEnded();
-
- // Called by |url_protocol_| whenever |data_source_| returns a read error.
- void OnDataSourceError();
-
- // Returns the stream from |streams_| that matches |type| as an
- // FFmpegDemuxerStream.
- scoped_refptr<FFmpegDemuxerStream> GetFFmpegStream(
- DemuxerStream::Type type) const;
-
- DemuxerHost* host_;
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // Thread on which all blocking FFmpeg operations are executed.
- base::Thread blocking_thread_;
-
- // Tracks if there's an outstanding av_read_frame() operation.
- //
- // TODO(scherkus): Allow more than one read in flight for higher read
- // throughput using demuxer_bench to verify improvements.
- bool pending_read_;
-
- // Tracks if there's an outstanding av_seek_frame() operation. Used to discard
- // results of pre-seek av_read_frame() operations.
- bool pending_seek_;
-
- // |streams_| mirrors the AVStream array in |format_context_|. It contains
- // FFmpegDemuxerStreams encapsluating AVStream objects at the same index.
- //
- // Since we only support a single audio and video stream, |streams_| will
- // contain NULL entries for additional audio/video streams as well as for
- // stream types that we do not currently support.
- //
- // Once initialized, operations on FFmpegDemuxerStreams should be carried out
- // on the demuxer thread.
- typedef std::vector<scoped_refptr<FFmpegDemuxerStream> > StreamVector;
- StreamVector streams_;
-
- // Reference to the data source. Asynchronous read requests are submitted to
- // this object.
- scoped_refptr<DataSource> data_source_;
-
- // Derived bitrate after initialization has completed.
- int bitrate_;
-
- // The first timestamp of the opened media file. This is used to set the
- // starting clock value to match the timestamps in the media file. Default
- // is 0.
- base::TimeDelta start_time_;
-
- // Whether audio has been disabled for this demuxer (in which case this class
- // drops packets destined for AUDIO demuxer streams on the floor).
- bool audio_disabled_;
-
- // Set if we know duration of the audio stream. Used when processing end of
- // stream -- at this moment we definitely know duration.
- bool duration_known_;
-
- // FFmpegURLProtocol implementation and corresponding glue bits.
- BlockingUrlProtocol url_protocol_;
- scoped_ptr<FFmpegGlue> glue_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxer);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FFMPEG_DEMUXER_H_
diff --git a/src/media/filters/ffmpeg_demuxer_unittest.cc b/src/media/filters/ffmpeg_demuxer_unittest.cc
deleted file mode 100644
index fbbcd00..0000000
--- a/src/media/filters/ffmpeg_demuxer_unittest.cc
+++ /dev/null
@@ -1,631 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <algorithm>
-#include <deque>
-#include <string>
-
-#include "base/bind.h"
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/path_service.h"
-#include "base/threading/thread.h"
-#include "media/base/mock_demuxer_host.h"
-#include "media/base/test_helpers.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_demuxer.h"
-#include "media/filters/file_data_source.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::AnyNumber;
-using ::testing::DoAll;
-using ::testing::InSequence;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::SaveArg;
-using ::testing::SetArgPointee;
-using ::testing::StrictMock;
-using ::testing::WithArgs;
-using ::testing::_;
-
-namespace media {
-
-MATCHER(IsEndOfStreamBuffer,
- std::string(negation ? "isn't" : "is") + " end of stream") {
- return arg->IsEndOfStream();
-}
-
-static void EosOnReadDone(bool* got_eos_buffer,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- MessageLoop::current()->PostTask(
- FROM_HERE, MessageLoop::QuitWhenIdleClosure());
-
- EXPECT_EQ(status, DemuxerStream::kOk);
- if (buffer->IsEndOfStream()) {
- *got_eos_buffer = true;
- EXPECT_TRUE(!buffer->GetData());
- EXPECT_EQ(buffer->GetDataSize(), 0);
- return;
- }
-
- EXPECT_TRUE(buffer->GetData());
- EXPECT_GT(buffer->GetDataSize(), 0);
- *got_eos_buffer = false;
-};
-
-
-// Fixture class to facilitate writing tests. Takes care of setting up the
-// FFmpeg, pipeline and filter host mocks.
-class FFmpegDemuxerTest : public testing::Test {
- protected:
- FFmpegDemuxerTest() {}
-
- virtual ~FFmpegDemuxerTest() {
- if (demuxer_) {
- demuxer_->Stop(MessageLoop::QuitWhenIdleClosure());
- message_loop_.Run();
- }
-
- demuxer_ = NULL;
- }
-
- void CreateDemuxer(const std::string& name) {
- CHECK(!demuxer_);
-
- EXPECT_CALL(host_, SetTotalBytes(_)).Times(AnyNumber());
- EXPECT_CALL(host_, AddBufferedByteRange(_, _)).Times(AnyNumber());
- EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
-
- CreateDataSource(name);
- demuxer_ = new FFmpegDemuxer(message_loop_.message_loop_proxy(),
- data_source_);
- }
-
- MOCK_METHOD1(CheckPoint, void(int v));
-
- void InitializeDemuxer() {
- EXPECT_CALL(host_, SetDuration(_));
- WaitableMessageLoopEvent event;
- demuxer_->Initialize(&host_, event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(PIPELINE_OK);
- }
-
- MOCK_METHOD2(OnReadDoneCalled, void(int, int64));
-
- // Verifies that |buffer| has a specific |size| and |timestamp|.
- // |location| simply indicates where the call to this function was made.
- // This makes it easier to track down where test failures occur.
- void OnReadDone(const tracked_objects::Location& location,
- int size, int64 timestampInMicroseconds,
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- std::string location_str;
- location.Write(true, false, &location_str);
- location_str += "\n";
- SCOPED_TRACE(location_str);
- EXPECT_EQ(status, DemuxerStream::kOk);
- OnReadDoneCalled(size, timestampInMicroseconds);
- EXPECT_TRUE(buffer.get() != NULL);
- EXPECT_EQ(size, buffer->GetDataSize());
- EXPECT_EQ(base::TimeDelta::FromMicroseconds(timestampInMicroseconds),
- buffer->GetTimestamp());
-
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- message_loop_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
- }
-
- DemuxerStream::ReadCB NewReadCB(const tracked_objects::Location& location,
- int size, int64 timestampInMicroseconds) {
- EXPECT_CALL(*this, OnReadDoneCalled(size, timestampInMicroseconds));
- return base::Bind(&FFmpegDemuxerTest::OnReadDone, base::Unretained(this),
- location, size, timestampInMicroseconds);
- }
-
- // Accessor to demuxer internals.
- void set_duration_known(bool duration_known) {
- demuxer_->duration_known_ = duration_known;
- }
-
- bool IsStreamStopped(DemuxerStream::Type type) {
- DemuxerStream* stream = demuxer_->GetStream(type);
- CHECK(stream);
- return static_cast<FFmpegDemuxerStream*>(stream)->stopped_;
- }
-
- // Fixture members.
- scoped_refptr<FileDataSource> data_source_;
- scoped_refptr<FFmpegDemuxer> demuxer_;
- StrictMock<MockDemuxerHost> host_;
- MessageLoop message_loop_;
-
- AVFormatContext* format_context() {
- return demuxer_->glue_->format_context();
- }
-
- void ReadUntilEndOfStream() {
- // We should expect an end of stream buffer.
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
-
- bool got_eos_buffer = false;
- const int kMaxBuffers = 170;
- for (int i = 0; !got_eos_buffer && i < kMaxBuffers; i++) {
- audio->Read(base::Bind(&EosOnReadDone, &got_eos_buffer));
- message_loop_.Run();
- }
-
- EXPECT_TRUE(got_eos_buffer);
- }
-
- private:
- void CreateDataSource(const std::string& name) {
- CHECK(!data_source_);
-
- FilePath file_path;
- EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
-
- file_path = file_path.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .AppendASCII(name);
-
- data_source_ = new FileDataSource();
- EXPECT_TRUE(data_source_->Initialize(file_path));
- }
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerTest);
-};
-
-TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) {
- // Simulate avformat_open_input() failing.
- CreateDemuxer("ten_byte_file");
- WaitableMessageLoopEvent event;
- demuxer_->Initialize(&host_, event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(DEMUXER_ERROR_COULD_NOT_OPEN);
-}
-
-// TODO(acolwell): Uncomment this test when we discover a file that passes
-// avformat_open_input(), but has avformat_find_stream_info() fail.
-//
-//TEST_F(FFmpegDemuxerTest, Initialize_ParseFails) {
-// ("find_stream_info_fail.webm");
-// demuxer_->Initialize(
-// &host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_PARSE));
-// message_loop_.RunUntilIdle();
-//}
-
-TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) {
- // Open a file with no streams whatsoever.
- CreateDemuxer("no_streams.webm");
- WaitableMessageLoopEvent event;
- demuxer_->Initialize(&host_, event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
-}
-
-TEST_F(FFmpegDemuxerTest, Initialize_NoAudioVideo) {
- // Open a file containing streams but none of which are audio/video streams.
- CreateDemuxer("no_audio_video.webm");
- WaitableMessageLoopEvent event;
- demuxer_->Initialize(&host_, event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
-}
-
-TEST_F(FFmpegDemuxerTest, Initialize_Successful) {
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Video stream should be present.
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- ASSERT_TRUE(stream);
- EXPECT_EQ(DemuxerStream::VIDEO, stream->type());
-
- const VideoDecoderConfig& video_config = stream->video_decoder_config();
- EXPECT_EQ(kCodecVP8, video_config.codec());
- EXPECT_EQ(VideoFrame::YV12, video_config.format());
- EXPECT_EQ(320, video_config.coded_size().width());
- EXPECT_EQ(240, video_config.coded_size().height());
- EXPECT_EQ(0, video_config.visible_rect().x());
- EXPECT_EQ(0, video_config.visible_rect().y());
- EXPECT_EQ(320, video_config.visible_rect().width());
- EXPECT_EQ(240, video_config.visible_rect().height());
- EXPECT_EQ(320, video_config.natural_size().width());
- EXPECT_EQ(240, video_config.natural_size().height());
- EXPECT_FALSE(video_config.extra_data());
- EXPECT_EQ(0u, video_config.extra_data_size());
-
- // Audio stream should be present.
- stream = demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(stream);
- EXPECT_EQ(DemuxerStream::AUDIO, stream->type());
-
- const AudioDecoderConfig& audio_config = stream->audio_decoder_config();
- EXPECT_EQ(kCodecVorbis, audio_config.codec());
- EXPECT_EQ(16, audio_config.bits_per_channel());
- EXPECT_EQ(CHANNEL_LAYOUT_STEREO, audio_config.channel_layout());
- EXPECT_EQ(44100, audio_config.samples_per_second());
- EXPECT_TRUE(audio_config.extra_data());
- EXPECT_GT(audio_config.extra_data_size(), 0u);
-
- // Unknown stream should never be present.
- EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::UNKNOWN));
-}
-
-TEST_F(FFmpegDemuxerTest, Initialize_Multitrack) {
- // Open a file containing the following streams:
- // Stream #0: Video (VP8)
- // Stream #1: Audio (Vorbis)
- // Stream #2: Subtitles (SRT)
- // Stream #3: Video (Theora)
- // Stream #4: Audio (16-bit signed little endian PCM)
- //
- // We should only pick the first audio/video streams we come across.
- CreateDemuxer("bear-320x240-multitrack.webm");
- InitializeDemuxer();
-
- // Video stream should be VP8.
- scoped_refptr<DemuxerStream> stream =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- ASSERT_TRUE(stream);
- EXPECT_EQ(DemuxerStream::VIDEO, stream->type());
- EXPECT_EQ(kCodecVP8, stream->video_decoder_config().codec());
-
- // Audio stream should be Vorbis.
- stream = demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(stream);
- EXPECT_EQ(DemuxerStream::AUDIO, stream->type());
- EXPECT_EQ(kCodecVorbis, stream->audio_decoder_config().codec());
-
- // Unknown stream should never be present.
- EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::UNKNOWN));
-}
-
-TEST_F(FFmpegDemuxerTest, Read_Audio) {
- // We test that on a successful audio packet read.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Attempt a read from the audio stream and run the message loop until done.
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
-
- audio->Read(NewReadCB(FROM_HERE, 29, 0));
- message_loop_.Run();
-
- audio->Read(NewReadCB(FROM_HERE, 27, 3000));
- message_loop_.Run();
-}
-
-TEST_F(FFmpegDemuxerTest, Read_Video) {
- // We test that on a successful video packet read.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Attempt a read from the video stream and run the message loop until done.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
-
- video->Read(NewReadCB(FROM_HERE, 22084, 0));
- message_loop_.Run();
-
- video->Read(NewReadCB(FROM_HERE, 1057, 33000));
- message_loop_.Run();
-}
-
-TEST_F(FFmpegDemuxerTest, Read_VideoNonZeroStart) {
- // Test the start time is the first timestamp of the video and audio stream.
- CreateDemuxer("nonzero-start-time.webm");
- InitializeDemuxer();
-
- // Attempt a read from the video stream and run the message loop until done.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
-
- // Check first buffer in video stream.
- video->Read(NewReadCB(FROM_HERE, 5636, 400000));
- message_loop_.Run();
-
- // Check first buffer in audio stream.
- audio->Read(NewReadCB(FROM_HERE, 165, 396000));
- message_loop_.Run();
-
- // Verify that the start time is equal to the lowest timestamp (ie the audio).
- EXPECT_EQ(demuxer_->GetStartTime().InMicroseconds(), 396000);
-}
-
-TEST_F(FFmpegDemuxerTest, Read_EndOfStream) {
- // Verify that end of stream buffers are created.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
- ReadUntilEndOfStream();
-}
-
-TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration) {
- // Verify that end of stream buffers are created.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
- set_duration_known(false);
- EXPECT_CALL(host_, SetDuration(_));
- ReadUntilEndOfStream();
-}
-
-TEST_F(FFmpegDemuxerTest, Seek) {
- // We're testing that the demuxer frees all queued packets when it receives
- // a Seek().
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Get our streams.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(video);
- ASSERT_TRUE(audio);
-
- // Read a video packet and release it.
- video->Read(NewReadCB(FROM_HERE, 22084, 0));
- message_loop_.Run();
-
- // Issue a simple forward seek, which should discard queued packets.
- WaitableMessageLoopEvent event;
- demuxer_->Seek(base::TimeDelta::FromMicroseconds(1000000),
- event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(PIPELINE_OK);
-
- // Audio read #1.
- audio->Read(NewReadCB(FROM_HERE, 145, 803000));
- message_loop_.Run();
-
- // Audio read #2.
- audio->Read(NewReadCB(FROM_HERE, 148, 826000));
- message_loop_.Run();
-
- // Video read #1.
- video->Read(NewReadCB(FROM_HERE, 5425, 801000));
- message_loop_.Run();
-
- // Video read #2.
- video->Read(NewReadCB(FROM_HERE, 1906, 834000));
- message_loop_.Run();
-}
-
-// A mocked callback specialization for calling Read(). Since RunWithParams()
-// is mocked we don't need to pass in object or method pointers.
-class MockReadCB : public base::RefCountedThreadSafe<MockReadCB> {
- public:
- MockReadCB() {}
-
- MOCK_METHOD0(OnDelete, void());
- MOCK_METHOD2(Run, void(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer));
-
- protected:
- virtual ~MockReadCB() {
- OnDelete();
- }
-
- private:
- friend class base::RefCountedThreadSafe<MockReadCB>;
-
- DISALLOW_COPY_AND_ASSIGN(MockReadCB);
-};
-
-TEST_F(FFmpegDemuxerTest, Stop) {
- // Tests that calling Read() on a stopped demuxer stream immediately deletes
- // the callback.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Get our stream.
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(audio);
-
- demuxer_->Stop(NewExpectedClosure());
-
- // Expect all calls in sequence.
- InSequence s;
-
- // Create our mocked callback. The Callback created by base::Bind() will take
- // ownership of this pointer.
- StrictMock<MockReadCB>* callback = new StrictMock<MockReadCB>();
-
- // The callback should be immediately deleted. We'll use a checkpoint to
- // verify that it has indeed been deleted.
- EXPECT_CALL(*callback, Run(DemuxerStream::kOk, IsEndOfStreamBuffer()));
- EXPECT_CALL(*callback, OnDelete());
- EXPECT_CALL(*this, CheckPoint(1));
-
- // Attempt the read...
- audio->Read(base::Bind(&MockReadCB::Run, callback));
-
- message_loop_.RunUntilIdle();
-
- // ...and verify that |callback| was deleted.
- CheckPoint(1);
-}
-
-// The streams can outlive the demuxer because the streams may still be in use
-// by the decoder when the demuxer is destroyed.
-// This test verifies that DemuxerStream::Read() does not use an invalid demuxer
-// pointer (no crash occurs) and calls the callback with an EndOfStream buffer.
-TEST_F(FFmpegDemuxerTest, StreamReadAfterStopAndDemuxerDestruction) {
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Get our stream.
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(audio);
-
- demuxer_->Stop(MessageLoop::QuitWhenIdleClosure());
- message_loop_.Run();
-
- // Expect all calls in sequence.
- InSequence s;
-
- // Create our mocked callback. The Callback created by base::Bind() will take
- // ownership of this pointer.
- StrictMock<MockReadCB>* callback = new StrictMock<MockReadCB>();
-
- // The callback should be immediately deleted. We'll use a checkpoint to
- // verify that it has indeed been deleted.
- EXPECT_CALL(*callback, Run(DemuxerStream::kOk, IsEndOfStreamBuffer()));
- EXPECT_CALL(*callback, OnDelete());
- EXPECT_CALL(*this, CheckPoint(1));
-
- // Release the reference to the demuxer. This should also destroy it.
- demuxer_ = NULL;
- // |audio| now has a demuxer_ pointer to invalid memory.
-
- // Attempt the read...
- audio->Read(base::Bind(&MockReadCB::Run, callback));
-
- message_loop_.RunUntilIdle();
-
- // ...and verify that |callback| was deleted.
- CheckPoint(1);
-}
-
-TEST_F(FFmpegDemuxerTest, DisableAudioStream) {
- // We are doing the following things here:
- // 1. Initialize the demuxer with audio and video stream.
- // 2. Send a "disable audio stream" message to the demuxer.
- // 3. Demuxer will free audio packets even if audio stream was initialized.
- CreateDemuxer("bear-320x240.webm");
- InitializeDemuxer();
-
- // Submit a "disable audio stream" message to the demuxer.
- demuxer_->OnAudioRendererDisabled();
- message_loop_.RunUntilIdle();
-
- // Get our streams.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(video);
- ASSERT_TRUE(audio);
-
- // The audio stream should have been prematurely stopped.
- EXPECT_FALSE(IsStreamStopped(DemuxerStream::VIDEO));
- EXPECT_TRUE(IsStreamStopped(DemuxerStream::AUDIO));
-
- // Attempt a read from the video stream: it should return valid data.
- video->Read(NewReadCB(FROM_HERE, 22084, 0));
- message_loop_.Run();
-
- // Attempt a read from the audio stream: it should immediately return end of
- // stream without requiring the message loop to read data.
- bool got_eos_buffer = false;
- audio->Read(base::Bind(&EosOnReadDone, &got_eos_buffer));
- message_loop_.RunUntilIdle();
- EXPECT_TRUE(got_eos_buffer);
-}
-
-// Verify that seek works properly when the WebM cues data is at the start of
-// the file instead of at the end.
-TEST_F(FFmpegDemuxerTest, SeekWithCuesBeforeFirstCluster) {
- CreateDemuxer("bear-320x240-cues-in-front.webm");
- InitializeDemuxer();
-
- // Get our streams.
- scoped_refptr<DemuxerStream> video =
- demuxer_->GetStream(DemuxerStream::VIDEO);
- scoped_refptr<DemuxerStream> audio =
- demuxer_->GetStream(DemuxerStream::AUDIO);
- ASSERT_TRUE(video);
- ASSERT_TRUE(audio);
-
- // Read a video packet and release it.
- video->Read(NewReadCB(FROM_HERE, 22084, 0));
- message_loop_.Run();
-
- // Issue a simple forward seek, which should discard queued packets.
- WaitableMessageLoopEvent event;
- demuxer_->Seek(base::TimeDelta::FromMicroseconds(2500000),
- event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(PIPELINE_OK);
-
- // Audio read #1.
- audio->Read(NewReadCB(FROM_HERE, 40, 2403000));
- message_loop_.Run();
-
- // Audio read #2.
- audio->Read(NewReadCB(FROM_HERE, 42, 2406000));
- message_loop_.Run();
-
- // Video read #1.
- video->Read(NewReadCB(FROM_HERE, 5276, 2402000));
- message_loop_.Run();
-
- // Video read #2.
- video->Read(NewReadCB(FROM_HERE, 1740, 2436000));
- message_loop_.Run();
-}
-
-// Ensure ID3v1 tag reading is disabled. id3_test.mp3 has an ID3v1 tag with the
-// field "title" set to "sample for id3 test".
-TEST_F(FFmpegDemuxerTest, NoID3TagData) {
-#if !defined(USE_PROPRIETARY_CODECS)
- return;
-#endif
- CreateDemuxer("id3_test.mp3");
- InitializeDemuxer();
- EXPECT_FALSE(av_dict_get(format_context()->metadata, "title", NULL, 0));
-}
-
-// Ensure MP3 files with large image/video based ID3 tags demux okay. FFmpeg
-// will hand us a video stream to the data which will likely be in a format we
-// don't accept as video; e.g. PNG.
-TEST_F(FFmpegDemuxerTest, Mp3WithVideoStreamID3TagData) {
-#if !defined(USE_PROPRIETARY_CODECS)
- return;
-#endif
- CreateDemuxer("id3_png_test.mp3");
- InitializeDemuxer();
-
- // Ensure the expected streams are present.
- EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::VIDEO));
- EXPECT_TRUE(demuxer_->GetStream(DemuxerStream::AUDIO));
-}
-
-// Ensure a video with an unsupported audio track still results in the video
-// stream being demuxed.
-TEST_F(FFmpegDemuxerTest, UnsupportedAudioSupportedVideoDemux) {
- CreateDemuxer("speex_audio_vorbis_video.ogv");
- InitializeDemuxer();
-
- // Ensure the expected streams are present.
- EXPECT_TRUE(demuxer_->GetStream(DemuxerStream::VIDEO));
- EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::AUDIO));
-}
-
-// Ensure a video with an unsupported video track still results in the audio
-// stream being demuxed.
-TEST_F(FFmpegDemuxerTest, UnsupportedVideoSupportedAudioDemux) {
- CreateDemuxer("vorbis_audio_wmv_video.mkv");
- InitializeDemuxer();
-
- // Ensure the expected streams are present.
- EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::VIDEO));
- EXPECT_TRUE(demuxer_->GetStream(DemuxerStream::AUDIO));
-}
-
-// FFmpeg returns null data pointers when samples have zero size, leading to
-// mistakenly creating end of stream buffers http://crbug.com/169133
-TEST_F(FFmpegDemuxerTest, MP4_ZeroStszEntry) {
-#if !defined(USE_PROPRIETARY_CODECS)
- return;
-#endif
- CreateDemuxer("bear-1280x720-zero-stsz-entry.mp4");
- InitializeDemuxer();
- ReadUntilEndOfStream();
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_glue.cc b/src/media/filters/ffmpeg_glue.cc
deleted file mode 100644
index e3b227b..0000000
--- a/src/media/filters/ffmpeg_glue.cc
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/ffmpeg_glue.h"
-
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/synchronization/lock.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-
-namespace media {
-
-// Internal buffer size used by AVIO for reading.
-// TODO(dalecurtis): Experiment with this buffer size and measure impact on
-// performance. Currently we want to use 32kb to preserve existing behavior
-// with the previous URLProtocol based approach.
-enum { kBufferSize = 32 * 1024 };
-
-static int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) {
- FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
- int result = protocol->Read(buf_size, buf);
- if (result < 0)
- result = AVERROR(EIO);
- return result;
-}
-
-static int64 AVIOSeekOperation(void* opaque, int64 offset, int whence) {
- FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
- int64 new_offset = AVERROR(EIO);
- switch (whence) {
- case SEEK_SET:
- if (protocol->SetPosition(offset))
- protocol->GetPosition(&new_offset);
- break;
-
- case SEEK_CUR:
- int64 pos;
- if (!protocol->GetPosition(&pos))
- break;
- if (protocol->SetPosition(pos + offset))
- protocol->GetPosition(&new_offset);
- break;
-
- case SEEK_END:
- int64 size;
- if (!protocol->GetSize(&size))
- break;
- if (protocol->SetPosition(size + offset))
- protocol->GetPosition(&new_offset);
- break;
-
- case AVSEEK_SIZE:
- protocol->GetSize(&new_offset);
- break;
-
- default:
- NOTREACHED();
- }
- if (new_offset < 0)
- new_offset = AVERROR(EIO);
- return new_offset;
-}
-
-static int LockManagerOperation(void** lock, enum AVLockOp op) {
- switch (op) {
- case AV_LOCK_CREATE:
- *lock = new base::Lock();
- return 0;
-
- case AV_LOCK_OBTAIN:
- static_cast<base::Lock*>(*lock)->Acquire();
- return 0;
-
- case AV_LOCK_RELEASE:
- static_cast<base::Lock*>(*lock)->Release();
- return 0;
-
- case AV_LOCK_DESTROY:
- delete static_cast<base::Lock*>(*lock);
- *lock = NULL;
- return 0;
- }
- return 1;
-}
-
-// FFmpeg must only be initialized once, so use a LazyInstance to ensure this.
-class FFmpegInitializer {
- public:
- bool initialized() { return initialized_; }
-
- private:
- friend struct base::DefaultLazyInstanceTraits<FFmpegInitializer>;
-
- FFmpegInitializer()
- : initialized_(false) {
- // Before doing anything disable logging as it interferes with layout tests.
- av_log_set_level(AV_LOG_QUIET);
-
- // Register our protocol glue code with FFmpeg.
- if (av_lockmgr_register(&LockManagerOperation) != 0)
- return;
-
- // Now register the rest of FFmpeg.
- av_register_all();
-
- initialized_ = true;
- }
-
- ~FFmpegInitializer() {
- NOTREACHED() << "FFmpegInitializer should be leaky!";
- }
-
- bool initialized_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegInitializer);
-};
-
-void FFmpegGlue::InitializeFFmpeg() {
- static base::LazyInstance<FFmpegInitializer>::Leaky li =
- LAZY_INSTANCE_INITIALIZER;
- CHECK(li.Get().initialized());
-}
-
-FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol)
- : open_called_(false) {
- InitializeFFmpeg();
-
- // Initialize an AVIOContext using our custom read and seek operations. Don't
- // keep pointers to the buffer since FFmpeg may reallocate it on the fly. It
- // will be cleaned up
- format_context_ = avformat_alloc_context();
- avio_context_.reset(avio_alloc_context(
- static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0,
- protocol, &AVIOReadOperation, NULL, &AVIOSeekOperation));
-
- // Ensure FFmpeg only tries to seek on resources we know to be seekable.
- avio_context_->seekable =
- protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL;
-
- // Ensure writing is disabled.
- avio_context_->write_flag = 0;
-
- // Tell the format context about our custom IO context. avformat_open_input()
- // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an
- // early error state doesn't cause FFmpeg to free our resources in error.
- format_context_->flags |= AVFMT_FLAG_CUSTOM_IO;
- format_context_->pb = avio_context_.get();
-}
-
-bool FFmpegGlue::OpenContext() {
- DCHECK(!open_called_) << "OpenContext() should't be called twice.";
-
- // If avformat_open_input() is called we have to take a slightly different
- // destruction path to avoid double frees.
- open_called_ = true;
-
- // By passing NULL for the filename (second parameter) we are telling FFmpeg
- // to use the AVIO context we setup from the AVFormatContext structure.
- return avformat_open_input(&format_context_, NULL, NULL, NULL) == 0;
-}
-
-FFmpegGlue::~FFmpegGlue() {
- // In the event of avformat_open_input() failure, FFmpeg may sometimes free
- // our AVFormatContext behind the scenes, but leave the buffer alive. It will
- // helpfully set |format_context_| to NULL in this case.
- if (!format_context_) {
- av_free(avio_context_->buffer);
- return;
- }
-
- // If avformat_open_input() hasn't been called, we should simply free the
- // AVFormatContext and buffer instead of using avformat_close_input().
- if (!open_called_) {
- avformat_free_context(format_context_);
- av_free(avio_context_->buffer);
- return;
- }
-
- // If avformat_open_input() has been called with this context, we need to
- // close out any codecs/streams before closing the context.
- if (format_context_->streams) {
- for (int i = format_context_->nb_streams - 1; i >= 0; --i) {
- AVStream* stream = format_context_->streams[i];
-
- // The conditions for calling avcodec_close():
- // 1. AVStream is alive.
- // 2. AVCodecContext in AVStream is alive.
- // 3. AVCodec in AVCodecContext is alive.
- //
- // Closing a codec context without prior avcodec_open2() will result in
- // a crash in FFmpeg.
- if (stream && stream->codec && stream->codec->codec) {
- stream->discard = AVDISCARD_ALL;
- avcodec_close(stream->codec);
- }
- }
- }
-
- avformat_close_input(&format_context_);
- av_free(avio_context_->buffer);
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_glue.h b/src/media/filters/ffmpeg_glue.h
deleted file mode 100644
index 17241b9..0000000
--- a/src/media/filters/ffmpeg_glue.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// FFmpegGlue is an interface between FFmpeg and Chrome used to proxy FFmpeg's
-// read and seek requests to Chrome's internal data structures. The glue works
-// through the AVIO interface provided by FFmpeg.
-//
-// AVIO works through a special AVIOContext created through avio_alloc_context()
-// which is attached to the AVFormatContext used for demuxing. The AVIO context
-// is initialized with read and seek methods which FFmpeg calls when necessary.
-//
-// During OpenContext() FFmpegGlue will tell FFmpeg to use Chrome's AVIO context
-// by passing NULL in for the filename parameter to avformat_open_input(). All
-// FFmpeg operations using the configured AVFormatContext will then redirect
-// reads and seeks through the glue.
-//
-// The glue in turn processes those read and seek requests using the
-// FFmpegURLProtocol provided during construction.
-//
-// FFmpegGlue is also responsible for initializing FFmpeg, which is done once
-// per process. Initialization includes: turning off log messages, registering
-// a lock manager, and finally registering all demuxers and codecs.
-
-#ifndef MEDIA_FILTERS_FFMPEG_GLUE_H_
-#define MEDIA_FILTERS_FFMPEG_GLUE_H_
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-struct AVFormatContext;
-struct AVIOContext;
-
-namespace media {
-
-class ScopedPtrAVFree;
-
-class MEDIA_EXPORT FFmpegURLProtocol {
- public:
- // Read the given amount of bytes into data, returns the number of bytes read
- // if successful, kReadError otherwise.
- virtual int Read(int size, uint8* data) = 0;
-
- // Returns true and the current file position for this file, false if the
- // file position could not be retrieved.
- virtual bool GetPosition(int64* position_out) = 0;
-
- // Returns true if the file position could be set, false otherwise.
- virtual bool SetPosition(int64 position) = 0;
-
- // Returns true and the file size, false if the file size could not be
- // retrieved.
- virtual bool GetSize(int64* size_out) = 0;
-
- // Returns false if this protocol supports random seeking.
- virtual bool IsStreaming() = 0;
-};
-
-class MEDIA_EXPORT FFmpegGlue {
- public:
- static void InitializeFFmpeg();
-
- // See file documentation for usage. |protocol| must outlive FFmpegGlue.
- explicit FFmpegGlue(FFmpegURLProtocol* protocol);
- ~FFmpegGlue();
-
- // Opens an AVFormatContext specially prepared to process reads and seeks
- // through the FFmpegURLProtocol provided during construction.
- bool OpenContext();
- AVFormatContext* format_context() { return format_context_; }
-
- private:
- bool open_called_;
- AVFormatContext* format_context_;
- scoped_ptr_malloc<AVIOContext, ScopedPtrAVFree> avio_context_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegGlue);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FFMPEG_GLUE_H_
diff --git a/src/media/filters/ffmpeg_glue_unittest.cc b/src/media/filters/ffmpeg_glue_unittest.cc
deleted file mode 100644
index 18d56f9..0000000
--- a/src/media/filters/ffmpeg_glue_unittest.cc
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_data_util.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/filters/in_memory_url_protocol.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::DoAll;
-using ::testing::InSequence;
-using ::testing::Return;
-using ::testing::SetArgumentPointee;
-using ::testing::StrictMock;
-
-namespace media {
-
-class MockProtocol : public FFmpegURLProtocol {
- public:
- MockProtocol() {}
-
- MOCK_METHOD2(Read, int(int size, uint8* data));
- MOCK_METHOD1(GetPosition, bool(int64* position_out));
- MOCK_METHOD1(SetPosition, bool(int64 position));
- MOCK_METHOD1(GetSize, bool(int64* size_out));
- MOCK_METHOD0(IsStreaming, bool());
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockProtocol);
-};
-
-class FFmpegGlueTest : public ::testing::Test {
- public:
- FFmpegGlueTest()
- : protocol_(new StrictMock<MockProtocol>()) {
- // IsStreaming() is called when opening.
- EXPECT_CALL(*protocol_.get(), IsStreaming()).WillOnce(Return(true));
- glue_.reset(new FFmpegGlue(protocol_.get()));
- CHECK(glue_->format_context());
- CHECK(glue_->format_context()->pb);
- }
-
- virtual ~FFmpegGlueTest() {
- // Ensure |glue_| and |protocol_| are still alive.
- CHECK(glue_.get());
- CHECK(protocol_.get());
-
- // |protocol_| should outlive |glue_|, so ensure it's destructed first.
- glue_.reset();
- }
-
- int ReadPacket(int size, uint8* data) {
- return glue_->format_context()->pb->read_packet(
- protocol_.get(), data, size);
- }
-
- int64 Seek(int64 offset, int whence) {
- return glue_->format_context()->pb->seek(protocol_.get(), offset, whence);
- }
-
- protected:
- scoped_ptr<FFmpegGlue> glue_;
- scoped_ptr< StrictMock<MockProtocol> > protocol_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FFmpegGlueTest);
-};
-
-class FFmpegGlueDestructionTest : public ::testing::Test {
- public:
- FFmpegGlueDestructionTest() {}
-
- void Initialize(const char* filename) {
- data_ = ReadTestDataFile(filename);
- protocol_.reset(new InMemoryUrlProtocol(
- data_->GetData(), data_->GetDataSize(), false));
- glue_.reset(new FFmpegGlue(protocol_.get()));
- CHECK(glue_->format_context());
- CHECK(glue_->format_context()->pb);
- }
-
- virtual ~FFmpegGlueDestructionTest() {
- // Ensure Initialize() was called.
- CHECK(glue_.get());
- CHECK(protocol_.get());
-
- // |glue_| should be destroyed before |protocol_|.
- glue_.reset();
-
- // |protocol_| should be destroyed before |data_|.
- protocol_.reset();
- data_ = NULL;
- }
-
- protected:
- scoped_ptr<FFmpegGlue> glue_;
-
- private:
- scoped_ptr<InMemoryUrlProtocol> protocol_;
- scoped_refptr<DecoderBuffer> data_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegGlueDestructionTest);
-};
-
-// Ensure writing has been disabled.
-TEST_F(FFmpegGlueTest, Write) {
- ASSERT_FALSE(glue_->format_context()->pb->write_packet);
- ASSERT_FALSE(glue_->format_context()->pb->write_flag);
-}
-
-// Test both successful and unsuccessful reads pass through correctly.
-TEST_F(FFmpegGlueTest, Read) {
- const int kBufferSize = 16;
- uint8 buffer[kBufferSize];
-
- // Reads are for the most part straight-through calls to Read().
- InSequence s;
- EXPECT_CALL(*protocol_, Read(0, buffer))
- .WillOnce(Return(0));
- EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
- .WillOnce(Return(kBufferSize));
- EXPECT_CALL(*protocol_, Read(kBufferSize, buffer))
- .WillOnce(Return(DataSource::kReadError));
-
- EXPECT_EQ(0, ReadPacket(0, buffer));
- EXPECT_EQ(kBufferSize, ReadPacket(kBufferSize, buffer));
- EXPECT_EQ(AVERROR(EIO), ReadPacket(kBufferSize, buffer));
-}
-
-// Test a variety of seek operations.
-TEST_F(FFmpegGlueTest, Seek) {
- // SEEK_SET should be a straight-through call to SetPosition(), which when
- // successful will return the result from GetPosition().
- InSequence s;
- EXPECT_CALL(*protocol_, SetPosition(-16))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, SetPosition(16))
- .WillOnce(Return(true));
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
-
- EXPECT_EQ(AVERROR(EIO), Seek(-16, SEEK_SET));
- EXPECT_EQ(8, Seek(16, SEEK_SET));
-
- // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset
- // to the result then call SetPosition()+GetPosition().
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
- EXPECT_CALL(*protocol_, SetPosition(16))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
- EXPECT_CALL(*protocol_, SetPosition(16))
- .WillOnce(Return(true));
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
-
- EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
- EXPECT_EQ(AVERROR(EIO), Seek(8, SEEK_CUR));
- EXPECT_EQ(16, Seek(8, SEEK_CUR));
-
- // SEEK_END should call GetSize() first, and if it succeeds add the offset
- // to the result then call SetPosition()+GetPosition().
- EXPECT_CALL(*protocol_, GetSize(_))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, GetSize(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
- EXPECT_CALL(*protocol_, SetPosition(8))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, GetSize(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
- EXPECT_CALL(*protocol_, SetPosition(8))
- .WillOnce(Return(true));
- EXPECT_CALL(*protocol_, GetPosition(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
-
- EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
- EXPECT_EQ(AVERROR(EIO), Seek(-8, SEEK_END));
- EXPECT_EQ(8, Seek(-8, SEEK_END));
-
- // AVSEEK_SIZE should be a straight-through call to GetSize().
- EXPECT_CALL(*protocol_, GetSize(_))
- .WillOnce(Return(false));
-
- EXPECT_CALL(*protocol_, GetSize(_))
- .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
-
- EXPECT_EQ(AVERROR(EIO), Seek(0, AVSEEK_SIZE));
- EXPECT_EQ(16, Seek(0, AVSEEK_SIZE));
-}
-
-// Ensure destruction release the appropriate resources when OpenContext() is
-// never called.
-TEST_F(FFmpegGlueDestructionTest, WithoutOpen) {
- Initialize("ten_byte_file");
-}
-
-// Ensure destruction releases the appropriate resources when
-// avformat_open_input() fails.
-TEST_F(FFmpegGlueDestructionTest, WithOpenFailure) {
- Initialize("ten_byte_file");
- ASSERT_FALSE(glue_->OpenContext());
-}
-
-// Ensure destruction release the appropriate resources when OpenContext() is
-// called, but no streams have been opened.
-TEST_F(FFmpegGlueDestructionTest, WithOpenNoStreams) {
- Initialize("no_streams.webm");
- ASSERT_TRUE(glue_->OpenContext());
-}
-
-// Ensure destruction release the appropriate resources when OpenContext() is
-// called and streams exist.
-TEST_F(FFmpegGlueDestructionTest, WithOpenWithStreams) {
- Initialize("bear-320x240.webm");
- ASSERT_TRUE(glue_->OpenContext());
-}
-
-// Ensure destruction release the appropriate resources when OpenContext() is
-// called and streams have been opened.
-TEST_F(FFmpegGlueDestructionTest, WithOpenWithOpenStreams) {
- Initialize("bear-320x240.webm");
- ASSERT_TRUE(glue_->OpenContext());
- ASSERT_GT(glue_->format_context()->nb_streams, 0u);
-
- AVCodecContext* context = glue_->format_context()->streams[0]->codec;
- ASSERT_EQ(avcodec_open2(
- context, avcodec_find_decoder(context->codec_id), NULL), 0);
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.cc b/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.cc
deleted file mode 100644
index 31f03f5..0000000
--- a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
-
-#include "base/logging.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-
-namespace media {
-
-FFmpegH264ToAnnexBBitstreamConverter::FFmpegH264ToAnnexBBitstreamConverter(
- AVCodecContext* stream_context)
- : configuration_processed_(false),
- stream_context_(stream_context) {
- CHECK(stream_context_);
-}
-
-FFmpegH264ToAnnexBBitstreamConverter::~FFmpegH264ToAnnexBBitstreamConverter() {}
-
-bool FFmpegH264ToAnnexBBitstreamConverter::ConvertPacket(AVPacket* packet) {
- uint32 output_packet_size = 0;
- uint32 configuration_size = 0;
- uint32 io_size = 0;
- if (packet == NULL) {
- return false;
- }
-
- // Calculate the needed output buffer size.
- if (!configuration_processed_) {
- // FFmpeg's AVCodecContext's extradata field contains the Decoder
- // Specific Information from MP4 headers that contain the H.264 SPS and
- // PPS members. See ISO/IEC 14496-15 Chapter 5.2.4
- // AVCDecoderConfigurationRecord for exact specification.
- // Extradata must be at least 7 bytes long.
- if (stream_context_->extradata == NULL ||
- stream_context_->extradata_size <= 7) {
- return false; // Can't go on with conversion without configuration.
- }
- configuration_size += converter_.ParseConfigurationAndCalculateSize(
- stream_context_->extradata,
- stream_context_->extradata_size);
- if (configuration_size == 0) {
- return false; // Not possible to parse the configuration.
- }
- }
- uint32 output_nal_size =
- converter_.CalculateNeededOutputBufferSize(packet->data, packet->size);
- if (output_nal_size == 0) {
- return false; // Invalid input packet.
- }
- output_packet_size = configuration_size + output_nal_size;
-
- // Allocate new packet for the output.
- AVPacket dest_packet;
- if (av_new_packet(&dest_packet, output_packet_size) != 0) {
- return false; // Memory allocation failure.
- }
- // This is a bit tricky: since the interface does not allow us to replace
- // the pointer of the old packet with a new one, we will initially copy the
- // metadata from old packet to new bigger packet.
- dest_packet.pts = packet->pts;
- dest_packet.dts = packet->dts;
- dest_packet.pos = packet->pos;
- dest_packet.duration = packet->duration;
- dest_packet.convergence_duration = packet->convergence_duration;
- dest_packet.flags = packet->flags;
- dest_packet.stream_index = packet->stream_index;
-
- // Process the configuration if not done earlier.
- if (!configuration_processed_) {
- if (!converter_.ConvertAVCDecoderConfigToByteStream(
- stream_context_->extradata, stream_context_->extradata_size,
- dest_packet.data, &configuration_size)) {
- return false; // Failed to convert the buffer.
- }
- configuration_processed_ = true;
- }
-
- // Proceed with the conversion of the actual in-band NAL units, leave room
- // for configuration in the beginning.
- io_size = dest_packet.size - configuration_size;
- if (!converter_.ConvertNalUnitStreamToByteStream(
- packet->data, packet->size,
- dest_packet.data + configuration_size, &io_size)) {
- return false;
- }
-
- // At the end we must destroy the old packet.
- av_free_packet(packet);
- *packet = dest_packet; // Finally, replace the values in the input packet.
-
- return true;
-}
-
-} // namespace media
-
diff --git a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h b/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h
deleted file mode 100644
index 1ad0a48..0000000
--- a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_FFMPEG_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
-#define MEDIA_FILTERS_FFMPEG_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
-
-#include "base/basictypes.h"
-#include "media/filters/h264_to_annex_b_bitstream_converter.h"
-
-// Forward declarations for FFmpeg datatypes used.
-struct AVCodecContext;
-struct AVPacket;
-
-namespace media {
-
-// Bitstream converter that converts H.264 bitstream based FFmpeg packets into
-// H.264 Annex B bytestream format.
-class MEDIA_EXPORT FFmpegH264ToAnnexBBitstreamConverter {
- public:
- // The |stream_context| will be used during conversion and should be the
- // AVCodecContext for the stream sourcing these packets. A reference to
- // |stream_context| is retained, so it must outlive this class.
- explicit FFmpegH264ToAnnexBBitstreamConverter(AVCodecContext* stream_context);
- ~FFmpegH264ToAnnexBBitstreamConverter();
-
- // Converts |packet| to H.264 Annex B bytestream format. This conversion is
- // on single NAL unit basis which is contained within the |packet| with the
- // exception of the first packet which is prepended with the AVC decoder
- // configuration record information. For example:
- //
- // NAL unit #1 ==> bytestream buffer #1 (AVC configuraion + NAL unit #1)
- // NAL unit #2 ==> bytestream buffer #2 (NAL unit #2)
- // ...
- // NAL unit #n ==> bytestream buffer #n (NAL unit #n)
- //
- // Returns true if conversion succeeded. In this case, the output will be
- // stored into the |packet|. But user should be aware that this conversion can
- // free and reallocate the |packet|, if it needs to do so to fit it in.
- // FFmpeg allocation methods will be used for buffer allocation to ensure
- // compatibility with FFmpeg's memory management.
- //
- // Returns false if conversion failed. In this case, the |packet| will not
- // be changed.
- bool ConvertPacket(AVPacket* packet);
-
- private:
- // Actual converter class.
- H264ToAnnexBBitstreamConverter converter_;
-
- // Flag for indicating whether global parameter sets have been processed.
- bool configuration_processed_;
-
- // Variable to hold a pointer to memory where we can access the global
- // data from the FFmpeg file format's global headers.
- AVCodecContext* stream_context_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegH264ToAnnexBBitstreamConverter);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FFMPEG_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
diff --git a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc b/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc
deleted file mode 100644
index 5831883..0000000
--- a/src/media/filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc
+++ /dev/null
@@ -1,346 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// Test data arrays.
-static const uint8 kHeaderDataOkWithFieldLen4[] = {
- 0x01, 0x42, 0x00, 0x28, 0xFF, 0xE1, 0x00, 0x08, 0x67, 0x42, 0x00, 0x28,
- 0xE9, 0x05, 0x89, 0xC8, 0x01, 0x00, 0x04, 0x68, 0xCE, 0x06, 0xF2, 0x00
-};
-
-static const uint8 kPacketDataOkWithFieldLen4[] = {
- 0x00, 0x00, 0x0B, 0xF7, 0x65, 0xB8, 0x40, 0x57, 0x0B, 0xF0,
- 0xDF, 0xF8, 0x00, 0x1F, 0x78, 0x98, 0x54, 0xAC, 0xF2, 0x00, 0x04, 0x9D, 0x26,
- 0xE0, 0x3B, 0x5C, 0x00, 0x0A, 0x00, 0x8F, 0x9E, 0x86, 0x63, 0x1B, 0x46, 0xE7,
- 0xD6, 0x45, 0x88, 0x88, 0xEA, 0x10, 0x89, 0x79, 0x01, 0x34, 0x30, 0x01, 0x8E,
- 0x7D, 0x1A, 0x39, 0x45, 0x4E, 0x69, 0x86, 0x12, 0xF2, 0xE7, 0xCF, 0x50, 0xF8,
- 0x26, 0x54, 0x17, 0xBE, 0x3F, 0xC4, 0x80, 0x32, 0xD8, 0x02, 0x32, 0xE4, 0xAE,
- 0xDD, 0x39, 0x11, 0x8E, 0x54, 0x42, 0xAE, 0xBD, 0x12, 0xA4, 0xCE, 0xE2, 0x98,
- 0x91, 0x05, 0xC4, 0xA8, 0x20, 0xC7, 0xB3, 0xD9, 0x47, 0x73, 0x09, 0xD5, 0xCF,
- 0x62, 0x57, 0x3F, 0xFF, 0xFD, 0xB9, 0x94, 0x2B, 0x3D, 0x12, 0x1A, 0x84, 0x0B,
- 0x28, 0xAD, 0x5C, 0x9E, 0x5C, 0xC3, 0xBB, 0xBD, 0x7F, 0xFE, 0x09, 0x87, 0x74,
- 0x39, 0x1C, 0xA5, 0x0E, 0x44, 0xD8, 0x5D, 0x41, 0xDB, 0xAA, 0xBC, 0x05, 0x16,
- 0xA3, 0x98, 0xEE, 0xEE, 0x9C, 0xA0, 0xF1, 0x23, 0x90, 0xF0, 0x5E, 0x9F, 0xF4,
- 0xFA, 0x7F, 0x4B, 0x69, 0x66, 0x49, 0x52, 0xDD, 0xD6, 0xC0, 0x0F, 0x8C, 0x6E,
- 0x80, 0xDD, 0x7A, 0xDF, 0x10, 0xCD, 0x4B, 0x54, 0x6F, 0xFC, 0x7D, 0x34, 0xBA,
- 0x8B, 0xD4, 0xD9, 0x30, 0x18, 0x9F, 0x39, 0x04, 0x9F, 0xCB, 0xDB, 0x1B, 0xA7,
- 0x70, 0x96, 0xAF, 0xFF, 0x6F, 0xB5, 0xBF, 0x58, 0x01, 0x98, 0xCD, 0xF2, 0x66,
- 0x28, 0x1A, 0xC4, 0x9E, 0x58, 0x40, 0x39, 0xAE, 0x07, 0x11, 0x3F, 0xF2, 0x9B,
- 0x06, 0x9C, 0xB8, 0xC9, 0x16, 0x12, 0x09, 0x8E, 0xD2, 0xD4, 0xF5, 0xC6, 0x77,
- 0x40, 0x0F, 0xFD, 0x12, 0x19, 0x55, 0x1A, 0x8E, 0x9C, 0x18, 0x8B, 0x0D, 0x18,
- 0xFA, 0xBA, 0x7F, 0xBB, 0x83, 0xBB, 0x85, 0xA0, 0xCC, 0xAF, 0xF6, 0xEA, 0x81,
- 0x10, 0x18, 0x8E, 0x10, 0x00, 0xCB, 0x7F, 0x27, 0x08, 0x06, 0xDE, 0x3C, 0x20,
- 0xE5, 0xFE, 0xCC, 0x4F, 0xB3, 0x41, 0xE0, 0xCC, 0x4C, 0x26, 0xC1, 0xC0, 0x2C,
- 0x16, 0x12, 0xAA, 0x04, 0x83, 0x51, 0x4E, 0xCA, 0x00, 0xCF, 0x42, 0x9C, 0x06,
- 0x2D, 0x06, 0xDD, 0x1D, 0x08, 0x75, 0xE0, 0x89, 0xC7, 0x62, 0x68, 0x2E, 0xBF,
- 0x4D, 0x2D, 0x0A, 0xC4, 0x86, 0xF6, 0x2F, 0xA1, 0x49, 0xA7, 0x0F, 0xDB, 0x1F,
- 0x82, 0xEC, 0xC1, 0x62, 0xFB, 0x7F, 0xF1, 0xAE, 0xA6, 0x1A, 0xD5, 0x6B, 0x06,
- 0x5E, 0xB6, 0x02, 0x50, 0xAE, 0x2D, 0xF9, 0xD9, 0x95, 0xAD, 0x01, 0x8C, 0x53,
- 0x01, 0xAF, 0xCE, 0xE5, 0xA5, 0xBB, 0x95, 0x8A, 0x85, 0x70, 0x77, 0xE3, 0x9A,
- 0x68, 0x1B, 0xDF, 0x47, 0xF9, 0xF4, 0xBD, 0x80, 0x7D, 0x76, 0x9A, 0x69, 0xFC,
- 0xBE, 0x14, 0x0D, 0x87, 0x09, 0x12, 0x98, 0x20, 0x05, 0x46, 0xB7, 0xAE, 0x10,
- 0xB7, 0x01, 0xB7, 0xDE, 0x3B, 0xDD, 0x7A, 0x8A, 0x55, 0x73, 0xAD, 0xDF, 0x69,
- 0xDE, 0xD0, 0x51, 0x97, 0xA0, 0xE6, 0x5E, 0xBA, 0xBA, 0x80, 0x0F, 0x4E, 0x9A,
- 0x68, 0x36, 0xE6, 0x9F, 0x5B, 0x39, 0xC0, 0x90, 0xA1, 0xC0, 0xC3, 0x82, 0xE4,
- 0x50, 0xEA, 0x60, 0x7A, 0xDD, 0x5F, 0x8B, 0x5F, 0xAF, 0xFC, 0x74, 0xAF, 0xDC,
- 0x56, 0xF7, 0x2E, 0x3E, 0x97, 0x6E, 0x2B, 0xF3, 0xAF, 0xFE, 0x7D, 0x32, 0xDC,
- 0x56, 0xF8, 0xAF, 0xB5, 0xA3, 0xBB, 0x00, 0x5B, 0x84, 0x3D, 0x9F, 0x0B, 0x40,
- 0x88, 0x61, 0x5F, 0x4F, 0x4F, 0xB0, 0xB3, 0x07, 0x81, 0x3E, 0xF2, 0xFB, 0x50,
- 0xCA, 0x77, 0x40, 0x12, 0xA8, 0xE6, 0x11, 0x8E, 0xD6, 0x8A, 0xC6, 0xD6, 0x8C,
- 0x1D, 0x63, 0x55, 0x3D, 0x34, 0xEA, 0xC3, 0xC6, 0x6A, 0xD2, 0x8C, 0xB0, 0x1D,
- 0x5E, 0x4A, 0x7A, 0x8B, 0xD5, 0x99, 0x80, 0x84, 0x32, 0xFB, 0xB7, 0x02, 0x6E,
- 0x61, 0xFE, 0xAC, 0x1B, 0x5D, 0x10, 0x23, 0x24, 0xC3, 0x8C, 0x7B, 0x58, 0x2C,
- 0x4D, 0x04, 0x74, 0x84, 0x25, 0x10, 0x4E, 0x94, 0x29, 0x4D, 0x88, 0xAE, 0x65,
- 0x53, 0xB9, 0x95, 0x4E, 0xE7, 0xDD, 0xEE, 0xF2, 0x70, 0x1F, 0x26, 0x4F, 0xA8,
- 0xBC, 0x3D, 0x35, 0x02, 0x3B, 0xC0, 0x98, 0x70, 0x38, 0x18, 0xE5, 0x1E, 0x05,
- 0xAC, 0x28, 0xAA, 0x46, 0x1A, 0xB0, 0x19, 0x99, 0x18, 0x35, 0x78, 0x1E, 0x41,
- 0x60, 0x0D, 0x4F, 0x7E, 0xEC, 0x37, 0xC3, 0x30, 0x73, 0x2A, 0x69, 0xFE, 0xEF,
- 0x27, 0xEE, 0x13, 0xCC, 0xD0, 0xDB, 0xE6, 0x45, 0xEC, 0x5C, 0xB5, 0x71, 0x54,
- 0x2E, 0xB1, 0xE9, 0x88, 0xB4, 0x3F, 0x6F, 0xFD, 0xF7, 0xFF, 0x9D, 0x2D, 0x52,
- 0x2E, 0xAE, 0xC9, 0x95, 0xDE, 0xBF, 0xDF, 0xFF, 0xBF, 0x21, 0xB3, 0x2B, 0xF5,
- 0xF7, 0xF7, 0xD1, 0xA0, 0xF0, 0x76, 0x68, 0x37, 0xDB, 0x8F, 0x85, 0x4D, 0xA8,
- 0x1A, 0xF9, 0x7F, 0x75, 0xA7, 0x93, 0xF5, 0x03, 0xC1, 0xF2, 0x60, 0x8A, 0x92,
- 0x53, 0xF5, 0xD1, 0xC1, 0x56, 0x4B, 0x68, 0x05, 0x16, 0x88, 0x61, 0xE7, 0x14,
- 0xC8, 0x0D, 0xF0, 0xDF, 0xEF, 0x46, 0x4A, 0xED, 0x0B, 0xD1, 0xD1, 0xD1, 0xA4,
- 0x85, 0xA3, 0x2C, 0x1D, 0xDE, 0x45, 0x14, 0xA1, 0x8E, 0xA8, 0xD9, 0x8C, 0xAB,
- 0x47, 0x31, 0xF1, 0x00, 0x15, 0xAD, 0x80, 0x20, 0xAA, 0xE4, 0x57, 0xF8, 0x05,
- 0x14, 0x58, 0x0B, 0xD3, 0x63, 0x00, 0x8F, 0x44, 0x15, 0x7F, 0x19, 0xC7, 0x0A,
- 0xE0, 0x49, 0x32, 0xFE, 0x36, 0x0E, 0xF3, 0x66, 0x10, 0x2B, 0x11, 0x73, 0x3D,
- 0x19, 0x92, 0x22, 0x20, 0x75, 0x1F, 0xF1, 0xDB, 0x96, 0x73, 0xCF, 0x1B, 0x53,
- 0xFF, 0xD2, 0x23, 0xF2, 0xB6, 0xAA, 0xB6, 0x44, 0xA3, 0x73, 0x7E, 0x00, 0x2D,
- 0x4D, 0x4D, 0x87, 0xE0, 0x84, 0x55, 0xD6, 0x03, 0xB8, 0xD8, 0x90, 0xEF, 0xC0,
- 0x76, 0x5D, 0x69, 0x02, 0x00, 0x0E, 0x17, 0xD0, 0x02, 0x96, 0x50, 0xEA, 0xAB,
- 0xBF, 0x0D, 0xAF, 0xCB, 0xD3, 0xFF, 0xAA, 0x9D, 0x7F, 0xD6, 0xBD, 0x2C, 0x14,
- 0xB4, 0xCD, 0x20, 0x73, 0xB4, 0xF4, 0x38, 0x96, 0xDE, 0xB0, 0x6B, 0xE5, 0x1B,
- 0xFD, 0x0E, 0x0B, 0xA4, 0x81, 0xBF, 0xC8, 0xA0, 0x21, 0x76, 0x7B, 0x25, 0x3F,
- 0xE6, 0x84, 0x40, 0x1A, 0xDA, 0x25, 0x5A, 0xFF, 0x73, 0x6B, 0x14, 0x1B, 0xF7,
- 0x08, 0xFA, 0x26, 0x73, 0x7A, 0x58, 0x02, 0x1A, 0xE6, 0x63, 0xB6, 0x45, 0x7B,
- 0xE3, 0xE0, 0x80, 0x14, 0x42, 0xA8, 0x7D, 0xF3, 0x80, 0x9B, 0x01, 0x43, 0x82,
- 0x82, 0x8C, 0xBE, 0x0D, 0xFD, 0xAE, 0x88, 0xA8, 0xB9, 0xC3, 0xEE, 0xFF, 0x46,
- 0x00, 0x84, 0xE6, 0xB4, 0x0C, 0xA9, 0x66, 0xC6, 0x74, 0x72, 0xAA, 0xA4, 0x3A,
- 0xB0, 0x1B, 0x06, 0xB4, 0xDB, 0xE8, 0xC2, 0x17, 0xA2, 0xBC, 0xBE, 0x5C, 0x0F,
- 0x2A, 0x76, 0xD5, 0xEE, 0x39, 0x36, 0x7C, 0x25, 0x94, 0x15, 0x3C, 0xC9, 0xB9,
- 0x93, 0x07, 0x19, 0xAF, 0xE6, 0x70, 0xC3, 0xF5, 0xD4, 0x17, 0x87, 0x57, 0x77,
- 0x7D, 0xCF, 0x0D, 0xDD, 0xDE, 0xB7, 0xFF, 0xB4, 0xDA, 0x20, 0x45, 0x1A, 0x45,
- 0xF4, 0x58, 0x01, 0xBC, 0xEB, 0x3F, 0x16, 0x7F, 0x4C, 0x15, 0x84, 0x8C, 0xE5,
- 0xF6, 0x96, 0xA6, 0xA1, 0xB9, 0xB2, 0x7F, 0x6B, 0xFF, 0x31, 0xF2, 0xF5, 0xC9,
- 0xFF, 0x61, 0xEE, 0xB5, 0x84, 0xAE, 0x68, 0x41, 0xEA, 0xD0, 0xF0, 0xA5, 0xCE,
- 0x0C, 0xE6, 0x4C, 0x6D, 0x6D, 0x94, 0x08, 0xC9, 0xA9, 0x4A, 0x60, 0x6D, 0x01,
- 0x3B, 0xEF, 0x4D, 0x99, 0x8D, 0x42, 0x2A, 0x6B, 0x8A, 0xC7, 0xFA, 0xA9, 0x90,
- 0x40, 0x00, 0x90, 0xF3, 0xA0, 0x75, 0x8E, 0xD5, 0xFE, 0xE7, 0xBD, 0x02, 0x87,
- 0x0C, 0x7D, 0xF0, 0xAF, 0x1E, 0x5F, 0x8D, 0xC8, 0xE1, 0xD4, 0x56, 0x08, 0xBF,
- 0x76, 0x80, 0xD4, 0x18, 0x89, 0x2D, 0x57, 0xDF, 0x66, 0xD0, 0x46, 0x68, 0x77,
- 0x55, 0x47, 0xF5, 0x7C, 0xF7, 0xA6, 0x66, 0xD6, 0x5A, 0x64, 0x55, 0xD4, 0x80,
- 0xC4, 0x55, 0xE9, 0x36, 0x3F, 0x5E, 0xE2, 0x5C, 0x7F, 0x5F, 0xCE, 0x7F, 0xE1,
- 0x0C, 0x82, 0x3D, 0x6B, 0x6E, 0xA2, 0xEA, 0x3B, 0x1F, 0xE8, 0x9E, 0xC7, 0x4E,
- 0x24, 0x3D, 0xDD, 0xFA, 0xEB, 0x71, 0xDF, 0xFE, 0x15, 0xFE, 0x41, 0x9B, 0xB4,
- 0x4E, 0xAB, 0x51, 0xE5, 0x1F, 0x7D, 0x2D, 0xAC, 0xD0, 0x66, 0xD9, 0xA1, 0x59,
- 0x78, 0xC6, 0xEF, 0xC4, 0x43, 0x08, 0x65, 0x18, 0x73, 0xDE, 0x2A, 0xAD, 0x72,
- 0xE7, 0x5A, 0x7E, 0x33, 0x04, 0x72, 0x38, 0x57, 0x47, 0x73, 0x10, 0x1D, 0x88,
- 0x57, 0x4C, 0xDF, 0xA7, 0x78, 0x16, 0xFB, 0x01, 0x21, 0x28, 0x2D, 0xB6, 0x7E,
- 0x05, 0x18, 0x32, 0x52, 0xC3, 0x49, 0x0B, 0x32, 0x18, 0x12, 0x93, 0x54, 0x15,
- 0x3B, 0xC8, 0x6D, 0x4A, 0x77, 0xEF, 0x0A, 0x46, 0x83, 0x89, 0x5C, 0x8B, 0xCB,
- 0x18, 0xA6, 0xDC, 0x97, 0x6F, 0xEE, 0xEE, 0x00, 0x6A, 0xF1, 0x10, 0xFE, 0x07,
- 0x0C, 0xE0, 0x53, 0xD2, 0xB8, 0x45, 0xF4, 0x6E, 0x16, 0x4B, 0xC9, 0x9C, 0xC7,
- 0x93, 0x83, 0x23, 0x1D, 0x4D, 0x00, 0xB9, 0x4F, 0x86, 0x51, 0xF0, 0x29, 0x69,
- 0x41, 0x21, 0xC5, 0x4A, 0xC6, 0x6D, 0xD1, 0x81, 0x38, 0xDB, 0x7C, 0x06, 0xA8,
- 0x26, 0x8E, 0x71, 0x00, 0x4C, 0x44, 0x14, 0x05, 0xF2, 0x1C, 0x00, 0x49, 0xFC,
- 0x29, 0x6A, 0xF9, 0x9E, 0xD1, 0x35, 0x4B, 0xB7, 0xE5, 0xDB, 0xFC, 0x01, 0x04,
- 0x3F, 0x70, 0x33, 0x56, 0x87, 0x69, 0x01, 0xB4, 0xCE, 0x1C, 0x4D, 0x2E, 0x83,
- 0x51, 0x51, 0xD0, 0x37, 0x3B, 0xB4, 0xBA, 0x47, 0xF5, 0xFF, 0xBF, 0xFA, 0xD5,
- 0x03, 0x65, 0xD3, 0x28, 0x9F, 0x38, 0x57, 0xFE, 0x71, 0xD8, 0x9C, 0x16, 0xEE,
- 0x72, 0x19, 0x03, 0x17, 0x6E, 0xC0, 0xEC, 0x49, 0x3D, 0x96, 0xE2, 0x30, 0x97,
- 0x97, 0x84, 0x38, 0x6B, 0xE8, 0x2E, 0xAB, 0x0E, 0x2E, 0x03, 0x52, 0xBA, 0x68,
- 0x55, 0xBA, 0x1D, 0x2C, 0x47, 0xAA, 0x72, 0xAE, 0x02, 0x31, 0x6E, 0xA1, 0xDC,
- 0xAD, 0x0F, 0x4A, 0x46, 0xC9, 0xF2, 0xA9, 0xAB, 0xFD, 0x87, 0x89, 0x5C, 0xB3,
- 0x75, 0x7E, 0xE3, 0xDE, 0x9F, 0xC4, 0x02, 0x1E, 0xA2, 0xF8, 0x8B, 0xD3, 0x00,
- 0x83, 0x96, 0xC4, 0xD0, 0xB9, 0x62, 0xB9, 0x69, 0xEC, 0x56, 0xDF, 0x7D, 0x91,
- 0x4B, 0x68, 0x27, 0xA8, 0x61, 0x78, 0xA7, 0x95, 0x66, 0x51, 0x41, 0xF6, 0xCE,
- 0x78, 0xD3, 0x9A, 0x91, 0xA0, 0x31, 0x09, 0x47, 0xB8, 0x47, 0xB8, 0x44, 0xE1,
- 0x13, 0x86, 0x7E, 0x92, 0x80, 0xC6, 0x1A, 0xF7, 0x79, 0x7E, 0xF1, 0x5D, 0x9F,
- 0x17, 0x2D, 0x80, 0x00, 0x79, 0x34, 0x7D, 0xE3, 0xAD, 0x60, 0x00, 0x20, 0x07,
- 0x80, 0x00, 0x40, 0x01, 0xF8, 0xA1, 0x86, 0xB1, 0xEE, 0x21, 0x63, 0x85, 0x60,
- 0x51, 0x84, 0x90, 0x7E, 0x92, 0x09, 0x39, 0x1C, 0x16, 0x87, 0x5C, 0xA6, 0x09,
- 0x90, 0x06, 0x34, 0x6E, 0xB8, 0x8D, 0x5D, 0xAC, 0x77, 0x97, 0xB5, 0x4D, 0x30,
- 0xFD, 0x39, 0xD0, 0x50, 0x00, 0xC9, 0x98, 0x04, 0x86, 0x00, 0x0D, 0xD8, 0x3E,
- 0x34, 0xC2, 0xA6, 0x25, 0xF8, 0x20, 0xCC, 0x6D, 0x9E, 0x63, 0x05, 0x30, 0xC4,
- 0xC6, 0xCC, 0x54, 0x31, 0x9F, 0x3C, 0xF5, 0x86, 0xB9, 0x08, 0x18, 0xC3, 0x1E,
- 0xB9, 0xA0, 0x0C, 0x45, 0x2C, 0x54, 0x32, 0x8B, 0x85, 0x86, 0x59, 0xC3, 0xB3,
- 0x50, 0x5A, 0xFE, 0xBA, 0xF7, 0x4D, 0xC9, 0x9C, 0x9E, 0x01, 0xDF, 0xD7, 0x6E,
- 0xB5, 0x15, 0x53, 0x08, 0x57, 0xA4, 0x71, 0x36, 0x80, 0x46, 0x05, 0x21, 0x48,
- 0x7B, 0x91, 0xC8, 0xAA, 0xFF, 0x07, 0x9F, 0x78, 0x68, 0xCF, 0x3C, 0xEF, 0xFF,
- 0xBC, 0xB6, 0xA2, 0x36, 0xB7, 0x9F, 0x54, 0xF6, 0x6F, 0x5D, 0xDD, 0x75, 0xD4,
- 0x3C, 0x75, 0xE8, 0xCF, 0x15, 0x02, 0x5B, 0x94, 0xC3, 0xA2, 0x41, 0x63, 0xA1,
- 0x14, 0xF6, 0xC0, 0x57, 0x15, 0x9F, 0x0C, 0x3F, 0x80, 0xF2, 0x98, 0xEE, 0x41,
- 0x85, 0xEE, 0xBC, 0xAA, 0xE9, 0x59, 0xAA, 0xA0, 0x92, 0xCA, 0x00, 0xF3, 0x50,
- 0xCC, 0xFF, 0xAD, 0x97, 0x69, 0xA7, 0xF2, 0x0B, 0x8F, 0xD7, 0xD7, 0x82, 0x3A,
- 0xBB, 0x98, 0x1D, 0xCB, 0x89, 0x0B, 0x9B, 0x05, 0xF7, 0xD0, 0x1A, 0x60, 0xF3,
- 0x29, 0x16, 0x12, 0xF8, 0xF4, 0xF1, 0x4A, 0x05, 0x9B, 0x57, 0x12, 0x7E, 0x3A,
- 0x4A, 0x8D, 0xA6, 0xDF, 0xB6, 0xDD, 0xDF, 0xC3, 0xF0, 0xD2, 0xD4, 0xD7, 0x41,
- 0xA6, 0x00, 0x76, 0x8C, 0x75, 0x08, 0xF0, 0x19, 0xD8, 0x14, 0x63, 0x55, 0x52,
- 0x18, 0x30, 0x98, 0xD0, 0x3F, 0x65, 0x52, 0xB3, 0x88, 0x6D, 0x17, 0x39, 0x93,
- 0xCA, 0x3B, 0xB4, 0x1D, 0x8D, 0xDF, 0xDF, 0xAD, 0x72, 0xDA, 0x74, 0xAF, 0xBD,
- 0x31, 0xF9, 0x12, 0x61, 0x45, 0x29, 0x4C, 0x2B, 0x61, 0xA1, 0x12, 0x90, 0x53,
- 0xE7, 0x5A, 0x9D, 0x44, 0xC8, 0x3A, 0x83, 0xDC, 0x34, 0x4C, 0x07, 0xAF, 0xDB,
- 0x90, 0xCD, 0x03, 0xA4, 0x64, 0x78, 0xBD, 0x55, 0xB2, 0x56, 0x59, 0x32, 0xAB,
- 0x13, 0x2C, 0xC9, 0x77, 0xF8, 0x3B, 0xDF, 0xFF, 0xAC, 0x07, 0xB9, 0x08, 0x7B,
- 0xE9, 0x82, 0xB9, 0x59, 0xC7, 0xFF, 0x86, 0x2C, 0x12, 0x7C, 0xC6, 0x65, 0x3C,
- 0x71, 0xB8, 0x98, 0x9F, 0xA2, 0x45, 0x03, 0xA5, 0xD9, 0xC3, 0xCF, 0xFA, 0xEB,
- 0x89, 0xAD, 0x03, 0xEE, 0xDD, 0x76, 0xD3, 0x4F, 0x10, 0x6F, 0xF0, 0xC1, 0x60,
- 0x0C, 0x00, 0xD4, 0x76, 0x12, 0x0A, 0x8D, 0xDC, 0xFD, 0x5E, 0x0B, 0x26, 0x2F,
- 0x01, 0x1D, 0xB9, 0xE7, 0x73, 0xD4, 0xF2, 0xCB, 0xD8, 0x78, 0x21, 0x52, 0x4B,
- 0x83, 0x3C, 0x44, 0x72, 0x0E, 0xB1, 0x4E, 0x37, 0xBC, 0xC7, 0x50, 0xFA, 0x07,
- 0x80, 0x71, 0x10, 0x0B, 0x24, 0xD1, 0x7E, 0xDA, 0x7F, 0xA7, 0x2F, 0x40, 0xAA,
- 0xD3, 0xF5, 0x44, 0x10, 0x56, 0x4E, 0x3B, 0xF1, 0x6E, 0x9A, 0xA0, 0xEA, 0x85,
- 0x66, 0x16, 0xFB, 0x5C, 0x0B, 0x2B, 0x74, 0x18, 0xAF, 0x3D, 0x04, 0x3E, 0xCE,
- 0x88, 0x9B, 0x3E, 0xF4, 0xB9, 0x00, 0x60, 0x0E, 0xE1, 0xE2, 0xCB, 0x12, 0xB9,
- 0x6D, 0x5A, 0xC7, 0x55, 0x1D, 0xB9, 0x79, 0xAC, 0x43, 0x43, 0xE6, 0x3B, 0xDD,
- 0x7E, 0x9F, 0x78, 0xD3, 0xEA, 0xA3, 0x11, 0xFF, 0xDB, 0xBB, 0xB8, 0x97, 0x37,
- 0x15, 0xDB, 0xF1, 0x97, 0x96, 0xC7, 0xFC, 0xE5, 0xBF, 0xF2, 0x86, 0xC0, 0xFA,
- 0x9B, 0x4C, 0x00, 0x04, 0x03, 0xA5, 0xB6, 0xB7, 0x9C, 0xD9, 0xAB, 0x09, 0x77,
- 0x51, 0x18, 0x3B, 0xAD, 0x61, 0x6C, 0xFC, 0x51, 0x96, 0xFB, 0x19, 0xC8, 0x52,
- 0x35, 0x07, 0x00, 0x63, 0x87, 0x14, 0x04, 0xFA, 0x7A, 0x48, 0x3E, 0x00, 0x47,
- 0x29, 0x07, 0x74, 0x97, 0x74, 0x84, 0xEB, 0xB2, 0x16, 0xB2, 0x31, 0x81, 0xCE,
- 0x2A, 0x31, 0xA7, 0xB1, 0xEB, 0x83, 0x34, 0x7A, 0x73, 0xD7, 0x2F, 0xFF, 0xBC,
- 0xFF, 0xE5, 0xAA, 0xF2, 0xB5, 0x6E, 0x9E, 0xA5, 0x70, 0x8A, 0x8C, 0xDF, 0x6A,
- 0x06, 0x16, 0xC1, 0xAB, 0x59, 0x70, 0xD9, 0x3D, 0x47, 0x7C, 0xDD, 0xEF, 0xDF,
- 0x2F, 0xFF, 0x42, 0x6B, 0xBA, 0x4B, 0xBF, 0xF8, 0x7F, 0xF2, 0x03, 0x0D, 0x79,
- 0xBC, 0x03, 0x76, 0x64, 0x1C, 0x0D, 0x7B, 0xD7, 0xBD, 0xB0, 0x6C, 0xD8, 0x61,
- 0x17, 0x17, 0x8C, 0xED, 0x4E, 0x20, 0xEB, 0x55, 0x33, 0x39, 0xE9, 0x7E, 0xBE,
- 0x8E, 0x05, 0x4B, 0x78, 0x96, 0x85, 0xCC, 0x68, 0xC9, 0x78, 0xAF, 0xAE, 0x44,
- 0x36, 0x61, 0xD3, 0x53, 0xEB, 0xB3, 0x3E, 0x4F, 0x97, 0xE2, 0x8D, 0xAE, 0x90,
- 0xED, 0xB5, 0x4F, 0x8E, 0xE4, 0x7A, 0x44, 0xCF, 0x9D, 0xC5, 0x77, 0x4D, 0xAB,
- 0x4F, 0xE5, 0xC5, 0x73, 0xA0, 0xC8, 0xA5, 0xBB, 0x4B, 0x7D, 0xD5, 0xFB, 0xFB,
- 0xFF, 0x61, 0xFD, 0xAA, 0x1A, 0x62, 0x7E, 0x3C, 0x66, 0x34, 0x15, 0x64, 0x25,
- 0xEC, 0x7C, 0x9D, 0x6A, 0x64, 0x4D, 0x80, 0xD5, 0x4F, 0xFE, 0x8E, 0xEE, 0x18,
- 0x53, 0xC1, 0x09, 0x51, 0xF7, 0xC0, 0xA6, 0xB2, 0x9B, 0x19, 0x2B, 0x14, 0x66,
- 0x66, 0x4B, 0x39, 0x62, 0x1D, 0x84, 0xB9, 0x02, 0x84, 0xAC, 0xC1, 0xDA, 0x6C,
- 0x80, 0xCD, 0x40, 0x20, 0x20, 0x19, 0x51, 0xDC, 0x2B, 0x7D, 0x5D, 0x7F, 0xE3,
- 0x86, 0x8E, 0xC3, 0x35, 0xFE, 0x5C, 0xF6, 0x1C, 0xFF, 0x05, 0x9E, 0xB5, 0xB6,
- 0xBB, 0xBE, 0xF7, 0x2F, 0xB7, 0xE1, 0xF5, 0x33, 0x86, 0xA0, 0x47, 0xDE, 0xF7,
- 0xE9, 0x3B, 0xBE, 0x7E, 0x9B, 0x17, 0xFC, 0xFD, 0x2E, 0x40, 0x86, 0x41, 0x75,
- 0xF1, 0xB2, 0x18, 0xA9, 0xDE, 0x2D, 0xD6, 0x04, 0x20, 0xA4, 0xBA, 0x81, 0xBC,
- 0x1D, 0x5A, 0xD6, 0xF7, 0xF6, 0xB8, 0x42, 0xF7, 0xF5, 0x3D, 0x97, 0xAC, 0xCD,
- 0x6F, 0xAD, 0xDB, 0x4F, 0x5A, 0x2E, 0x64, 0xB9, 0x5D, 0xDD, 0x8B, 0x4A, 0x35,
- 0x44, 0xFE, 0x3D, 0xC6, 0x77, 0x7A, 0xBF, 0xDA, 0xAC, 0x9E, 0xB0, 0xA2, 0xB9,
- 0x6C, 0xAF, 0x02, 0xDD, 0xF2, 0x71, 0x2B, 0xEF, 0xD3, 0x51, 0x0E, 0x07, 0x11,
- 0xBD, 0xED, 0x39, 0x7F, 0xD9, 0xB8, 0xBD, 0xEE, 0x35, 0xE9, 0x5C, 0x67, 0x42,
- 0xDA, 0x05, 0x6E, 0x39, 0xCE, 0x55, 0xFB, 0x26, 0xB7, 0x90, 0x4B, 0xDA, 0x91,
- 0x48, 0xFD, 0xDE, 0xB2, 0xEC, 0x88, 0x9A, 0x46, 0x1A, 0x4C, 0xD4, 0x05, 0x12,
- 0x85, 0x57, 0x37, 0x22, 0xD3, 0x0E, 0x4F, 0x79, 0xE3, 0x81, 0xA9, 0x2B, 0x5F,
- 0xD7, 0x6D, 0xBD, 0x21, 0x98, 0x6F, 0x7D, 0xF5, 0x32, 0x7A, 0x6E, 0xF8, 0x20,
- 0x01, 0x50, 0x90, 0x7A, 0x88, 0x3E, 0x0D, 0x57, 0xB1, 0x58, 0x65, 0xE6, 0x82,
- 0xCE, 0x08, 0x69, 0x8B, 0x87, 0x62, 0x36, 0xB1, 0x7B, 0xDE, 0x74, 0xBD, 0xFE,
- 0x10, 0xBE, 0x26, 0xAB, 0x7E, 0xB7, 0x8D, 0xF7, 0x83, 0x2E, 0x0F, 0xAF, 0x7E,
- 0xBC, 0x17, 0x31, 0xFF, 0xB0, 0x4F, 0x7F, 0x4B, 0x13, 0x83, 0xDF, 0xEE, 0x23,
- 0xD3, 0xE7, 0xC8, 0xAF, 0x75, 0xAB, 0xEA, 0xBD, 0x7D, 0xD2, 0x9D, 0xE9, 0xC1,
- 0x18, 0x8B, 0x7C, 0x9F, 0x51, 0xDC, 0x37, 0xA3, 0xDB, 0xFC, 0xD4, 0x6A, 0x91,
- 0x44, 0x7F, 0x72, 0xC5, 0xD9, 0xC8, 0x37, 0x38, 0x63, 0x0D, 0x59, 0x8B, 0x7F,
- 0x7D, 0x96, 0xC1, 0x5F, 0x4C, 0x7C, 0x88, 0xCB, 0x65, 0x07, 0x2B, 0x0E, 0x1D,
- 0x24, 0xAA, 0x20, 0x2E, 0x6C, 0x33, 0xAB, 0xEF, 0x23, 0xE5, 0xE3, 0x6C, 0xA3,
- 0xA5, 0x2D, 0x01, 0xDF, 0x26, 0x92, 0x52, 0xF5, 0xE6, 0x3E, 0xE3, 0xDD, 0xC6,
- 0xED, 0x42, 0x0F, 0x71, 0x7B, 0xD1, 0xF4, 0x06, 0xF6, 0x82, 0xD5, 0x13, 0xB3,
- 0x60, 0x31, 0x09, 0x89, 0x63, 0x15, 0xD2, 0xCB, 0xAA, 0x77, 0xFD, 0xF4, 0xEB,
- 0xF4, 0xED, 0x2E, 0xE2, 0xBA, 0x27, 0x2E, 0x74, 0xD2, 0x91, 0x7F, 0x0F, 0xDE,
- 0x25, 0xFE, 0x78, 0x20, 0x05, 0x0A, 0x6A, 0xFE, 0x89, 0x14, 0x23, 0xF3, 0xF5,
- 0x3A, 0x1E, 0xF3, 0x22, 0xCE, 0x12, 0x82, 0x24, 0x11, 0x05, 0x04, 0x71, 0x99,
- 0xE5, 0xF0, 0xA6, 0xDB, 0x7B, 0xF5, 0x8F, 0xF9, 0x3C, 0x02, 0x0C, 0x46, 0xFD,
- 0xB6, 0xEA, 0x06, 0x11, 0xF4, 0x1E, 0x7A, 0x20, 0x6A, 0x54, 0xBB, 0x4A, 0x60,
- 0xB0, 0x30, 0x28, 0x9A, 0xF3, 0x3B, 0xE9, 0xBD, 0xD6, 0x04, 0xCA, 0x3A, 0x33,
- 0x37, 0x5F, 0xB7, 0xAD, 0xE7, 0x9C, 0xE2, 0x95, 0x21, 0xF7, 0xB5, 0xC4, 0xF0,
- 0xD1, 0x51, 0x09, 0x44, 0x3F, 0x07, 0xFC, 0x5F, 0x37, 0xFD, 0x7D, 0x7E, 0xD5,
- 0xF7, 0xEB, 0x69, 0xB9, 0x54, 0x98, 0x5A, 0x2A, 0x56, 0xE3, 0xC0, 0x21, 0x57,
- 0xD1, 0xEB, 0x75, 0x15, 0xED, 0xAC, 0xAF, 0x5D, 0xFF, 0xC2, 0xFE, 0x4E, 0xFB,
- 0xBA, 0x13, 0xB8, 0x87, 0xFA, 0x4E, 0x5E, 0x5C, 0x24, 0x15, 0x5B, 0x2B, 0x2C,
- 0x32, 0x68, 0x1F, 0x30, 0x5F, 0xC1, 0xF7, 0xE7, 0xE1, 0x9C, 0x00, 0xC1, 0x9C,
- 0xB1, 0xAB, 0xFA, 0xFF, 0xC1, 0x1E, 0x72, 0xA1, 0x46, 0x9E, 0x2E, 0xCD, 0x76,
- 0x96, 0x4F, 0x14, 0xDC, 0x68, 0xC1, 0x10, 0x9F, 0xDF, 0xEB, 0x5A, 0xBA, 0x8D,
- 0x91, 0x4E, 0x76, 0xE9, 0x3A, 0x43, 0x2D, 0x88, 0xD2, 0x81, 0x0C, 0xEC, 0x6F,
- 0xB7, 0xA4, 0x8B, 0x97, 0x4F, 0xC4, 0x1E, 0xF3, 0x0F, 0xF5, 0x66, 0x66, 0xBF,
- 0x6C, 0x3F, 0xFB, 0x6E, 0x2B, 0x48, 0x6C, 0x7B, 0xD1, 0x2E, 0x64, 0xD1, 0x0B,
- 0x6E, 0x5B, 0x05, 0x16, 0xDD, 0xCB, 0x1B, 0xDE, 0xA2, 0xB9, 0xA8, 0x94, 0xD6,
- 0x5A, 0x5B, 0xE2, 0xC9, 0xBC, 0xD5, 0xAB, 0x64, 0x5B, 0x0F, 0x9A, 0xFD, 0xC7,
- 0x2E, 0xB7, 0xEF, 0xAE, 0xE9, 0x1F, 0x32, 0xD2, 0xCA, 0xA0, 0x37, 0x63, 0x86,
- 0x72, 0x41, 0x07, 0xBC, 0xAB, 0x6F, 0xFF, 0xB7, 0x16, 0xAA, 0xA9, 0x58, 0x9E,
- 0x43, 0x9C, 0x22, 0x8D, 0x48, 0xCE, 0xE5, 0xEF, 0xE0, 0x7D, 0x47, 0x87, 0x5A,
- 0xA8, 0x5B, 0x06, 0xA9, 0x47, 0xF0, 0x26, 0xB4, 0x99, 0xD8, 0xA3, 0x64, 0xED,
- 0x73, 0xB3, 0x96, 0xB4, 0x21, 0x19, 0xA5, 0xC1, 0xDC, 0x88, 0x2E, 0xEE, 0xF2,
- 0x77, 0x91, 0xEC, 0xFB, 0xD5, 0xF9, 0xF8, 0x90, 0x47, 0xAD, 0xF5, 0xEB, 0x96,
- 0x6D, 0xF1, 0x1C, 0xE0, 0xDC, 0x74, 0x1C, 0xE6, 0x2E, 0xE1, 0x76, 0x9D, 0xEE,
- 0xF4, 0xEF, 0xA5, 0x31, 0x03, 0x87, 0x0E, 0x2C, 0x84, 0xA5, 0xF1, 0x22, 0xBE,
- 0x48, 0xA9, 0xCD, 0x09, 0x07, 0xC1, 0xF0, 0xD4, 0xE7, 0x03, 0x82, 0x39, 0xE2,
- 0xA0, 0x0B, 0xDE, 0xAC, 0x37, 0xAC, 0x62, 0x97, 0x8E, 0x79, 0xCE, 0x52, 0x24,
- 0x78, 0xF9, 0x17, 0xD2, 0xF1, 0x5D, 0x2D, 0xA1, 0xDF, 0x12, 0x2C, 0x83, 0xE5,
- 0x1A, 0x28, 0x9A, 0x2D, 0xED, 0x8A, 0xBF, 0xFC, 0x41, 0xC3, 0xEB, 0x0E, 0x91,
- 0xDB, 0xF2, 0xA1, 0xC8, 0xA8, 0x01, 0x8B, 0xF2, 0xF3, 0x59, 0xB7, 0xA7, 0x6F,
- 0x80, 0xFF, 0x0B, 0x46, 0xE1, 0x63, 0xA7, 0x5F, 0x6B, 0xBE, 0x33, 0x71, 0xBE,
- 0x3A, 0xAF, 0xA9, 0x53, 0x5D, 0x3B, 0xB2, 0xF6, 0xEB, 0x42, 0x1C, 0x3E, 0x3F,
- 0x1D, 0x6A, 0x34, 0xAE, 0xB1, 0x05, 0xA1, 0x32, 0x6C, 0xB5, 0xE4, 0xD3, 0xBB,
- 0xE8, 0x10, 0x14, 0x9E, 0x68, 0x6A, 0x24, 0x51, 0xA5, 0x66, 0x64, 0xCC, 0xC4,
- 0x2D, 0x96, 0xA2, 0xC7, 0x2D, 0x1F, 0x0A, 0x0F, 0x6B, 0xD9, 0xAD, 0xA3, 0x11,
- 0x8F, 0x00, 0xAA, 0x06, 0xC2, 0x1E, 0xF3, 0xE8, 0x5A, 0x37, 0x4C, 0xD6, 0x4B,
- 0x6B, 0x01, 0xC9, 0xB0, 0xB6, 0xB9, 0x92, 0xED, 0x1D, 0x08, 0xB0, 0x80, 0x06,
- 0x20, 0xEA, 0xEE, 0xF9, 0x1D, 0xA4, 0x57, 0x73, 0x2E, 0x1B, 0xA5, 0xAF, 0xF6,
- 0xAF, 0xAE, 0x04, 0x7C, 0x4C, 0x7E, 0xC8, 0xDB, 0xC0, 0xFB, 0x37, 0xC8, 0x7E,
- 0xFE, 0x47, 0x0A, 0x3C, 0xFA, 0x61, 0xE7, 0xEB, 0x1B, 0xF3, 0x7C, 0x32, 0xE3,
- 0x7C, 0x37, 0x66, 0x7C, 0x53, 0x07, 0xC2, 0x37, 0xA3, 0xBD, 0xF7, 0xFA, 0xE3,
- 0x8A, 0x76, 0xCB, 0x6C, 0xC8, 0x13, 0xC4, 0x53, 0x53, 0xDB, 0xAD, 0x37, 0x1A,
- 0xEB, 0xE0
-};
-
-// Class for testing the FFmpegH264ToAnnexBBitstreamConverter.
-class FFmpegH264ToAnnexBBitstreamConverterTest : public testing::Test {
- protected:
- FFmpegH264ToAnnexBBitstreamConverterTest() {
- // Set up AVCConfigurationRecord correctly for tests.
- // It's ok to do const cast here as data in kHeaderDataOkWithFieldLen4 is
- // never written to.
- memset(&test_context_, 0, sizeof(AVCodecContext));
- test_context_.extradata = const_cast<uint8*>(kHeaderDataOkWithFieldLen4);
- test_context_.extradata_size = sizeof(kHeaderDataOkWithFieldLen4);
- }
-
- virtual ~FFmpegH264ToAnnexBBitstreamConverterTest() {}
-
- void CreatePacket(AVPacket* packet, const uint8* data, uint32 data_size) {
- // Create new packet sized of |data_size| from |data|.
- EXPECT_EQ(av_new_packet(packet, data_size), 0);
- memcpy(packet->data, data, data_size);
- }
-
- // Variable to hold valid dummy context for testing.
- AVCodecContext test_context_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FFmpegH264ToAnnexBBitstreamConverterTest);
-};
-
-TEST_F(FFmpegH264ToAnnexBBitstreamConverterTest, Conversion_Success) {
- FFmpegH264ToAnnexBBitstreamConverter converter(&test_context_);
-
- AVPacket test_packet;
- CreatePacket(&test_packet, kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4));
-
- // Try out the actual conversion (should be successful and allocate new
- // packet and destroy the old one).
- EXPECT_TRUE(converter.ConvertPacket(&test_packet));
-
- // Clean-up the test packet.
- av_destruct_packet(&test_packet);
-
- // Converter will be automatically cleaned up.
-}
-
-TEST_F(FFmpegH264ToAnnexBBitstreamConverterTest, Conversion_SuccessBigPacket) {
- FFmpegH264ToAnnexBBitstreamConverter converter(&test_context_);
-
- // Create new packet with 1000 excess bytes.
- AVPacket test_packet;
- static uint8 excess_data[sizeof(kPacketDataOkWithFieldLen4) + 1000] = {0};
- memcpy(excess_data, kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4));
- CreatePacket(&test_packet, excess_data, sizeof(excess_data));
-
- // Try out the actual conversion (should be successful and allocate new
- // packet and destroy the old one as we do NOT support in place transform).
- EXPECT_TRUE(converter.ConvertPacket(&test_packet));
-
- // Clean-up the test packet.
- av_destruct_packet(&test_packet);
-
- // Converter will be automatically cleaned up.
-}
-
-TEST_F(FFmpegH264ToAnnexBBitstreamConverterTest, Conversion_FailureNullParams) {
- // Set up AVCConfigurationRecord to represent NULL data.
- AVCodecContext dummy_context;
- dummy_context.extradata = NULL;
- dummy_context.extradata_size = 0;
- FFmpegH264ToAnnexBBitstreamConverter converter(&dummy_context);
-
- // Try out the actual conversion with NULL parameter.
- EXPECT_FALSE(converter.ConvertPacket(NULL));
-
- // Create new packet to test actual conversion.
- AVPacket test_packet;
- CreatePacket(&test_packet, kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4));
-
- // Try out the actual conversion (should be successful and allocate new
- // packet and destroy the old one). This should fail due to missing extradata.
- EXPECT_FALSE(converter.ConvertPacket(&test_packet));
-
- // Clean-up the test packet.
- av_destruct_packet(&test_packet);
-
- // Converted will be automatically cleaned up.
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_video_decoder.cc b/src/media/filters/ffmpeg_video_decoder.cc
deleted file mode 100644
index d7bbf82..0000000
--- a/src/media/filters/ffmpeg_video_decoder.cc
+++ /dev/null
@@ -1,455 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/ffmpeg_video_decoder.h"
-
-#include <algorithm>
-#include <string>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/command_line.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-#include "base/string_number_conversions.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/limits.h"
-#include "media/base/media_switches.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_util.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-
-namespace media {
-
-// Always try to use three threads for video decoding. There is little reason
-// not to since current day CPUs tend to be multi-core and we measured
-// performance benefits on older machines such as P4s with hyperthreading.
-//
-// Handling decoding on separate threads also frees up the pipeline thread to
-// continue processing. Although it'd be nice to have the option of a single
-// decoding thread, FFmpeg treats having one thread the same as having zero
-// threads (i.e., avcodec_decode_video() will execute on the calling thread).
-// Yet another reason for having two threads :)
-static const int kDecodeThreads = 2;
-static const int kMaxDecodeThreads = 16;
-
-// Returns the number of threads given the FFmpeg CodecID. Also inspects the
-// command line for a valid --video-threads flag.
-static int GetThreadCount(CodecID codec_id) {
- // Refer to http://crbug.com/93932 for tsan suppressions on decoding.
- int decode_threads = kDecodeThreads;
-
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads));
- if (threads.empty() || !base::StringToInt(threads, &decode_threads))
- return decode_threads;
-
- decode_threads = std::max(decode_threads, 0);
- decode_threads = std::min(decode_threads, kMaxDecodeThreads);
- return decode_threads;
-}
-
-FFmpegVideoDecoder::FFmpegVideoDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop)
- : message_loop_(message_loop),
- state_(kUninitialized),
- codec_context_(NULL),
- av_frame_(NULL) {
-}
-
-int FFmpegVideoDecoder::GetVideoBuffer(AVCodecContext* codec_context,
- AVFrame* frame) {
- // Don't use |codec_context_| here! With threaded decoding,
- // it will contain unsynchronized width/height/pix_fmt values,
- // whereas |codec_context| contains the current threads's
- // updated width/height/pix_fmt, which can change for adaptive
- // content.
- VideoFrame::Format format = PixelFormatToVideoFormat(codec_context->pix_fmt);
- if (format == VideoFrame::INVALID)
- return AVERROR(EINVAL);
- DCHECK(format == VideoFrame::YV12 || format == VideoFrame::YV16);
-
- gfx::Size size(codec_context->width, codec_context->height);
- int ret;
- if ((ret = av_image_check_size(size.width(), size.height(), 0, NULL)) < 0)
- return ret;
-
- gfx::Size natural_size;
- if (codec_context->sample_aspect_ratio.num > 0) {
- natural_size = GetNaturalSize(size,
- codec_context->sample_aspect_ratio.num,
- codec_context->sample_aspect_ratio.den);
- } else {
- natural_size = demuxer_stream_->video_decoder_config().natural_size();
- }
-
- if (!VideoFrame::IsValidConfig(format, size, gfx::Rect(size), natural_size))
- return AVERROR(EINVAL);
-
- scoped_refptr<VideoFrame> video_frame =
- VideoFrame::CreateFrame(format, size, gfx::Rect(size), natural_size,
- kNoTimestamp());
-
- for (int i = 0; i < 3; i++) {
- frame->base[i] = video_frame->data(i);
- frame->data[i] = video_frame->data(i);
- frame->linesize[i] = video_frame->stride(i);
- }
-
- frame->opaque = NULL;
- video_frame.swap(reinterpret_cast<VideoFrame**>(&frame->opaque));
- frame->type = FF_BUFFER_TYPE_USER;
- frame->pkt_pts = codec_context->pkt ? codec_context->pkt->pts :
- AV_NOPTS_VALUE;
- frame->width = codec_context->width;
- frame->height = codec_context->height;
- frame->format = codec_context->pix_fmt;
-
- return 0;
-}
-
-static int GetVideoBufferImpl(AVCodecContext* s, AVFrame* frame) {
- FFmpegVideoDecoder* vd = static_cast<FFmpegVideoDecoder*>(s->opaque);
- return vd->GetVideoBuffer(s, frame);
-}
-
-static void ReleaseVideoBufferImpl(AVCodecContext* s, AVFrame* frame) {
- scoped_refptr<VideoFrame> video_frame;
- video_frame.swap(reinterpret_cast<VideoFrame**>(&frame->opaque));
-
- // The FFmpeg API expects us to zero the data pointers in
- // this callback
- memset(frame->data, 0, sizeof(frame->data));
- frame->opaque = NULL;
-}
-
-void FFmpegVideoDecoder::Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- PipelineStatusCB initialize_cb = BindToCurrentLoop(status_cb);
-
- FFmpegGlue::InitializeFFmpeg();
- DCHECK(!demuxer_stream_) << "Already initialized.";
-
- if (!stream) {
- initialize_cb.Run(PIPELINE_ERROR_DECODE);
- return;
- }
-
- demuxer_stream_ = stream;
- statistics_cb_ = statistics_cb;
-
- if (!ConfigureDecoder()) {
- initialize_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- // Success!
- state_ = kNormal;
- initialize_cb.Run(PIPELINE_OK);
-}
-
-void FFmpegVideoDecoder::Read(const ReadCB& read_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!read_cb.is_null());
- CHECK_NE(state_, kUninitialized);
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
- read_cb_ = BindToCurrentLoop(read_cb);
-
- // Return empty frames if decoding has finished.
- if (state_ == kDecodeFinished) {
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- ReadFromDemuxerStream();
-}
-
-void FFmpegVideoDecoder::Reset(const base::Closure& closure) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(reset_cb_.is_null());
- reset_cb_ = BindToCurrentLoop(closure);
-
- // Defer the reset if a read is pending.
- if (!read_cb_.is_null())
- return;
-
- DoReset();
-}
-
-void FFmpegVideoDecoder::DoReset() {
- DCHECK(read_cb_.is_null());
-
- avcodec_flush_buffers(codec_context_);
- state_ = kNormal;
- base::ResetAndReturn(&reset_cb_).Run();
-}
-
-void FFmpegVideoDecoder::Stop(const base::Closure& closure) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::ScopedClosureRunner runner(BindToCurrentLoop(closure));
-
- if (state_ == kUninitialized)
- return;
-
- if (!read_cb_.is_null())
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
-
- ReleaseFFmpegResources();
- state_ = kUninitialized;
-}
-
-FFmpegVideoDecoder::~FFmpegVideoDecoder() {
- DCHECK_EQ(kUninitialized, state_);
- DCHECK(!codec_context_);
- DCHECK(!av_frame_);
-}
-
-void FFmpegVideoDecoder::ReadFromDemuxerStream() {
- DCHECK_NE(state_, kUninitialized);
- DCHECK_NE(state_, kDecodeFinished);
- DCHECK(!read_cb_.is_null());
-
- demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::BufferReady, this));
-}
-
-void FFmpegVideoDecoder::BufferReady(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_NE(state_, kDecodeFinished);
- DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status;
-
- if (state_ == kUninitialized)
- return;
-
- DCHECK(!read_cb_.is_null());
-
- if (status == DemuxerStream::kConfigChanged) {
- if (!ConfigureDecoder()) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- state_ = kDecodeFinished;
- if (!reset_cb_.is_null())
- base::ResetAndReturn(&reset_cb_).Run();
- return;
- }
-
- if (reset_cb_.is_null()) {
- ReadFromDemuxerStream();
- return;
- }
- }
-
- if (!reset_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- DoReset();
- return;
- }
-
- if (status == DemuxerStream::kAborted) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- return;
- }
-
- DCHECK_EQ(status, DemuxerStream::kOk);
- DecodeBuffer(buffer);
-}
-
-void FFmpegVideoDecoder::DecodeBuffer(
- const scoped_refptr<DecoderBuffer>& buffer) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_NE(state_, kUninitialized);
- DCHECK_NE(state_, kDecodeFinished);
- DCHECK(reset_cb_.is_null());
- DCHECK(!read_cb_.is_null());
- DCHECK(buffer);
-
- // During decode, because reads are issued asynchronously, it is possible to
- // receive multiple end of stream buffers since each read is acked. When the
- // first end of stream buffer is read, FFmpeg may still have frames queued
- // up in the decoder so we need to go through the decode loop until it stops
- // giving sensible data. After that, the decoder should output empty
- // frames. There are three states the decoder can be in:
- //
- // kNormal: This is the starting state. Buffers are decoded. Decode errors
- // are discarded.
- // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
- // until no more data is returned to flush out remaining
- // frames. The input buffer is ignored at this point.
- // kDecodeFinished: All calls return empty frames.
- //
- // These are the possible state transitions.
- //
- // kNormal -> kFlushCodec:
- // When buffer->IsEndOfStream() is first true.
- // kNormal -> kDecodeFinished:
- // A decoding error occurs and decoding needs to stop.
- // kFlushCodec -> kDecodeFinished:
- // When avcodec_decode_video2() returns 0 data or errors out.
- // (any state) -> kNormal:
- // Any time Reset() is called.
-
- // Transition to kFlushCodec on the first end of stream buffer.
- if (state_ == kNormal && buffer->IsEndOfStream()) {
- state_ = kFlushCodec;
- }
-
- scoped_refptr<VideoFrame> video_frame;
- if (!Decode(buffer, &video_frame)) {
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- // Any successful decode counts!
- if (buffer->GetDataSize()) {
- PipelineStatistics statistics;
- statistics.video_bytes_decoded = buffer->GetDataSize();
- statistics_cb_.Run(statistics);
- }
-
- // If we didn't get a frame then we've either completely finished decoding or
- // we need more data.
- if (!video_frame) {
- if (state_ == kFlushCodec) {
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- ReadFromDemuxerStream();
- return;
- }
-
- base::ResetAndReturn(&read_cb_).Run(kOk, video_frame);
-}
-
-bool FFmpegVideoDecoder::Decode(
- const scoped_refptr<DecoderBuffer>& buffer,
- scoped_refptr<VideoFrame>* video_frame) {
- DCHECK(video_frame);
-
- // Create a packet for input data.
- // Due to FFmpeg API changes we no longer have const read-only pointers.
- AVPacket packet;
- av_init_packet(&packet);
- packet.data = const_cast<uint8*>(buffer->GetData());
- packet.size = buffer->GetDataSize();
-
- // Let FFmpeg handle presentation timestamp reordering.
- codec_context_->reordered_opaque = buffer->GetTimestamp().InMicroseconds();
-
- // Reset frame to default values.
- avcodec_get_frame_defaults(av_frame_);
-
- // This is for codecs not using get_buffer to initialize
- // |av_frame_->reordered_opaque|
- av_frame_->reordered_opaque = codec_context_->reordered_opaque;
-
- int frame_decoded = 0;
- int result = avcodec_decode_video2(codec_context_,
- av_frame_,
- &frame_decoded,
- &packet);
- // Log the problem if we can't decode a video frame and exit early.
- if (result < 0) {
- LOG(ERROR) << "Error decoding a video frame with timestamp: "
- << buffer->GetTimestamp().InMicroseconds() << " us, duration: "
- << buffer->GetDuration().InMicroseconds() << " us, packet size: "
- << buffer->GetDataSize() << " bytes";
- *video_frame = NULL;
- return false;
- }
-
- // If no frame was produced then signal that more data is required to
- // produce more frames. This can happen under two circumstances:
- // 1) Decoder was recently initialized/flushed
- // 2) End of stream was reached and all internal frames have been output
- if (frame_decoded == 0) {
- *video_frame = NULL;
- return true;
- }
-
- // TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675
- // The decoder is in a bad state and not decoding correctly.
- // Checking for NULL avoids a crash in CopyPlane().
- if (!av_frame_->data[VideoFrame::kYPlane] ||
- !av_frame_->data[VideoFrame::kUPlane] ||
- !av_frame_->data[VideoFrame::kVPlane]) {
- LOG(ERROR) << "Video frame was produced yet has invalid frame data.";
- *video_frame = NULL;
- return false;
- }
-
- if (!av_frame_->opaque) {
- LOG(ERROR) << "VideoFrame object associated with frame data not set.";
- return false;
- }
- *video_frame = static_cast<VideoFrame*>(av_frame_->opaque);
-
- (*video_frame)->SetTimestamp(
- base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque));
-
- return true;
-}
-
-void FFmpegVideoDecoder::ReleaseFFmpegResources() {
- if (codec_context_) {
- av_free(codec_context_->extradata);
- avcodec_close(codec_context_);
- av_free(codec_context_);
- codec_context_ = NULL;
- }
- if (av_frame_) {
- av_free(av_frame_);
- av_frame_ = NULL;
- }
-}
-
-bool FFmpegVideoDecoder::ConfigureDecoder() {
- const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config();
-
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString();
- return false;
- }
-
- if (config.is_encrypted()) {
- DLOG(ERROR) << "Encrypted video stream not supported.";
- return false;
- }
-
- // Release existing decoder resources if necessary.
- ReleaseFFmpegResources();
-
- // Initialize AVCodecContext structure.
- codec_context_ = avcodec_alloc_context3(NULL);
- VideoDecoderConfigToAVCodecContext(config, codec_context_);
-
- // Enable motion vector search (potentially slow), strong deblocking filter
- // for damaged macroblocks, and set our error detection sensitivity.
- codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
- codec_context_->thread_count = GetThreadCount(codec_context_->codec_id);
- codec_context_->opaque = this;
- codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
- codec_context_->get_buffer = GetVideoBufferImpl;
- codec_context_->release_buffer = ReleaseVideoBufferImpl;
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) {
- ReleaseFFmpegResources();
- return false;
- }
-
- av_frame_ = avcodec_alloc_frame();
- return true;
-}
-
-} // namespace media
diff --git a/src/media/filters/ffmpeg_video_decoder.h b/src/media/filters/ffmpeg_video_decoder.h
deleted file mode 100644
index e26a194..0000000
--- a/src/media/filters/ffmpeg_video_decoder.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_FFMPEG_VIDEO_DECODER_H_
-#define MEDIA_FILTERS_FFMPEG_VIDEO_DECODER_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/video_decoder.h"
-
-struct AVCodecContext;
-struct AVFrame;
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-
-class MEDIA_EXPORT FFmpegVideoDecoder : public VideoDecoder {
- public:
- explicit FFmpegVideoDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- // VideoDecoder implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
- virtual void Stop(const base::Closure& closure) OVERRIDE;
-
- // Callback called from within FFmpeg to allocate a buffer based on
- // the dimensions of |codec_context|. See AVCodecContext.get_buffer
- // documentation inside FFmpeg.
- int GetVideoBuffer(AVCodecContext *codec_context, AVFrame* frame);
-
- protected:
- virtual ~FFmpegVideoDecoder();
-
- private:
- enum DecoderState {
- kUninitialized,
- kNormal,
- kFlushCodec,
- kDecodeFinished
- };
-
- // Reads from the demuxer stream and corresponding read callback.
- void ReadFromDemuxerStream();
- void BufferReady(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- // Handles decoding an unencrypted encoded buffer.
- void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer);
- bool Decode(const scoped_refptr<DecoderBuffer>& buffer,
- scoped_refptr<VideoFrame>* video_frame);
-
- // Handles (re-)initializing the decoder with a (new) config.
- // Returns true if initialization was successful.
- bool ConfigureDecoder();
-
- // Releases resources associated with |codec_context_| and |av_frame_|
- // and resets them to NULL.
- void ReleaseFFmpegResources();
-
- // Reset decoder and call |reset_cb_|.
- void DoReset();
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- DecoderState state_;
-
- StatisticsCB statistics_cb_;
-
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- // FFmpeg structures owned by this object.
- AVCodecContext* codec_context_;
- AVFrame* av_frame_;
-
- // Pointer to the demuxer stream that will feed us compressed buffers.
- scoped_refptr<DemuxerStream> demuxer_stream_;
-
- DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FFMPEG_VIDEO_DECODER_H_
diff --git a/src/media/filters/ffmpeg_video_decoder_unittest.cc b/src/media/filters/ffmpeg_video_decoder_unittest.cc
deleted file mode 100644
index 001b538..0000000
--- a/src/media/filters/ffmpeg_video_decoder_unittest.cc
+++ /dev/null
@@ -1,671 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "base/memory/singleton.h"
-#include "base/string_util.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/limits.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_data_util.h"
-#include "media/base/test_helpers.h"
-#include "media/base/video_decoder.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_util.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/filters/ffmpeg_video_decoder.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-using ::testing::_;
-using ::testing::AtLeast;
-using ::testing::InSequence;
-using ::testing::IsNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(320, 240);
-static const gfx::Rect kVisibleRect(320, 240);
-static const gfx::Size kNaturalSize(320, 240);
-static const VideoFrame::Format kInitVideoFormat = VideoFrame::RGB32;
-static const gfx::Size kInitCodedSize(100, 100);
-static const gfx::Rect kInitVisibleRect(100, 100);
-static const gfx::Size kInitNaturalSize(100, 100);
-
-ACTION_P(ReturnBuffer, buffer) {
- arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
-}
-
-class FFmpegVideoDecoderTest : public testing::Test {
- public:
- FFmpegVideoDecoderTest()
- : decoder_(NULL),
- demuxer_(new StrictMock<MockDemuxerStream>()),
- read_cb_(base::Bind(&FFmpegVideoDecoderTest::FrameReady,
- base::Unretained(this))) {
- FFmpegGlue::InitializeFFmpeg();
-
- decoder_ = new FFmpegVideoDecoder(message_loop_.message_loop_proxy());
-
- // Initialize various test buffers.
- frame_buffer_.reset(new uint8[kCodedSize.GetArea()]);
- end_of_stream_buffer_ = DecoderBuffer::CreateEOSBuffer();
- i_frame_buffer_ = ReadTestDataFile("vp8-I-frame-320x240");
- corrupt_i_frame_buffer_ = ReadTestDataFile("vp8-corrupt-I-frame");
- }
-
- virtual ~FFmpegVideoDecoderTest() {
- Stop();
- }
-
- void Initialize() {
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, false, true);
- InitializeWithConfig(config_);
- }
-
- void InitializeWithEncryptedConfig() {
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, true, true);
- InitializeWithConfig(config_);
- }
-
- void InitializeWithConfigAndStatus(const VideoDecoderConfig& config,
- PipelineStatus status) {
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .WillRepeatedly(ReturnRef(config));
-
- decoder_->Initialize(demuxer_, NewExpectedStatusCB(status),
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_)));
-
- message_loop_.RunUntilIdle();
- }
-
- void InitializeWithConfig(const VideoDecoderConfig& config) {
- InitializeWithConfigAndStatus(config, PIPELINE_OK);
- }
-
- void Reset() {
- decoder_->Reset(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- void Stop() {
- decoder_->Stop(NewExpectedClosure());
- message_loop_.RunUntilIdle();
- }
-
- // Sets up expectations and actions to put FFmpegVideoDecoder in an active
- // decoding state.
- void EnterDecodingState() {
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
- DecodeSingleFrame(i_frame_buffer_, &status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_FALSE(video_frame->IsEndOfStream());
- }
-
- // Sets up expectations and actions to put FFmpegVideoDecoder in an end
- // of stream state.
- void EnterEndOfStreamState() {
- scoped_refptr<VideoFrame> video_frame;
- VideoDecoder::Status status;
- Read(&status, &video_frame);
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_TRUE(video_frame->IsEndOfStream());
- }
-
- // Decodes the single compressed frame in |buffer| and writes the
- // uncompressed output to |video_frame|. This method works with single
- // and multithreaded decoders. End of stream buffers are used to trigger
- // the frame to be returned in the multithreaded decoder case.
- void DecodeSingleFrame(const scoped_refptr<DecoderBuffer>& buffer,
- VideoDecoder::Status* status,
- scoped_refptr<VideoFrame>* video_frame) {
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(buffer))
- .WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
-
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- Read(status, video_frame);
- }
-
- // Decodes |i_frame_buffer_| and then decodes the data contained in
- // the file named |test_file_name|. This function expects both buffers
- // to decode to frames that are the same size.
- void DecodeIFrameThenTestFile(const std::string& test_file_name,
- int expected_width,
- int expected_height) {
- Initialize();
-
- VideoDecoder::Status status_a;
- VideoDecoder::Status status_b;
- scoped_refptr<VideoFrame> video_frame_a;
- scoped_refptr<VideoFrame> video_frame_b;
-
- scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(test_file_name);
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(i_frame_buffer_))
- .WillOnce(ReturnBuffer(buffer))
- .WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
-
- EXPECT_CALL(statistics_cb_, OnStatistics(_))
- .Times(2);
-
- Read(&status_a, &video_frame_a);
- Read(&status_b, &video_frame_b);
-
- gfx::Size original_size = kVisibleRect.size();
- EXPECT_EQ(VideoDecoder::kOk, status_a);
- EXPECT_EQ(VideoDecoder::kOk, status_b);
- ASSERT_TRUE(video_frame_a);
- ASSERT_TRUE(video_frame_b);
- EXPECT_EQ(original_size.width(),
- video_frame_a->visible_rect().size().width());
- EXPECT_EQ(original_size.height(),
- video_frame_a->visible_rect().size().height());
- EXPECT_EQ(expected_width, video_frame_b->visible_rect().size().width());
- EXPECT_EQ(expected_height, video_frame_b->visible_rect().size().height());
- }
-
- void Read(VideoDecoder::Status* status,
- scoped_refptr<VideoFrame>* video_frame) {
- EXPECT_CALL(*this, FrameReady(_, _))
- .WillOnce(DoAll(SaveArg<0>(status), SaveArg<1>(video_frame)));
-
- decoder_->Read(read_cb_);
-
- message_loop_.RunUntilIdle();
- }
-
- MOCK_METHOD2(FrameReady, void(VideoDecoder::Status,
- const scoped_refptr<VideoFrame>&));
-
- MessageLoop message_loop_;
- scoped_refptr<FFmpegVideoDecoder> decoder_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
- MockStatisticsCB statistics_cb_;
- VideoDecoderConfig config_;
-
- VideoDecoder::ReadCB read_cb_;
-
- // Various buffers for testing.
- scoped_array<uint8_t> frame_buffer_;
- scoped_refptr<DecoderBuffer> end_of_stream_buffer_;
- scoped_refptr<DecoderBuffer> i_frame_buffer_;
- scoped_refptr<DecoderBuffer> corrupt_i_frame_buffer_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoderTest);
-};
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_Normal) {
- Initialize();
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_UnsupportedDecoder) {
- // Test avcodec_find_decoder() returning NULL.
- VideoDecoderConfig config(kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN,
- kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_UnsupportedPixelFormat) {
- // Ensure decoder handles unsupport pixel formats without crashing.
- VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- VideoFrame::INVALID,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_OpenDecoderFails) {
- // Specify Theora w/o extra data so that avcodec_open2() fails.
- VideoDecoderConfig config(kCodecTheora, VIDEO_CODEC_PROFILE_UNKNOWN,
- kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioNumeratorZero) {
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 0, 1);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioDenominatorZero) {
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 1, 0);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioNumeratorNegative) {
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), -1, 1);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioDenominatorNegative) {
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 1, -1);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioNumeratorTooLarge) {
- int width = kVisibleRect.size().width();
- int num = ceil(static_cast<double>(limits::kMaxDimension + 1) / width);
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), num, 1);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, Initialize_AspectRatioDenominatorTooLarge) {
- int den = kVisibleRect.size().width() + 1;
- gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 1, den);
- VideoDecoderConfig config(kCodecVP8, VP8PROFILE_MAIN,
- kVideoFormat,
- kCodedSize, kVisibleRect, natural_size,
- NULL, 0, false);
- InitializeWithConfigAndStatus(config, DECODER_ERROR_NOT_SUPPORTED);
-}
-
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) {
- Initialize();
-
- // Simulate decoding a single frame.
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
- DecodeSingleFrame(i_frame_buffer_, &status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_FALSE(video_frame->IsEndOfStream());
-}
-
-// Verify current behavior for 0 byte frames. FFmpeg simply ignores
-// the 0 byte frames.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
- Initialize();
-
- scoped_refptr<DecoderBuffer> zero_byte_buffer = new DecoderBuffer(0);
-
- VideoDecoder::Status status_a;
- VideoDecoder::Status status_b;
- VideoDecoder::Status status_c;
- scoped_refptr<VideoFrame> video_frame_a;
- scoped_refptr<VideoFrame> video_frame_b;
- scoped_refptr<VideoFrame> video_frame_c;
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(i_frame_buffer_))
- .WillOnce(ReturnBuffer(zero_byte_buffer))
- .WillOnce(ReturnBuffer(i_frame_buffer_))
- .WillRepeatedly(ReturnBuffer(end_of_stream_buffer_));
-
- EXPECT_CALL(statistics_cb_, OnStatistics(_))
- .Times(2);
-
- Read(&status_a, &video_frame_a);
- Read(&status_b, &video_frame_b);
- Read(&status_c, &video_frame_c);
-
- EXPECT_EQ(VideoDecoder::kOk, status_a);
- EXPECT_EQ(VideoDecoder::kOk, status_b);
- EXPECT_EQ(VideoDecoder::kOk, status_c);
-
- ASSERT_TRUE(video_frame_a);
- ASSERT_TRUE(video_frame_b);
- ASSERT_TRUE(video_frame_c);
-
- EXPECT_FALSE(video_frame_a->IsEndOfStream());
- EXPECT_FALSE(video_frame_b->IsEndOfStream());
- EXPECT_TRUE(video_frame_c->IsEndOfStream());
-}
-
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeError) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(corrupt_i_frame_buffer_))
- .WillRepeatedly(ReturnBuffer(i_frame_buffer_));
-
- // The error is only raised on the second decode attempt, so we expect at
- // least one successful decode but we don't expect FrameReady() to be
- // executed as an error is raised instead.
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- // Our read should still get satisfied with end of stream frame during an
- // error.
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
- Read(&status, &video_frame);
- EXPECT_EQ(VideoDecoder::kDecodeError, status);
- EXPECT_FALSE(video_frame);
-}
-
-// Multi-threaded decoders have different behavior than single-threaded
-// decoders at the end of the stream. Multithreaded decoders hide errors
-// that happen on the last |codec_context_->thread_count| frames to avoid
-// prematurely signalling EOS. This test just exposes that behavior so we can
-// detect if it changes.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeErrorAtEndOfStream) {
- Initialize();
-
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
- DecodeSingleFrame(corrupt_i_frame_buffer_, &status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_TRUE(video_frame->IsEndOfStream());
-}
-
-// Decode |i_frame_buffer_| and then a frame with a larger width and verify
-// the output size was adjusted.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerWidth) {
- DecodeIFrameThenTestFile("vp8-I-frame-640x240", 640, 240);
-}
-
-// Decode |i_frame_buffer_| and then a frame with a smaller width and verify
-// the output size was adjusted.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerWidth) {
- DecodeIFrameThenTestFile("vp8-I-frame-160x240", 160, 240);
-}
-
-// Decode |i_frame_buffer_| and then a frame with a larger height and verify
-// the output size was adjusted.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_LargerHeight) {
- DecodeIFrameThenTestFile("vp8-I-frame-320x480", 320, 480);
-}
-
-// Decode |i_frame_buffer_| and then a frame with a smaller height and verify
-// the output size was adjusted.
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) {
- DecodeIFrameThenTestFile("vp8-I-frame-320x120", 320, 120);
-}
-
-// Test resetting when decoder has initialized but not decoded.
-TEST_F(FFmpegVideoDecoderTest, Reset_Initialized) {
- Initialize();
- Reset();
-}
-
-// Test resetting when decoder has decoded single frame.
-TEST_F(FFmpegVideoDecoderTest, Reset_Decoding) {
- Initialize();
- EnterDecodingState();
- Reset();
-}
-
-// Test resetting when decoder has hit end of stream.
-TEST_F(FFmpegVideoDecoderTest, Reset_EndOfStream) {
- Initialize();
- EnterDecodingState();
- EnterEndOfStreamState();
- Reset();
-}
-
-// Test resetting when there is a pending read on the demuxer.
-TEST_F(FFmpegVideoDecoderTest, Reset_DuringPendingRead) {
- Initialize();
-
- // Request a read on the decoder and ensure the demuxer has been called.
- DemuxerStream::ReadCB read_cb;
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&read_cb));
- decoder_->Read(read_cb_);
- ASSERT_FALSE(read_cb.is_null());
-
- // Reset the decoder.
- Reset();
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- read_cb.Run(DemuxerStream::kOk, i_frame_buffer_);
-}
-
-// Test stopping when decoder has initialized but not decoded.
-TEST_F(FFmpegVideoDecoderTest, Stop_Initialized) {
- Initialize();
- Stop();
-}
-
-// Test stopping when decoder has decoded single frame.
-TEST_F(FFmpegVideoDecoderTest, Stop_Decoding) {
- Initialize();
- EnterDecodingState();
- Stop();
-}
-
-// Test stopping when decoder has hit end of stream.
-TEST_F(FFmpegVideoDecoderTest, Stop_EndOfStream) {
- Initialize();
- EnterDecodingState();
- EnterEndOfStreamState();
- Stop();
-}
-
-// Test stopping when there is a pending read on the demuxer.
-TEST_F(FFmpegVideoDecoderTest, Stop_DuringPendingRead) {
- Initialize();
-
- // Request a read on the decoder and ensure the demuxer has been called.
- DemuxerStream::ReadCB read_cb;
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&read_cb));
- decoder_->Read(read_cb_);
- ASSERT_FALSE(read_cb.is_null());
-
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- Stop();
-
- read_cb.Run(DemuxerStream::kOk, i_frame_buffer_);
-}
-
-// Test aborted read on the demuxer stream.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_Aborted) {
- Initialize();
-
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>()));
-
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
-
- Read(&status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- EXPECT_FALSE(video_frame);
-}
-
-// Test aborted read on the demuxer stream during pending reset.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_AbortedDuringReset) {
- Initialize();
-
- // Request a read on the decoder and ensure the demuxer has been called.
- DemuxerStream::ReadCB read_cb;
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&read_cb));
- decoder_->Read(read_cb_);
- ASSERT_FALSE(read_cb.is_null());
-
- // Reset while there is still an outstanding read on the demuxer.
- Reset();
-
- // Signal an aborted demuxer read: a NULL video frame should be returned.
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
- read_cb.Run(DemuxerStream::kAborted, NULL);
- message_loop_.RunUntilIdle();
-}
-
-// Test config change on the demuxer stream.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_ConfigChange) {
- // TODO(xhwang): Ideally we should use a codec other than VP8 in the init
- // config. Theora is the only codec that's supported on all platforms but it
- // needs extra data to be initialized properly.
- VideoDecoderConfig init_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kInitVideoFormat,
- kInitCodedSize, kInitVisibleRect, kInitNaturalSize, NULL, 0, false);
- InitializeWithConfig(init_config);
-
- VideoDecoderConfig new_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false);
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .WillRepeatedly(ReturnRef(new_config));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()))
- .WillOnce(RunCallback<0>(DemuxerStream::kOk, i_frame_buffer_))
- .WillRepeatedly(RunCallback<0>(DemuxerStream::kOk,
- end_of_stream_buffer_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
-
- Read(&status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_FALSE(video_frame->IsEndOfStream());
-}
-
-// Test config change failure.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_ConfigChangeFailed) {
- Initialize();
-
- VideoDecoderConfig invalid_config(
- kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::INVALID,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false);
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .WillRepeatedly(ReturnRef(invalid_config));
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
- scoped_refptr<DecoderBuffer>()));
-
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
-
- Read(&status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kDecodeError, status);
- ASSERT_FALSE(video_frame);
-}
-
-// Test config change on the demuxer stream during pending reset.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_ConfigChangeDuringReset) {
- // TODO(xhwang): Ideally we should use a codec other than VP8 in the init
- // config. Theora is the only codec that's supported on all platforms but it
- // needs extra data to be initialized properly.
- VideoDecoderConfig init_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kInitVideoFormat,
- kInitCodedSize, kInitVisibleRect, kInitNaturalSize, NULL, 0, false);
- InitializeWithConfig(init_config);
-
- // Request a read on the decoder and ensure the demuxer has been called.
- DemuxerStream::ReadCB read_cb;
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&read_cb));
- decoder_->Read(read_cb_);
- ASSERT_FALSE(read_cb.is_null());
-
- // Reset while there is still an outstanding read on the demuxer.
- Reset();
-
- VideoDecoderConfig new_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false);
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .Times(AtLeast(1))
- .WillRepeatedly(ReturnRef(new_config));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull()));
-
- // Signal a config change.
- base::ResetAndReturn(&read_cb).Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-
- // Now the decoder should be in a clean initialized state (initialized with
- // |new_config|) and be ready to decode |i_frame_buffer_|.
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(RunCallback<0>(DemuxerStream::kOk, i_frame_buffer_))
- .WillRepeatedly(RunCallback<0>(DemuxerStream::kOk,
- end_of_stream_buffer_));
- EXPECT_CALL(statistics_cb_, OnStatistics(_));
-
- VideoDecoder::Status status;
- scoped_refptr<VideoFrame> video_frame;
-
- Read(&status, &video_frame);
-
- EXPECT_EQ(VideoDecoder::kOk, status);
- ASSERT_TRUE(video_frame);
- EXPECT_FALSE(video_frame->IsEndOfStream());
-}
-
-// Test failed config change during pending reset.
-TEST_F(FFmpegVideoDecoderTest, DemuxerRead_ConfigChangeFailedDuringReset) {
- Initialize();
-
- // Request a read on the decoder and ensure the demuxer has been called.
- DemuxerStream::ReadCB read_cb;
- EXPECT_CALL(*demuxer_, Read(_))
- .WillOnce(SaveArg<0>(&read_cb));
- decoder_->Read(read_cb_);
- ASSERT_FALSE(read_cb.is_null());
-
- // Reset while there is still an outstanding read on the demuxer.
- Reset();
-
- VideoDecoderConfig invalid_config(
- kUnknownVideoCodec, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::INVALID,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false);
- EXPECT_CALL(*demuxer_, video_decoder_config())
- .Times(AtLeast(1))
- .WillRepeatedly(ReturnRef(invalid_config));
- EXPECT_CALL(*this, FrameReady(VideoDecoder::kDecodeError, IsNull()));
-
- // Signal a config change.
- base::ResetAndReturn(&read_cb).Run(DemuxerStream::kConfigChanged, NULL);
- message_loop_.RunUntilIdle();
-}
-
-} // namespace media
diff --git a/src/media/filters/file_data_source.cc b/src/media/filters/file_data_source.cc
deleted file mode 100644
index dab5c58..0000000
--- a/src/media/filters/file_data_source.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/file_data_source.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-
-namespace media {
-
-FileDataSource::FileDataSource()
- : force_read_errors_(false),
- force_streaming_(false) {
-}
-
-bool FileDataSource::Initialize(const FilePath& file_path) {
- DCHECK(!file_.IsValid());
-
- if (!file_.Initialize(file_path))
- return false;
-
- UpdateHostBytes();
- return true;
-}
-
-void FileDataSource::set_host(DataSourceHost* host) {
- DataSource::set_host(host);
- UpdateHostBytes();
-}
-
-void FileDataSource::Stop(const base::Closure& callback) {
- callback.Run();
-}
-
-void FileDataSource::Read(int64 position, int size, uint8* data,
- const DataSource::ReadCB& read_cb) {
- if (force_read_errors_ || !file_.IsValid()) {
- read_cb.Run(kReadError);
- return;
- }
-
- int64 file_size = file_.length();
-
- CHECK_GE(file_size, 0);
- CHECK_GE(position, 0);
- CHECK_GE(size, 0);
-
- // Cap position and size within bounds.
- position = std::min(position, file_size);
- int64 clamped_size = std::min(static_cast<int64>(size), file_size - position);
-
- memcpy(data, file_.data() + position, clamped_size);
- read_cb.Run(clamped_size);
-}
-
-bool FileDataSource::GetSize(int64* size_out) {
- *size_out = file_.length();
- return true;
-}
-
-bool FileDataSource::IsStreaming() {
- return force_streaming_;
-}
-
-void FileDataSource::SetBitrate(int bitrate) {}
-
-FileDataSource::~FileDataSource() {}
-
-void FileDataSource::UpdateHostBytes() {
- if (host() && file_.IsValid()) {
- host()->SetTotalBytes(file_.length());
- host()->AddBufferedByteRange(0, file_.length());
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/file_data_source.h b/src/media/filters/file_data_source.h
deleted file mode 100644
index bc46bf7..0000000
--- a/src/media/filters/file_data_source.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_FILE_DATA_SOURCE_H_
-#define MEDIA_FILTERS_FILE_DATA_SOURCE_H_
-
-#include <string>
-
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "media/base/data_source.h"
-
-namespace media {
-
-// Basic data source that treats the URL as a file path, and uses the file
-// system to read data for a media pipeline.
-class MEDIA_EXPORT FileDataSource : public DataSource {
- public:
- FileDataSource();
-
- bool Initialize(const FilePath& file_path);
-
- // Implementation of DataSource.
- virtual void set_host(DataSourceHost* host) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void Read(int64 position, int size, uint8* data,
- const DataSource::ReadCB& read_cb) OVERRIDE;
- virtual bool GetSize(int64* size_out) OVERRIDE;
- virtual bool IsStreaming() OVERRIDE;
- virtual void SetBitrate(int bitrate) OVERRIDE;
-
- // Unit test helpers. Recreate the object if you want the default behaviour.
- void force_read_errors_for_testing() { force_read_errors_ = true; }
- void force_streaming_for_testing() { force_streaming_ = true; }
-
- protected:
- virtual ~FileDataSource();
-
- private:
- // Informs the host of changes in total and buffered bytes.
- void UpdateHostBytes();
-
- file_util::MemoryMappedFile file_;
-
- bool force_read_errors_;
- bool force_streaming_;
-
- DISALLOW_COPY_AND_ASSIGN(FileDataSource);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_FILE_DATA_SOURCE_H_
diff --git a/src/media/filters/file_data_source_unittest.cc b/src/media/filters/file_data_source_unittest.cc
deleted file mode 100644
index d7eaad0..0000000
--- a/src/media/filters/file_data_source_unittest.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string>
-
-#include "base/base_paths.h"
-#include "base/bind.h"
-#include "base/file_path.h"
-#include "base/path_service.h"
-#include "base/utf_string_conversions.h"
-#include "media/base/mock_data_source_host.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/file_data_source.h"
-
-using ::testing::NiceMock;
-using ::testing::StrictMock;
-
-namespace media {
-
-class ReadCBHandler {
- public:
- ReadCBHandler() {}
-
- MOCK_METHOD1(ReadCB, void(int size));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ReadCBHandler);
-};
-
-// Returns a path to the test file which contains the string "0123456789"
-// without the quotes or any trailing space or null termination. The file lives
-// under the media/test/data directory. Under Windows, strings for the
-// FilePath class are unicode, and the pipeline wants char strings. Convert
-// the string to UTF8 under Windows. For Mac and Linux, file paths are already
-// chars so just return the string from the FilePath.
-FilePath TestFileURL() {
- FilePath data_dir;
- EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir));
- data_dir = data_dir.Append(FILE_PATH_LITERAL("media"))
- .Append(FILE_PATH_LITERAL("test"))
- .Append(FILE_PATH_LITERAL("data"))
- .Append(FILE_PATH_LITERAL("ten_byte_file"));
- return data_dir;
-}
-
-// Test that FileDataSource call the appropriate methods on its filter host.
-TEST(FileDataSourceTest, OpenFile) {
- StrictMock<MockDataSourceHost> host;
- EXPECT_CALL(host, SetTotalBytes(10));
- EXPECT_CALL(host, AddBufferedByteRange(0, 10));
-
- scoped_refptr<FileDataSource> filter(new FileDataSource());
- filter->set_host(&host);
- EXPECT_TRUE(filter->Initialize(TestFileURL()));
-
- filter->Stop(NewExpectedClosure());
-}
-
-// Use the mock filter host to directly call the Read and GetPosition methods.
-TEST(FileDataSourceTest, ReadData) {
- int64 size;
- uint8 ten_bytes[10];
-
- // Create our mock filter host and initialize the data source.
- NiceMock<MockDataSourceHost> host;
- scoped_refptr<FileDataSource> filter(new FileDataSource());
-
- filter->set_host(&host);
- EXPECT_TRUE(filter->Initialize(TestFileURL()));
-
- EXPECT_TRUE(filter->GetSize(&size));
- EXPECT_EQ(10, size);
-
- ReadCBHandler handler;
- EXPECT_CALL(handler, ReadCB(10));
- filter->Read(0, 10, ten_bytes, base::Bind(
- &ReadCBHandler::ReadCB, base::Unretained(&handler)));
- EXPECT_EQ('0', ten_bytes[0]);
- EXPECT_EQ('5', ten_bytes[5]);
- EXPECT_EQ('9', ten_bytes[9]);
-
- EXPECT_CALL(handler, ReadCB(1));
- filter->Read(9, 1, ten_bytes, base::Bind(
- &ReadCBHandler::ReadCB, base::Unretained(&handler)));
- EXPECT_EQ('9', ten_bytes[0]);
-
- EXPECT_CALL(handler, ReadCB(0));
- filter->Read(10, 10, ten_bytes, base::Bind(
- &ReadCBHandler::ReadCB, base::Unretained(&handler)));
-
- EXPECT_CALL(handler, ReadCB(5));
- filter->Read(5, 10, ten_bytes, base::Bind(
- &ReadCBHandler::ReadCB, base::Unretained(&handler)));
- EXPECT_EQ('5', ten_bytes[0]);
-
- filter->Stop(NewExpectedClosure());
-}
-
-} // namespace media
diff --git a/src/media/filters/h264_to_annex_b_bitstream_converter.cc b/src/media/filters/h264_to_annex_b_bitstream_converter.cc
deleted file mode 100644
index 8a830ea..0000000
--- a/src/media/filters/h264_to_annex_b_bitstream_converter.cc
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/h264_to_annex_b_bitstream_converter.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-static const uint8 kStartCodePrefix[3] = {0, 0, 1};
-
-// Helper function which determines whether NAL unit of given type marks
-// access unit boundary.
-static bool IsAccessUnitBoundaryNal(int nal_unit_type) {
- // Check if this packet marks access unit boundary by checking the
- // packet type.
- if (nal_unit_type == 6 || // Supplemental enhancement information
- nal_unit_type == 7 || // Picture parameter set
- nal_unit_type == 8 || // Sequence parameter set
- nal_unit_type == 9 || // Access unit delimiter
- (nal_unit_type >= 14 && nal_unit_type <= 18)) { // Reserved types
- return true;
- }
- return false;
-}
-
-H264ToAnnexBBitstreamConverter::H264ToAnnexBBitstreamConverter()
- : configuration_processed_(false),
- first_nal_unit_in_access_unit_(true),
- nal_unit_length_field_width_(0) {
-}
-
-H264ToAnnexBBitstreamConverter::~H264ToAnnexBBitstreamConverter() {}
-
-uint32 H264ToAnnexBBitstreamConverter::ParseConfigurationAndCalculateSize(
- const uint8* configuration_record,
- uint32 configuration_record_size) {
- // FFmpeg's AVCodecContext's extradata field contains the Decoder Specific
- // Information from MP4 headers that contain the H.264 SPS and PPS members.
- // ISO 14496-15 Chapter 5.2.4 AVCDecoderConfigurationRecord.
- // AVCConfigurationRecord must be at least 7 bytes long.
- if (configuration_record == NULL || configuration_record_size < 7) {
- return 0; // Error: invalid input
- }
- const uint8* decoder_configuration = configuration_record;
- uint32 parameter_set_size_bytes = 0;
-
- // We can skip the four first bytes as they're only profile information
- decoder_configuration += 4;
- // Fifth byte's two LSBs contain the interleaving field's size minus one
- uint8 size_of_len_field = (*decoder_configuration & 0x3) + 1;
- if (size_of_len_field != 1 && size_of_len_field != 2 &&
- size_of_len_field != 4) {
- return 0; // Error: invalid input, NAL unit field len is not correct
- }
- decoder_configuration++;
- // Sixth byte's five LSBs contain the number of SPSs
- uint8 sps_count = *decoder_configuration & 0x1F;
- decoder_configuration++;
- // Then we have N * SPS's with two byte length field and actual SPS
- while (sps_count-- > 0) {
- if ((decoder_configuration - configuration_record) + 2 >
- static_cast<int32>(configuration_record_size)) {
- return 0; // Error: ran out of data
- }
- uint16 sps_len = decoder_configuration[0] << 8 | decoder_configuration[1];
- decoder_configuration += 2;
- // write the SPS to output, always with zero byte + start code prefix
- parameter_set_size_bytes += 1 + sizeof(kStartCodePrefix);
- decoder_configuration += sps_len;
- parameter_set_size_bytes += sps_len;
- }
- // Then we have the numner of pps in one byte
- uint8 pps_count = *decoder_configuration;
- decoder_configuration++;
- // And finally, we have N * PPS with two byte length field and actual PPS
- while (pps_count-- > 0) {
- if ((decoder_configuration - configuration_record) + 2 >
- static_cast<int32>(configuration_record_size)) {
- return 0; // Error: ran out of data
- }
- uint16 pps_len = decoder_configuration[0] << 8 | decoder_configuration[1];
- decoder_configuration += 2;
- // write the SPS to output, always with zero byte + start code prefix
- parameter_set_size_bytes += 1 + sizeof(kStartCodePrefix);
- decoder_configuration += pps_len;
- parameter_set_size_bytes += pps_len;
- }
- // We're done processing the AVCDecoderConfigurationRecord,
- // store the needed information for parsing actual payload
- nal_unit_length_field_width_ = size_of_len_field;
- configuration_processed_ = true;
- return parameter_set_size_bytes;
-}
-
-uint32 H264ToAnnexBBitstreamConverter::CalculateNeededOutputBufferSize(
- const uint8* input,
- uint32 input_size) const {
- uint32 output_size = 0;
- uint32 data_left = input_size;
- bool first_nal_in_this_access_unit = first_nal_unit_in_access_unit_;
-
- if (input == NULL || input_size == 0) {
- return 0; // Error: invalid input data
- }
- if (!configuration_processed_) {
- return 0; // Error: configuration not handled, we don't know nal unit width
- }
- CHECK(nal_unit_length_field_width_ == 1 ||
- nal_unit_length_field_width_ == 2 ||
- nal_unit_length_field_width_ == 4);
-
- // Then add the needed size for the actual packet
- while (data_left > 0) {
- // Read the next NAL unit length from the input buffer
- uint8 size_of_len_field;
- uint32 nal_unit_length;
- for (nal_unit_length = 0, size_of_len_field = nal_unit_length_field_width_;
- size_of_len_field > 0;
- input++, size_of_len_field--, data_left--) {
- nal_unit_length <<= 8;
- nal_unit_length |= *input;
- }
-
- if (nal_unit_length == 0) {
- break; // Signifies that no more data left in the buffer
- } else if (nal_unit_length > data_left) {
- return 0; // Error: Not enough data for correct conversion
- }
- data_left -= nal_unit_length;
-
- // five least significant bits of first NAL unit byte signify nal_unit_type
- int nal_unit_type = *input & 0x1F;
- if (first_nal_in_this_access_unit ||
- IsAccessUnitBoundaryNal(nal_unit_type)) {
- output_size += 1; // Extra zero_byte for these nal units
- first_nal_in_this_access_unit = false;
- }
- // Start code prefix
- output_size += sizeof(kStartCodePrefix);
- // Actual NAL unit size
- output_size += nal_unit_length;
- input += nal_unit_length;
- // No need for trailing zero bits
- }
- return output_size;
-}
-
-bool H264ToAnnexBBitstreamConverter::ConvertAVCDecoderConfigToByteStream(
- const uint8* input,
- uint32 input_size,
- uint8* output,
- uint32* output_size) {
- uint8* outscan = output;
- // FFmpeg's AVCodecContext's extradata field contains the Decoder Specific
- // Information from MP4 headers that contain the H.264 SPS and PPS members.
- // ISO 14496-15 Chapter 5.2.4 AVCDecoderConfigurationRecord.
- const uint8* decoder_configuration = input;
- uint32 decoderconfiguration_size = input_size;
- uint32 out_size = 0;
-
- if (decoder_configuration == NULL || decoderconfiguration_size == 0) {
- return 0; // Error: input invalid
- }
-
- // We can skip the four first bytes as they're only profile information.
- decoder_configuration += 4;
- // Fifth byte's two LSBs contain the interleaving field's size minus one
- uint8 size_of_len_field = (*decoder_configuration & 0x3) + 1;
- if (size_of_len_field != 1 && size_of_len_field != 2 &&
- size_of_len_field != 4) {
- return 0; // Error: invalid input, NAL unit field len is not correct
- }
- decoder_configuration++;
- // Sixth byte's five LSBs contain the number of SPSs
- uint8 sps_count = *decoder_configuration & 0x1F;
- decoder_configuration++;
- // Then we have N * SPS's with two byte length field and actual SPS
- while (sps_count-- > 0) {
- uint16 sps_len = decoder_configuration[0] << 8 |
- decoder_configuration[1];
- decoder_configuration += 2;
- if (out_size + 1 + sizeof(kStartCodePrefix) + sps_len >
- *output_size) {
- *output_size = 0;
- return 0; // too small output buffer;
- }
- // write the SPS to output, always with zero byte + start code prefix
- *outscan = 0; // zero byte
- outscan += 1;
- memcpy(outscan, kStartCodePrefix, sizeof(kStartCodePrefix));
- outscan += sizeof(kStartCodePrefix);
- memcpy(outscan, decoder_configuration, sps_len);
- decoder_configuration += sps_len;
- outscan += sps_len;
- out_size += 1 + sizeof(kStartCodePrefix) + sps_len;
- }
- // Then we have the numner of pps in one byte
- uint8 pps_count = *decoder_configuration;
- decoder_configuration++;
- // And finally, we have N * PPS with two byte length field and actual PPS
- while (pps_count-- > 0) {
- uint16 pps_len = decoder_configuration[0] << 8 | decoder_configuration[1];
- decoder_configuration += 2;
- if (out_size + 1 + sizeof(kStartCodePrefix) + pps_len >
- *output_size) {
- *output_size = 0;
- return 0; // too small output buffer;
- }
- // write the SPS to output, always with zero byte + start code prefix
- *outscan = 0; // zero byte
- outscan += 1;
- memcpy(outscan, kStartCodePrefix, sizeof(kStartCodePrefix));
- outscan += sizeof(kStartCodePrefix);
- memcpy(outscan, decoder_configuration, pps_len);
- decoder_configuration += pps_len;
- outscan += pps_len;
- out_size += 1 + sizeof(kStartCodePrefix) + pps_len;
- }
- // We're done processing the AVCDecoderConfigurationRecord, store the needed
- // information
- nal_unit_length_field_width_ = size_of_len_field;
- configuration_processed_ = true;
- *output_size = out_size;
- return true;
-}
-
-bool H264ToAnnexBBitstreamConverter::ConvertNalUnitStreamToByteStream(
- const uint8* input, uint32 input_size,
- uint8* output, uint32* output_size) {
- const uint8* inscan = input; // We read the input from here progressively
- uint8* outscan = output; // We write the output to here progressively
- uint32 data_left = input_size;
-
- if (inscan == NULL || input_size == 0 ||
- outscan == NULL || *output_size == 0) {
- *output_size = 0;
- return false; // Error: invalid input
- }
-
- // NAL unit width should be known at this point
- CHECK(nal_unit_length_field_width_ == 1 ||
- nal_unit_length_field_width_ == 2 ||
- nal_unit_length_field_width_ == 4);
-
- // Do the actual conversion for the actual input packet
- while (data_left > 0) {
- uint8 i;
- uint32 nal_unit_length;
-
- // Read the next NAL unit length from the input buffer by scanning
- // the input stream with the specific length field width
- for (nal_unit_length = 0, i = nal_unit_length_field_width_;
- i > 0 && data_left > 0;
- inscan++, i--, data_left--) {
- nal_unit_length <<= 8;
- nal_unit_length |= *inscan;
- }
-
- if (nal_unit_length == 0) {
- break; // Successful conversion, end of buffer
- } else if (nal_unit_length > data_left) {
- *output_size = 0;
- return false; // Error: not enough data for correct conversion
- }
-
- uint32 start_code_len;
- first_nal_unit_in_access_unit_ ?
- start_code_len = sizeof(kStartCodePrefix) + 1 :
- start_code_len = sizeof(kStartCodePrefix);
- if (static_cast<uint32>(outscan - output) +
- start_code_len + nal_unit_length > *output_size) {
- *output_size = 0;
- return false; // Error: too small output buffer
- }
-
- // Five least significant bits of first NAL unit byte signify
- // nal_unit_type.
- int nal_unit_type = *inscan & 0x1F;
-
- // Check if this packet marks access unit boundary by checking the
- // packet type.
- if (IsAccessUnitBoundaryNal(nal_unit_type)) {
- first_nal_unit_in_access_unit_ = true;
- }
-
- // Write extra zero-byte before start code prefix if this packet
- // signals next access unit.
- if (first_nal_unit_in_access_unit_) {
- *outscan = 0;
- outscan++;
- first_nal_unit_in_access_unit_ = false;
- }
-
- // No need to write leading zero bits.
- // Write start-code prefix.
- memcpy(outscan, kStartCodePrefix, sizeof(kStartCodePrefix));
- outscan += sizeof(kStartCodePrefix);
- // Then write the actual NAL unit from the input buffer.
- memcpy(outscan, inscan, nal_unit_length);
- inscan += nal_unit_length;
- data_left -= nal_unit_length;
- outscan += nal_unit_length;
- // No need for trailing zero bits.
- }
- // Successful conversion, output the freshly allocated bitstream buffer.
- *output_size = static_cast<uint32>(outscan - output);
- return true;
-}
-
-} // namespace media
diff --git a/src/media/filters/h264_to_annex_b_bitstream_converter.h b/src/media/filters/h264_to_annex_b_bitstream_converter.h
deleted file mode 100644
index afb204a..0000000
--- a/src/media/filters/h264_to_annex_b_bitstream_converter.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
-#define MEDIA_FILTERS_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// H264ToAnnexBBitstreamConverter is a class to convert H.264 bitstream from
-// MP4 format (as specified in ISO/IEC 14496-15) into H.264 bytestream
-// (as specified in ISO/IEC 14496-10 Annex B).
-class MEDIA_EXPORT H264ToAnnexBBitstreamConverter {
- public:
- H264ToAnnexBBitstreamConverter();
- ~H264ToAnnexBBitstreamConverter();
-
- // Parses the global AVCDecoderConfigurationRecord from the file format's
- // headers. Converter will remember the field length from the configuration
- // headers after this.
- //
- // Parameters
- // configuration_record
- // Pointer to buffer containing AVCDecoderConfigurationRecord.
- // configuration_record_size
- // Size of the buffer in bytes.
- //
- // Returns
- // Required buffer size for AVCDecoderConfigurationRecord when converted
- // to bytestream format, or 0 if could not determine the configuration
- // from the input buffer.
- uint32 ParseConfigurationAndCalculateSize(const uint8* configuration_record,
- uint32 configuration_record_size);
-
- // Calculates needed buffer size for the bitstream converted into bytestream.
- // Lightweight implementation that does not do the actual conversion.
- //
- // Parameters
- // configuration_record
- // Pointer to buffer containing AVCDecoderConfigurationRecord.
- // configuration_record_size
- // Size of the buffer in bytes.
- //
- // Returns
- // Required buffer size for the input NAL unit buffer when converted
- // to bytestream format, or 0 if could not determine the configuration
- // from the input buffer.
- uint32 CalculateNeededOutputBufferSize(const uint8* input,
- uint32 input_size) const;
-
- // ConvertAVCDecoderConfigToByteStream converts the
- // AVCDecoderConfigurationRecord from the MP4 headers to bytestream format.
- // Client is responsible for making sure the output buffer is large enough
- // to hold the output data. Client can precalculate the needed output buffer
- // size by using ParseConfigurationAndCalculateSize.
- //
- // In case of failed conversion object H264BitstreamConverter may have written
- // some bytes to buffer pointed by pinput but user should ignore those bytes.
- // None of the outputs should be considered valid.
- //
- // Parameters
- // pinput
- // Pointer to buffer containing AVCDecoderConfigurationRecord.
- // input_size
- // Size of the buffer in bytes.
- // poutput
- // Pointer to buffer where the output should be written to.
- // poutput_size (i/o)
- // Pointer to the size of the output buffer. Will contain the number of
- // bytes written to output after successful call.
- //
- // Returns
- // true if successful conversion
- // false if conversion not successful (poutput_size will hold the amount
- // of converted data)
- bool ConvertAVCDecoderConfigToByteStream(const uint8* input,
- uint32 input_size,
- uint8* output,
- uint32* output_size);
-
- // ConvertNalUnitStreamToByteStream converts the NAL unit from MP4 format
- // to bytestream format. Client is responsible for making sure the output
- // buffer is large enough to hold the output data. Client can precalculate the
- // needed output buffer size by using CalculateNeededOutputBufferSize.
- //
- // In case of failed conversion object H264BitstreamConverter may have written
- // some bytes to buffer pointed by pinput but user should ignore those bytes.
- // None of the outputs should be considered valid.
- //
- // Parameters
- // pinput
- // Pointer to buffer containing AVCDecoderConfigurationRecord.
- // input_size
- // Size of the buffer in bytes.
- // poutput
- // Pointer to buffer where the output should be written to.
- // poutput_size (i/o)
- // Pointer to the size of the output buffer. Will contain the number of
- // bytes written to output after successful call.
- //
- // Returns
- // true if successful conversion
- // false if conversion not successful (poutput_size will hold the amount
- // of converted data)
- bool ConvertNalUnitStreamToByteStream(const uint8* input, uint32 input_size,
- uint8* output, uint32* output_size);
-
- private:
- // Flag for indicating whether global parameter sets have been processed.
- bool configuration_processed_;
- // Flag for indicating whether next NAL unit starts new access unit.
- bool first_nal_unit_in_access_unit_;
- // Variable to hold interleaving field's length in bytes.
- uint8 nal_unit_length_field_width_;
-
- DISALLOW_COPY_AND_ASSIGN(H264ToAnnexBBitstreamConverter);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_H264_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
-
diff --git a/src/media/filters/h264_to_annex_b_bitstream_converter_unittest.cc b/src/media/filters/h264_to_annex_b_bitstream_converter_unittest.cc
deleted file mode 100644
index 0236e77..0000000
--- a/src/media/filters/h264_to_annex_b_bitstream_converter_unittest.cc
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/memory/scoped_ptr.h"
-#include "media/filters/h264_to_annex_b_bitstream_converter.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class H264ToAnnexBBitstreamConverterTest : public testing::Test {
- protected:
- H264ToAnnexBBitstreamConverterTest() {}
-
- virtual ~H264ToAnnexBBitstreamConverterTest() {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(H264ToAnnexBBitstreamConverterTest);
-};
-
-static const uint8 kHeaderDataOkWithFieldLen4[] = {
- 0x01, 0x42, 0x00, 0x28, 0xFF, 0xE1, 0x00, 0x08, 0x67, 0x42, 0x00, 0x28,
- 0xE9, 0x05, 0x89, 0xC8, 0x01, 0x00, 0x04, 0x68, 0xCE, 0x06, 0xF2, 0x00
-};
-
-static const uint8 kPacketDataOkWithFieldLen4[] = {
- 0x00, 0x00, 0x0B, 0xF7, 0x65, 0xB8, 0x40, 0x57, 0x0B, 0xF0,
- 0xDF, 0xF8, 0x00, 0x1F, 0x78, 0x98, 0x54, 0xAC, 0xF2, 0x00, 0x04, 0x9D, 0x26,
- 0xE0, 0x3B, 0x5C, 0x00, 0x0A, 0x00, 0x8F, 0x9E, 0x86, 0x63, 0x1B, 0x46, 0xE7,
- 0xD6, 0x45, 0x88, 0x88, 0xEA, 0x10, 0x89, 0x79, 0x01, 0x34, 0x30, 0x01, 0x8E,
- 0x7D, 0x1A, 0x39, 0x45, 0x4E, 0x69, 0x86, 0x12, 0xF2, 0xE7, 0xCF, 0x50, 0xF8,
- 0x26, 0x54, 0x17, 0xBE, 0x3F, 0xC4, 0x80, 0x32, 0xD8, 0x02, 0x32, 0xE4, 0xAE,
- 0xDD, 0x39, 0x11, 0x8E, 0x54, 0x42, 0xAE, 0xBD, 0x12, 0xA4, 0xCE, 0xE2, 0x98,
- 0x91, 0x05, 0xC4, 0xA8, 0x20, 0xC7, 0xB3, 0xD9, 0x47, 0x73, 0x09, 0xD5, 0xCF,
- 0x62, 0x57, 0x3F, 0xFF, 0xFD, 0xB9, 0x94, 0x2B, 0x3D, 0x12, 0x1A, 0x84, 0x0B,
- 0x28, 0xAD, 0x5C, 0x9E, 0x5C, 0xC3, 0xBB, 0xBD, 0x7F, 0xFE, 0x09, 0x87, 0x74,
- 0x39, 0x1C, 0xA5, 0x0E, 0x44, 0xD8, 0x5D, 0x41, 0xDB, 0xAA, 0xBC, 0x05, 0x16,
- 0xA3, 0x98, 0xEE, 0xEE, 0x9C, 0xA0, 0xF1, 0x23, 0x90, 0xF0, 0x5E, 0x9F, 0xF4,
- 0xFA, 0x7F, 0x4B, 0x69, 0x66, 0x49, 0x52, 0xDD, 0xD6, 0xC0, 0x0F, 0x8C, 0x6E,
- 0x80, 0xDD, 0x7A, 0xDF, 0x10, 0xCD, 0x4B, 0x54, 0x6F, 0xFC, 0x7D, 0x34, 0xBA,
- 0x8B, 0xD4, 0xD9, 0x30, 0x18, 0x9F, 0x39, 0x04, 0x9F, 0xCB, 0xDB, 0x1B, 0xA7,
- 0x70, 0x96, 0xAF, 0xFF, 0x6F, 0xB5, 0xBF, 0x58, 0x01, 0x98, 0xCD, 0xF2, 0x66,
- 0x28, 0x1A, 0xC4, 0x9E, 0x58, 0x40, 0x39, 0xAE, 0x07, 0x11, 0x3F, 0xF2, 0x9B,
- 0x06, 0x9C, 0xB8, 0xC9, 0x16, 0x12, 0x09, 0x8E, 0xD2, 0xD4, 0xF5, 0xC6, 0x77,
- 0x40, 0x0F, 0xFD, 0x12, 0x19, 0x55, 0x1A, 0x8E, 0x9C, 0x18, 0x8B, 0x0D, 0x18,
- 0xFA, 0xBA, 0x7F, 0xBB, 0x83, 0xBB, 0x85, 0xA0, 0xCC, 0xAF, 0xF6, 0xEA, 0x81,
- 0x10, 0x18, 0x8E, 0x10, 0x00, 0xCB, 0x7F, 0x27, 0x08, 0x06, 0xDE, 0x3C, 0x20,
- 0xE5, 0xFE, 0xCC, 0x4F, 0xB3, 0x41, 0xE0, 0xCC, 0x4C, 0x26, 0xC1, 0xC0, 0x2C,
- 0x16, 0x12, 0xAA, 0x04, 0x83, 0x51, 0x4E, 0xCA, 0x00, 0xCF, 0x42, 0x9C, 0x06,
- 0x2D, 0x06, 0xDD, 0x1D, 0x08, 0x75, 0xE0, 0x89, 0xC7, 0x62, 0x68, 0x2E, 0xBF,
- 0x4D, 0x2D, 0x0A, 0xC4, 0x86, 0xF6, 0x2F, 0xA1, 0x49, 0xA7, 0x0F, 0xDB, 0x1F,
- 0x82, 0xEC, 0xC1, 0x62, 0xFB, 0x7F, 0xF1, 0xAE, 0xA6, 0x1A, 0xD5, 0x6B, 0x06,
- 0x5E, 0xB6, 0x02, 0x50, 0xAE, 0x2D, 0xF9, 0xD9, 0x95, 0xAD, 0x01, 0x8C, 0x53,
- 0x01, 0xAF, 0xCE, 0xE5, 0xA5, 0xBB, 0x95, 0x8A, 0x85, 0x70, 0x77, 0xE3, 0x9A,
- 0x68, 0x1B, 0xDF, 0x47, 0xF9, 0xF4, 0xBD, 0x80, 0x7D, 0x76, 0x9A, 0x69, 0xFC,
- 0xBE, 0x14, 0x0D, 0x87, 0x09, 0x12, 0x98, 0x20, 0x05, 0x46, 0xB7, 0xAE, 0x10,
- 0xB7, 0x01, 0xB7, 0xDE, 0x3B, 0xDD, 0x7A, 0x8A, 0x55, 0x73, 0xAD, 0xDF, 0x69,
- 0xDE, 0xD0, 0x51, 0x97, 0xA0, 0xE6, 0x5E, 0xBA, 0xBA, 0x80, 0x0F, 0x4E, 0x9A,
- 0x68, 0x36, 0xE6, 0x9F, 0x5B, 0x39, 0xC0, 0x90, 0xA1, 0xC0, 0xC3, 0x82, 0xE4,
- 0x50, 0xEA, 0x60, 0x7A, 0xDD, 0x5F, 0x8B, 0x5F, 0xAF, 0xFC, 0x74, 0xAF, 0xDC,
- 0x56, 0xF7, 0x2E, 0x3E, 0x97, 0x6E, 0x2B, 0xF3, 0xAF, 0xFE, 0x7D, 0x32, 0xDC,
- 0x56, 0xF8, 0xAF, 0xB5, 0xA3, 0xBB, 0x00, 0x5B, 0x84, 0x3D, 0x9F, 0x0B, 0x40,
- 0x88, 0x61, 0x5F, 0x4F, 0x4F, 0xB0, 0xB3, 0x07, 0x81, 0x3E, 0xF2, 0xFB, 0x50,
- 0xCA, 0x77, 0x40, 0x12, 0xA8, 0xE6, 0x11, 0x8E, 0xD6, 0x8A, 0xC6, 0xD6, 0x8C,
- 0x1D, 0x63, 0x55, 0x3D, 0x34, 0xEA, 0xC3, 0xC6, 0x6A, 0xD2, 0x8C, 0xB0, 0x1D,
- 0x5E, 0x4A, 0x7A, 0x8B, 0xD5, 0x99, 0x80, 0x84, 0x32, 0xFB, 0xB7, 0x02, 0x6E,
- 0x61, 0xFE, 0xAC, 0x1B, 0x5D, 0x10, 0x23, 0x24, 0xC3, 0x8C, 0x7B, 0x58, 0x2C,
- 0x4D, 0x04, 0x74, 0x84, 0x25, 0x10, 0x4E, 0x94, 0x29, 0x4D, 0x88, 0xAE, 0x65,
- 0x53, 0xB9, 0x95, 0x4E, 0xE7, 0xDD, 0xEE, 0xF2, 0x70, 0x1F, 0x26, 0x4F, 0xA8,
- 0xBC, 0x3D, 0x35, 0x02, 0x3B, 0xC0, 0x98, 0x70, 0x38, 0x18, 0xE5, 0x1E, 0x05,
- 0xAC, 0x28, 0xAA, 0x46, 0x1A, 0xB0, 0x19, 0x99, 0x18, 0x35, 0x78, 0x1E, 0x41,
- 0x60, 0x0D, 0x4F, 0x7E, 0xEC, 0x37, 0xC3, 0x30, 0x73, 0x2A, 0x69, 0xFE, 0xEF,
- 0x27, 0xEE, 0x13, 0xCC, 0xD0, 0xDB, 0xE6, 0x45, 0xEC, 0x5C, 0xB5, 0x71, 0x54,
- 0x2E, 0xB1, 0xE9, 0x88, 0xB4, 0x3F, 0x6F, 0xFD, 0xF7, 0xFF, 0x9D, 0x2D, 0x52,
- 0x2E, 0xAE, 0xC9, 0x95, 0xDE, 0xBF, 0xDF, 0xFF, 0xBF, 0x21, 0xB3, 0x2B, 0xF5,
- 0xF7, 0xF7, 0xD1, 0xA0, 0xF0, 0x76, 0x68, 0x37, 0xDB, 0x8F, 0x85, 0x4D, 0xA8,
- 0x1A, 0xF9, 0x7F, 0x75, 0xA7, 0x93, 0xF5, 0x03, 0xC1, 0xF2, 0x60, 0x8A, 0x92,
- 0x53, 0xF5, 0xD1, 0xC1, 0x56, 0x4B, 0x68, 0x05, 0x16, 0x88, 0x61, 0xE7, 0x14,
- 0xC8, 0x0D, 0xF0, 0xDF, 0xEF, 0x46, 0x4A, 0xED, 0x0B, 0xD1, 0xD1, 0xD1, 0xA4,
- 0x85, 0xA3, 0x2C, 0x1D, 0xDE, 0x45, 0x14, 0xA1, 0x8E, 0xA8, 0xD9, 0x8C, 0xAB,
- 0x47, 0x31, 0xF1, 0x00, 0x15, 0xAD, 0x80, 0x20, 0xAA, 0xE4, 0x57, 0xF8, 0x05,
- 0x14, 0x58, 0x0B, 0xD3, 0x63, 0x00, 0x8F, 0x44, 0x15, 0x7F, 0x19, 0xC7, 0x0A,
- 0xE0, 0x49, 0x32, 0xFE, 0x36, 0x0E, 0xF3, 0x66, 0x10, 0x2B, 0x11, 0x73, 0x3D,
- 0x19, 0x92, 0x22, 0x20, 0x75, 0x1F, 0xF1, 0xDB, 0x96, 0x73, 0xCF, 0x1B, 0x53,
- 0xFF, 0xD2, 0x23, 0xF2, 0xB6, 0xAA, 0xB6, 0x44, 0xA3, 0x73, 0x7E, 0x00, 0x2D,
- 0x4D, 0x4D, 0x87, 0xE0, 0x84, 0x55, 0xD6, 0x03, 0xB8, 0xD8, 0x90, 0xEF, 0xC0,
- 0x76, 0x5D, 0x69, 0x02, 0x00, 0x0E, 0x17, 0xD0, 0x02, 0x96, 0x50, 0xEA, 0xAB,
- 0xBF, 0x0D, 0xAF, 0xCB, 0xD3, 0xFF, 0xAA, 0x9D, 0x7F, 0xD6, 0xBD, 0x2C, 0x14,
- 0xB4, 0xCD, 0x20, 0x73, 0xB4, 0xF4, 0x38, 0x96, 0xDE, 0xB0, 0x6B, 0xE5, 0x1B,
- 0xFD, 0x0E, 0x0B, 0xA4, 0x81, 0xBF, 0xC8, 0xA0, 0x21, 0x76, 0x7B, 0x25, 0x3F,
- 0xE6, 0x84, 0x40, 0x1A, 0xDA, 0x25, 0x5A, 0xFF, 0x73, 0x6B, 0x14, 0x1B, 0xF7,
- 0x08, 0xFA, 0x26, 0x73, 0x7A, 0x58, 0x02, 0x1A, 0xE6, 0x63, 0xB6, 0x45, 0x7B,
- 0xE3, 0xE0, 0x80, 0x14, 0x42, 0xA8, 0x7D, 0xF3, 0x80, 0x9B, 0x01, 0x43, 0x82,
- 0x82, 0x8C, 0xBE, 0x0D, 0xFD, 0xAE, 0x88, 0xA8, 0xB9, 0xC3, 0xEE, 0xFF, 0x46,
- 0x00, 0x84, 0xE6, 0xB4, 0x0C, 0xA9, 0x66, 0xC6, 0x74, 0x72, 0xAA, 0xA4, 0x3A,
- 0xB0, 0x1B, 0x06, 0xB4, 0xDB, 0xE8, 0xC2, 0x17, 0xA2, 0xBC, 0xBE, 0x5C, 0x0F,
- 0x2A, 0x76, 0xD5, 0xEE, 0x39, 0x36, 0x7C, 0x25, 0x94, 0x15, 0x3C, 0xC9, 0xB9,
- 0x93, 0x07, 0x19, 0xAF, 0xE6, 0x70, 0xC3, 0xF5, 0xD4, 0x17, 0x87, 0x57, 0x77,
- 0x7D, 0xCF, 0x0D, 0xDD, 0xDE, 0xB7, 0xFF, 0xB4, 0xDA, 0x20, 0x45, 0x1A, 0x45,
- 0xF4, 0x58, 0x01, 0xBC, 0xEB, 0x3F, 0x16, 0x7F, 0x4C, 0x15, 0x84, 0x8C, 0xE5,
- 0xF6, 0x96, 0xA6, 0xA1, 0xB9, 0xB2, 0x7F, 0x6B, 0xFF, 0x31, 0xF2, 0xF5, 0xC9,
- 0xFF, 0x61, 0xEE, 0xB5, 0x84, 0xAE, 0x68, 0x41, 0xEA, 0xD0, 0xF0, 0xA5, 0xCE,
- 0x0C, 0xE6, 0x4C, 0x6D, 0x6D, 0x94, 0x08, 0xC9, 0xA9, 0x4A, 0x60, 0x6D, 0x01,
- 0x3B, 0xEF, 0x4D, 0x99, 0x8D, 0x42, 0x2A, 0x6B, 0x8A, 0xC7, 0xFA, 0xA9, 0x90,
- 0x40, 0x00, 0x90, 0xF3, 0xA0, 0x75, 0x8E, 0xD5, 0xFE, 0xE7, 0xBD, 0x02, 0x87,
- 0x0C, 0x7D, 0xF0, 0xAF, 0x1E, 0x5F, 0x8D, 0xC8, 0xE1, 0xD4, 0x56, 0x08, 0xBF,
- 0x76, 0x80, 0xD4, 0x18, 0x89, 0x2D, 0x57, 0xDF, 0x66, 0xD0, 0x46, 0x68, 0x77,
- 0x55, 0x47, 0xF5, 0x7C, 0xF7, 0xA6, 0x66, 0xD6, 0x5A, 0x64, 0x55, 0xD4, 0x80,
- 0xC4, 0x55, 0xE9, 0x36, 0x3F, 0x5E, 0xE2, 0x5C, 0x7F, 0x5F, 0xCE, 0x7F, 0xE1,
- 0x0C, 0x82, 0x3D, 0x6B, 0x6E, 0xA2, 0xEA, 0x3B, 0x1F, 0xE8, 0x9E, 0xC7, 0x4E,
- 0x24, 0x3D, 0xDD, 0xFA, 0xEB, 0x71, 0xDF, 0xFE, 0x15, 0xFE, 0x41, 0x9B, 0xB4,
- 0x4E, 0xAB, 0x51, 0xE5, 0x1F, 0x7D, 0x2D, 0xAC, 0xD0, 0x66, 0xD9, 0xA1, 0x59,
- 0x78, 0xC6, 0xEF, 0xC4, 0x43, 0x08, 0x65, 0x18, 0x73, 0xDE, 0x2A, 0xAD, 0x72,
- 0xE7, 0x5A, 0x7E, 0x33, 0x04, 0x72, 0x38, 0x57, 0x47, 0x73, 0x10, 0x1D, 0x88,
- 0x57, 0x4C, 0xDF, 0xA7, 0x78, 0x16, 0xFB, 0x01, 0x21, 0x28, 0x2D, 0xB6, 0x7E,
- 0x05, 0x18, 0x32, 0x52, 0xC3, 0x49, 0x0B, 0x32, 0x18, 0x12, 0x93, 0x54, 0x15,
- 0x3B, 0xC8, 0x6D, 0x4A, 0x77, 0xEF, 0x0A, 0x46, 0x83, 0x89, 0x5C, 0x8B, 0xCB,
- 0x18, 0xA6, 0xDC, 0x97, 0x6F, 0xEE, 0xEE, 0x00, 0x6A, 0xF1, 0x10, 0xFE, 0x07,
- 0x0C, 0xE0, 0x53, 0xD2, 0xB8, 0x45, 0xF4, 0x6E, 0x16, 0x4B, 0xC9, 0x9C, 0xC7,
- 0x93, 0x83, 0x23, 0x1D, 0x4D, 0x00, 0xB9, 0x4F, 0x86, 0x51, 0xF0, 0x29, 0x69,
- 0x41, 0x21, 0xC5, 0x4A, 0xC6, 0x6D, 0xD1, 0x81, 0x38, 0xDB, 0x7C, 0x06, 0xA8,
- 0x26, 0x8E, 0x71, 0x00, 0x4C, 0x44, 0x14, 0x05, 0xF2, 0x1C, 0x00, 0x49, 0xFC,
- 0x29, 0x6A, 0xF9, 0x9E, 0xD1, 0x35, 0x4B, 0xB7, 0xE5, 0xDB, 0xFC, 0x01, 0x04,
- 0x3F, 0x70, 0x33, 0x56, 0x87, 0x69, 0x01, 0xB4, 0xCE, 0x1C, 0x4D, 0x2E, 0x83,
- 0x51, 0x51, 0xD0, 0x37, 0x3B, 0xB4, 0xBA, 0x47, 0xF5, 0xFF, 0xBF, 0xFA, 0xD5,
- 0x03, 0x65, 0xD3, 0x28, 0x9F, 0x38, 0x57, 0xFE, 0x71, 0xD8, 0x9C, 0x16, 0xEE,
- 0x72, 0x19, 0x03, 0x17, 0x6E, 0xC0, 0xEC, 0x49, 0x3D, 0x96, 0xE2, 0x30, 0x97,
- 0x97, 0x84, 0x38, 0x6B, 0xE8, 0x2E, 0xAB, 0x0E, 0x2E, 0x03, 0x52, 0xBA, 0x68,
- 0x55, 0xBA, 0x1D, 0x2C, 0x47, 0xAA, 0x72, 0xAE, 0x02, 0x31, 0x6E, 0xA1, 0xDC,
- 0xAD, 0x0F, 0x4A, 0x46, 0xC9, 0xF2, 0xA9, 0xAB, 0xFD, 0x87, 0x89, 0x5C, 0xB3,
- 0x75, 0x7E, 0xE3, 0xDE, 0x9F, 0xC4, 0x02, 0x1E, 0xA2, 0xF8, 0x8B, 0xD3, 0x00,
- 0x83, 0x96, 0xC4, 0xD0, 0xB9, 0x62, 0xB9, 0x69, 0xEC, 0x56, 0xDF, 0x7D, 0x91,
- 0x4B, 0x68, 0x27, 0xA8, 0x61, 0x78, 0xA7, 0x95, 0x66, 0x51, 0x41, 0xF6, 0xCE,
- 0x78, 0xD3, 0x9A, 0x91, 0xA0, 0x31, 0x09, 0x47, 0xB8, 0x47, 0xB8, 0x44, 0xE1,
- 0x13, 0x86, 0x7E, 0x92, 0x80, 0xC6, 0x1A, 0xF7, 0x79, 0x7E, 0xF1, 0x5D, 0x9F,
- 0x17, 0x2D, 0x80, 0x00, 0x79, 0x34, 0x7D, 0xE3, 0xAD, 0x60, 0x00, 0x20, 0x07,
- 0x80, 0x00, 0x40, 0x01, 0xF8, 0xA1, 0x86, 0xB1, 0xEE, 0x21, 0x63, 0x85, 0x60,
- 0x51, 0x84, 0x90, 0x7E, 0x92, 0x09, 0x39, 0x1C, 0x16, 0x87, 0x5C, 0xA6, 0x09,
- 0x90, 0x06, 0x34, 0x6E, 0xB8, 0x8D, 0x5D, 0xAC, 0x77, 0x97, 0xB5, 0x4D, 0x30,
- 0xFD, 0x39, 0xD0, 0x50, 0x00, 0xC9, 0x98, 0x04, 0x86, 0x00, 0x0D, 0xD8, 0x3E,
- 0x34, 0xC2, 0xA6, 0x25, 0xF8, 0x20, 0xCC, 0x6D, 0x9E, 0x63, 0x05, 0x30, 0xC4,
- 0xC6, 0xCC, 0x54, 0x31, 0x9F, 0x3C, 0xF5, 0x86, 0xB9, 0x08, 0x18, 0xC3, 0x1E,
- 0xB9, 0xA0, 0x0C, 0x45, 0x2C, 0x54, 0x32, 0x8B, 0x85, 0x86, 0x59, 0xC3, 0xB3,
- 0x50, 0x5A, 0xFE, 0xBA, 0xF7, 0x4D, 0xC9, 0x9C, 0x9E, 0x01, 0xDF, 0xD7, 0x6E,
- 0xB5, 0x15, 0x53, 0x08, 0x57, 0xA4, 0x71, 0x36, 0x80, 0x46, 0x05, 0x21, 0x48,
- 0x7B, 0x91, 0xC8, 0xAA, 0xFF, 0x07, 0x9F, 0x78, 0x68, 0xCF, 0x3C, 0xEF, 0xFF,
- 0xBC, 0xB6, 0xA2, 0x36, 0xB7, 0x9F, 0x54, 0xF6, 0x6F, 0x5D, 0xDD, 0x75, 0xD4,
- 0x3C, 0x75, 0xE8, 0xCF, 0x15, 0x02, 0x5B, 0x94, 0xC3, 0xA2, 0x41, 0x63, 0xA1,
- 0x14, 0xF6, 0xC0, 0x57, 0x15, 0x9F, 0x0C, 0x3F, 0x80, 0xF2, 0x98, 0xEE, 0x41,
- 0x85, 0xEE, 0xBC, 0xAA, 0xE9, 0x59, 0xAA, 0xA0, 0x92, 0xCA, 0x00, 0xF3, 0x50,
- 0xCC, 0xFF, 0xAD, 0x97, 0x69, 0xA7, 0xF2, 0x0B, 0x8F, 0xD7, 0xD7, 0x82, 0x3A,
- 0xBB, 0x98, 0x1D, 0xCB, 0x89, 0x0B, 0x9B, 0x05, 0xF7, 0xD0, 0x1A, 0x60, 0xF3,
- 0x29, 0x16, 0x12, 0xF8, 0xF4, 0xF1, 0x4A, 0x05, 0x9B, 0x57, 0x12, 0x7E, 0x3A,
- 0x4A, 0x8D, 0xA6, 0xDF, 0xB6, 0xDD, 0xDF, 0xC3, 0xF0, 0xD2, 0xD4, 0xD7, 0x41,
- 0xA6, 0x00, 0x76, 0x8C, 0x75, 0x08, 0xF0, 0x19, 0xD8, 0x14, 0x63, 0x55, 0x52,
- 0x18, 0x30, 0x98, 0xD0, 0x3F, 0x65, 0x52, 0xB3, 0x88, 0x6D, 0x17, 0x39, 0x93,
- 0xCA, 0x3B, 0xB4, 0x1D, 0x8D, 0xDF, 0xDF, 0xAD, 0x72, 0xDA, 0x74, 0xAF, 0xBD,
- 0x31, 0xF9, 0x12, 0x61, 0x45, 0x29, 0x4C, 0x2B, 0x61, 0xA1, 0x12, 0x90, 0x53,
- 0xE7, 0x5A, 0x9D, 0x44, 0xC8, 0x3A, 0x83, 0xDC, 0x34, 0x4C, 0x07, 0xAF, 0xDB,
- 0x90, 0xCD, 0x03, 0xA4, 0x64, 0x78, 0xBD, 0x55, 0xB2, 0x56, 0x59, 0x32, 0xAB,
- 0x13, 0x2C, 0xC9, 0x77, 0xF8, 0x3B, 0xDF, 0xFF, 0xAC, 0x07, 0xB9, 0x08, 0x7B,
- 0xE9, 0x82, 0xB9, 0x59, 0xC7, 0xFF, 0x86, 0x2C, 0x12, 0x7C, 0xC6, 0x65, 0x3C,
- 0x71, 0xB8, 0x98, 0x9F, 0xA2, 0x45, 0x03, 0xA5, 0xD9, 0xC3, 0xCF, 0xFA, 0xEB,
- 0x89, 0xAD, 0x03, 0xEE, 0xDD, 0x76, 0xD3, 0x4F, 0x10, 0x6F, 0xF0, 0xC1, 0x60,
- 0x0C, 0x00, 0xD4, 0x76, 0x12, 0x0A, 0x8D, 0xDC, 0xFD, 0x5E, 0x0B, 0x26, 0x2F,
- 0x01, 0x1D, 0xB9, 0xE7, 0x73, 0xD4, 0xF2, 0xCB, 0xD8, 0x78, 0x21, 0x52, 0x4B,
- 0x83, 0x3C, 0x44, 0x72, 0x0E, 0xB1, 0x4E, 0x37, 0xBC, 0xC7, 0x50, 0xFA, 0x07,
- 0x80, 0x71, 0x10, 0x0B, 0x24, 0xD1, 0x7E, 0xDA, 0x7F, 0xA7, 0x2F, 0x40, 0xAA,
- 0xD3, 0xF5, 0x44, 0x10, 0x56, 0x4E, 0x3B, 0xF1, 0x6E, 0x9A, 0xA0, 0xEA, 0x85,
- 0x66, 0x16, 0xFB, 0x5C, 0x0B, 0x2B, 0x74, 0x18, 0xAF, 0x3D, 0x04, 0x3E, 0xCE,
- 0x88, 0x9B, 0x3E, 0xF4, 0xB9, 0x00, 0x60, 0x0E, 0xE1, 0xE2, 0xCB, 0x12, 0xB9,
- 0x6D, 0x5A, 0xC7, 0x55, 0x1D, 0xB9, 0x79, 0xAC, 0x43, 0x43, 0xE6, 0x3B, 0xDD,
- 0x7E, 0x9F, 0x78, 0xD3, 0xEA, 0xA3, 0x11, 0xFF, 0xDB, 0xBB, 0xB8, 0x97, 0x37,
- 0x15, 0xDB, 0xF1, 0x97, 0x96, 0xC7, 0xFC, 0xE5, 0xBF, 0xF2, 0x86, 0xC0, 0xFA,
- 0x9B, 0x4C, 0x00, 0x04, 0x03, 0xA5, 0xB6, 0xB7, 0x9C, 0xD9, 0xAB, 0x09, 0x77,
- 0x51, 0x18, 0x3B, 0xAD, 0x61, 0x6C, 0xFC, 0x51, 0x96, 0xFB, 0x19, 0xC8, 0x52,
- 0x35, 0x07, 0x00, 0x63, 0x87, 0x14, 0x04, 0xFA, 0x7A, 0x48, 0x3E, 0x00, 0x47,
- 0x29, 0x07, 0x74, 0x97, 0x74, 0x84, 0xEB, 0xB2, 0x16, 0xB2, 0x31, 0x81, 0xCE,
- 0x2A, 0x31, 0xA7, 0xB1, 0xEB, 0x83, 0x34, 0x7A, 0x73, 0xD7, 0x2F, 0xFF, 0xBC,
- 0xFF, 0xE5, 0xAA, 0xF2, 0xB5, 0x6E, 0x9E, 0xA5, 0x70, 0x8A, 0x8C, 0xDF, 0x6A,
- 0x06, 0x16, 0xC1, 0xAB, 0x59, 0x70, 0xD9, 0x3D, 0x47, 0x7C, 0xDD, 0xEF, 0xDF,
- 0x2F, 0xFF, 0x42, 0x6B, 0xBA, 0x4B, 0xBF, 0xF8, 0x7F, 0xF2, 0x03, 0x0D, 0x79,
- 0xBC, 0x03, 0x76, 0x64, 0x1C, 0x0D, 0x7B, 0xD7, 0xBD, 0xB0, 0x6C, 0xD8, 0x61,
- 0x17, 0x17, 0x8C, 0xED, 0x4E, 0x20, 0xEB, 0x55, 0x33, 0x39, 0xE9, 0x7E, 0xBE,
- 0x8E, 0x05, 0x4B, 0x78, 0x96, 0x85, 0xCC, 0x68, 0xC9, 0x78, 0xAF, 0xAE, 0x44,
- 0x36, 0x61, 0xD3, 0x53, 0xEB, 0xB3, 0x3E, 0x4F, 0x97, 0xE2, 0x8D, 0xAE, 0x90,
- 0xED, 0xB5, 0x4F, 0x8E, 0xE4, 0x7A, 0x44, 0xCF, 0x9D, 0xC5, 0x77, 0x4D, 0xAB,
- 0x4F, 0xE5, 0xC5, 0x73, 0xA0, 0xC8, 0xA5, 0xBB, 0x4B, 0x7D, 0xD5, 0xFB, 0xFB,
- 0xFF, 0x61, 0xFD, 0xAA, 0x1A, 0x62, 0x7E, 0x3C, 0x66, 0x34, 0x15, 0x64, 0x25,
- 0xEC, 0x7C, 0x9D, 0x6A, 0x64, 0x4D, 0x80, 0xD5, 0x4F, 0xFE, 0x8E, 0xEE, 0x18,
- 0x53, 0xC1, 0x09, 0x51, 0xF7, 0xC0, 0xA6, 0xB2, 0x9B, 0x19, 0x2B, 0x14, 0x66,
- 0x66, 0x4B, 0x39, 0x62, 0x1D, 0x84, 0xB9, 0x02, 0x84, 0xAC, 0xC1, 0xDA, 0x6C,
- 0x80, 0xCD, 0x40, 0x20, 0x20, 0x19, 0x51, 0xDC, 0x2B, 0x7D, 0x5D, 0x7F, 0xE3,
- 0x86, 0x8E, 0xC3, 0x35, 0xFE, 0x5C, 0xF6, 0x1C, 0xFF, 0x05, 0x9E, 0xB5, 0xB6,
- 0xBB, 0xBE, 0xF7, 0x2F, 0xB7, 0xE1, 0xF5, 0x33, 0x86, 0xA0, 0x47, 0xDE, 0xF7,
- 0xE9, 0x3B, 0xBE, 0x7E, 0x9B, 0x17, 0xFC, 0xFD, 0x2E, 0x40, 0x86, 0x41, 0x75,
- 0xF1, 0xB2, 0x18, 0xA9, 0xDE, 0x2D, 0xD6, 0x04, 0x20, 0xA4, 0xBA, 0x81, 0xBC,
- 0x1D, 0x5A, 0xD6, 0xF7, 0xF6, 0xB8, 0x42, 0xF7, 0xF5, 0x3D, 0x97, 0xAC, 0xCD,
- 0x6F, 0xAD, 0xDB, 0x4F, 0x5A, 0x2E, 0x64, 0xB9, 0x5D, 0xDD, 0x8B, 0x4A, 0x35,
- 0x44, 0xFE, 0x3D, 0xC6, 0x77, 0x7A, 0xBF, 0xDA, 0xAC, 0x9E, 0xB0, 0xA2, 0xB9,
- 0x6C, 0xAF, 0x02, 0xDD, 0xF2, 0x71, 0x2B, 0xEF, 0xD3, 0x51, 0x0E, 0x07, 0x11,
- 0xBD, 0xED, 0x39, 0x7F, 0xD9, 0xB8, 0xBD, 0xEE, 0x35, 0xE9, 0x5C, 0x67, 0x42,
- 0xDA, 0x05, 0x6E, 0x39, 0xCE, 0x55, 0xFB, 0x26, 0xB7, 0x90, 0x4B, 0xDA, 0x91,
- 0x48, 0xFD, 0xDE, 0xB2, 0xEC, 0x88, 0x9A, 0x46, 0x1A, 0x4C, 0xD4, 0x05, 0x12,
- 0x85, 0x57, 0x37, 0x22, 0xD3, 0x0E, 0x4F, 0x79, 0xE3, 0x81, 0xA9, 0x2B, 0x5F,
- 0xD7, 0x6D, 0xBD, 0x21, 0x98, 0x6F, 0x7D, 0xF5, 0x32, 0x7A, 0x6E, 0xF8, 0x20,
- 0x01, 0x50, 0x90, 0x7A, 0x88, 0x3E, 0x0D, 0x57, 0xB1, 0x58, 0x65, 0xE6, 0x82,
- 0xCE, 0x08, 0x69, 0x8B, 0x87, 0x62, 0x36, 0xB1, 0x7B, 0xDE, 0x74, 0xBD, 0xFE,
- 0x10, 0xBE, 0x26, 0xAB, 0x7E, 0xB7, 0x8D, 0xF7, 0x83, 0x2E, 0x0F, 0xAF, 0x7E,
- 0xBC, 0x17, 0x31, 0xFF, 0xB0, 0x4F, 0x7F, 0x4B, 0x13, 0x83, 0xDF, 0xEE, 0x23,
- 0xD3, 0xE7, 0xC8, 0xAF, 0x75, 0xAB, 0xEA, 0xBD, 0x7D, 0xD2, 0x9D, 0xE9, 0xC1,
- 0x18, 0x8B, 0x7C, 0x9F, 0x51, 0xDC, 0x37, 0xA3, 0xDB, 0xFC, 0xD4, 0x6A, 0x91,
- 0x44, 0x7F, 0x72, 0xC5, 0xD9, 0xC8, 0x37, 0x38, 0x63, 0x0D, 0x59, 0x8B, 0x7F,
- 0x7D, 0x96, 0xC1, 0x5F, 0x4C, 0x7C, 0x88, 0xCB, 0x65, 0x07, 0x2B, 0x0E, 0x1D,
- 0x24, 0xAA, 0x20, 0x2E, 0x6C, 0x33, 0xAB, 0xEF, 0x23, 0xE5, 0xE3, 0x6C, 0xA3,
- 0xA5, 0x2D, 0x01, 0xDF, 0x26, 0x92, 0x52, 0xF5, 0xE6, 0x3E, 0xE3, 0xDD, 0xC6,
- 0xED, 0x42, 0x0F, 0x71, 0x7B, 0xD1, 0xF4, 0x06, 0xF6, 0x82, 0xD5, 0x13, 0xB3,
- 0x60, 0x31, 0x09, 0x89, 0x63, 0x15, 0xD2, 0xCB, 0xAA, 0x77, 0xFD, 0xF4, 0xEB,
- 0xF4, 0xED, 0x2E, 0xE2, 0xBA, 0x27, 0x2E, 0x74, 0xD2, 0x91, 0x7F, 0x0F, 0xDE,
- 0x25, 0xFE, 0x78, 0x20, 0x05, 0x0A, 0x6A, 0xFE, 0x89, 0x14, 0x23, 0xF3, 0xF5,
- 0x3A, 0x1E, 0xF3, 0x22, 0xCE, 0x12, 0x82, 0x24, 0x11, 0x05, 0x04, 0x71, 0x99,
- 0xE5, 0xF0, 0xA6, 0xDB, 0x7B, 0xF5, 0x8F, 0xF9, 0x3C, 0x02, 0x0C, 0x46, 0xFD,
- 0xB6, 0xEA, 0x06, 0x11, 0xF4, 0x1E, 0x7A, 0x20, 0x6A, 0x54, 0xBB, 0x4A, 0x60,
- 0xB0, 0x30, 0x28, 0x9A, 0xF3, 0x3B, 0xE9, 0xBD, 0xD6, 0x04, 0xCA, 0x3A, 0x33,
- 0x37, 0x5F, 0xB7, 0xAD, 0xE7, 0x9C, 0xE2, 0x95, 0x21, 0xF7, 0xB5, 0xC4, 0xF0,
- 0xD1, 0x51, 0x09, 0x44, 0x3F, 0x07, 0xFC, 0x5F, 0x37, 0xFD, 0x7D, 0x7E, 0xD5,
- 0xF7, 0xEB, 0x69, 0xB9, 0x54, 0x98, 0x5A, 0x2A, 0x56, 0xE3, 0xC0, 0x21, 0x57,
- 0xD1, 0xEB, 0x75, 0x15, 0xED, 0xAC, 0xAF, 0x5D, 0xFF, 0xC2, 0xFE, 0x4E, 0xFB,
- 0xBA, 0x13, 0xB8, 0x87, 0xFA, 0x4E, 0x5E, 0x5C, 0x24, 0x15, 0x5B, 0x2B, 0x2C,
- 0x32, 0x68, 0x1F, 0x30, 0x5F, 0xC1, 0xF7, 0xE7, 0xE1, 0x9C, 0x00, 0xC1, 0x9C,
- 0xB1, 0xAB, 0xFA, 0xFF, 0xC1, 0x1E, 0x72, 0xA1, 0x46, 0x9E, 0x2E, 0xCD, 0x76,
- 0x96, 0x4F, 0x14, 0xDC, 0x68, 0xC1, 0x10, 0x9F, 0xDF, 0xEB, 0x5A, 0xBA, 0x8D,
- 0x91, 0x4E, 0x76, 0xE9, 0x3A, 0x43, 0x2D, 0x88, 0xD2, 0x81, 0x0C, 0xEC, 0x6F,
- 0xB7, 0xA4, 0x8B, 0x97, 0x4F, 0xC4, 0x1E, 0xF3, 0x0F, 0xF5, 0x66, 0x66, 0xBF,
- 0x6C, 0x3F, 0xFB, 0x6E, 0x2B, 0x48, 0x6C, 0x7B, 0xD1, 0x2E, 0x64, 0xD1, 0x0B,
- 0x6E, 0x5B, 0x05, 0x16, 0xDD, 0xCB, 0x1B, 0xDE, 0xA2, 0xB9, 0xA8, 0x94, 0xD6,
- 0x5A, 0x5B, 0xE2, 0xC9, 0xBC, 0xD5, 0xAB, 0x64, 0x5B, 0x0F, 0x9A, 0xFD, 0xC7,
- 0x2E, 0xB7, 0xEF, 0xAE, 0xE9, 0x1F, 0x32, 0xD2, 0xCA, 0xA0, 0x37, 0x63, 0x86,
- 0x72, 0x41, 0x07, 0xBC, 0xAB, 0x6F, 0xFF, 0xB7, 0x16, 0xAA, 0xA9, 0x58, 0x9E,
- 0x43, 0x9C, 0x22, 0x8D, 0x48, 0xCE, 0xE5, 0xEF, 0xE0, 0x7D, 0x47, 0x87, 0x5A,
- 0xA8, 0x5B, 0x06, 0xA9, 0x47, 0xF0, 0x26, 0xB4, 0x99, 0xD8, 0xA3, 0x64, 0xED,
- 0x73, 0xB3, 0x96, 0xB4, 0x21, 0x19, 0xA5, 0xC1, 0xDC, 0x88, 0x2E, 0xEE, 0xF2,
- 0x77, 0x91, 0xEC, 0xFB, 0xD5, 0xF9, 0xF8, 0x90, 0x47, 0xAD, 0xF5, 0xEB, 0x96,
- 0x6D, 0xF1, 0x1C, 0xE0, 0xDC, 0x74, 0x1C, 0xE6, 0x2E, 0xE1, 0x76, 0x9D, 0xEE,
- 0xF4, 0xEF, 0xA5, 0x31, 0x03, 0x87, 0x0E, 0x2C, 0x84, 0xA5, 0xF1, 0x22, 0xBE,
- 0x48, 0xA9, 0xCD, 0x09, 0x07, 0xC1, 0xF0, 0xD4, 0xE7, 0x03, 0x82, 0x39, 0xE2,
- 0xA0, 0x0B, 0xDE, 0xAC, 0x37, 0xAC, 0x62, 0x97, 0x8E, 0x79, 0xCE, 0x52, 0x24,
- 0x78, 0xF9, 0x17, 0xD2, 0xF1, 0x5D, 0x2D, 0xA1, 0xDF, 0x12, 0x2C, 0x83, 0xE5,
- 0x1A, 0x28, 0x9A, 0x2D, 0xED, 0x8A, 0xBF, 0xFC, 0x41, 0xC3, 0xEB, 0x0E, 0x91,
- 0xDB, 0xF2, 0xA1, 0xC8, 0xA8, 0x01, 0x8B, 0xF2, 0xF3, 0x59, 0xB7, 0xA7, 0x6F,
- 0x80, 0xFF, 0x0B, 0x46, 0xE1, 0x63, 0xA7, 0x5F, 0x6B, 0xBE, 0x33, 0x71, 0xBE,
- 0x3A, 0xAF, 0xA9, 0x53, 0x5D, 0x3B, 0xB2, 0xF6, 0xEB, 0x42, 0x1C, 0x3E, 0x3F,
- 0x1D, 0x6A, 0x34, 0xAE, 0xB1, 0x05, 0xA1, 0x32, 0x6C, 0xB5, 0xE4, 0xD3, 0xBB,
- 0xE8, 0x10, 0x14, 0x9E, 0x68, 0x6A, 0x24, 0x51, 0xA5, 0x66, 0x64, 0xCC, 0xC4,
- 0x2D, 0x96, 0xA2, 0xC7, 0x2D, 0x1F, 0x0A, 0x0F, 0x6B, 0xD9, 0xAD, 0xA3, 0x11,
- 0x8F, 0x00, 0xAA, 0x06, 0xC2, 0x1E, 0xF3, 0xE8, 0x5A, 0x37, 0x4C, 0xD6, 0x4B,
- 0x6B, 0x01, 0xC9, 0xB0, 0xB6, 0xB9, 0x92, 0xED, 0x1D, 0x08, 0xB0, 0x80, 0x06,
- 0x20, 0xEA, 0xEE, 0xF9, 0x1D, 0xA4, 0x57, 0x73, 0x2E, 0x1B, 0xA5, 0xAF, 0xF6,
- 0xAF, 0xAE, 0x04, 0x7C, 0x4C, 0x7E, 0xC8, 0xDB, 0xC0, 0xFB, 0x37, 0xC8, 0x7E,
- 0xFE, 0x47, 0x0A, 0x3C, 0xFA, 0x61, 0xE7, 0xEB, 0x1B, 0xF3, 0x7C, 0x32, 0xE3,
- 0x7C, 0x37, 0x66, 0x7C, 0x53, 0x07, 0xC2, 0x37, 0xA3, 0xBD, 0xF7, 0xFA, 0xE3,
- 0x8A, 0x76, 0xCB, 0x6C, 0xC8, 0x13, 0xC4, 0x53, 0x53, 0xDB, 0xAD, 0x37, 0x1A,
- 0xEB, 0xE0
-};
-
-TEST_F(H264ToAnnexBBitstreamConverterTest, Success) {
- // Initialize converter.
- scoped_array<uint8> output;
- H264ToAnnexBBitstreamConverter converter;
-
- // Parse the headers.
- uint32 config_size = converter.ParseConfigurationAndCalculateSize(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4));
- EXPECT_GT(config_size, 0U);
-
- // Go on with converting the headers.
- output.reset(new uint8[config_size]);
- EXPECT_TRUE(output.get() != NULL);
- EXPECT_TRUE(converter.ConvertAVCDecoderConfigToByteStream(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4),
- output.get(),
- &config_size));
-
- // Calculate buffer size for actual NAL unit.
- uint32 output_size = converter.CalculateNeededOutputBufferSize(
- kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4));
- EXPECT_GT(output_size, 0U);
- output_size += config_size;
- output.reset(new uint8[output_size]);
- EXPECT_TRUE(output.get() != NULL);
-
- uint32 output_size_left_for_nal_unit = output_size - config_size;
- // Do the conversion for actual NAL unit.
- EXPECT_TRUE(converter.ConvertNalUnitStreamToByteStream(
- kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4),
- output.get() + config_size,
- &output_size_left_for_nal_unit));
-
- // Classes allocated in stack are automatically destroyed.
-}
-
-TEST_F(H264ToAnnexBBitstreamConverterTest, FailureNullData) {
- // Initialize converter.
- H264ToAnnexBBitstreamConverter converter;
-
- // Simulate situation where there is no header data.
- uint32 config_size = converter.ParseConfigurationAndCalculateSize(NULL, 0);
- EXPECT_EQ(config_size, 0U);
-
- // Go on with converting the headers with NULL parameters.
- EXPECT_FALSE(converter.ConvertAVCDecoderConfigToByteStream(NULL,
- 0,
- NULL,
- &config_size));
-
- // Simulate NULL parameters for buffer calculation.
- uint32 output_size = converter.CalculateNeededOutputBufferSize(NULL, 0);
- EXPECT_EQ(output_size, 0U);
-
- // Do the conversion for actual NAL unit with NULL paramaters.
- EXPECT_FALSE(converter.ConvertNalUnitStreamToByteStream(NULL,
- 0,
- NULL,
- &output_size));
-
- // Classes allocated in stack are automatically destroyed.
-}
-
-TEST_F(H264ToAnnexBBitstreamConverterTest, FailureHeaderBufferOverflow) {
- // Initialize converter
- H264ToAnnexBBitstreamConverter converter;
-
- // Simulate 10 sps AVCDecoderConfigurationRecord,
- // which would extend beyond the buffer.
- uint8 corrupted_header[sizeof(kHeaderDataOkWithFieldLen4)];
- memcpy(corrupted_header, kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4));
- // 6th byte, 5 LSBs contain the number of sps's.
- corrupted_header[5] = corrupted_header[5] | 0xA;
-
- // Parse the headers
- uint32 config_size = converter.ParseConfigurationAndCalculateSize(
- corrupted_header,
- sizeof(corrupted_header));
- EXPECT_EQ(config_size, 0U); // Failure as a result of buffer overflows.
-
- // Classes allocated in stack are automatically destroyed.
-}
-
-TEST_F(H264ToAnnexBBitstreamConverterTest, FailureNalUnitBreakage) {
- // Initialize converter.
- scoped_array<uint8> output;
- H264ToAnnexBBitstreamConverter converter;
-
- // Parse the headers.
- uint32 config_size = converter.ParseConfigurationAndCalculateSize(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4));
- EXPECT_GT(config_size, 0U);
-
- // Go on with converting the headers.
- output.reset(new uint8[config_size]);
- EXPECT_TRUE(output.get() != NULL);
- EXPECT_TRUE(converter.ConvertAVCDecoderConfigToByteStream(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4),
- output.get(),
- &config_size));
-
- // Simulate NAL unit broken in middle by writing only some of the data.
- uint8 corrupted_nal_unit[sizeof(kPacketDataOkWithFieldLen4) - 100];
- memcpy(corrupted_nal_unit, kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4) - 100);
-
- // Calculate buffer size for actual NAL unit, should return 0 because of
- // incomplete input buffer.
- uint32 output_size = converter.CalculateNeededOutputBufferSize(
- corrupted_nal_unit,
- sizeof(corrupted_nal_unit));
- EXPECT_EQ(output_size, 0U);
-
- // Ignore the error and try to go on with conversion simulating wrong usage.
- output_size = sizeof(kPacketDataOkWithFieldLen4) + config_size;
- output.reset(new uint8[output_size]);
- EXPECT_TRUE(output.get() != NULL);
-
- uint32 output_size_left_for_nal_unit = output_size - config_size;
- // Do the conversion for actual NAL unit, expecting failure.
- EXPECT_FALSE(converter.ConvertNalUnitStreamToByteStream(
- corrupted_nal_unit,
- sizeof(corrupted_nal_unit),
- output.get() + config_size,
- &output_size_left_for_nal_unit));
- EXPECT_EQ(output_size_left_for_nal_unit, 0U);
-
- // Classes allocated in stack are automatically destroyed.
-}
-
-TEST_F(H264ToAnnexBBitstreamConverterTest, FailureTooSmallOutputBuffer) {
- // Initialize converter.
- scoped_array<uint8> output;
- H264ToAnnexBBitstreamConverter converter;
-
- // Parse the headers.
- uint32 config_size = converter.ParseConfigurationAndCalculateSize(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4));
- EXPECT_GT(config_size, 0U);
- uint32 real_config_size = config_size;
-
- // Go on with converting the headers with too small buffer.
- config_size -= 10;
- output.reset(new uint8[config_size]);
- EXPECT_TRUE(output.get() != NULL);
- EXPECT_FALSE(converter.ConvertAVCDecoderConfigToByteStream(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4),
- output.get(),
- &config_size));
- EXPECT_EQ(config_size, 0U);
-
- // Still too small (but only 1 byte short).
- config_size = real_config_size - 1;
- output.reset(new uint8[config_size]);
- EXPECT_TRUE(output.get() != NULL);
- EXPECT_FALSE(converter.ConvertAVCDecoderConfigToByteStream(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4),
- output.get(),
- &config_size));
- EXPECT_EQ(config_size, 0U);
-
- // Finally, retry with valid buffer.
- config_size = real_config_size;
- output.reset(new uint8[config_size]);
- EXPECT_TRUE(output.get() != NULL);
- EXPECT_TRUE(converter.ConvertAVCDecoderConfigToByteStream(
- kHeaderDataOkWithFieldLen4,
- sizeof(kHeaderDataOkWithFieldLen4),
- output.get(),
- &config_size));
-
- // Calculate buffer size for actual NAL unit.
- uint32 output_size = converter.CalculateNeededOutputBufferSize(
- kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4));
- EXPECT_GT(output_size, 0U);
- output_size += config_size;
- // Simulate too small output buffer.
- output_size -= 1;
- output.reset(new uint8[output_size]);
- EXPECT_TRUE(output.get() != NULL);
-
- uint32 output_size_left_for_nal_unit = output_size - config_size;
- // Do the conversion for actual NAL unit (expect failure).
- EXPECT_FALSE(converter.ConvertNalUnitStreamToByteStream(
- kPacketDataOkWithFieldLen4,
- sizeof(kPacketDataOkWithFieldLen4),
- output.get() + config_size,
- &output_size_left_for_nal_unit));
- EXPECT_EQ(output_size_left_for_nal_unit, 0U);
-
- // Classes allocated in stack are automatically destroyed.
-}
-
-} // namespace media
diff --git a/src/media/filters/in_memory_url_protocol.cc b/src/media/filters/in_memory_url_protocol.cc
deleted file mode 100644
index c55438c..0000000
--- a/src/media/filters/in_memory_url_protocol.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/filters/in_memory_url_protocol.h"
-
-namespace media {
-
-InMemoryUrlProtocol::InMemoryUrlProtocol(const uint8* data, int64 size,
- bool streaming)
- : data_(data),
- size_(size >= 0 ? size : 0),
- position_(0),
- streaming_(streaming) {
-}
-
-InMemoryUrlProtocol::~InMemoryUrlProtocol() {}
-
-int InMemoryUrlProtocol::Read(int size, uint8* data) {
- int available_bytes = size_ - position_;
- if (size > available_bytes)
- size = available_bytes;
-
- memcpy(data, data_ + position_, size);
- position_ += size;
- return size;
-}
-
-bool InMemoryUrlProtocol::GetPosition(int64* position_out) {
- if (!position_out)
- return false;
-
- *position_out = position_;
- return true;
-}
-
-bool InMemoryUrlProtocol::SetPosition(int64 position) {
- if (position < 0 || position >= size_)
- return false;
- position_ = position;
- return true;
-}
-
-bool InMemoryUrlProtocol::GetSize(int64* size_out) {
- if (!size_out)
- return false;
-
- *size_out = size_;
- return true;
-}
-
-bool InMemoryUrlProtocol::IsStreaming() {
- return streaming_;
-}
-
-} // namespace media
diff --git a/src/media/filters/in_memory_url_protocol.h b/src/media/filters/in_memory_url_protocol.h
deleted file mode 100644
index 5c323c1..0000000
--- a/src/media/filters/in_memory_url_protocol.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
-#define MEDIA_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "media/filters/ffmpeg_glue.h"
-
-namespace media {
-
-// Simple FFmpegURLProtocol that reads from a buffer.
-// NOTE: This object does not copy the buffer so the
-// buffer pointer passed into the constructor
-// needs to remain valid for the entire lifetime of
-// this object.
-class MEDIA_EXPORT InMemoryUrlProtocol : public FFmpegURLProtocol {
- public:
- InMemoryUrlProtocol(const uint8* buf, int64 size, bool streaming);
- virtual ~InMemoryUrlProtocol();
-
- // FFmpegURLProtocol methods.
- virtual int Read(int size, uint8* data) OVERRIDE;
- virtual bool GetPosition(int64* position_out) OVERRIDE;
- virtual bool SetPosition(int64 position) OVERRIDE;
- virtual bool GetSize(int64* size_out) OVERRIDE;
- virtual bool IsStreaming() OVERRIDE;
-
- private:
- const uint8* data_;
- int64 size_;
- int64 position_;
- bool streaming_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(InMemoryUrlProtocol);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
diff --git a/src/media/filters/opus_audio_decoder.cc b/src/media/filters/opus_audio_decoder.cc
deleted file mode 100644
index 8837d2c..0000000
--- a/src/media/filters/opus_audio_decoder.cc
+++ /dev/null
@@ -1,561 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/opus_audio_decoder.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-#include "base/sys_byteorder.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/data_buffer.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/demuxer.h"
-#include "media/base/pipeline.h"
-#include "third_party/opus/src/include/opus.h"
-#include "third_party/opus/src/include/opus_multistream.h"
-
-namespace media {
-
-static uint16 ReadLE16(const uint8* data, size_t data_size, int read_offset) {
- DCHECK(data);
- uint16 value = 0;
- DCHECK_LE(read_offset + sizeof(value), data_size);
- memcpy(&value, data + read_offset, sizeof(value));
- return base::ByteSwapToLE16(value);
-}
-
-// Returns true if the decode result was end of stream.
-static inline bool IsEndOfStream(int decoded_size, Buffer* input) {
- // Two conditions to meet to declare end of stream for this decoder:
- // 1. Opus didn't output anything.
- // 2. An end of stream buffer is received.
- return decoded_size == 0 && input->IsEndOfStream();
-}
-
-// The Opus specification is part of IETF RFC 6716:
-// http://tools.ietf.org/html/rfc6716
-
-// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
-// mappings for up to 8 channels. See section 4.3.9 of the vorbis
-// specification:
-// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
-static const int kMaxVorbisChannels = 8;
-
-// Opus allows for decode of S16 or float samples. OpusAudioDecoder always uses
-// S16 samples.
-static const int kBitsPerChannel = 16;
-static const int kBytesPerChannel = kBitsPerChannel / 8;
-
-// Maximum packet size used in Xiph's opusdec and FFmpeg's libopusdec.
-static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
-static const int kMaxOpusOutputPacketSizeBytes =
- kMaxOpusOutputPacketSizeSamples * kBytesPerChannel;
-
-static void RemapOpusChannelLayout(const uint8* opus_mapping,
- int num_channels,
- uint8* channel_layout) {
- DCHECK_LE(num_channels, kMaxVorbisChannels);
-
- // Opus uses Vorbis channel layout.
- const int32 num_layouts = kMaxVorbisChannels;
- const int32 num_layout_values = kMaxVorbisChannels;
- const uint8 kVorbisChannelLayouts[num_layouts][num_layout_values] = {
- { 0 },
- { 0, 1 },
- { 0, 2, 1 },
- { 0, 1, 2, 3 },
- { 0, 2, 1, 3, 4 },
- { 0, 2, 1, 5, 3, 4 },
- { 0, 2, 1, 6, 5, 3, 4 },
- { 0, 2, 1, 7, 5, 6, 3, 4 },
- };
-
- // Reorder the channels to produce the same ordering as FFmpeg, which is
- // what the pipeline expects.
- const uint8* vorbis_layout_offset = kVorbisChannelLayouts[num_channels - 1];
- for (int channel = 0; channel < num_channels; ++channel)
- channel_layout[channel] = opus_mapping[vorbis_layout_offset[channel]];
-}
-
-// Opus Header contents:
-// - "OpusHead" (64 bits)
-// - version number (8 bits)
-// - Channels C (8 bits)
-// - Pre-skip (16 bits)
-// - Sampling rate (32 bits)
-// - Gain in dB (16 bits, S7.8)
-// - Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping,
-// 2..254: reserved, 255: multistream with no mapping)
-//
-// - if (mapping != 0)
-// - N = totel number of streams (8 bits)
-// - M = number of paired streams (8 bits)
-// - C times channel origin
-// - if (C<2*M)
-// - stream = byte/2
-// - if (byte&0x1 == 0)
-// - left
-// else
-// - right
-// - else
-// - stream = byte-M
-
-// Default audio output channel layout. Used to initialize |stream_map| in
-// OpusHeader, and passed to opus_multistream_decoder_create() when the header
-// does not contain mapping information. The values are valid only for mono and
-// stereo output: Opus streams with more than 2 channels require a stream map.
-static const int kMaxChannelsWithDefaultLayout = 2;
-static const uint8 kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = {
- 0, 1 };
-
-// Size of the Opus header excluding optional mapping information.
-static const int kOpusHeaderSize = 19;
-
-// Offset to the channel count byte in the Opus header.
-static const int kOpusHeaderChannelsOffset = 9;
-
-// Offset to the pre-skip value in the Opus header.
-static const int kOpusHeaderSkipSamplesOffset = 10;
-
-// Offset to the channel mapping byte in the Opus header.
-static const int kOpusHeaderChannelMappingOffset = 18;
-
-// Header contains a stream map. The mapping values are in extra data beyond
-// the always present |kOpusHeaderSize| bytes of data. The mapping data
-// contains stream count, coupling information, and per channel mapping values:
-// - Byte 0: Number of streams.
-// - Byte 1: Number coupled.
-// - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping values.
-static const int kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
-static const int kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
-static const int kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
-
-struct OpusHeader {
- OpusHeader()
- : channels(0),
- skip_samples(0),
- channel_mapping(0),
- num_streams(0),
- num_coupled(0) {
- memcpy(stream_map,
- kDefaultOpusChannelLayout,
- kMaxChannelsWithDefaultLayout);
- }
- int channels;
- int skip_samples;
- int channel_mapping;
- int num_streams;
- int num_coupled;
- uint8 stream_map[kMaxVorbisChannels];
-};
-
-// Returns true when able to successfully parse and store Opus header data in
-// data parsed in |header|. Based on opus header parsing code in libopusdec
-// from FFmpeg, and opus_header from Xiph's opus-tools project.
-static void ParseOpusHeader(const uint8* data, int data_size,
- const AudioDecoderConfig& config,
- OpusHeader* header) {
- CHECK_GE(data_size, kOpusHeaderSize);
-
- header->channels = *(data + kOpusHeaderChannelsOffset);
-
- CHECK(header->channels > 0 && header->channels <= kMaxVorbisChannels)
- << "invalid channel count in header: " << header->channels;
-
- header->skip_samples =
- ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset);
-
- header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset);
-
- if (!header->channel_mapping) {
- CHECK_LE(header->channels, kMaxChannelsWithDefaultLayout)
- << "Invalid header, missing stream map.";
-
- header->num_streams = 1;
- header->num_coupled =
- (ChannelLayoutToChannelCount(config.channel_layout()) > 1) ? 1 : 0;
- return;
- }
-
- CHECK_GE(data_size, kOpusHeaderStreamMapOffset + header->channels)
- << "Invalid stream map.";
-
- header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
- header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
-
- if (header->num_streams + header->num_coupled != header->channels)
- LOG(WARNING) << "Inconsistent channel mapping.";
-
- for (int i = 0; i < kMaxVorbisChannels; ++i)
- header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
-}
-
-OpusAudioDecoder::OpusAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop)
- : message_loop_(message_loop),
- opus_decoder_(NULL),
- bits_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- samples_per_second_(0),
- last_input_timestamp_(kNoTimestamp()),
- output_bytes_to_drop_(0),
- skip_samples_(0) {
-}
-
-void OpusAudioDecoder::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &OpusAudioDecoder::DoInitialize, this,
- stream, status_cb, statistics_cb));
- return;
- }
- DoInitialize(stream, status_cb, statistics_cb);
-}
-
-void OpusAudioDecoder::Read(const ReadCB& read_cb) {
- // Complete operation asynchronously on different stack of execution as per
- // the API contract of AudioDecoder::Read()
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &OpusAudioDecoder::DoRead, this, read_cb));
-}
-
-int OpusAudioDecoder::bits_per_channel() {
- return bits_per_channel_;
-}
-
-ChannelLayout OpusAudioDecoder::channel_layout() {
- return channel_layout_;
-}
-
-int OpusAudioDecoder::samples_per_second() {
- return samples_per_second_;
-}
-
-void OpusAudioDecoder::Reset(const base::Closure& closure) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &OpusAudioDecoder::DoReset, this, closure));
-}
-
-OpusAudioDecoder::~OpusAudioDecoder() {
- // TODO(scherkus): should we require Stop() to be called? this might end up
- // getting called on a random thread due to refcounting.
- CloseDecoder();
-}
-
-void OpusAudioDecoder::DoInitialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- if (demuxer_stream_) {
- // TODO(scherkus): initialization currently happens more than once in
- // PipelineIntegrationTest.BasicPlayback.
- LOG(ERROR) << "Initialize has already been called.";
- CHECK(false);
- }
-
- demuxer_stream_ = stream;
-
- if (!ConfigureDecoder()) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- statistics_cb_ = statistics_cb;
- status_cb.Run(PIPELINE_OK);
-}
-
-void OpusAudioDecoder::DoReset(const base::Closure& closure) {
- opus_multistream_decoder_ctl(opus_decoder_, OPUS_RESET_STATE);
- ResetTimestampState();
- closure.Run();
-}
-
-void OpusAudioDecoder::DoRead(const ReadCB& read_cb) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!read_cb.is_null());
- CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
-
- read_cb_ = read_cb;
- ReadFromDemuxerStream();
-}
-
-void OpusAudioDecoder::DoDecodeBuffer(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& input) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &OpusAudioDecoder::DoDecodeBuffer, this, status, input));
- return;
- }
-
- DCHECK(!read_cb_.is_null());
- DCHECK_EQ(status != DemuxerStream::kOk, !input) << status;
-
- if (status == DemuxerStream::kAborted) {
- DCHECK(!input);
- base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
- return;
- }
-
- if (status == DemuxerStream::kConfigChanged) {
- DCHECK(!input);
-
- scoped_refptr<DataBuffer> output_buffer;
-
- // Send a "end of stream" buffer to the decode loop
- // to output any remaining data still in the decoder.
- if (!Decode(DecoderBuffer::CreateEOSBuffer(), true, &output_buffer)) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- DVLOG(1) << "Config changed.";
-
- if (!ConfigureDecoder()) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- ResetTimestampState();
-
- if (output_buffer) {
- // Execute callback to return the decoded audio.
- base::ResetAndReturn(&read_cb_).Run(kOk, output_buffer);
- } else {
- // We exhausted the input data, but it wasn't enough for a frame. Ask for
- // more data in order to fulfill this read.
- ReadFromDemuxerStream();
- }
-
- return;
- }
-
- DCHECK_EQ(status, DemuxerStream::kOk);
- DCHECK(input);
-
- // Make sure we are notified if http://crbug.com/49709 returns. Issue also
- // occurs with some damaged files.
- if (!input->IsEndOfStream() && input->GetTimestamp() == kNoTimestamp() &&
- output_timestamp_helper_->base_timestamp() == kNoTimestamp()) {
- DVLOG(1) << "Received a buffer without timestamps!";
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- if (!input->IsEndOfStream()) {
- if (last_input_timestamp_ != kNoTimestamp() &&
- input->GetTimestamp() != kNoTimestamp() &&
- input->GetTimestamp() < last_input_timestamp_) {
- base::TimeDelta diff = input->GetTimestamp() - last_input_timestamp_;
- DVLOG(1) << "Input timestamps are not monotonically increasing! "
- << " ts " << input->GetTimestamp().InMicroseconds() << " us"
- << " diff " << diff.InMicroseconds() << " us";
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- }
-
- last_input_timestamp_ = input->GetTimestamp();
- }
-
- scoped_refptr<DataBuffer> output_buffer;
-
- if (!Decode(input, false, &output_buffer)) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- if (output_buffer) {
- // Execute callback to return the decoded audio.
- base::ResetAndReturn(&read_cb_).Run(kOk, output_buffer);
- } else {
- // We exhausted the input data, but it wasn't enough for a frame. Ask for
- // more data in order to fulfill this read.
- ReadFromDemuxerStream();
- }
-}
-
-void OpusAudioDecoder::ReadFromDemuxerStream() {
- DCHECK(!read_cb_.is_null());
-
- demuxer_stream_->Read(base::Bind(&OpusAudioDecoder::DoDecodeBuffer, this));
-}
-
-bool OpusAudioDecoder::ConfigureDecoder() {
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
-
- if (config.codec() != kCodecOpus) {
- DLOG(ERROR) << "ConfigureDecoder(): codec must be kCodecOpus.";
- return false;
- }
-
- const int channel_count =
- ChannelLayoutToChannelCount(config.channel_layout());
- if (!config.IsValidConfig() || channel_count > kMaxVorbisChannels) {
- DLOG(ERROR) << "ConfigureDecoder(): Invalid or unsupported audio stream -"
- << " codec: " << config.codec()
- << " channel count: " << channel_count
- << " channel layout: " << config.channel_layout()
- << " bits per channel: " << config.bits_per_channel()
- << " samples per second: " << config.samples_per_second();
- return false;
- }
-
- if (config.bits_per_channel() != kBitsPerChannel) {
- DLOG(ERROR) << "ConfigureDecoder(): 16 bit samples required.";
- return false;
- }
-
- if (config.is_encrypted()) {
- DLOG(ERROR) << "ConfigureDecoder(): Encrypted audio stream not supported.";
- return false;
- }
-
- if (opus_decoder_ &&
- (bits_per_channel_ != config.bits_per_channel() ||
- channel_layout_ != config.channel_layout() ||
- samples_per_second_ != config.samples_per_second())) {
- DVLOG(1) << "Unsupported config change :";
- DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_
- << " -> " << config.bits_per_channel();
- DVLOG(1) << "\tchannel_layout : " << channel_layout_
- << " -> " << config.channel_layout();
- DVLOG(1) << "\tsample_rate : " << samples_per_second_
- << " -> " << config.samples_per_second();
- return false;
- }
-
- // Clean up existing decoder if necessary.
- CloseDecoder();
-
- // Allocate the output buffer if necessary.
- if (!output_buffer_)
- output_buffer_.reset(new int16[kMaxOpusOutputPacketSizeSamples]);
-
- // Parse the Opus header.
- OpusHeader opus_header;
- ParseOpusHeader(config.extra_data(), config.extra_data_size(),
- config,
- &opus_header);
-
- skip_samples_ = opus_header.skip_samples;
-
- if (skip_samples_ > 0)
- output_bytes_to_drop_ = skip_samples_ * config.bytes_per_frame();
-
- uint8 channel_mapping[kMaxVorbisChannels];
- memcpy(&channel_mapping,
- kDefaultOpusChannelLayout,
- kMaxChannelsWithDefaultLayout);
-
- if (channel_count > kMaxChannelsWithDefaultLayout) {
- RemapOpusChannelLayout(opus_header.stream_map,
- channel_count,
- channel_mapping);
- }
-
- // Init Opus.
- int status = OPUS_INVALID_STATE;
- opus_decoder_ = opus_multistream_decoder_create(config.samples_per_second(),
- channel_count,
- opus_header.num_streams,
- opus_header.num_coupled,
- channel_mapping,
- &status);
- if (!opus_decoder_ || status != OPUS_OK) {
- LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decoder_create failed"
- << " status=" << opus_strerror(status);
- return false;
- }
-
- // TODO(tomfinegan): Handle audio delay once the matroska spec is updated
- // to represent the value.
-
- bits_per_channel_ = config.bits_per_channel();
- channel_layout_ = config.channel_layout();
- samples_per_second_ = config.samples_per_second();
- output_timestamp_helper_.reset(new AudioTimestampHelper(
- config.bytes_per_frame(), config.samples_per_second()));
- return true;
-}
-
-void OpusAudioDecoder::CloseDecoder() {
- if (opus_decoder_) {
- opus_multistream_decoder_destroy(opus_decoder_);
- opus_decoder_ = NULL;
- }
-}
-
-void OpusAudioDecoder::ResetTimestampState() {
- output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
- last_input_timestamp_ = kNoTimestamp();
- output_bytes_to_drop_ = 0;
-}
-
-bool OpusAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append,
- scoped_refptr<DataBuffer>* output_buffer) {
- int samples_decoded =
- opus_multistream_decode(opus_decoder_,
- input->GetData(), input->GetDataSize(),
- &output_buffer_[0],
- kMaxOpusOutputPacketSizeSamples,
- 0);
- if (samples_decoded < 0) {
- DCHECK(!input->IsEndOfStream())
- << "Decode(): End of stream buffer produced an error!";
-
- LOG(ERROR) << "ConfigureDecoder(): opus_multistream_decode failed for"
- << " timestamp: " << input->GetTimestamp().InMicroseconds()
- << " us, duration: " << input->GetDuration().InMicroseconds()
- << " us, packet size: " << input->GetDataSize() << " bytes with"
- << " status: " << opus_strerror(samples_decoded);
- return false;
- }
-
- uint8* decoded_audio_data = reinterpret_cast<uint8*>(&output_buffer_[0]);
- int decoded_audio_size = samples_decoded *
- demuxer_stream_->audio_decoder_config().bytes_per_frame();
- DCHECK_LE(decoded_audio_size, kMaxOpusOutputPacketSizeBytes);
-
- if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
- !input->IsEndOfStream()) {
- DCHECK(input->GetTimestamp() != kNoTimestamp());
- output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp());
- }
-
- if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
- int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
- DCHECK_EQ(dropped_size % kBytesPerChannel, 0);
- decoded_audio_data += dropped_size;
- decoded_audio_size -= dropped_size;
- output_bytes_to_drop_ -= dropped_size;
- }
-
- if (decoded_audio_size > 0) {
- // Copy the audio samples into an output buffer.
- *output_buffer = new DataBuffer(decoded_audio_data, decoded_audio_size);
- (*output_buffer)->SetTimestamp(output_timestamp_helper_->GetTimestamp());
- (*output_buffer)->SetDuration(
- output_timestamp_helper_->GetDuration(decoded_audio_size));
- output_timestamp_helper_->AddBytes(decoded_audio_size);
- } else if (IsEndOfStream(decoded_audio_size, input) && !skip_eos_append) {
- DCHECK_EQ(input->GetDataSize(), 0);
- // Create an end of stream output buffer.
- *output_buffer = new DataBuffer(0);
- }
-
- // Decoding finished successfully, update statistics.
- PipelineStatistics statistics;
- statistics.audio_bytes_decoded = decoded_audio_size;
- statistics_cb_.Run(statistics);
-
- return true;
-}
-
-} // namespace media
diff --git a/src/media/filters/opus_audio_decoder.h b/src/media/filters/opus_audio_decoder.h
deleted file mode 100644
index 4db8a53..0000000
--- a/src/media/filters/opus_audio_decoder.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_OPUS_AUDIO_DECODER_H_
-#define MEDIA_FILTERS_OPUS_AUDIO_DECODER_H_
-
-#include "base/callback.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/demuxer_stream.h"
-
-struct OpusMSDecoder;
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class AudioTimestampHelper;
-class DataBuffer;
-class DecoderBuffer;
-struct QueuedAudioBuffer;
-
-class MEDIA_EXPORT OpusAudioDecoder : public AudioDecoder {
- public:
- explicit OpusAudioDecoder(
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- // AudioDecoder implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual int bits_per_channel() OVERRIDE;
- virtual ChannelLayout channel_layout() OVERRIDE;
- virtual int samples_per_second() OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
-
- protected:
- virtual ~OpusAudioDecoder();
-
- private:
- // Methods running on decoder thread.
- void DoInitialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb);
- void DoReset(const base::Closure& closure);
- void DoRead(const ReadCB& read_cb);
- void DoDecodeBuffer(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& input);
-
- // Reads from the demuxer stream with corresponding callback method.
- void ReadFromDemuxerStream();
-
- bool ConfigureDecoder();
- void CloseDecoder();
- void ResetTimestampState();
- bool Decode(const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append,
- scoped_refptr<DataBuffer>* output_buffer);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- scoped_refptr<DemuxerStream> demuxer_stream_;
- StatisticsCB statistics_cb_;
- OpusMSDecoder* opus_decoder_;
-
- // Decoded audio format.
- int bits_per_channel_;
- ChannelLayout channel_layout_;
- int samples_per_second_;
-
- // Used for computing output timestamps.
- scoped_ptr<AudioTimestampHelper> output_timestamp_helper_;
- base::TimeDelta last_input_timestamp_;
-
- // Number of output sample bytes to drop before generating
- // output buffers.
- int output_bytes_to_drop_;
-
- ReadCB read_cb_;
-
- int skip_samples_;
-
- // Buffer for output from libopus.
- scoped_array<int16> output_buffer_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(OpusAudioDecoder);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_OPUS_AUDIO_DECODER_H_
diff --git a/src/media/filters/pipeline_integration_test.cc b/src/media/filters/pipeline_integration_test.cc
deleted file mode 100644
index 088d991..0000000
--- a/src/media/filters/pipeline_integration_test.cc
+++ /dev/null
@@ -1,450 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/pipeline_integration_test_base.h"
-
-#include "base/bind.h"
-#include "base/string_util.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decryptor_client.h"
-#include "media/base/test_data_util.h"
-#include "media/crypto/aes_decryptor.h"
-
-using testing::AtMost;
-
-namespace media {
-
-static const char kSourceId[] = "SourceId";
-static const char kClearKeySystem[] = "org.w3.clearkey";
-static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 };
-
-static const char kWebM[] = "video/webm; codecs=\"vp8,vorbis\"";
-static const char kAudioOnlyWebM[] = "video/webm; codecs=\"vorbis\"";
-static const char kVideoOnlyWebM[] = "video/webm; codecs=\"vp8\"";
-static const char kMP4[] = "video/mp4; codecs=\"avc1.4D4041,mp4a.40.2\"";
-
-// Key used to encrypt video track in test file "bear-320x240-encrypted.webm".
-static const uint8 kSecretKey[] = {
- 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
- 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
-};
-
-static const int kAppendWholeFile = -1;
-
-// Helper class that emulates calls made on the ChunkDemuxer by the
-// Media Source API.
-class MockMediaSource {
- public:
- MockMediaSource(const std::string& filename, const std::string& mimetype,
- int initial_append_size)
- : file_path_(GetTestDataFilePath(filename)),
- current_position_(0),
- initial_append_size_(initial_append_size),
- mimetype_(mimetype) {
- chunk_demuxer_ = new ChunkDemuxer(
- base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)),
- base::Bind(&MockMediaSource::DemuxerNeedKey, base::Unretained(this)),
- LogCB());
-
- file_data_ = ReadTestDataFile(filename);
-
- if (initial_append_size_ == kAppendWholeFile)
- initial_append_size_ = file_data_->GetDataSize();
-
- DCHECK_GT(initial_append_size_, 0);
- DCHECK_LE(initial_append_size_, file_data_->GetDataSize());
- }
-
- virtual ~MockMediaSource() {}
-
- const scoped_refptr<ChunkDemuxer>& demuxer() const { return chunk_demuxer_; }
- void set_decryptor_client(DecryptorClient* decryptor_client) {
- decryptor_client_ = decryptor_client;
- }
-
- void Seek(int new_position, int seek_append_size) {
- chunk_demuxer_->StartWaitingForSeek();
-
- chunk_demuxer_->Abort(kSourceId);
-
- DCHECK_GE(new_position, 0);
- DCHECK_LT(new_position, file_data_->GetDataSize());
- current_position_ = new_position;
-
- AppendData(seek_append_size);
- }
-
- void AppendData(int size) {
- DCHECK(chunk_demuxer_.get());
- DCHECK_LT(current_position_, file_data_->GetDataSize());
- DCHECK_LE(current_position_ + size, file_data_->GetDataSize());
- CHECK(chunk_demuxer_->AppendData(
- kSourceId, file_data_->GetData() + current_position_, size));
- current_position_ += size;
- }
-
- void AppendAtTime(const base::TimeDelta& timestampOffset,
- const uint8* pData, int size) {
- CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, timestampOffset));
- CHECK(chunk_demuxer_->AppendData(kSourceId, pData, size));
- CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, base::TimeDelta()));
- }
-
- void EndOfStream() {
- chunk_demuxer_->EndOfStream(PIPELINE_OK);
- }
-
- void Abort() {
- if (!chunk_demuxer_.get())
- return;
- chunk_demuxer_->Shutdown();
- chunk_demuxer_ = NULL;
- }
-
- void DemuxerOpened() {
- MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(&MockMediaSource::DemuxerOpenedTask,
- base::Unretained(this)));
- }
-
- void DemuxerOpenedTask() {
- size_t semicolon = mimetype_.find(";");
- std::string type = mimetype_.substr(0, semicolon);
- size_t quote1 = mimetype_.find("\"");
- size_t quote2 = mimetype_.find("\"", quote1 + 1);
- std::string codecStr = mimetype_.substr(quote1 + 1, quote2 - quote1 - 1);
- std::vector<std::string> codecs;
- Tokenize(codecStr, ",", &codecs);
-
- CHECK_EQ(chunk_demuxer_->AddId(kSourceId, type, codecs), ChunkDemuxer::kOk);
- AppendData(initial_append_size_);
- }
-
- void DemuxerNeedKey(const std::string& type,
- scoped_array<uint8> init_data, int init_data_size) {
- DCHECK(init_data.get());
- DCHECK_GT(init_data_size, 0);
- DCHECK(decryptor_client_);
- decryptor_client_->NeedKey("", "", type, init_data.Pass(), init_data_size);
- }
-
- private:
- FilePath file_path_;
- scoped_refptr<DecoderBuffer> file_data_;
- int current_position_;
- int initial_append_size_;
- std::string mimetype_;
- scoped_refptr<ChunkDemuxer> chunk_demuxer_;
- DecryptorClient* decryptor_client_;
-};
-
-class FakeDecryptorClient : public DecryptorClient {
- public:
- FakeDecryptorClient() : decryptor_(this) {}
-
- AesDecryptor* decryptor() {
- return &decryptor_;
- }
-
- // DecryptorClient implementation.
- virtual void KeyAdded(const std::string& key_system,
- const std::string& session_id) {
- EXPECT_EQ(kClearKeySystem, key_system);
- EXPECT_FALSE(session_id.empty());
- }
-
- virtual void KeyError(const std::string& key_system,
- const std::string& session_id,
- AesDecryptor::KeyError error_code,
- int system_code) {
- NOTIMPLEMENTED();
- }
-
- virtual void KeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url) {
- EXPECT_EQ(kClearKeySystem, key_system);
- EXPECT_FALSE(session_id.empty());
- EXPECT_FALSE(message.empty());
-
- current_key_system_ = key_system;
- current_session_id_ = session_id;
- }
-
- virtual void NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_length) {
- current_key_system_ = key_system;
- current_session_id_ = session_id;
-
- // When NeedKey is called from the demuxer, the |key_system| will be empty.
- // In this case, we need to call GenerateKeyRequest() to initialize a
- // session (which will call KeyMessage).
- if (current_key_system_.empty()) {
- DCHECK(current_session_id_.empty());
- EXPECT_TRUE(decryptor_.GenerateKeyRequest(
- kClearKeySystem, type, kInitData, arraysize(kInitData)));
- }
-
- EXPECT_FALSE(current_key_system_.empty());
- EXPECT_FALSE(current_session_id_.empty());
- decryptor_.AddKey(current_key_system_, kSecretKey, arraysize(kSecretKey),
- init_data.get(), init_data_length, current_session_id_);
- }
-
- private:
- AesDecryptor decryptor_;
- std::string current_key_system_;
- std::string current_session_id_;
-};
-
-class PipelineIntegrationTest
- : public testing::Test,
- public PipelineIntegrationTestBase {
- public:
- void StartPipelineWithMediaSource(MockMediaSource* source) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
- .Times(AtMost(1));
- pipeline_->Start(
- CreateFilterCollection(source->demuxer(), NULL),
- base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
- base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
- QuitOnStatusCB(PIPELINE_OK),
- base::Bind(&PipelineIntegrationTest::OnBufferingState,
- base::Unretained(this)));
-
- message_loop_.Run();
- }
-
- void StartPipelineWithEncryptedMedia(
- MockMediaSource* source,
- FakeDecryptorClient* encrypted_media) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
- .Times(AtMost(1));
- pipeline_->Start(
- CreateFilterCollection(source->demuxer(), encrypted_media->decryptor()),
- base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
- base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
- QuitOnStatusCB(PIPELINE_OK),
- base::Bind(&PipelineIntegrationTest::OnBufferingState,
- base::Unretained(this)));
-
- source->set_decryptor_client(encrypted_media);
-
- message_loop_.Run();
- }
-
- // Verifies that seeking works properly for ChunkDemuxer when the
- // seek happens while there is a pending read on the ChunkDemuxer
- // and no data is available.
- bool TestSeekDuringRead(const std::string& filename,
- const std::string& mimetype,
- int initial_append_size,
- base::TimeDelta start_seek_time,
- base::TimeDelta seek_time,
- int seek_file_position,
- int seek_append_size) {
- MockMediaSource source(filename, mimetype, initial_append_size);
- StartPipelineWithMediaSource(&source);
-
- if (pipeline_status_ != PIPELINE_OK)
- return false;
-
- Play();
- if (!WaitUntilCurrentTimeIsAfter(start_seek_time))
- return false;
-
- source.Seek(seek_file_position, seek_append_size);
- if (!Seek(seek_time))
- return false;
-
- source.EndOfStream();
-
- source.Abort();
- Stop();
- return true;
- }
-};
-
-
-TEST_F(PipelineIntegrationTest, BasicPlayback) {
- ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
-}
-
-TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) {
- ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"),
- PIPELINE_OK, true));
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
-
- EXPECT_EQ(GetVideoHash(), "f0be120a90a811506777c99a2cdf7cc1");
- EXPECT_EQ(GetAudioHash(), "5699a4415b620e45b9d0aae531c9df76");
-}
-
-TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) {
- MockMediaSource source("bear-320x240.webm", kWebM, 219229);
- StartPipelineWithMediaSource(&source);
- source.EndOfStream();
- ASSERT_EQ(pipeline_status_, PIPELINE_OK);
-
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().size(), 1u);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds(), 0);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds(), 2737);
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
- source.Abort();
- Stop();
-}
-
-TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_WebM) {
- MockMediaSource source("bear-320x240-16x9-aspect.webm", kWebM,
- kAppendWholeFile);
- StartPipelineWithMediaSource(&source);
-
- scoped_refptr<DecoderBuffer> second_file =
- ReadTestDataFile("bear-640x360.webm");
-
- source.AppendAtTime(base::TimeDelta::FromSeconds(2),
- second_file->GetData(), second_file->GetDataSize());
-
- source.EndOfStream();
- ASSERT_EQ(pipeline_status_, PIPELINE_OK);
-
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().size(), 1u);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds(), 0);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds(), 4763);
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
- source.Abort();
- Stop();
-}
-
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
-TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_MP4) {
- MockMediaSource source("bear.640x360_dash.mp4", kMP4, kAppendWholeFile);
- StartPipelineWithMediaSource(&source);
-
- scoped_refptr<DecoderBuffer> second_file =
- ReadTestDataFile("bear.1280x720_dash.mp4");
-
- source.AppendAtTime(base::TimeDelta::FromSeconds(2),
- second_file->GetData(), second_file->GetDataSize());
-
- source.EndOfStream();
- ASSERT_EQ(pipeline_status_, PIPELINE_OK);
-
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().size(), 1u);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds(), 0);
- EXPECT_EQ(pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds(), 4736);
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
- source.Abort();
- Stop();
-}
-#endif
-
-TEST_F(PipelineIntegrationTest, BasicPlayback_16x9AspectRatio) {
- ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-16x9-aspect.webm"),
- PIPELINE_OK));
- Play();
- ASSERT_TRUE(WaitUntilOnEnded());
-}
-
-TEST_F(PipelineIntegrationTest, EncryptedPlayback) {
- MockMediaSource source("bear-320x240-encrypted.webm", kWebM, 219816);
- FakeDecryptorClient encrypted_media;
- StartPipelineWithEncryptedMedia(&source, &encrypted_media);
-
- source.EndOfStream();
- ASSERT_EQ(PIPELINE_OK, pipeline_status_);
-
- Play();
-
- ASSERT_TRUE(WaitUntilOnEnded());
- source.Abort();
- Stop();
-}
-
-// TODO(acolwell): Fix flakiness http://crbug.com/117921
-TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePaused) {
- ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
-
- base::TimeDelta duration(pipeline_->GetMediaDuration());
- base::TimeDelta start_seek_time(duration / 4);
- base::TimeDelta seek_time(duration * 3 / 4);
-
- Play();
- ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(start_seek_time));
- Pause();
- ASSERT_TRUE(Seek(seek_time));
- EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);
- Play();
- ASSERT_TRUE(WaitUntilOnEnded());
-
- // Make sure seeking after reaching the end works as expected.
- Pause();
- ASSERT_TRUE(Seek(seek_time));
- EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);
- Play();
- ASSERT_TRUE(WaitUntilOnEnded());
-}
-
-// TODO(acolwell): Fix flakiness http://crbug.com/117921
-TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePlaying) {
- ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
-
- base::TimeDelta duration(pipeline_->GetMediaDuration());
- base::TimeDelta start_seek_time(duration / 4);
- base::TimeDelta seek_time(duration * 3 / 4);
-
- Play();
- ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(start_seek_time));
- ASSERT_TRUE(Seek(seek_time));
- EXPECT_GE(pipeline_->GetMediaTime(), seek_time);
- ASSERT_TRUE(WaitUntilOnEnded());
-
- // Make sure seeking after reaching the end works as expected.
- ASSERT_TRUE(Seek(seek_time));
- EXPECT_GE(pipeline_->GetMediaTime(), seek_time);
- ASSERT_TRUE(WaitUntilOnEnded());
-}
-
-// Verify audio decoder & renderer can handle aborted demuxer reads.
-TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) {
- ASSERT_TRUE(TestSeekDuringRead("bear-320x240-audio-only.webm", kAudioOnlyWebM,
- 8192,
- base::TimeDelta::FromMilliseconds(464),
- base::TimeDelta::FromMilliseconds(617),
- 0x10CA, 19730));
-}
-
-// Verify video decoder & renderer can handle aborted demuxer reads.
-TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) {
- ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", kVideoOnlyWebM,
- 32768,
- base::TimeDelta::FromMilliseconds(200),
- base::TimeDelta::FromMilliseconds(1668),
- 0x1C896, 65536));
-}
-
-} // namespace media
diff --git a/src/media/filters/pipeline_integration_test_base.cc b/src/media/filters/pipeline_integration_test_base.cc
deleted file mode 100644
index 1829e0a..0000000
--- a/src/media/filters/pipeline_integration_test_base.cc
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/pipeline_integration_test_base.h"
-
-#include "base/bind.h"
-#include "media/base/media_log.h"
-#include "media/filters/audio_renderer_impl.h"
-#include "media/filters/chunk_demuxer.h"
-#include "media/filters/ffmpeg_audio_decoder.h"
-#include "media/filters/ffmpeg_demuxer.h"
-#include "media/filters/ffmpeg_video_decoder.h"
-#include "media/filters/file_data_source.h"
-
-using ::testing::AnyNumber;
-using ::testing::AtMost;
-
-namespace media {
-
-const char kNullHash[] = "d41d8cd98f00b204e9800998ecf8427e";
-
-PipelineIntegrationTestBase::PipelineIntegrationTestBase()
- : hashing_enabled_(false),
- pipeline_(new Pipeline(message_loop_.message_loop_proxy(),
- new MediaLog())),
- ended_(false),
- pipeline_status_(PIPELINE_OK) {
- base::MD5Init(&md5_context_);
- EXPECT_CALL(*this, OnSetOpaque(true)).Times(AnyNumber());
-}
-
-PipelineIntegrationTestBase::~PipelineIntegrationTestBase() {
- if (!pipeline_->IsRunning())
- return;
-
- Stop();
-}
-
-void PipelineIntegrationTestBase::OnStatusCallback(
- PipelineStatus status) {
- pipeline_status_ = status;
- message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-void PipelineIntegrationTestBase::OnStatusCallbackChecked(
- PipelineStatus expected_status,
- PipelineStatus status) {
- EXPECT_EQ(expected_status, status);
- OnStatusCallback(status);
-}
-
-PipelineStatusCB PipelineIntegrationTestBase::QuitOnStatusCB(
- PipelineStatus expected_status) {
- return base::Bind(&PipelineIntegrationTestBase::OnStatusCallbackChecked,
- base::Unretained(this),
- expected_status);
-}
-
-void PipelineIntegrationTestBase::OnEnded(PipelineStatus status) {
- DCHECK_EQ(status, PIPELINE_OK);
- DCHECK(!ended_);
- ended_ = true;
- pipeline_status_ = status;
- message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-bool PipelineIntegrationTestBase::WaitUntilOnEnded() {
- if (ended_)
- return (pipeline_status_ == PIPELINE_OK);
- message_loop_.Run();
- EXPECT_TRUE(ended_);
- return ended_ && (pipeline_status_ == PIPELINE_OK);
-}
-
-PipelineStatus PipelineIntegrationTestBase::WaitUntilEndedOrError() {
- if (ended_ || pipeline_status_ != PIPELINE_OK)
- return pipeline_status_;
- message_loop_.Run();
- return pipeline_status_;
-}
-
-void PipelineIntegrationTestBase::OnError(PipelineStatus status) {
- DCHECK_NE(status, PIPELINE_OK);
- pipeline_status_ = status;
- message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
-}
-
-bool PipelineIntegrationTestBase::Start(const FilePath& file_path,
- PipelineStatus expected_status) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
- .Times(AtMost(1));
- pipeline_->Start(
- CreateFilterCollection(file_path),
- base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
- base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
- QuitOnStatusCB(expected_status),
- base::Bind(&PipelineIntegrationTestBase::OnBufferingState,
- base::Unretained(this)));
- message_loop_.Run();
- return (pipeline_status_ == PIPELINE_OK);
-}
-
-bool PipelineIntegrationTestBase::Start(const FilePath& file_path,
- PipelineStatus expected_status,
- bool hashing_enabled) {
- hashing_enabled_ = hashing_enabled;
- return Start(file_path, expected_status);
-}
-
-bool PipelineIntegrationTestBase::Start(const FilePath& file_path) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
- .Times(AtMost(1));
- pipeline_->Start(
- CreateFilterCollection(file_path),
- base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
- base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
- base::Bind(&PipelineIntegrationTestBase::OnStatusCallback,
- base::Unretained(this)),
- base::Bind(&PipelineIntegrationTestBase::OnBufferingState,
- base::Unretained(this)));
- message_loop_.Run();
- return (pipeline_status_ == PIPELINE_OK);
-}
-
-void PipelineIntegrationTestBase::Play() {
- pipeline_->SetPlaybackRate(1);
-}
-
-void PipelineIntegrationTestBase::Pause() {
- pipeline_->SetPlaybackRate(0);
-}
-
-bool PipelineIntegrationTestBase::Seek(base::TimeDelta seek_time) {
- ended_ = false;
-
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted));
- pipeline_->Seek(seek_time, QuitOnStatusCB(PIPELINE_OK));
- message_loop_.Run();
- return (pipeline_status_ == PIPELINE_OK);
-}
-
-void PipelineIntegrationTestBase::Stop() {
- DCHECK(pipeline_->IsRunning());
- pipeline_->Stop(MessageLoop::QuitClosure());
- message_loop_.Run();
-}
-
-void PipelineIntegrationTestBase::QuitAfterCurrentTimeTask(
- const base::TimeDelta& quit_time) {
- if (pipeline_->GetMediaTime() >= quit_time ||
- pipeline_status_ != PIPELINE_OK) {
- message_loop_.Quit();
- return;
- }
-
- message_loop_.PostDelayedTask(
- FROM_HERE,
- base::Bind(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
- base::Unretained(this), quit_time),
- base::TimeDelta::FromMilliseconds(10));
-}
-
-bool PipelineIntegrationTestBase::WaitUntilCurrentTimeIsAfter(
- const base::TimeDelta& wait_time) {
- DCHECK(pipeline_->IsRunning());
- DCHECK_GT(pipeline_->GetPlaybackRate(), 0);
- DCHECK(wait_time <= pipeline_->GetMediaDuration());
-
- message_loop_.PostDelayedTask(
- FROM_HERE,
- base::Bind(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
- base::Unretained(this),
- wait_time),
- base::TimeDelta::FromMilliseconds(10));
- message_loop_.Run();
- return (pipeline_status_ == PIPELINE_OK);
-}
-
-scoped_ptr<FilterCollection>
-PipelineIntegrationTestBase::CreateFilterCollection(const FilePath& file_path) {
- scoped_refptr<FileDataSource> data_source = new FileDataSource();
- CHECK(data_source->Initialize(file_path));
- return CreateFilterCollection(
- new FFmpegDemuxer(message_loop_.message_loop_proxy(), data_source),
- NULL);
-}
-
-scoped_ptr<FilterCollection>
-PipelineIntegrationTestBase::CreateFilterCollection(
- const scoped_refptr<Demuxer>& demuxer,
- Decryptor* decryptor) {
- scoped_ptr<FilterCollection> collection(new FilterCollection());
- collection->SetDemuxer(demuxer);
- scoped_refptr<AudioDecoder> audio_decoder = new FFmpegAudioDecoder(
- message_loop_.message_loop_proxy());
- scoped_refptr<VideoDecoder> video_decoder = new FFmpegVideoDecoder(
- message_loop_.message_loop_proxy());
- collection->GetAudioDecoders()->push_back(audio_decoder);
- collection->GetVideoDecoders()->push_back(video_decoder);
-
- // Disable frame dropping if hashing is enabled.
- renderer_ = new VideoRendererBase(
- message_loop_.message_loop_proxy(),
- base::Bind(&PipelineIntegrationTestBase::SetDecryptor,
- base::Unretained(this), decryptor),
- base::Bind(&PipelineIntegrationTestBase::OnVideoRendererPaint,
- base::Unretained(this)),
- base::Bind(&PipelineIntegrationTestBase::OnSetOpaque,
- base::Unretained(this)),
- !hashing_enabled_);
- collection->AddVideoRenderer(renderer_);
- audio_sink_ = new NullAudioSink();
- if (hashing_enabled_)
- audio_sink_->StartAudioHashForTesting();
- scoped_refptr<AudioRendererImpl> audio_renderer(new AudioRendererImpl(
- audio_sink_,
- base::Bind(&PipelineIntegrationTestBase::SetDecryptor,
- base::Unretained(this), decryptor)));
- // Disable underflow if hashing is enabled.
- if (hashing_enabled_)
- audio_renderer->DisableUnderflowForTesting();
- collection->AddAudioRenderer(audio_renderer);
- return collection.Pass();
-}
-
-void PipelineIntegrationTestBase::SetDecryptor(
- Decryptor* decryptor,
- const DecryptorReadyCB& decryptor_ready_cb) {
- decryptor_ready_cb.Run(decryptor);
-}
-
-void PipelineIntegrationTestBase::OnVideoRendererPaint() {
- if (!hashing_enabled_)
- return;
- scoped_refptr<VideoFrame> frame;
- renderer_->GetCurrentFrame(&frame);
- if (frame)
- frame->HashFrameForTesting(&md5_context_);
- renderer_->PutCurrentFrame(frame);
-}
-
-std::string PipelineIntegrationTestBase::GetVideoHash() {
- DCHECK(hashing_enabled_);
- base::MD5Digest digest;
- base::MD5Final(&digest, &md5_context_);
- return base::MD5DigestToBase16(digest);
-}
-
-std::string PipelineIntegrationTestBase::GetAudioHash() {
- DCHECK(hashing_enabled_);
- return audio_sink_->GetAudioHashForTesting();
-}
-
-} // namespace media
diff --git a/src/media/filters/pipeline_integration_test_base.h b/src/media/filters/pipeline_integration_test_base.h
deleted file mode 100644
index bc67e80..0000000
--- a/src/media/filters/pipeline_integration_test_base.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_PIPELINE_INTEGRATION_TEST_BASE_H_
-#define MEDIA_FILTERS_PIPELINE_INTEGRATION_TEST_BASE_H_
-
-#include "base/message_loop.h"
-#include "base/md5.h"
-#include "media/audio/null_audio_sink.h"
-#include "media/base/filter_collection.h"
-#include "media/base/pipeline.h"
-#include "media/filters/chunk_demuxer.h"
-#include "media/filters/video_renderer_base.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-class FilePath;
-
-namespace media {
-
-class Decryptor;
-
-// Empty MD5 hash string. Used to verify empty audio or video tracks.
-extern const char kNullHash[];
-
-// Integration tests for Pipeline. Real demuxers, real decoders, and
-// base renderer implementations are used to verify pipeline functionality. The
-// renderers used in these tests rely heavily on the AudioRendererBase &
-// VideoRendererBase implementations which contain a majority of the code used
-// in the real AudioRendererImpl & SkCanvasVideoRenderer implementations used in
-// the browser. The renderers in this test don't actually write data to a
-// display or audio device. Both of these devices are simulated since they have
-// little effect on verifying pipeline behavior and allow tests to run faster
-// than real-time.
-class PipelineIntegrationTestBase {
- public:
- PipelineIntegrationTestBase();
- virtual ~PipelineIntegrationTestBase();
-
- bool WaitUntilOnEnded();
- PipelineStatus WaitUntilEndedOrError();
- bool Start(const FilePath& file_path, PipelineStatus expected_status);
- // Enable playback with audio and video hashing enabled. Frame dropping and
- // audio underflow will be disabled to ensure consistent hashes.
- bool Start(const FilePath& file_path, PipelineStatus expected_status,
- bool hashing_enabled);
- // Initialize the pipeline and ignore any status updates. Useful for testing
- // invalid audio/video clips which don't have deterministic results.
- bool Start(const FilePath& file_path);
-
- void Play();
- void Pause();
- bool Seek(base::TimeDelta seek_time);
- void Stop();
- bool WaitUntilCurrentTimeIsAfter(const base::TimeDelta& wait_time);
- scoped_ptr<FilterCollection> CreateFilterCollection(
- const FilePath& file_path);
-
- // Returns the MD5 hash of all video frames seen. Should only be called once
- // after playback completes. First time hashes should be generated with
- // --video-threads=1 to ensure correctness. Pipeline must have been started
- // with hashing enabled.
- std::string GetVideoHash();
-
- // Returns the MD5 hash of all audio frames seen. Should only be called once
- // after playback completes. Pipeline must have been started with hashing
- // enabled.
- std::string GetAudioHash();
-
- protected:
- MessageLoop message_loop_;
- base::MD5Context md5_context_;
- bool hashing_enabled_;
- scoped_refptr<Pipeline> pipeline_;
- scoped_refptr<VideoRendererBase> renderer_;
- scoped_refptr<NullAudioSink> audio_sink_;
- bool ended_;
- PipelineStatus pipeline_status_;
-
- void OnStatusCallbackChecked(PipelineStatus expected_status,
- PipelineStatus status);
- void OnStatusCallback(PipelineStatus status);
- PipelineStatusCB QuitOnStatusCB(PipelineStatus expected_status);
- void OnEnded(PipelineStatus status);
- void OnError(PipelineStatus status);
- void QuitAfterCurrentTimeTask(const base::TimeDelta& quit_time);
- scoped_ptr<FilterCollection> CreateFilterCollection(
- const scoped_refptr<Demuxer>& demuxer, Decryptor* decryptor);
- void SetDecryptor(Decryptor* decryptor,
- const DecryptorReadyCB& decryptor_ready_cb);
- void OnVideoRendererPaint();
-
- MOCK_METHOD1(OnSetOpaque, void(bool));
- MOCK_METHOD1(OnBufferingState, void(Pipeline::BufferingState));
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_PIPELINE_INTEGRATION_TEST_BASE_H_
diff --git a/src/media/filters/shell_au.cc b/src/media/filters/shell_au.cc
deleted file mode 100644
index 169e88b..0000000
--- a/src/media/filters/shell_au.cc
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/base/endian_util.h"
-#include "media/filters/shell_au.h"
-#include "media/filters/shell_parser.h"
-
-namespace {
-
-using namespace media;
-
-bool ReadBytes(uint64 offset,
- size_t size,
- uint8* buffer,
- ShellDataSourceReader* reader) {
- if (reader->BlockingRead(offset, size, buffer) != size) {
- DLOG(ERROR) << "unable to download AU";
- return false;
- }
- return true;
-}
-
-// ==== ShellEndOfStreamAU ==================================================
-
-class ShellEndOfStreamAU : public media::ShellAU {
- public:
- ShellEndOfStreamAU(Type type, TimeDelta timestamp)
- : type_(type), timestamp_(timestamp), duration_(kInfiniteDuration()) {}
-
- private:
- bool IsEndOfStream() const OVERRIDE { return true; }
- bool IsValid() const OVERRIDE { return true; }
- bool Read(ShellDataSourceReader* reader,
- media::DecoderBuffer* buffer) OVERRIDE {
- NOTREACHED();
- return false;
- }
- Type GetType() const OVERRIDE { return type_; }
- bool IsKeyframe() const OVERRIDE {
- NOTREACHED();
- return false;
- }
- bool AddPrepend() const OVERRIDE {
- NOTREACHED();
- return false;
- }
- size_t GetSize() const OVERRIDE { return 0; }
- size_t GetMaxSize() const OVERRIDE { return 0; }
- TimeDelta GetTimestamp() const OVERRIDE { return timestamp_; }
- TimeDelta GetDuration() const OVERRIDE { return duration_; }
- void SetDuration(TimeDelta duration) OVERRIDE { duration_ = duration; }
- void SetTimestamp(TimeDelta timestamp) OVERRIDE { timestamp_ = timestamp; }
-
- Type type_;
- TimeDelta timestamp_;
- TimeDelta duration_;
-};
-
-// ==== ShellAudioAU =======================================================
-
-class ShellAudioAU : public media::ShellAU {
- public:
- ShellAudioAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser);
-
- private:
- bool IsEndOfStream() const OVERRIDE { return false; }
- bool IsValid() const OVERRIDE {
- return offset_ != 0 && size_ != 0 && timestamp_ != media::kNoTimestamp();
- }
- bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) OVERRIDE;
- Type GetType() const OVERRIDE { return media::DemuxerStream::AUDIO; }
- bool IsKeyframe() const OVERRIDE { return is_keyframe_; }
- bool AddPrepend() const OVERRIDE { return true; }
- size_t GetSize() const OVERRIDE { return size_; }
- size_t GetMaxSize() const OVERRIDE { return size_ + prepend_size_; }
- TimeDelta GetTimestamp() const OVERRIDE { return timestamp_; }
- TimeDelta GetDuration() const OVERRIDE { return duration_; }
- void SetDuration(TimeDelta duration) OVERRIDE { duration_ = duration; }
- void SetTimestamp(TimeDelta timestamp) OVERRIDE { timestamp_ = timestamp; }
-
- uint64 offset_;
- size_t size_;
- size_t prepend_size_;
- bool is_keyframe_;
- TimeDelta timestamp_;
- TimeDelta duration_;
- media::ShellParser* parser_;
-};
-
-ShellAudioAU::ShellAudioAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser)
- : offset_(offset),
- size_(size),
- prepend_size_(prepend_size),
- is_keyframe_(is_keyframe),
- timestamp_(timestamp),
- duration_(duration),
- parser_(parser) {}
-
-bool ShellAudioAU::Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) {
- DCHECK_LE(size_ + prepend_size_, buffer->GetDataSize());
- if (!ReadBytes(offset_, size_, buffer->GetWritableData() + prepend_size_,
- reader))
- return false;
-
- if (!parser_->Prepend(this, buffer)) {
- DLOG(ERROR) << "prepend fail";
- return false;
- }
-
- return true;
-}
-
-// ==== ShellVideoAU =======================================================
-
-class ShellVideoAU : public media::ShellAU {
- public:
- ShellVideoAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- uint8 length_of_nalu_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser);
-
- private:
- bool IsEndOfStream() const OVERRIDE { return false; }
- bool IsValid() const OVERRIDE {
- return offset_ != 0 && size_ != 0 && timestamp_ != media::kNoTimestamp();
- }
- bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) OVERRIDE;
- Type GetType() const OVERRIDE { return media::DemuxerStream::VIDEO; }
- bool IsKeyframe() const OVERRIDE { return is_keyframe_; }
- bool AddPrepend() const OVERRIDE { return is_keyframe_; }
- size_t GetSize() const OVERRIDE { return size_; }
- size_t GetMaxSize() const OVERRIDE {
- // TODO : This code is a proof of concept. It should be fixed
- // with more reasonable value once we have enough data.
- return size_ + prepend_size_ + size_ / 1024 + 1024;
- }
- TimeDelta GetTimestamp() const OVERRIDE { return timestamp_; }
- TimeDelta GetDuration() const OVERRIDE { return duration_; }
- void SetDuration(TimeDelta duration) OVERRIDE { duration_ = duration; }
- void SetTimestamp(TimeDelta timestamp) OVERRIDE { timestamp_ = timestamp; }
-
- uint64 offset_;
- size_t size_;
- size_t prepend_size_;
- uint8 length_of_nalu_size_;
- bool is_keyframe_;
- TimeDelta timestamp_;
- TimeDelta duration_;
- media::ShellParser* parser_;
-};
-
-ShellVideoAU::ShellVideoAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- uint8 length_of_nalu_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser)
- : offset_(offset),
- size_(size),
- prepend_size_(prepend_size),
- length_of_nalu_size_(length_of_nalu_size),
- is_keyframe_(is_keyframe),
- timestamp_(timestamp),
- duration_(duration),
- parser_(parser) {
- CHECK_LE(length_of_nalu_size_, 4);
- CHECK_NE(length_of_nalu_size_, 3);
-}
-
-bool ShellVideoAU::Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) {
- size_t au_left = size_; // bytes left in the AU
- uint64 au_offset = offset_; // offset to read in the reader
- size_t buf_left = buffer->GetAllocatedSize(); // bytes left in the buffer
- // The current write position in the buffer
- uint8* buf = buffer->GetWritableData() + prepend_size_;
-
- // The NALU is stored as [size][data][size][data].... We are going to
- // transform it into [start code][data][start code][data]....
- // The length of size is indicated by length_of_nalu_size_
- while (au_left >= length_of_nalu_size_ && buf_left >= kAnnexBStartCode) {
- uint8 size_buf[4];
- uint32 nal_size;
-
- // Store [start code]
- endian_util::store_uint32_big_endian(kAnnexBStartCode, buf);
- buf += kAnnexBStartCodeSize;
- buf_left -= kAnnexBStartCodeSize;
-
- // Read [size]
- if (!ReadBytes(au_offset, length_of_nalu_size_, size_buf, reader))
- return false;
-
- au_offset += length_of_nalu_size_;
- au_left -= length_of_nalu_size_;
-
- if (length_of_nalu_size_ == 1) {
- nal_size = size_buf[0];
- } else if (length_of_nalu_size_ == 2) {
- nal_size = endian_util::load_uint16_big_endian(size_buf);
- } else {
- DCHECK_EQ(length_of_nalu_size_, 4);
- nal_size = endian_util::load_uint32_big_endian(size_buf);
- }
-
- if (au_left < nal_size || buf_left < nal_size)
- break;
-
- // Read the [data] from reader into buf
- if (!ReadBytes(au_offset, nal_size, buf, reader))
- return false;
-
- buf += nal_size;
- au_offset += nal_size;
- au_left -= nal_size;
- buf_left -= nal_size;
- }
-
- if (au_left != 0) {
- DLOG(ERROR) << "corrupted NALU";
- return false;
- }
-
- size_ = buf - buffer->GetWritableData();
- buffer->ShrinkTo(size_);
-
- if (!parser_->Prepend(this, buffer)) {
- DLOG(ERROR) << "prepend fail";
- return false;
- }
-
- return true;
-}
-
-} // namespace
-
-namespace media {
-
-// ==== ShellAU ================================================================
-
-ShellAU::ShellAU() {}
-
-ShellAU::~ShellAU() {}
-
-// static
-scoped_refptr<ShellAU> ShellAU::CreateEndOfStreamAU(DemuxerStream::Type type,
- TimeDelta timestamp) {
- return new ShellEndOfStreamAU(type, timestamp);
-}
-
-// static
-scoped_refptr<ShellAU> ShellAU::CreateAudioAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser) {
- return new ShellAudioAU(offset, size, prepend_size, is_keyframe, timestamp,
- duration, parser);
-}
-
-// static
-scoped_refptr<ShellAU> ShellAU::CreateVideoAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- uint8 length_of_nalu_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser) {
- return new ShellVideoAU(offset, size, prepend_size, length_of_nalu_size,
- is_keyframe, timestamp, duration, parser);
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_au.h b/src/media/filters/shell_au.h
deleted file mode 100644
index 0d8ca39..0000000
--- a/src/media/filters/shell_au.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AU_H_
-#define MEDIA_FILTERS_SHELL_AU_H_
-
-#include "base/memory/ref_counted.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_data_source_reader.h"
-
-namespace media {
-
-class ShellParser;
-
-// AnnexB start code is 4 bytes 0x00000001 big-endian
-static const int kAnnexBStartCodeSize = 4;
-static const uint32 kAnnexBStartCode = 0x00000001;
-
-// The basic unit of currency between ShellDemuxer and ShellParser, the ShellAU
-// defines all needed information for a given AccessUnit (Frame) of encoded
-// media data.
-class ShellAU : public base::RefCountedThreadSafe<ShellAU> {
- public:
- typedef base::TimeDelta TimeDelta;
- typedef DemuxerStream::Type Type;
-
- static scoped_refptr<ShellAU> CreateEndOfStreamAU(Type type,
- TimeDelta timestamp);
- static scoped_refptr<ShellAU> CreateAudioAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser);
- static scoped_refptr<ShellAU> CreateVideoAU(uint64 offset,
- size_t size,
- size_t prepend_size,
- uint8 length_of_nalu_size,
- bool is_keyframe,
- TimeDelta timestamp,
- TimeDelta duration,
- ShellParser* parser);
-
- virtual bool IsEndOfStream() const = 0;
- virtual bool IsValid() const = 0;
- // Read an AU from reader to buffer and also do all the necessary operations
- // like prepending head to make it ready to decode.
- virtual bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) = 0;
- virtual Type GetType() const = 0;
- virtual bool IsKeyframe() const = 0;
- virtual bool AddPrepend() const = 0;
- // Get the size of this AU, it is always no larger than its max size.
- virtual size_t GetSize() const = 0;
- // Get the max required buffer of this AU
- virtual size_t GetMaxSize() const = 0;
- virtual TimeDelta GetTimestamp() const = 0;
- virtual TimeDelta GetDuration() const = 0;
- virtual void SetDuration(TimeDelta duration) = 0;
- virtual void SetTimestamp(TimeDelta timestamp) = 0;
-
- protected:
- friend class base::RefCountedThreadSafe<ShellAU>;
-
- ShellAU();
- virtual ~ShellAU();
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AU_H_
diff --git a/src/media/filters/shell_audio_decoder_impl.cc b/src/media/filters/shell_audio_decoder_impl.cc
deleted file mode 100644
index 2141df3..0000000
--- a/src/media/filters/shell_audio_decoder_impl.cc
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_audio_decoder_impl.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/shell_media_statistics.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-using base::Time;
-using base::TimeDelta;
-
-const size_t kFramesPerAccessUnit = mp4::AAC::kFramesPerAccessUnit;
-
-namespace {
-
-bool ValidateConfig(const AudioDecoderConfig& config) {
- if (!config.IsValidConfig()) {
- NOTREACHED() << "Invalid audio stream -"
- << " codec: " << config.codec()
- << " channel layout: " << config.channel_layout()
- << " bits per channel: " << config.bits_per_channel()
- << " samples per second: " << config.samples_per_second();
- return false;
- }
-
- // check this is a config we can decode and play back
- if (config.codec() != kCodecAAC) {
- NOTREACHED();
- return false;
- }
-
- return true;
-}
-
-} // namespace
-
-//==============================================================================
-// ShellAudioDecoderImpl
-//
-ShellAudioDecoderImpl::ShellAudioDecoderImpl(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- ShellRawAudioDecoderFactory* raw_audio_decoder_factory)
- : message_loop_(message_loop),
- raw_audio_decoder_factory_(raw_audio_decoder_factory),
- demuxer_read_and_decode_in_progress_(false),
- samples_per_second_(0),
- num_channels_(0) {
- DCHECK(message_loop_);
- DCHECK(raw_audio_decoder_factory);
-}
-
-ShellAudioDecoderImpl::~ShellAudioDecoderImpl() {
- DCHECK(read_cb_.is_null());
- DCHECK(reset_cb_.is_null());
-}
-
-void ShellAudioDecoderImpl::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- TRACE_EVENT0("media_stack", "ShellAudioDecoderImpl::Initialize()");
-
- DCHECK(stream);
- DCHECK(!status_cb.is_null());
- DCHECK(!statistics_cb.is_null());
- DCHECK(!demuxer_stream_);
-
- demuxer_stream_ = stream;
- statistics_cb_ = statistics_cb;
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
-
- // check config for support
- if (!ValidateConfig(config)) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- samples_per_second_ = config.samples_per_second();
- num_channels_ = ChannelLayoutToChannelCount(config.channel_layout());
-
-#if __SAVE_DECODER_OUTPUT__
- test_probe_.Initialize("shell_audio_decoder_probe.wav", num_channels_,
- config.samples_per_second(), bits_per_channel(),
- bits_per_channel() == sizeof(float)); // use_floats?
- test_probe_.CloseAfter(30 * 1000);
-#endif
-
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_AUDIO_CODEC, config.codec());
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_AUDIO_CHANNELS, num_channels_);
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_AUDIO_SAMPLE_PER_SECOND,
- samples_per_second_);
-
- LOG(INFO) << "Configuration at Start: "
- << demuxer_stream_->audio_decoder_config().AsHumanReadableString();
- raw_decoder_ = raw_audio_decoder_factory_->Create(config);
-
- if (!raw_decoder_) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- status_cb.Run(PIPELINE_OK);
-}
-
-void ShellAudioDecoderImpl::Read(const AudioDecoder::ReadCB& read_cb) {
- TRACE_EVENT0("media_stack", "ShellAudioDecoderImpl::Read()");
-
- // This may be called from another thread (H/W audio thread) so redirect
- // this request to the decoder's message loop
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::Read, this, read_cb));
- return;
- }
-
- DCHECK(demuxer_stream_); // Initialize() has been called.
- DCHECK(!read_cb.is_null());
- DCHECK(read_cb_.is_null()); // No Read() in progress.
- DCHECK(reset_cb_.is_null()); // No Reset() in progress.
-
- if (queued_buffers_.empty()) {
- read_cb_ = read_cb;
- } else {
- scoped_refptr<DecoderBuffer> buffer = queued_buffers_.front();
- queued_buffers_.pop();
- TRACE_EVENT1("media_stack",
- "ShellAudioDecoderImpl::Read() deliver audio data.",
- "timestamp", buffer->GetTimestamp().InMicroseconds());
-
- Buffers buffers;
- buffers.push_back(buffer);
- read_cb.Run(kOk, buffers);
- }
-
- TryToReadFromDemuxerStream();
-}
-
-int ShellAudioDecoderImpl::bits_per_channel() {
- DCHECK(raw_decoder_);
-
- if (raw_decoder_) {
- return raw_decoder_->GetBytesPerSample() * 8;
- }
-
- return sizeof(float) * 8;
-}
-
-ChannelLayout ShellAudioDecoderImpl::channel_layout() {
- DCHECK(demuxer_stream_);
- const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config();
- return config.channel_layout();
-}
-
-int ShellAudioDecoderImpl::samples_per_second() {
- return samples_per_second_;
-}
-
-void ShellAudioDecoderImpl::Reset(const base::Closure& reset_cb) {
- TRACE_EVENT0("media_stack", "ShellAudioDecoderImpl::Reset()");
-
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::Reset, this, reset_cb));
- return;
- }
-
- DCHECK(demuxer_stream_); // Initialize() has been called.
- DCHECK(!reset_cb.is_null());
- DCHECK(reset_cb_.is_null()); // No Reset() in progress.
-
- if (demuxer_read_and_decode_in_progress_) {
- reset_cb_ = reset_cb;
- return;
- }
-
-#if __SAVE_DECODER_OUTPUT__
- test_probe_.Close();
-#endif
-
- DCHECK(read_cb_.is_null()); // No Read() in progress.
-
- // Release the buffers we queued internally
- while (!queued_buffers_.empty()) {
- queued_buffers_.pop();
- }
- raw_decoder_->Flush();
- eos_buffer_ = NULL;
- reset_cb.Run();
-}
-
-void ShellAudioDecoderImpl::TryToReadFromDemuxerStream() {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (!demuxer_read_and_decode_in_progress_ &&
- queued_buffers_.size() < kMaxQueuedBuffers) {
- demuxer_read_and_decode_in_progress_ = true;
- if (eos_buffer_) {
- // We have already received EOS from demuxer stream, so we no longer need
- // to request from DemuxerStream. However, we still have to send the eos
- // buffer to raw decoder so it can keep decoding any left over buffers.
- TRACE_EVENT0("media_stack",
- "ShellAudioDecoderImpl::TryToReadFromDemuxerStream() EOS");
- DCHECK(eos_buffer_);
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::OnDemuxerRead, this,
- DemuxerStream::kOk, eos_buffer_));
- } else {
- TRACE_EVENT0("media_stack",
- "ShellAudioDecoderImpl::TryToReadFromDemuxerStream() Read");
- demuxer_stream_->Read(BindToCurrentLoop(
- base::Bind(&ShellAudioDecoderImpl::OnDemuxerRead, this)));
- }
- }
-}
-
-void ShellAudioDecoderImpl::OnDemuxerRead(
- DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- TRACE_EVENT0("media_stack", "ShellAudioDecoderImpl::OnDemuxerRead()");
-
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::OnDemuxerRead, this,
- status, buffer));
- return;
- }
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- // What the test does is:
- // Play for kTimeBetweenUnderflow + kTimeToUnderflow;
- // for (;;) {
- // Play for kTimeBetweenUnderflow;
- // Underflow for kTimeToUnderflow;
- // }
- const bool kEnableUnderflowTest = false;
- if (kEnableUnderflowTest) {
- static const TimeDelta kTimeBetweenUnderflow =
- TimeDelta::FromMilliseconds(27293);
- static const TimeDelta kTimeToUnderflow = TimeDelta::FromMilliseconds(4837);
- static Time last_hit;
-
- if (status == DemuxerStream::kOk && !buffer->IsEndOfStream() &&
- buffer->GetTimestamp().InMicroseconds() == 0) {
- last_hit = Time::Now();
- }
- if (Time::Now() - last_hit > kTimeBetweenUnderflow + kTimeToUnderflow) {
- last_hit = Time::Now();
- message_loop_->PostDelayedTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::OnDemuxerRead, this,
- status, buffer),
- kTimeToUnderflow);
- return;
- }
- }
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-
- DCHECK(demuxer_read_and_decode_in_progress_);
-
- if (status == DemuxerStream::kOk) {
- if (buffer->IsEndOfStream()) {
- TRACE_EVENT0("media_stack",
- "ShellAudioDecoderImpl::DecodeBuffer() EOS reached.");
- eos_buffer_ = buffer;
- }
- raw_decoder_->Decode(
- buffer, base::Bind(&ShellAudioDecoderImpl::OnBufferDecoded, this));
- return;
- }
-
- DCHECK(!buffer);
- demuxer_read_and_decode_in_progress_ = false;
-
- if (status == DemuxerStream::kConfigChanged) {
- // We don't support audio config change.
- // We must call audio_decoder_config() to acknowledge the config change.
- // Otherwise, the demuxer will keep sending back kConfigChange for any
- // Read() call until the config change is acknowledged.
- demuxer_stream_->audio_decoder_config();
- TryToReadFromDemuxerStream();
- } else {
- DCHECK_EQ(status, DemuxerStream::kAborted);
- // We are seeking or stopping, fulfill any outstanding callbacks.
- if (!read_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kAborted, Buffers());
- }
- if (!reset_cb_.is_null()) {
- Reset(base::ResetAndReturn(&reset_cb_));
- }
- }
-}
-
-void ShellAudioDecoderImpl::OnBufferDecoded(
- ShellRawAudioDecoder::DecodeStatus status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- if (!message_loop_->BelongsToCurrentThread()) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioDecoderImpl::OnBufferDecoded, this,
- status, buffer));
- return;
- }
-
- DCHECK(demuxer_read_and_decode_in_progress_);
- demuxer_read_and_decode_in_progress_ = false;
-
- bool eos_decoded = false;
- if (status == ShellRawAudioDecoder::BUFFER_DECODED) {
- DCHECK(buffer);
- queued_buffers_.push(buffer);
-
- eos_decoded = buffer->IsEndOfStream();
- if (!eos_decoded) {
- TRACE_EVENT1("media_stack",
- "ShellAudioDecoderImpl::DecodeBuffer() data decoded.",
- "timestamp", buffer->GetTimestamp().InMicroseconds());
- DCHECK_EQ(buffer->GetDataSize(),
- kFramesPerAccessUnit * bits_per_channel() / 8 * num_channels_);
-
- PipelineStatistics statistics;
- statistics.audio_bytes_decoded = buffer->GetDataSize();
- statistics_cb_.Run(statistics);
-
-#if __SAVE_DECODER_OUTPUT__
- test_probe_.AddData(buffer->GetData());
-#endif
- }
- } else if (status == ShellRawAudioDecoder::NEED_MORE_DATA) {
- DCHECK_EQ(status, ShellRawAudioDecoder::NEED_MORE_DATA);
- DCHECK(!buffer);
- if (reset_cb_.is_null()) {
- TryToReadFromDemuxerStream();
- } else {
- Reset(base::ResetAndReturn(&reset_cb_));
- }
- return;
- }
-
- if (!read_cb_.is_null()) {
- DCHECK(!queued_buffers_.empty());
- scoped_refptr<DecoderBuffer> buffer = queued_buffers_.front();
- queued_buffers_.pop();
- TRACE_EVENT1("media_stack",
- "ShellAudioDecoderImpl::OnBufferDecoded() deliver audio data.",
- "timestamp", buffer->GetTimestamp().InMicroseconds());
-
- Buffers buffers;
- buffers.push_back(buffer);
- base::ResetAndReturn(&read_cb_).Run(kOk, buffers);
- }
-
- if (reset_cb_.is_null()) {
- if (!eos_decoded) {
- TryToReadFromDemuxerStream();
- }
- } else {
- Reset(base::ResetAndReturn(&reset_cb_));
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_audio_decoder_impl.h b/src/media/filters/shell_audio_decoder_impl.h
deleted file mode 100644
index aaa9a2c..0000000
--- a/src/media/filters/shell_audio_decoder_impl.h
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AUDIO_DECODER_IMPL_H_
-#define MEDIA_FILTERS_SHELL_AUDIO_DECODER_IMPL_H_
-
-#include <queue>
-
-#include "base/callback.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "media/base/audio_decoder.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/demuxer_stream.h"
-
-// set me to 1 to save decoded audio output to disk for debugging
-#define __SAVE_DECODER_OUTPUT__ 0
-
-#if __SAVE_DECODER_OUTPUT__
-#include "media/audio/shell_wav_test_probe.h"
-#endif
-
-namespace media {
-
-class ShellRawAudioDecoder {
- public:
- enum DecodeStatus {
- BUFFER_DECODED, // Successfully decoded audio data.
- NEED_MORE_DATA // Need more data to produce decoded audio data.
- };
- typedef media::DecoderBuffer DecoderBuffer;
- typedef media::AudioDecoderConfig AudioDecoderConfig;
- typedef base::Callback<void(DecodeStatus,
- const scoped_refptr<DecoderBuffer>&)> DecodeCB;
-
- virtual ~ShellRawAudioDecoder() {}
- virtual int GetBytesPerSample() const = 0;
- virtual void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) = 0;
- virtual bool Flush() = 0;
- virtual bool UpdateConfig(const AudioDecoderConfig& config) = 0;
-
- protected:
- ShellRawAudioDecoder() {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ShellRawAudioDecoder);
-};
-
-class ShellRawAudioDecoderFactory {
- public:
- virtual ~ShellRawAudioDecoderFactory() {}
-
- virtual scoped_ptr<ShellRawAudioDecoder> Create(
- const AudioDecoderConfig& config) = 0;
-};
-
-// The audio decoder mainly has two features:
-// 1. It tries to read buffers from the demuxer, then decode the buffers and put
-// decoded buffers into a queue. In this way the decoder can cache some
-// buffers to speed up audio caching.
-// 2. When its Read() function is called, it tries to fulfill the Read() with
-// decoded data.
-// A Reset() call will break both of them and halt the decoder until another
-// Read() is called.
-// The ShellAudioDecoderImpl is only used by the AudioRenderer. The renderer
-// will call Initialize() before any other function is called. The renderer may
-// then call Read() repeatly to request decoded audio data. Reset() is called
-// when a seek is initiated to clear any internal data of the decoder.
-// Note that the renderer will not explicitly destroy the decoder when playback
-// is finished. It simply release its reference to the decoder and the decoder
-// may be alive for a short time. When playback is finished, the demuxer will
-// keep return EOS buffer. So the decoder will stop reading quickly. Once no
-// callback is in progress, it will be destroyed automatically because there is
-// no reference to it.
-class MEDIA_EXPORT ShellAudioDecoderImpl : public AudioDecoder {
- public:
- ShellAudioDecoderImpl(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- ShellRawAudioDecoderFactory* raw_audio_decoder_factory);
- ~ShellAudioDecoderImpl() OVERRIDE;
-
- private:
- static const int kMaxQueuedBuffers = 32;
-
- // AudioDecoder implementation.
- void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- void Read(const ReadCB& read_cb) OVERRIDE;
- int bits_per_channel() OVERRIDE;
- ChannelLayout channel_layout() OVERRIDE;
- int samples_per_second() OVERRIDE;
- void Reset(const base::Closure& reset_cb) OVERRIDE;
-
- void TryToReadFromDemuxerStream();
- void OnDemuxerRead(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
- // the callback from the raw decoder indicates an operation has been finished.
- void OnBufferDecoded(ShellRawAudioDecoder::DecodeStatus status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- // The message loop that all functions run on to avoid explicit sync.
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- ShellRawAudioDecoderFactory* raw_audio_decoder_factory_;
- scoped_refptr<DemuxerStream> demuxer_stream_;
- StatisticsCB statistics_cb_;
-
- scoped_ptr<ShellRawAudioDecoder> raw_decoder_;
-
- // Are we currently in a read/decode cycle? One thing worth noting is, Read()
- // and Reset() will only be deferred when demuxer_read_and_decode_in_progress_
- // is true, so if demuxer_read_and_decode_in_progress_ is false, read_cb_ and
- // reset_cb_ will be NULL.
- bool demuxer_read_and_decode_in_progress_;
- // Save the EOS buffer received so we can send it again to the raw decoder
- // with correct timestamp. When this is non NULL, we shouldn't read from the
- // demuxer again until it is reset in Reset().
- scoped_refptr<DecoderBuffer> eos_buffer_;
-
-#if __SAVE_DECODER_OUTPUT__
- ShellWavTestProbe test_probe_;
-#endif
-
- int samples_per_second_;
- int num_channels_;
-
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- std::queue<scoped_refptr<DecoderBuffer> > queued_buffers_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ShellAudioDecoderImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AUDIO_DECODER_IMPL_H_
diff --git a/src/media/filters/shell_audio_renderer.h b/src/media/filters/shell_audio_renderer.h
deleted file mode 100644
index 528ace8..0000000
--- a/src/media/filters/shell_audio_renderer.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AUDIO_RENDERER_H_
-#define MEDIA_FILTERS_SHELL_AUDIO_RENDERER_H_
-
-#include "base/message_loop_proxy.h"
-#include "media/base/audio_renderer.h"
-#include "media/base/audio_renderer_sink.h"
-#include "media/base/decryptor.h"
-
-namespace media {
-
-class MEDIA_EXPORT ShellAudioRenderer
- : public AudioRenderer,
- public media::AudioRendererSink::RenderCallback {
- public:
- // platform-specific factory method
- static ShellAudioRenderer* Create(
- media::AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- // ======== AudioRenderer Implementation
-
- // ======== Filter Implementation
-
- protected:
- virtual ~ShellAudioRenderer() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AUDIO_RENDERER_H_
diff --git a/src/media/filters/shell_audio_renderer_algorithm.h b/src/media/filters/shell_audio_renderer_algorithm.h
deleted file mode 100644
index f567362..0000000
--- a/src/media/filters/shell_audio_renderer_algorithm.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AUDIO_RENDERER_ALGORITHM_H_
-#define MEDIA_FILTERS_SHELL_AUDIO_RENDERER_ALGORITHM_H_
-
-#include "base/callback.h"
-#include "media/base/seekable_buffer.h"
-
-namespace media {
-
-class Buffer;
-
-// Converts this object to an abstract interface for a platform-specific
-// implementation.
-class MEDIA_EXPORT AudioRendererAlgorithm {
- public:
- // returns a platform-specific AudioRendererAlgorithm implementation
- static AudioRendererAlgorithm* Create();
-
- // Call prior to Initialize() to validate configuration. Returns false if the
- // configuration is invalid.
- static bool ValidateConfig(int channels,
- int samples_per_second,
- int bits_per_channel);
-
- // Initializes this object with information about the audio stream.
- // |samples_per_second| is in Hz. |read_request_callback| is called to
- // request more data from the client, requests that are fulfilled through
- // calls to EnqueueBuffer().
- virtual void Initialize(int channels,
- int samples_per_second,
- int bits_per_channel,
- float initial_playback_rate,
- const base::Closure& request_read_cb) = 0;
-
- // Tries to fill |requested_frames| frames into |dest| with output.
- // Returns the number of frames copied into |dest|.
- // May request more reads via |request_read_cb_| before returning.
- virtual int FillBuffer(uint8* dest, int requested_frames) = 0;
-
- // Clears internal audio buffers
- virtual void FlushBuffers() = 0;
-
- // Returns the time of the next byte in our data or kNoTimestamp() if current
- // time is unknown.
- virtual base::TimeDelta GetTime() = 0;
-
- // Enqueues a buffer. It is called from the owner of the algorithm after a
- // read completes.
- virtual void EnqueueBuffer(Buffer* buffer_in) = 0;
-
- // we currently don't support playback rates that aren't 0.0 or 1.0
- virtual float playback_rate() const = 0;
- virtual void SetPlaybackRate(float new_rate) = 0;
-
- // Returns whether the algorithm has enough data at the current playback rate
- // such that it can write data on the next call to FillBuffer().
- virtual bool CanFillBuffer() = 0;
-
- // Returns true if input buffer is full.
- virtual bool IsQueueFull() = 0;
-
- // Returns the capacity of input buffer.
- virtual int QueueCapacity() = 0;
-
- // Increase the capacity of input buffer if possible.
- virtual void IncreaseQueueCapacity() = 0;
-
- // Returns the number of bytes left in input buffer, which may be larger
- // than QueueCapacity() in the event that a read callback delivered more data
- // than input buffer was intended to hold.
- virtual int bytes_buffered() = 0;
-
- virtual int bytes_per_frame() = 0;
-
- virtual int bytes_per_channel() = 0;
-
- virtual bool is_muted() = 0;
-
- // NOT part of Chromium's AudioRendererAlgorithm. Added because our algorithm
- // sometimes has to resample to a different sample rate as part of processing.
- virtual int output_sample_rate() = 0;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AUDIO_RENDERER_ALGORITHM_H_
diff --git a/src/media/filters/shell_audio_renderer_impl.cc b/src/media/filters/shell_audio_renderer_impl.cc
deleted file mode 100644
index 81b5115..0000000
--- a/src/media/filters/shell_audio_renderer_impl.cc
+++ /dev/null
@@ -1,561 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_audio_renderer_impl.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "base/stringprintf.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/buffers.h"
-#include "media/base/demuxer_stream.h"
-#include "media/filters/audio_decoder_selector.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-// static
-ShellAudioRenderer* ShellAudioRenderer::Create(
- AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const scoped_refptr<base::MessageLoopProxy>& message_loop) {
- return new ShellAudioRendererImpl(sink, set_decryptor_ready_cb, message_loop);
-}
-
-ShellAudioRendererImpl::ShellAudioRendererImpl(
- AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const scoped_refptr<base::MessageLoopProxy>& message_loop)
- : sink_(sink),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- message_loop_(message_loop),
- state_(kUninitialized),
- end_of_stream_state_(kWaitingForEOS),
- decoded_audio_bus_(NULL),
- decode_complete_(false) {
- DCHECK(message_loop_ != NULL);
-}
-
-ShellAudioRendererImpl::~ShellAudioRendererImpl() {
- // Stop() does the main teardown work, and should always be called
- // before the object is destroyed.
- DCHECK(state_ == kUninitialized || state_ == kStopped);
-}
-
-void ShellAudioRendererImpl::Play(const base::Closure& callback) {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::Play()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPaused);
- state_ = kPlaying;
- callback.Run();
-
- DoPlay();
-}
-
-void ShellAudioRendererImpl::DoPlay() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPlaying);
- if (playback_rate_ > 0.0f) {
- sink_->Play();
- }
-}
-
-void ShellAudioRendererImpl::Pause(const base::Closure& callback) {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::Pause()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(state_ == kPlaying || state_ == kUnderflow || state_ == kRebuffering);
- sink_->Pause(false);
-
- state_ = kPaused;
- {
- base::AutoLock lock(renderer_cb_lock_);
- DCHECK(renderer_idle_cb_.is_null());
- renderer_idle_cb_ = BindToCurrentLoop(callback);
- }
-}
-
-void ShellAudioRendererImpl::Flush(const base::Closure& callback) {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::Flush()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kPaused);
- DCHECK_EQ(decoded_audio_bus_, (AudioBus*)NULL);
-
- // Pause and flush the sink. Do this only if there is not a decode pending
- // because the sink expects that it will not be flushed while a read is
- // in process
- sink_->Pause(true);
-
- // Reset the decoder. It will call the callback when it has finished.
- decoder_->Reset(callback);
-}
-
-void ShellAudioRendererImpl::Stop(const base::Closure& callback) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- // Called by the Decoder once it finishes resetting
- base::Closure reset_complete_cb =
- base::Bind(&ShellAudioRendererImpl::OnDecoderReset, this, callback);
-
- switch (state_) {
- case kUninitialized:
- case kStopped:
- DCHECK_EQ(decoder_, reinterpret_cast<AudioDecoder*>(NULL));
- // Just post this to the message loop, since the Sink
- // is not yet running
- message_loop_->PostTask(FROM_HERE, reset_complete_cb);
- break;
- case kPrerolling:
- case kPlaying:
- case kRebuffering:
- case kUnderflow:
- case kPaused: {
- // Set to kStopping state to stop further requests for audio, and set
- // a callback that the H/W renderer thread will call when it is idle.
- state_ = kStopping;
- sink_->Pause(false);
- {
- base::AutoLock lock(renderer_cb_lock_);
- // Called when the sink is no longer requesting audio
- renderer_idle_cb_ = BindToCurrentLoop(
- base::Bind(&AudioDecoder::Reset, decoder_, reset_complete_cb));
- }
- break;
- }
- case kStopping:
- default:
- NOTREACHED();
- error_cb_.Run(PIPELINE_ERROR_INVALID_STATE);
- break;
- }
-}
-
-void ShellAudioRendererImpl::OnDecoderReset(const base::Closure& callback) {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::OnDecoderReset()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(state_ == kStopping || state_ == kUninitialized);
- DCHECK_EQ(decoded_audio_bus_, (AudioBus*)NULL);
- DCHECK(renderer_idle_cb_.is_null());
-
- decoder_ = NULL;
- if (state_ != kUninitialized && state_ != kStopped) {
- DCHECK_NE(sink_, (AudioRendererSink*)NULL);
- sink_->Stop();
- }
- sink_ = NULL;
- decoded_audio_bus_ = NULL;
- decode_complete_ = false;
- buffered_timestamp_ = base::TimeDelta::FromSeconds(0);
- rendered_timestamp_ = base::TimeDelta::FromSeconds(0);
- silence_rendered_ = base::TimeDelta::FromSeconds(0);
- end_of_stream_state_ = kWaitingForEOS;
- playback_rate_ = 1.0f;
- state_ = kStopped;
- callback.Run();
-}
-
-void ShellAudioRendererImpl::SetPlaybackRate(float rate) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- playback_rate_ = rate;
- if (playback_rate_ == 0.0f)
- sink_->Pause(false);
- else if (state_ == kPlaying)
- DoPlay();
-}
-
-void ShellAudioRendererImpl::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb) {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::OnDecoderReset()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- underflow_cb_ = underflow_cb;
- time_cb_ = time_cb;
- init_cb_ = init_cb;
- ended_cb_ = ended_cb;
- error_cb_ = error_cb;
-
- state_ = kUninitialized;
- playback_rate_ = 1.0f;
- end_of_stream_state_ = kWaitingForEOS;
- DCHECK_EQ(decoded_audio_bus_, (AudioBus*)NULL);
- decoded_audio_bus_ = NULL;
- decode_complete_ = false;
-
- scoped_ptr<AudioDecoderSelector> decoder_selector(new AudioDecoderSelector(
- base::MessageLoopProxy::current(), decoders, set_decryptor_ready_cb_));
- AudioDecoderSelector* decoder_selector_ptr = decoder_selector.get();
- decoder_selector_ptr->SelectAudioDecoder(
- stream, statistics_cb,
- base::Bind(&ShellAudioRendererImpl::OnDecoderSelected, this,
- base::Passed(&decoder_selector)));
-}
-
-void ShellAudioRendererImpl::OnDecoderSelected(
- scoped_ptr<AudioDecoderSelector> decoder_selector,
- const scoped_refptr<AudioDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (sink_ == NULL) {
- // Stop has been called, we shouldn't do anything further.
- return;
- }
-
- if (selected_decoder == NULL) {
- DLOG(WARNING) << "NULL decoder selected";
- init_cb_.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- // extract configuration information from the demuxer
- ChannelLayout channel_layout = selected_decoder->channel_layout();
- int channels = ChannelLayoutToChannelCount(channel_layout);
- // TODO: Find a proper way to check native channel support here, including
- // proper way to determinate if 5.1 or 7.1 is present.
- if (channels != 1 && channels != 2 && channels != 6) {
- DLOG(WARNING) << "we only support 1, 2 and 6 channel audio";
- init_cb_.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- decoder_ = selected_decoder;
- decrypting_demuxer_stream_ = decrypting_demuxer_stream;
-
- // construct audio parameters for the sink
- audio_parameters_ = AudioParameters(
- AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
- decoder_->samples_per_second(), decoder_->bits_per_channel(),
- mp4::AAC::kFramesPerAccessUnit);
-
- state_ = kPaused;
-
- DCHECK(sink_.get());
- // initialize and start the sink
- sink_->Initialize(audio_parameters_, this);
- sink_->Start();
-
- // run the startup callback, we're ready to go
- init_cb_.Run(PIPELINE_OK);
-}
-
-void ShellAudioRendererImpl::Preroll(base::TimeDelta time,
- const PipelineStatusCB& callback) {
- message_loop_->PostTask(
- FROM_HERE,
- base::Bind(&ShellAudioRendererImpl::DoPreroll, this, time, callback));
-}
-
-void ShellAudioRendererImpl::DoPreroll(base::TimeDelta time,
- const PipelineStatusCB& callback) {
- // Stop() can be called immediately after Preroll() which will set the state
- // to kStopping or kStopped. We shouldn't continue the preroll process in this
- // case.
- if (state_ == kStopping || state_ == kStopped) {
- return;
- }
-
- // Start will begin filling the AudioBus with data, and the Streamer
- // will be paused until data is full
- buffered_timestamp_ = base::TimeDelta::FromMicroseconds(0);
- preroll_cb_ = callback;
- preroll_timestamp_ = time;
- state_ = kPrerolling;
- end_of_stream_state_ = kWaitingForEOS;
-}
-
-void ShellAudioRendererImpl::DoUnderflow() {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::DoUnderflow()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- // don't reprocess if we've already set our state, or already got EOS
- if (state_ == kUnderflow || end_of_stream_state_ >= kReceivedEOS) {
- return;
- }
- DLOG(INFO) << "audio renderer registered underflow";
- // now set the state flag
- state_ = kUnderflow;
- // pause the sink, which won't stop requests but will stop consumption
- // of audio by the rendering hardware.
- sink_->Pause(false);
- // inform the pipeline that we've underflowed
- underflow_cb_.Run();
-}
-
-// called by the Pipeline to indicate that it has processed the underflow
-// we announced, and that we should begin rebuffering
-void ShellAudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioRendererImpl::DoResumeAfterUnderflow,
- this, buffer_more_audio));
-}
-
-void ShellAudioRendererImpl::DoResumeAfterUnderflow(bool buffer_more_audio) {
- TRACE_EVENT0("media_stack",
- "ShellAudioRendererImpl::DoResumeAfterUnderflow()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (state_ == kUnderflow) {
- state_ = kRebuffering;
- sink_->ResumeAfterUnderflow(buffer_more_audio);
- }
-}
-
-void ShellAudioRendererImpl::SinkFull() {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioRendererImpl::DoSinkFull, this));
-}
-
-void ShellAudioRendererImpl::DoSinkFull() {
- TRACE_EVENT0("media_stack", "ShellAudioRendererImpl::DoSinkFull()");
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (state_ == kPrerolling) {
- DCHECK(!preroll_cb_.is_null());
- // we transition from preroll to a pause
- state_ = kPaused;
- base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
- } else if (state_ == kUnderflow || state_ == kRebuffering) {
- // we pause the sink during an underflow, so now resume
- state_ = kPlaying;
- DoPlay();
- }
-}
-
-void ShellAudioRendererImpl::SinkUnderflow() {
- // This will be called by the Audio Renderer thread
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&ShellAudioRendererImpl::DoUnderflow, this));
-}
-
-void ShellAudioRendererImpl::SetVolume(float volume) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (sink_)
- sink_->SetVolume(volume);
-}
-
-void ShellAudioRendererImpl::DecodedAudioReady(
- AudioDecoder::Status status,
- const AudioDecoder::Buffers& buffers) {
- TRACE_EVENT1("media_stack", "ShellAudioRendererImpl::DecodedAudioReady()",
- "status", status);
- DCHECK(message_loop_->BelongsToCurrentThread());
- // ShellAudioRendererImpl doesn't support decoding output of more than one
- // buffer.
- DCHECK_LE(buffers.size(), 1);
- DCHECK(!decode_complete_);
- DCHECK_NE(decoded_audio_bus_, (AudioBus*)NULL);
-
- if (status == AudioDecoder::kOk) {
- if (buffers.empty()) {
- DLOG(WARNING) << "got empty buffers from decoder";
- } else if (state_ == kStopped) {
- DLOG(WARNING) << "enqueue after stop.";
- } else {
- scoped_refptr<Buffer> buffer = buffers[0];
- if (buffer->IsEndOfStream()) {
- if (end_of_stream_state_ == kWaitingForEOS)
- end_of_stream_state_ = kReceivedEOS;
- // if we were rebuffering transition back to playing as we
- // will never receive additional data
- if (state_ == kUnderflow || state_ == kRebuffering) {
- state_ = kPlaying;
- DoPlay();
- }
- } else {
- // Here we assume that a non-interleaved audio_bus means that the
- // decoder output is in planar form, where each channel follows the
- // other in the decoded buffer.
- int audio_bus_size_per_channel_in_bytes =
- buffer->GetDataSize() / decoded_audio_bus_->channels();
- // TODO: Change this to DCHECK_LE once we support Read with arbitrary
- // size.
- DCHECK_EQ(audio_bus_size_per_channel_in_bytes,
- decoded_audio_bus_->frames() * sizeof(float));
- for (int i = 0; i < decoded_audio_bus_->channels(); ++i) {
- const uint8_t* decoded_channel_data =
- buffer->GetData() + (i * audio_bus_size_per_channel_in_bytes);
- memcpy(decoded_audio_bus_->channel(i), decoded_channel_data,
- audio_bus_size_per_channel_in_bytes);
- }
- }
- // This is read by the renderer thread in Render(), but will only be read
- // when decode_complete_ is set to true.
- buffered_timestamp_ = buffer->GetTimestamp();
- }
- } else {
- DCHECK(status == AudioDecoder::kAborted ||
- status == AudioDecoder::kDecodeError);
- DLOG_IF(WARNING, status == AudioDecoder::kDecodeError)
- << "audio decoder returned decode error";
- DLOG_IF(INFO, status == AudioDecoder::kAborted)
- << "audio decoder returned abort";
- // We need to call the preroll callback to unwedge the pipeline on failure
- if (state_ == kPrerolling) {
- // kAborted does not signify an error in the pipeline, so return OK for
- // that case and ERROR for all others
- state_ = kPaused;
- PipelineStatus pipeline_status = status == AudioDecoder::kAborted
- ? PIPELINE_OK
- : PIPELINE_ERROR_DECODE;
- base::ResetAndReturn(&preroll_cb_).Run(pipeline_status);
- }
- }
-
- // Signal that the data in pending_decode_ is now valid;
- decode_status_ = status;
- decode_complete_ = true;
-}
-
-// CAUTION: this method will not usually execute on the renderer thread!
-// This is normally called by the system audio thread, and must not block or
-// perform high-latency operations.
-int ShellAudioRendererImpl::Render(AudioBus* dest,
- int audio_delay_milliseconds) {
- // Use the previous value for rendered_timestamp_ and audio_delay_milliseconds
- // to calculate the current playback time of the audio streamer
- const base::TimeDelta audio_delay =
- base::TimeDelta::FromMilliseconds(audio_delay_milliseconds);
-
- // Once the delay is less than the amount of silence we have rendered, then
- // we know that we have played the last sample
- if (end_of_stream_state_ == kRenderedEOS) {
- if (silence_rendered_ > audio_delay) {
- end_of_stream_state_ = kPlayedEOS;
- message_loop_->PostTask(FROM_HERE, ended_cb_);
- }
- }
-
- // Once we've called the ended_cb_, we should no longer call the time_cb_
- if (end_of_stream_state_ < kPlayedEOS && state_ != kPaused) {
- // adjust the delay length to account for the amount of silence that has
- // been rendered to the sink
- const base::TimeDelta kZeroTimeDelta = base::TimeDelta::FromMilliseconds(0);
- base::TimeDelta adjusted_delay = (audio_delay - silence_rendered_);
- if (adjusted_delay < kZeroTimeDelta)
- adjusted_delay = kZeroTimeDelta;
-
- const base::TimeDelta current_time = rendered_timestamp_ - adjusted_delay;
- time_cb_.Run(current_time, rendered_timestamp_);
- }
-
- // 0 indicates the read is still pending
- int frames_rendered = 0;
-
- // dest is NULL, this means the sink doesn't need data and is just
- // pinging us with an updated timestamp
- if (dest != NULL) {
- // No pending decode, so fire off the Read if we are in an appropriate state
- if (ShouldQueueRead(state_) && decoded_audio_bus_ == NULL) {
- DCHECK_NE(dest, (AudioBus*)NULL);
- decode_complete_ = false;
- decoded_audio_bus_ = dest;
- decoder_->Read(
- base::Bind(&ShellAudioRendererImpl::DecodedAudioReady, this));
- }
-
- // Must be polling for the Read that is currently in progress
- if (decoded_audio_bus_ != NULL && decode_complete_) {
- DCHECK_EQ(dest, decoded_audio_bus_);
- decoded_audio_bus_ = NULL;
- if (decode_status_ != AudioDecoder::kOk) {
- // An error has occurred. Indicate this to the sink
- frames_rendered = -1;
- } else {
- if (end_of_stream_state_ == kWaitingForEOS) {
- // Normal decode event
- rendered_timestamp_ = buffered_timestamp_;
- frames_rendered = mp4::AAC::kFramesPerAccessUnit;
- }
- if (end_of_stream_state_ == kReceivedEOS) {
- end_of_stream_state_ = kRenderedEOS;
- }
- if (end_of_stream_state_ == kRenderedEOS) {
- const int bytes_per_sample = audio_parameters_.bits_per_sample() / 8;
- // TODO: Change this to DCHECK_LE and fill only kFramesPerAccessUnit
- // instead of dest->frames() once we support Read with arbitrary size.
- DCHECK_EQ(mp4::AAC::kFramesPerAccessUnit * bytes_per_sample *
- audio_parameters_.channels(),
- dest->frames() * dest->channels() * sizeof(float));
- // Write zeros (silence) to each channel
- for (int i = 0; i < dest->channels(); ++i) {
- void* channel_data = dest->channel(i);
- size_t num_bytes =
- dest->frames() * sizeof(float); // NOLINT(runtime/sizeof)
- memset(channel_data, 0, num_bytes);
- }
- frames_rendered = mp4::AAC::kFramesPerAccessUnit;
- uint64_t silence_ms = mp4::AAC::kFramesPerAccessUnit * 1000 /
- audio_parameters_.sample_rate();
- silence_rendered_ += base::TimeDelta::FromMilliseconds(silence_ms);
- }
- }
- }
- }
-
- if (state_ == kStopping && decrypting_demuxer_stream_ != NULL) {
- // Use a dummy callback as we don't care about the exact time that Reset()
- // finishes. Once Reset() is done the decoded_audio_bus_ will be set to NULL
- // and the renderer_idle_cb_ will be called in the next if block. This will
- // reset the decoder and finish the Stop().
- decrypting_demuxer_stream_->Reset(base::Bind(base::DoNothing));
- decrypting_demuxer_stream_ = NULL;
- }
- if (decoded_audio_bus_ == NULL) {
- // No pending decode, so we are idle. Call and clear
- // the callback if it is set.
- renderer_cb_lock_.Acquire();
- base::Closure callback = renderer_idle_cb_;
- renderer_idle_cb_.Reset();
- renderer_cb_lock_.Release();
- if (!callback.is_null())
- callback.Run();
- }
-
- return frames_rendered;
-}
-
-void ShellAudioRendererImpl::OnRenderError() {
- state_ = kUninitialized;
- error_cb_.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
-}
-
-bool ShellAudioRendererImpl::ShouldQueueRead(State state) {
- switch (state) {
- case kPrerolling:
- case kPlaying:
- case kRebuffering:
- return true;
- case kUnderflow:
- case kPaused:
- case kUninitialized:
- case kStopping:
- case kStopped:
- return false;
- }
- NOTREACHED();
- return false;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_audio_renderer_impl.h b/src/media/filters/shell_audio_renderer_impl.h
deleted file mode 100644
index ec154e3..0000000
--- a/src/media/filters/shell_audio_renderer_impl.h
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AUDIO_RENDERER_IMPL_H_
-#define MEDIA_FILTERS_SHELL_AUDIO_RENDERER_IMPL_H_
-
-#include "base/callback.h"
-#include "base/message_loop.h"
-#include "media/base/audio_decoder.h"
-#include "media/filters/shell_audio_renderer.h"
-
-namespace media {
-
-class AudioDecoderSelector;
-class DecryptingDemuxerStream;
-
-class MEDIA_EXPORT ShellAudioRendererImpl : public ShellAudioRenderer {
- public:
- explicit ShellAudioRendererImpl(
- AudioRendererSink* sink,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const scoped_refptr<base::MessageLoopProxy>& message_loop);
-
- // ======== AudioRenderer Implementation
-
- // Initialize a AudioRenderer with the given AudioDecoder, executing the
- // |init_cb| upon completion.
- //
- // |statistics_cb| is executed periodically with audio rendering stats.
- //
- // |underflow_cb| is executed when the renderer runs out of data to pass to
- // the audio card during playback. ResumeAfterUnderflow() must be called
- // to resume playback. Pause(), Preroll(), or Stop() cancels the underflow
- // condition.
- //
- // |time_cb| is executed whenever time has advanced by way of audio rendering.
- //
- // |ended_cb| is executed when audio rendering has reached the end of stream.
- //
- // |disabled_cb| is executed when audio rendering has been disabled due to
- // external factors (i.e., device was removed). |time_cb| will no longer be
- // executed.
- //
- // |error_cb| is executed if an error was encountered.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const AudioDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const base::Closure& underflow_cb,
- const TimeCB& time_cb,
- const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
- const PipelineStatusCB& error_cb) OVERRIDE;
-
- // Start prerolling audio data for samples starting at |time|, executing
- // |callback| when completed.
- //
- // Only valid to call after a successful Initialize() or Flush().
- virtual void Preroll(base::TimeDelta time,
- const PipelineStatusCB& callback) OVERRIDE;
-
- // Sets the output volume.
- virtual void SetVolume(float volume) OVERRIDE;
-
- // Resumes playback after underflow occurs.
- //
- // |buffer_more_audio| is set to true if you want to increase the size of the
- // decoded audio buffer.
- virtual void ResumeAfterUnderflow(bool buffer_more_audio) OVERRIDE;
-
- // The pipeline has resumed playback. Filters can continue requesting reads.
- // Filters may implement this method if they need to respond to this call.
- virtual void Play(const base::Closure& callback) OVERRIDE;
-
- // The pipeline has paused playback. Filters should stop buffer exchange.
- // Filters may implement this method if they need to respond to this call.
- virtual void Pause(const base::Closure& callback) OVERRIDE;
-
- // The pipeline has been flushed. Filters should return buffer to owners.
- // Filters may implement this method if they need to respond to this call.
- virtual void Flush(const base::Closure& callback) OVERRIDE;
-
- // The pipeline is being stopped either as a result of an error or because
- // the client called Stop().
- virtual void Stop(const base::Closure& callback) OVERRIDE;
-
- // The pipeline playback rate has been changed. Filters may implement this
- // method if they need to respond to this call.
- virtual void SetPlaybackRate(float playback_rate) OVERRIDE;
-
- // Carry out any actions required to seek to the given time, executing the
- // callback upon completion.
- // virtual void Seek(base::TimeDelta time,
- // const PipelineStatusCB& callback) OVERRIDE;
-
- protected:
- virtual ~ShellAudioRendererImpl();
-
- void DoPlay();
- void DoPreroll(base::TimeDelta time, const PipelineStatusCB& callback);
- void DoUnderflow();
- void DoResumeAfterUnderflow(bool buffer_more_audio);
- void DoSinkFull();
-
- // AudioRendererSink::RenderCallback implementation.
- // Attempts to completely fill all channels of |dest|, returns actual
- // number of frames filled.
- // Render() is run on the system audio thread
- virtual int Render(AudioBus* dest, int audio_delay_milliseconds) OVERRIDE;
- virtual void OnRenderError() OVERRIDE;
- virtual void SinkFull() OVERRIDE;
- virtual void SinkUnderflow() OVERRIDE;
-
- void DecodedAudioReady(AudioDecoder::Status status,
- const AudioDecoder::Buffers& buffers);
-
- void OnDecoderSelected(
- scoped_ptr<AudioDecoderSelector> decoder_selector,
- const scoped_refptr<AudioDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream);
-
- void OnDecoderReset(const base::Closure& callback);
-
- enum State {
- kUninitialized,
- kPaused,
- kPrerolling,
- kPlaying,
- kUnderflow,
- kRebuffering,
- kStopping,
- kStopped,
- };
-
- bool ShouldQueueRead(State state);
-
- // objects communicating with the rest of the filter graph
- scoped_refptr<AudioRendererSink> sink_;
- SetDecryptorReadyCB set_decryptor_ready_cb_;
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- scoped_refptr<AudioDecoder> decoder_;
- base::Closure underflow_cb_;
- TimeCB time_cb_;
- base::Closure ended_cb_;
- PipelineStatusCB preroll_cb_;
- base::TimeDelta preroll_timestamp_;
- PipelineStatusCB init_cb_;
- PipelineStatusCB error_cb_;
-
- State state_;
-
- enum EOSState {
- kWaitingForEOS,
- kReceivedEOS,
- kRenderedEOS,
- kPlayedEOS,
- };
- EOSState end_of_stream_state_;
- float playback_rate_;
- AudioParameters audio_parameters_;
-
- AudioBus* decoded_audio_bus_;
- AudioDecoder::Status decode_status_;
- bool decode_complete_;
-
- // Callback that is called from the H/W renderer thread when it is finished
- // requesting audio samples
- base::Lock renderer_cb_lock_;
- base::Closure renderer_idle_cb_;
-
- // modified only by MediaPipeline thread while decode_complete is false
- // Can be read by any thread when it is true
- base::TimeDelta buffered_timestamp_;
- // Read/written only in Render callback
- base::TimeDelta rendered_timestamp_;
- // Read/written only in Render callback. Indicates how much silence has been
- // written to the sink after EOS was encountered
- base::TimeDelta silence_rendered_;
-
- // Store the DecryptingDemuxerStream so we can reset it when Stop is
- // requested. As the top level demuxer has no knowledge of the decrypting
- // demuxer stream, the pipeline will not be able to reset the streams during
- // stopping. So we have to reset it.
- scoped_refptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AUDIO_RENDERER_IMPL_H_
diff --git a/src/media/filters/shell_audio_renderer_unittest.cc b/src/media/filters/shell_audio_renderer_unittest.cc
deleted file mode 100644
index 7ca4e09..0000000
--- a/src/media/filters/shell_audio_renderer_unittest.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_audio_renderer.h"
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "base/message_loop_proxy.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_bus.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_audio_renderer_sink.h"
-#include "media/base/mock_filters.h"
-
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AtMost;
-using ::testing::InSequence;
-using ::testing::NiceMock;
-using ::testing::Return;
-using ::testing::ReturnRef;
-
-namespace media {
-
-class ShellAudioRendererTest : public ::testing::Test {
- public:
- ShellAudioRendererTest()
- : audio_decoder_config_(kCodecVorbis,
- 32,
- CHANNEL_LAYOUT_MONO,
- 44100,
- NULL,
- 0,
- false),
- sink_(new NiceMock<MockAudioRendererSink>),
- demuxer_stream_(new NiceMock<MockDemuxerStream>),
- decoder_(new NiceMock<MockAudioDecoder>) {
- renderer_ = ShellAudioRenderer::Create(sink_, SetDecryptorReadyCB(),
- base::MessageLoopProxy::current());
-
- ON_CALL(*demuxer_stream_, type())
- .WillByDefault(Return(DemuxerStream::AUDIO));
- ON_CALL(*demuxer_stream_, audio_decoder_config())
- .WillByDefault(ReturnRef(audio_decoder_config_));
-
- ON_CALL(*decoder_, bits_per_channel()).WillByDefault(Return(32));
- ON_CALL(*decoder_, channel_layout())
- .WillByDefault(Return(CHANNEL_LAYOUT_MONO));
- ON_CALL(*decoder_, samples_per_second()).WillByDefault(Return(44100));
- ON_CALL(*decoder_, Reset(_)).WillByDefault(RunCallback<0>());
- }
-
- MOCK_METHOD1(OnPipelineStatus, void(PipelineStatus));
- MOCK_METHOD1(OnPipelineStatistics, void(const PipelineStatistics&));
- MOCK_METHOD0(OnUnderflow, void());
- MOCK_METHOD0(OnEnded, void());
- MOCK_METHOD0(OnDisabled, void());
- MOCK_METHOD1(OnError, void(PipelineStatus));
- MOCK_METHOD0(OnStopped, void());
-
- void OnAudioTimeCallback(base::TimeDelta current_time,
- base::TimeDelta max_time) {
- CHECK(current_time <= max_time);
- }
-
- void InitializeWithoutWaitForPendingTasks() { Initialize(false); }
-
- void InitializeAndWaitForPendingTasks() { Initialize(true); }
-
- void Preroll() {
- renderer_->Preroll(base::TimeDelta(),
- base::Bind(&ShellAudioRendererTest::OnPipelineStatus,
- base::Unretained(this)));
- }
-
- void StopAndWaitForPendingTasks() {
- EXPECT_CALL(*this, OnStopped()).Times(1);
- renderer_->Stop(
- base::Bind(&ShellAudioRendererTest::OnStopped, base::Unretained(this)));
-#if !defined(__LB_PS3__)
- // The extra Render call is required by the ShellAudioRendererImpl when the
- // renderer starts to running.
- scoped_ptr<AudioBus> audio_bus = AudioBus::Create(1, 1024, 4, false);
- renderer_->Render(audio_bus.get(), 0);
-#endif // !defined(__LB_PS3__)
- FinishPendingTasks();
- }
-
- void FinishPendingTasks() { message_loop_.RunUntilIdle(); }
-
- private:
- void Initialize(bool wait_until_finished) {
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- if (wait_until_finished) {
- EXPECT_CALL(*this, OnPipelineStatus(_)).Times(1);
- } else {
- EXPECT_CALL(*this, OnPipelineStatus(_)).Times(AtMost(1));
- }
- AudioRenderer::AudioDecoderList decoders;
- decoders.push_back(decoder_);
- renderer_->Initialize(
- demuxer_stream_, decoders,
- base::Bind(&ShellAudioRendererTest::OnPipelineStatus,
- base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnPipelineStatistics,
- base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnUnderflow,
- base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnAudioTimeCallback,
- base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnEnded, base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnDisabled, base::Unretained(this)),
- base::Bind(&ShellAudioRendererTest::OnError, base::Unretained(this)));
- if (wait_until_finished) {
- FinishPendingTasks();
- }
- }
-
- AudioDecoderConfig audio_decoder_config_;
- scoped_refptr<NiceMock<MockAudioRendererSink> > sink_;
- scoped_refptr<NiceMock<MockDemuxerStream> > demuxer_stream_;
- scoped_refptr<NiceMock<MockAudioDecoder> > decoder_;
- scoped_refptr<ShellAudioRenderer> renderer_;
- MessageLoop message_loop_;
-};
-
-TEST_F(ShellAudioRendererTest, Initialize) {
- InSequence in_sequence;
- InitializeAndWaitForPendingTasks();
- StopAndWaitForPendingTasks();
-}
-
-TEST_F(ShellAudioRendererTest, StopImmediatelyAfterInitialize) {
- // Not using InSequence here as the audio renderer may call init_cb after
- // stop_cb is called.
- InitializeWithoutWaitForPendingTasks();
- StopAndWaitForPendingTasks();
-}
-
-TEST_F(ShellAudioRendererTest, Preroll) {
- InSequence in_sequence;
- InitializeAndWaitForPendingTasks();
- Preroll();
- FinishPendingTasks();
- StopAndWaitForPendingTasks();
-}
-
-TEST_F(ShellAudioRendererTest, StopImmediatelyAfterPreroll) {
- InSequence in_sequence;
- InitializeAndWaitForPendingTasks();
- Preroll();
- StopAndWaitForPendingTasks();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_avc_parser.cc b/src/media/filters/shell_avc_parser.cc
deleted file mode 100644
index 833aece..0000000
--- a/src/media/filters/shell_avc_parser.cc
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_avc_parser.h"
-
-#include <limits>
-
-#include "base/stringprintf.h"
-#include "media/base/endian_util.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/video_types.h"
-#include "media/filters/shell_au.h"
-#include "media/filters/shell_rbsp_stream.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-// what's the smallest meaningful AVC config we can parse?
-static const int kAVCConfigMinSize = 8;
-// lower five bits of first byte in SPS should be 7
-static const uint8 kSPSNALType = 7;
-
-ShellAVCParser::ShellAVCParser(scoped_refptr<ShellDataSourceReader> reader)
- : ShellParser(reader), nal_header_size_(0), video_prepend_size_(0) {}
-
-ShellAVCParser::~ShellAVCParser() {}
-
-bool ShellAVCParser::Prepend(scoped_refptr<ShellAU> au,
- scoped_refptr<DecoderBuffer> buffer) {
- // sanity-check inputs
- if (!au || !buffer) {
- NOTREACHED() << "bad input to Prepend()";
- return false;
- }
- uint8* prepend_buffer = buffer->GetWritableData();
- if (!prepend_buffer) {
- NOTREACHED() << "empty/undersized buffer to Prepend()";
- return false;
- }
- if (au->GetType() == DemuxerStream::VIDEO) {
- if (au->AddPrepend())
- memcpy(prepend_buffer, video_prepend_, video_prepend_size_);
- } else if (au->GetType() == DemuxerStream::AUDIO) {
-#if defined(COBALT_WIN)
- // We use raw AAC instead of ADTS on these platforms.
- DCHECK(audio_prepend_.empty());
- return true;
-#endif
- if (audio_prepend_.empty()) // valid ADTS header not available
- return false;
- // audio, need to copy ADTS header and then add buffer size
- uint32 buffer_size = au->GetSize() + audio_prepend_.size();
- // we can't express an AU size larger than 13 bits, something's bad here.
- if (buffer_size & 0xffffe000) {
- return false;
- }
- memcpy(prepend_buffer, &audio_prepend_[0], audio_prepend_.size());
- // OR size into buffer, byte 3 gets 2 MSb of 13-bit size
- prepend_buffer[3] |= (uint8)((buffer_size & 0x00001800) >> 11);
- // byte 4 gets bits 10-3 of size
- prepend_buffer[4] = (uint8)((buffer_size & 0x000007f8) >> 3);
- // byte 5 gets bits 2-0 of size
- prepend_buffer[5] |= (uint8)((buffer_size & 0x00000007) << 5);
- } else {
- NOTREACHED() << "unsupported demuxer stream type.";
- return false;
- }
-
- return true;
-}
-
-bool ShellAVCParser::DownloadAndParseAVCConfigRecord(uint64 offset,
- uint32 size) {
- scoped_refptr<ShellScopedArray> record_buffer =
- ShellBufferFactory::Instance()->AllocateArray(size);
- // It's possible that the size of this record buffer may exceed the
- // memory available to the media stack, in which case we skip it.
- if (!record_buffer || !record_buffer->Get()) {
- DLOG(WARNING) << base::StringPrintf(
- "insufficient memory to download AVCConfigRecord of %d bytes", size);
- return false;
- }
- int bytes_read = reader_->BlockingRead(offset, size, record_buffer->Get());
- DCHECK_LE(size, static_cast<uint32>(std::numeric_limits<int32>::max()));
- if (bytes_read < static_cast<int>(size)) {
- return false;
- }
- // ok, successfully downloaded the record, parse it
- return ParseAVCConfigRecord(record_buffer->Get(), size);
-}
-
-// static
-bool ShellAVCParser::ParseSPS(const uint8* sps,
- size_t sps_size,
- ShellSPSRecord* record_out) {
- DCHECK(sps) << "no sps provided";
- DCHECK(record_out) << "no output structure provided";
- // first byte is NAL type id, check that it is SPS
- if ((*sps & 0x1f) != kSPSNALType) {
- DLOG(ERROR) << "bad NAL type on SPS";
- return false;
- }
- // convert SPS NALU to RBSP stream
- ShellRBSPStream sps_rbsp(sps + 1, sps_size - 1);
- uint8 profile_idc = 0;
- if (!sps_rbsp.ReadByte(profile_idc)) {
- DLOG(ERROR) << "failure reading profile_idc from sps RBSP";
- return false;
- }
- // skip 3 constraint flags, 5 reserved bits, and level_idc (16 bits)
- sps_rbsp.SkipBytes(2);
- // ReadUEV/ReadSEV require a value to be passed by reference but
- // there are many times in which we ignore this value.
- uint32 disposable_uev = 0;
- int32 disposable_sev = 0;
- // seq_parameter_set_id
- sps_rbsp.ReadUEV(disposable_uev);
- // skip profile-specific encoding information if there
- if (profile_idc == 100 || profile_idc == 103 || profile_idc == 110 ||
- profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
- profile_idc == 83 || profile_idc == 86 || profile_idc == 118) {
- uint32 chroma_format_idc = 0;
- if (!sps_rbsp.ReadUEV(chroma_format_idc)) {
- DLOG(WARNING) << "failure reading chroma_format_idc from sps RBSP";
- return false;
- }
- if (chroma_format_idc == 3) {
- // separate_color_plane_flag
- sps_rbsp.SkipBits(1);
- }
- // bit_depth_luma_minus8
- sps_rbsp.ReadUEV(disposable_uev);
- // bit_depth_chroma_minus8
- sps_rbsp.ReadUEV(disposable_uev);
- // qpprime_y_zero_transform_bypass_flag
- sps_rbsp.SkipBits(1);
- // seq_scaling_matrix_present_flag
- uint8 seq_scaling_matrix_present_flag = 0;
- if (!sps_rbsp.ReadBit(seq_scaling_matrix_present_flag)) {
- DLOG(ERROR)
- << "failure reading seq_scaling_matrix_present_flag from sps RBSP";
- return false;
- }
- if (seq_scaling_matrix_present_flag) {
- // seq_scaling_list_present_flag[]
- sps_rbsp.SkipBits(chroma_format_idc != 3 ? 8 : 12);
- }
- }
- // log2_max_frame_num_minus4
- sps_rbsp.ReadUEV(disposable_uev);
- // pic_order_cnt_type
- uint32 pic_order_cnt_type = 0;
- if (!sps_rbsp.ReadUEV(pic_order_cnt_type)) {
- DLOG(ERROR) << "failure reading pic_order_cnt_type from sps RBSP";
- return false;
- }
- if (pic_order_cnt_type == 0) {
- // log2_max_pic_order_cnt_lsb_minus4
- sps_rbsp.ReadUEV(disposable_uev);
- } else if (pic_order_cnt_type == 1) {
- // delta_pic_order_always_zero_flag
- sps_rbsp.SkipBits(1);
- // offset_for_non_ref_pic
- sps_rbsp.ReadSEV(disposable_sev);
- // offset_for_top_to_bottom_field
- sps_rbsp.ReadSEV(disposable_sev);
- // num_ref_frames_in_pic_order_cnt_cycle
- uint32 num_ref_frames_in_pic_order_cnt_cycle = 0;
- if (!sps_rbsp.ReadUEV(num_ref_frames_in_pic_order_cnt_cycle)) {
- DLOG(ERROR)
- << "failure reading num_ref_frames_in_pic_order_cnt_cycle from sps";
- return false;
- }
- for (uint32 i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) {
- sps_rbsp.ReadSEV(disposable_sev);
- }
- }
- // number of reference frames used to decode
- uint32 num_ref_frames = 0;
- if (!sps_rbsp.ReadUEV(num_ref_frames)) {
- DLOG(ERROR) << "failure reading number of ref frames from sps RBSP";
- return false;
- }
- // gaps_in_frame_num_value_allowed_flag
- sps_rbsp.SkipBits(1);
- // width is calculated from pic_width_in_mbs_minus1
- uint32 pic_width_in_mbs_minus1 = 0;
- if (!sps_rbsp.ReadUEV(pic_width_in_mbs_minus1)) {
- DLOG(WARNING) << "failure reading image width from sps RBSP";
- return false;
- }
- // 16 pxs per macroblock
- uint32 width = (pic_width_in_mbs_minus1 + 1) * 16;
- // pic_height_in_map_units_minus1
- uint32 pic_height_in_map_units_minus1 = 0;
- if (!sps_rbsp.ReadUEV(pic_height_in_map_units_minus1)) {
- DLOG(ERROR)
- << "failure reading pic_height_in_map_uints_minus1 from sps RBSP";
- return false;
- }
- uint8 frame_mbs_only_flag = 0;
- if (!sps_rbsp.ReadBit(frame_mbs_only_flag)) {
- DLOG(ERROR) << "failure reading frame_mbs_only_flag from sps RBSP";
- return false;
- }
- uint32 height = (2 - (uint32)frame_mbs_only_flag) *
- (pic_height_in_map_units_minus1 + 1) * 16;
- if (!frame_mbs_only_flag) {
- sps_rbsp.SkipBits(1);
- }
- // direct_8x8_inference_flag
- sps_rbsp.SkipBits(1);
- // frame cropping flag
- uint8 frame_cropping_flag = 0;
- if (!sps_rbsp.ReadBit(frame_cropping_flag)) {
- DLOG(ERROR) << "failure reading frame_cropping_flag from sps RBSP";
- return false;
- }
- // distance in pixels from the associated edge of the media:
- //
- // <---coded_size---width--------------------->
- //
- // +------------------------------------------+ ^
- // | ^ | |
- // | | | |
- // | crop_top | |
- // | | | |
- // | v | height
- // | +---------+ | |
- // |<--crop_left-->| visible | | |
- // | | rect |<--crop_right-->| |
- // | +---------+ | |
- // | ^ | |
- // | | | |
- // | crop_bottom | |
- // | | | |
- // | v | |
- // +------------------------------------------+ v
- //
- uint32 crop_left = 0;
- uint32 crop_right = 0;
- uint32 crop_top = 0;
- uint32 crop_bottom = 0;
- // cropping values are stored divided by two
- if (frame_cropping_flag) {
- if (!sps_rbsp.ReadUEV(crop_left)) {
- DLOG(ERROR) << "failure reading crop_left from sps RBSP";
- return false;
- }
- if (!sps_rbsp.ReadUEV(crop_right)) {
- DLOG(ERROR) << "failure reading crop_right from sps RBSP";
- return false;
- }
- if (!sps_rbsp.ReadUEV(crop_top)) {
- DLOG(ERROR) << "failure reading crop_top from sps RBSP";
- return false;
- }
- if (!sps_rbsp.ReadUEV(crop_bottom)) {
- DLOG(ERROR) << "failure reading crop_bottom from sps RBSP";
- return false;
- }
- crop_left *= 2;
- crop_right *= 2;
- crop_top *= 2;
- crop_bottom *= 2;
- }
- // remainder of SPS are values we can safely ignore, everything
- // checks out, write output structure
- int visible_width = width - (crop_left + crop_right);
- int visible_height = height - (crop_top + crop_bottom);
- record_out->coded_size = gfx::Size(width, height),
- record_out->visible_rect =
- gfx::Rect(crop_left, crop_top, visible_width, visible_height),
- record_out->natural_size = gfx::Size(visible_width, visible_height);
- record_out->num_ref_frames = num_ref_frames;
- return true;
-}
-
-bool ShellAVCParser::ParseAVCConfigRecord(uint8* buffer, uint32 size) {
- if (size < kAVCConfigMinSize) {
- DLOG(ERROR) << base::StringPrintf("AVC config record bad size: %d", size);
- return false;
- }
-
- // get the NALU header size
- nal_header_size_ = (buffer[4] & 0x03) + 1;
- // validate size, needs to be 1, 2 or 4 bytes only
- if (nal_header_size_ != 4 && nal_header_size_ != 2 && nal_header_size_ != 1) {
- return false;
- }
- // AVCConfigRecords contain a variable number of SPS NALU
- // (Sequence Parameter Set) (Network Abstraction Layer Units)
- // from which we can extract width, height, and cropping info.
- // That means we need at least 1 SPS NALU in this stream for extraction.
- uint8 number_of_sps_nalus = buffer[5] & 0x1f;
- if (number_of_sps_nalus == 0) {
- DLOG(WARNING) << "got AVCConfigRecord without any SPS NALUs!";
- return false;
- }
- // iterate through SPS NALUs finding one of valid size for our purposes
- // (this should usually be the first one), but also advancing through
- // the ConfigRecord until we encounter the PPS sets
- bool have_valid_sps = false;
- int record_offset = 6;
- size_t usable_sps_size = 0;
- int usable_sps_offset = 0;
- for (uint8 i = 0; i < number_of_sps_nalus; i++) {
- // make sure we haven't run out of record for the 2-byte size record
- DCHECK_LE(size, static_cast<uint32>(std::numeric_limits<int32>::max()));
- if (record_offset + 2 > static_cast<int>(size)) {
- DLOG(WARNING) << "ran out of AVCConfig record while parsing SPS size.";
- return false;
- }
- // extract 2-byte size of this SPS
- size_t sps_size =
- endian_util::load_uint16_big_endian(buffer + record_offset);
- // advance past the 2-byte size record
- record_offset += 2;
- // see if we jumped over record size
- if (record_offset + sps_size > size) {
- DLOG(WARNING) << "ran out of AVCConfig record while parsing SPS blocks.";
- return false;
- }
- if (!have_valid_sps) {
- have_valid_sps = true;
- // save size and offset for later copying and parsing
- usable_sps_size = sps_size;
- usable_sps_offset = record_offset;
- // continue to iterate through sps records to get to pps which follow
- }
- record_offset += sps_size;
- }
- if (!have_valid_sps) {
- DLOG(WARNING)
- << "unable to parse a suitable SPS. Perhaps increase max size?";
- return false;
- }
- // we don't strictly require a PPS, so we're even willing to accept that
- // this could be the end of the bytestream, but if not the next byte should
- // define the number of PPS objects in the record. Not sure if
- // specific decoders could decode something without a PPS prepend but this
- // doesn't break demuxing so we'll let them complain if that isn't going
- // to work for them :)
- size_t usable_pps_size = 0;
- size_t usable_pps_offset = 0;
- bool have_valid_pps = false;
- DCHECK_LE(size, static_cast<uint32>(std::numeric_limits<int32>::max()));
- if (record_offset + 1 < static_cast<int>(size)) {
- uint8 number_of_pps_nalus = buffer[record_offset];
- record_offset++;
- for (uint8 i = 0; i < number_of_pps_nalus; i++) {
- // make sure we don't run out of room for 2-byte size record
- DCHECK_LE(size, static_cast<uint32>(std::numeric_limits<int32>::max()));
- if (record_offset + 2 >= static_cast<int>(size)) {
- DLOG(WARNING) << "ran out of AVCConfig record while parsing PPS size.";
- return false;
- }
- // extract 2-byte size of this PPS
- size_t pps_size =
- endian_util::load_uint16_big_endian(buffer + record_offset);
- record_offset += 2;
- // see if there's actually room for this record in the buffer
- if (record_offset + pps_size > size) {
- DLOG(WARNING)
- << "ran out of AVCConfig record while scanning PPS blocks.";
- return false;
- }
- if (!have_valid_pps) {
- have_valid_pps = true;
- usable_pps_size = pps_size;
- usable_pps_offset = record_offset;
- break;
- }
- }
- }
- // now we parse the valid SPS we extracted from byte stream earlier.
- ShellSPSRecord sps_record;
- if (!ParseSPS(buffer + usable_sps_offset, usable_sps_size, &sps_record)) {
- DLOG(WARNING) << "error parsing SPS";
- return false;
- }
- // we can now initialize our video decoder config
- video_config_.Initialize(
- kCodecH264,
- H264PROFILE_MAIN, // profile is ignored currently
- VideoFrame::NATIVE_TEXTURE, // we always decode directly to texture
- COLOR_SPACE_HD_REC709,
- sps_record.coded_size, sps_record.visible_rect, sps_record.natural_size,
- // no extra data needed
- NULL, 0,
- // is not currently encrypted
- false,
- // ignore stats for now
- false);
-
- return BuildAnnexBPrepend(buffer + usable_sps_offset, usable_sps_size,
- buffer + usable_pps_offset, usable_pps_size);
-}
-
-bool ShellAVCParser::BuildAnnexBPrepend(uint8* sps,
- uint32 sps_size,
- uint8* pps,
- uint32 pps_size) {
- // We will need to attach the sps and pps (if provided) to each keyframe
- // video packet, with the AnnexB start code in front of each. Start with
- // sps size and start code
- video_prepend_size_ = sps_size + kAnnexBStartCodeSize;
- if (pps_size > 0) {
- // Add pps and pps start code size if needed.
- video_prepend_size_ += pps_size + kAnnexBStartCodeSize;
- }
- // this should be a very rare case for typical videos
- if (video_prepend_size_ > kAnnexBPrependMaxSize) {
- NOTREACHED() << base::StringPrintf("Bad AnnexB prepend size: %d",
- video_prepend_size_);
- return false;
- }
- // start code for sps comes first
- endian_util::store_uint32_big_endian(kAnnexBStartCode, video_prepend_);
- // followed by sps body
- memcpy(video_prepend_ + kAnnexBStartCodeSize, sps, sps_size);
- int prepend_offset = kAnnexBStartCodeSize + sps_size;
- if (pps_size > 0) {
- // pps start code comes next
- endian_util::store_uint32_big_endian(kAnnexBStartCode,
- video_prepend_ + prepend_offset);
- prepend_offset += kAnnexBStartCodeSize;
- // followed by pps
- memcpy(video_prepend_ + prepend_offset, pps, pps_size);
- prepend_offset += pps_size;
- }
-
- // make sure we haven't wandered off into memory somewhere
- DCHECK_EQ(prepend_offset, video_prepend_size_);
- return true;
-}
-
-void ShellAVCParser::ParseAudioSpecificConfig(uint8 b0, uint8 b1) {
- media::mp4::AAC aac;
- std::vector<uint8> aac_config(2);
-
- aac_config[0] = b0;
- aac_config[1] = b1;
- audio_prepend_.clear();
-
- if (!aac.Parse(aac_config) || !aac.ConvertEsdsToADTS(&audio_prepend_)) {
- DLOG(WARNING) << "Error in parsing AudioSpecificConfig.";
- return;
- }
-
- // Clear the length, it is 13 bits and stored as ******LL LLLLLLLL LLL*****
- // in bytes 3 to 5.
- audio_prepend_[3] &= 0xfc;
- audio_prepend_[4] = 0;
- audio_prepend_[5] &= 0x1f;
-
-#if defined(COBALT_WIN)
- // We use raw AAC instead of ADTS on these platforms.
- audio_prepend_.clear();
-#endif // defined(COBALT_WIN)
-
- audio_config_.Initialize(
- kCodecAAC,
- 16, // AAC is always 16 bit
- aac.channel_layout(), aac.GetOutputSamplesPerSecond(false),
- aac.raw_data().empty() ? NULL : &aac.raw_data().front(),
- aac.raw_data().size(), false, false);
-}
-
-size_t ShellAVCParser::CalculatePrependSize(DemuxerStream::Type type,
- bool is_keyframe) {
- size_t prepend_size = 0;
- if (type == DemuxerStream::VIDEO) {
- bool needs_prepend = is_keyframe;
- if (needs_prepend)
- prepend_size = video_prepend_size_;
- } else if (type == DemuxerStream::AUDIO) {
- prepend_size = audio_prepend_.size();
- } else {
- NOTREACHED() << "unsupported stream type";
- }
- return prepend_size;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_avc_parser.h b/src/media/filters/shell_avc_parser.h
deleted file mode 100644
index b47641b..0000000
--- a/src/media/filters/shell_avc_parser.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_AVC_PARSER_H_
-#define MEDIA_FILTERS_SHELL_AVC_PARSER_H_
-
-#include "media/filters/shell_parser.h"
-
-namespace media {
-
-// Typical size of an annexB prepend will be around 60 bytes. We make more room
-// to ensure that only a very few videos will fail to play for lack of room
-// in the prepend.
-static const int kAnnexBPrependMaxSize = 1024;
-
-// while not an actual parser, provides shared functionality to both the
-// mp4 and flv parsers which derive from it. Implements part of ShellParser
-// while leaving the rest for its children.
-class ShellAVCParser : public ShellParser {
- public:
- explicit ShellAVCParser(scoped_refptr<ShellDataSourceReader> reader);
- virtual ~ShellAVCParser();
-
- struct ShellSPSRecord {
- gfx::Size coded_size;
- gfx::Rect visible_rect;
- gfx::Size natural_size;
- uint32 num_ref_frames;
- };
- static bool ParseSPS(const uint8* sps,
- size_t sps_size,
- ShellSPSRecord* record_out);
-
- // GetNextAU we must pass on to FLV or MP4 children.
- virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) = 0;
- // Prepends are common to all AVC/AAC containers so we can do this one here.
- virtual bool Prepend(scoped_refptr<ShellAU> au,
- scoped_refptr<DecoderBuffer> buffer) OVERRIDE;
-
- protected:
- virtual bool DownloadAndParseAVCConfigRecord(uint64 offset, uint32 size);
- virtual bool ParseAVCConfigRecord(uint8* buffer, uint32 size);
- // pps_size can be 0. Returns false on unable to construct.
- virtual bool BuildAnnexBPrepend(uint8* sps,
- uint32 sps_size,
- uint8* pps,
- uint32 pps_size);
- virtual void ParseAudioSpecificConfig(uint8 b0, uint8 b1);
- virtual size_t CalculatePrependSize(DemuxerStream::Type type,
- bool is_keyframe);
-
- uint8 nal_header_size_;
- // audio frames have a fixed-size small prepend that we attach to every
- // audio buffer created by DownloadBuffer()
- std::vector<uint8> audio_prepend_;
- // video frames have a variable-size prepend that we limit to a reasonable
- // upper bound. We only need to attach it to keyframes, however, the rest
- // of the frames need only an AnnexB start code.
- uint8 video_prepend_[kAnnexBPrependMaxSize];
- uint32 video_prepend_size_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_AVC_PARSER_H_
diff --git a/src/media/filters/shell_demuxer.cc b/src/media/filters/shell_demuxer.cc
deleted file mode 100644
index 32d7ab7..0000000
--- a/src/media/filters/shell_demuxer.cc
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_demuxer.h"
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/stringprintf.h"
-#include "base/task_runner_util.h"
-#include "base/time.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/data_source.h"
-#include "media/base/shell_media_platform.h"
-
-#include <inttypes.h>
-
-namespace media {
-
-ShellDemuxerStream::ShellDemuxerStream(ShellDemuxer* demuxer, Type type)
- : demuxer_(demuxer),
- type_(type),
- last_buffer_timestamp_(kNoTimestamp()),
- stopped_(false) {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::ShellDemuxerStream()");
- DCHECK(demuxer_);
-}
-
-bool ShellDemuxerStream::StreamWasEncrypted() const {
- if (type_ == VIDEO)
- return demuxer_->VideoConfig().is_encrypted();
- else if (type_ == AUDIO)
- return demuxer_->AudioConfig().is_encrypted();
-
- NOTREACHED();
- return false;
-}
-
-void ShellDemuxerStream::Read(const ReadCB& read_cb) {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read()");
- DCHECK(!read_cb.is_null());
-
- base::AutoLock auto_lock(lock_);
-
- // Don't accept any additional reads if we've been told to stop.
- // The demuxer_ may have been destroyed in the pipleine thread.
- if (stopped_) {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() EOS sent.");
- read_cb.Run(DemuxerStream::kOk,
- scoped_refptr<DecoderBuffer>(
- DecoderBuffer::CreateEOSBuffer(kNoTimestamp())));
- return;
- }
-
- // Buffers are only queued when there are no pending reads.
- DCHECK(buffer_queue_.empty() || read_queue_.empty());
-
- if (!buffer_queue_.empty()) {
- // Send the oldest buffer back.
- scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front();
- if (buffer->IsEndOfStream()) {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() EOS sent.");
- } else {
- // Do not pop EOS buffers, so that subsequent read requests also get EOS
- buffer_queue_.pop_front();
- }
- read_cb.Run(
- DemuxerStream::kOk,
- ShellMediaPlatform::Instance()->ProcessBeforeLeavingDemuxer(buffer));
- } else {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() request queued.");
- read_queue_.push_back(read_cb);
- }
-}
-
-const AudioDecoderConfig& ShellDemuxerStream::audio_decoder_config() {
- return demuxer_->AudioConfig();
-}
-
-const VideoDecoderConfig& ShellDemuxerStream::video_decoder_config() {
- return demuxer_->VideoConfig();
-}
-
-Ranges<base::TimeDelta> ShellDemuxerStream::GetBufferedRanges() {
- base::AutoLock auto_lock(lock_);
- return buffered_ranges_;
-}
-
-DemuxerStream::Type ShellDemuxerStream::type() {
- return type_;
-}
-
-void ShellDemuxerStream::EnableBitstreamConverter() {
- NOTIMPLEMENTED();
-}
-
-void ShellDemuxerStream::EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) {
- TRACE_EVENT1("media_stack", "ShellDemuxerStream::EnqueueBuffer()",
- "timestamp", buffer->GetTimestamp().InMicroseconds());
- base::AutoLock auto_lock(lock_);
- if (stopped_) {
- // it's possible due to pipelining both downstream and within the
- // demuxer that several pipelined reads will be enqueuing packets
- // on a stopped stream. Drop them after complaining.
- DLOG(WARNING) << "attempted to enqueue packet on stopped stream";
- return;
- }
-
- if (buffer->IsEndOfStream()) {
- TRACE_EVENT0("media_stack",
- "ShellDemuxerStream::EnqueueBuffer() EOS received.");
- } else if (buffer->GetTimestamp() != kNoTimestamp()) {
- if (last_buffer_timestamp_ != kNoTimestamp() &&
- last_buffer_timestamp_ < buffer->GetTimestamp()) {
- buffered_ranges_.Add(last_buffer_timestamp_, buffer->GetTimestamp());
- }
- last_buffer_timestamp_ = buffer->GetTimestamp();
- } else {
- DLOG(WARNING) << "bad timestamp info on enqueued buffer.";
- }
-
- // Check for any already waiting reads, service oldest read if there
- if (read_queue_.size()) {
- // assumption here is that buffer queue is empty
- DCHECK_EQ(buffer_queue_.size(), 0);
- ReadCB read_cb(read_queue_.front());
- read_queue_.pop_front();
- read_cb.Run(
- DemuxerStream::kOk,
- ShellMediaPlatform::Instance()->ProcessBeforeLeavingDemuxer(buffer));
- } else {
- // save the buffer for next read request
- buffer_queue_.push_back(buffer);
- }
-}
-
-base::TimeDelta ShellDemuxerStream::GetLastBufferTimestamp() const {
- base::AutoLock auto_lock(lock_);
- return last_buffer_timestamp_;
-}
-
-void ShellDemuxerStream::FlushBuffers() {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::FlushBuffers()");
- base::AutoLock auto_lock(lock_);
- // TODO: Investigate if the following warning is valid.
- DLOG_IF(WARNING, !read_queue_.empty()) << "Read requests should be empty";
- buffer_queue_.clear();
- last_buffer_timestamp_ = kNoTimestamp();
-}
-
-void ShellDemuxerStream::Stop() {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Stop()");
- DCHECK(demuxer_->MessageLoopBelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- buffer_queue_.clear();
- last_buffer_timestamp_ = kNoTimestamp();
- // fulfill any pending callbacks with EOS buffers set to end timestamp
- for (ReadQueue::iterator it = read_queue_.begin(); it != read_queue_.end();
- ++it) {
- TRACE_EVENT0("media_stack", "ShellDemuxerStream::Stop() EOS sent.");
- it->Run(DemuxerStream::kOk,
- scoped_refptr<DecoderBuffer>(
- DecoderBuffer::CreateEOSBuffer(kNoTimestamp())));
- }
- read_queue_.clear();
- stopped_ = true;
-}
-
-//
-// ShellDemuxer
-//
-ShellDemuxer::ShellDemuxer(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- DataSource* data_source)
- : message_loop_(message_loop),
- host_(NULL),
- blocking_thread_("ShellDemuxerBlockingThread"),
- data_source_(data_source),
- stopped_(false),
- flushing_(false),
- audio_reached_eos_(false),
- video_reached_eos_(false) {
- DCHECK(data_source_);
- DCHECK(message_loop_);
- reader_ = new ShellDataSourceReader();
- reader_->SetDataSource(data_source_);
-}
-
-ShellDemuxer::~ShellDemuxer() {}
-
-void ShellDemuxer::Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) {
- TRACE_EVENT0("media_stack", "ShellDemuxer::Initialize()");
- DCHECK(MessageLoopBelongsToCurrentThread());
- DCHECK(reader_);
- DCHECK(!parser_);
-
- DLOG(INFO) << "this is a PROGRESSIVE playback.";
-
- host_ = host;
- data_source_->set_host(host);
-
- // create audio and video demuxer stream objects
- audio_demuxer_stream_ = new ShellDemuxerStream(this, DemuxerStream::AUDIO);
- video_demuxer_stream_ = new ShellDemuxerStream(this, DemuxerStream::VIDEO);
-
- // start the blocking thread and have it download and parse the media config
- if (!blocking_thread_.Start()) {
- status_cb.Run(DEMUXER_ERROR_COULD_NOT_PARSE);
- return;
- }
-
- base::PostTaskAndReplyWithResult(
- blocking_thread_.message_loop_proxy(), FROM_HERE,
- base::Bind(&ShellDemuxer::ParseConfigBlocking, this, status_cb),
- base::Bind(&ShellDemuxer::ParseConfigDone, this, status_cb));
-}
-
-PipelineStatus ShellDemuxer::ParseConfigBlocking(
- const PipelineStatusCB& status_cb) {
- DCHECK(blocking_thread_.message_loop_proxy()->BelongsToCurrentThread());
- DCHECK(!parser_);
-
- // construct stream parser with error callback
- PipelineStatus status = ShellParser::Construct(reader_, &parser_);
- // if we can't construct a parser for this stream it's a fatal error, return
- // false so ParseConfigDone will notify the caller to Initialize() via
- // status_cb.
- if (!parser_ || status != PIPELINE_OK) {
- DCHECK(!parser_);
- DCHECK_NE(status, PIPELINE_OK);
- if (status == PIPELINE_OK) {
- status = DEMUXER_ERROR_COULD_NOT_PARSE;
- }
- return status;
- }
-
- // instruct the parser to extract audio and video config from the file
- if (!parser_->ParseConfig()) {
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
-
- // make sure we got a valid and complete configuration
- if (!parser_->IsConfigComplete()) {
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
-
- // IsConfigComplete() should guarantee we know the duration
- DCHECK(parser_->Duration() != kInfiniteDuration());
- host_->SetDuration(parser_->Duration());
- // Bitrate may not be known, however
- uint32 bitrate = parser_->BitsPerSecond();
- if (bitrate > 0) {
- data_source_->SetBitrate(bitrate);
- }
-
- // successful parse of config data, inform the nonblocking demuxer thread
- DCHECK_EQ(status, PIPELINE_OK);
- return PIPELINE_OK;
-}
-
-void ShellDemuxer::ParseConfigDone(const PipelineStatusCB& status_cb,
- PipelineStatus status) {
- DCHECK(MessageLoopBelongsToCurrentThread());
- // if the blocking parser thread cannot parse config we're done.
- if (status != PIPELINE_OK) {
- status_cb.Run(status);
- return;
- }
- DCHECK(parser_);
- // start downloading data
- Request(DemuxerStream::AUDIO);
-
- status_cb.Run(PIPELINE_OK);
-}
-
-void ShellDemuxer::Request(DemuxerStream::Type type) {
- // post task to our blocking thread
- blocking_thread_.message_loop_proxy()->PostTask(
- FROM_HERE, base::Bind(&ShellDemuxer::RequestTask, this, type));
-}
-
-void ShellDemuxer::RequestTask(DemuxerStream::Type type) {
- DCHECK(blocking_thread_.message_loop_proxy()->BelongsToCurrentThread());
- DCHECK(!requested_au_) << "overlapping requests not supported!";
- flushing_ = false;
- // Ask parser for next AU
- scoped_refptr<ShellAU> au = parser_->GetNextAU(type);
- // fatal parsing error returns NULL or malformed AU
- if (!au || !au->IsValid()) {
- if (!stopped_) {
- DLOG(ERROR) << "got back bad AU from parser";
- host_->OnDemuxerError(DEMUXER_ERROR_COULD_NOT_PARSE);
- }
- return;
- }
-
- // make sure we got back an AU of the correct type
- DCHECK(au->GetType() == type);
-
- const char* ALLOW_UNUSED event_type =
- type == DemuxerStream::AUDIO ? "audio" : "video";
- TRACE_EVENT2("media_stack", "ShellDemuxer::RequestTask()", "type", event_type,
- "timestamp", au->GetTimestamp().InMicroseconds());
-
- // don't issue allocation requests for EOS AUs
- if (au->IsEndOfStream()) {
- TRACE_EVENT0("media_stack", "ShellDemuxer::RequestTask() EOS sent");
- // enqueue EOS buffer with correct stream
- scoped_refptr<DecoderBuffer> eos_buffer =
- DecoderBuffer::CreateEOSBuffer(au->GetTimestamp());
- if (type == DemuxerStream::AUDIO) {
- audio_reached_eos_ = true;
- audio_demuxer_stream_->EnqueueBuffer(eos_buffer);
- } else if (type == DemuxerStream::VIDEO) {
- video_reached_eos_ = true;
- video_demuxer_stream_->EnqueueBuffer(eos_buffer);
- }
- IssueNextRequestTask();
- return;
- }
-
- // enqueue the request
- requested_au_ = au;
-
- // AllocateBuffer will return false if the requested size is larger
- // than the maximum limit for a single buffer.
- if (!ShellBufferFactory::Instance()->AllocateBuffer(
- au->GetMaxSize(), au->IsKeyframe(),
- base::Bind(&ShellDemuxer::BufferAllocated, this))) {
- DLOG(ERROR) << "buffer allocation failed.";
- host_->OnDemuxerError(PIPELINE_ERROR_COULD_NOT_RENDER);
- return;
- }
-}
-
-// callback from ShellBufferAllocated, post a task to the blocking thread
-void ShellDemuxer::BufferAllocated(scoped_refptr<DecoderBuffer> buffer) {
- if (!stopped_) {
- blocking_thread_.message_loop_proxy()->PostTask(
- FROM_HERE, base::Bind(&ShellDemuxer::DownloadTask, this, buffer));
- }
-}
-
-void ShellDemuxer::DownloadTask(scoped_refptr<DecoderBuffer> buffer) {
- DCHECK(blocking_thread_.message_loop_proxy()->BelongsToCurrentThread());
- // We need a requested_au_ or to have canceled this request and
- // are buffering to a new location for this to make sense
- DCHECK(requested_au_);
-
- const char* ALLOW_UNUSED event_type =
- requested_au_->GetType() == DemuxerStream::AUDIO ? "audio" : "video";
- TRACE_EVENT2("media_stack", "ShellDemuxer::DownloadTask()", "type",
- event_type, "timestamp",
- requested_au_->GetTimestamp().InMicroseconds());
- // do nothing if stopped
- if (stopped_) {
- DLOG(INFO) << "aborting download task, stopped";
- return;
- }
-
- // Flushing is a signal to restart the request->download cycle with
- // a new request. Drop current request and issue a new one.
- // flushing_ will be reset by the next call to RequestTask()
- if (flushing_) {
- DLOG(INFO) << "skipped AU download due to flush";
- requested_au_ = NULL;
- IssueNextRequestTask();
- return;
- }
-
- if (!requested_au_->Read(reader_, buffer)) {
- DLOG(ERROR) << "au read failed";
- host_->OnDemuxerError(PIPELINE_ERROR_READ);
- return;
- }
-
- // copy timestamp and duration values
- buffer->SetTimestamp(requested_au_->GetTimestamp());
- buffer->SetDuration(requested_au_->GetDuration());
-
- // enqueue buffer into appropriate stream
- if (requested_au_->GetType() == DemuxerStream::AUDIO) {
- audio_demuxer_stream_->EnqueueBuffer(buffer);
- } else if (requested_au_->GetType() == DemuxerStream::VIDEO) {
- video_demuxer_stream_->EnqueueBuffer(buffer);
- } else {
- NOTREACHED() << "invalid buffer type enqueued";
- }
-
- // finished with this au, deref
- requested_au_ = NULL;
-
- // Calculate total range of buffered data for both audio and video.
- Ranges<base::TimeDelta> buffered(
- audio_demuxer_stream_->GetBufferedRanges().IntersectionWith(
- video_demuxer_stream_->GetBufferedRanges()));
- // Notify host of each disjoint range.
- for (size_t i = 0; i < buffered.size(); ++i) {
- host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i));
- }
-
- IssueNextRequestTask();
-}
-
-void ShellDemuxer::IssueNextRequestTask() {
- DCHECK(!requested_au_);
- // if we're stopped don't download anymore
- if (stopped_) {
- DLOG(INFO) << "stopped so request loop is stopping";
- return;
- }
- // if we have eos in one or both buffers the decision is easy
- if (audio_reached_eos_ || video_reached_eos_) {
- if (audio_reached_eos_) {
- if (video_reached_eos_) {
- // both are true, issue no more requests!
- DLOG(INFO) << "both streams at EOS, request loop stopping";
- return;
- } else {
- // audio is at eos, video isn't, get more video
- Request(DemuxerStream::VIDEO);
- }
- } else {
- // audio is not at eos, video is, get more audio
- Request(DemuxerStream::AUDIO);
- }
- return;
- }
-
- // priority order for figuring out what to download next
- base::TimeDelta audio_stamp = audio_demuxer_stream_->GetLastBufferTimestamp();
- base::TimeDelta video_stamp = video_demuxer_stream_->GetLastBufferTimestamp();
- // if the audio demuxer stream is empty, always fill it first
- if (audio_stamp == kNoTimestamp()) {
- Request(DemuxerStream::AUDIO);
- } else if (video_stamp == kNoTimestamp()) {
- // the video demuxer stream is empty, we need data for it
- Request(DemuxerStream::VIDEO);
- } else if (video_stamp < audio_stamp) {
- // video is earlier, fill it first
- Request(DemuxerStream::VIDEO);
- } else {
- Request(DemuxerStream::AUDIO);
- }
-}
-
-void ShellDemuxer::Stop(const base::Closure& callback) {
- DCHECK(MessageLoopBelongsToCurrentThread());
- // set our internal stop flag, to not treat read failures as
- // errors anymore but as a natural part of stopping
- stopped_ = true;
- // stop the reader, which will stop the datasource and call back
- reader_->Stop(base::Bind(&ShellDemuxer::DataSourceStopped, this, callback));
-}
-
-void ShellDemuxer::DataSourceStopped(const base::Closure& callback) {
- TRACE_EVENT0("media_stack", "ShellDemuxer::DataSourceStopped()");
- DCHECK(MessageLoopBelongsToCurrentThread());
- // stop the download thread
- blocking_thread_.Stop();
-
- // tell downstream we've stopped
- if (audio_demuxer_stream_)
- audio_demuxer_stream_->Stop();
- if (video_demuxer_stream_)
- video_demuxer_stream_->Stop();
-
- callback.Run();
-}
-
-void ShellDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
- blocking_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&ShellDemuxer::SeekTask, this, time, BindToCurrentLoop(cb)));
-}
-
-// runs on blocking thread
-void ShellDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) {
- TRACE_EVENT1("media_stack", "ShellDemuxer::SeekTask()", "timestamp",
- time.InMicroseconds());
- DLOG(INFO) << base::StringPrintf("seek to: %" PRId64 " ms",
- time.InMilliseconds());
- // clear any enqueued buffers on demuxer streams
- audio_demuxer_stream_->FlushBuffers();
- video_demuxer_stream_->FlushBuffers();
- // advance parser to new timestamp
- if (!parser_->SeekTo(time)) {
- DLOG(ERROR) << "parser seek failed.";
- cb.Run(PIPELINE_ERROR_READ);
- return;
- }
- // if both streams had finished downloading, we need to restart the request
- if (audio_reached_eos_ && video_reached_eos_) {
- DLOG(INFO) << "restarting stopped request loop";
- Request(DemuxerStream::AUDIO);
- }
- audio_reached_eos_ = false;
- video_reached_eos_ = false;
- flushing_ = true;
- cb.Run(PIPELINE_OK);
-}
-
-void ShellDemuxer::OnAudioRendererDisabled() {
- NOTIMPLEMENTED();
-}
-
-void ShellDemuxer::SetPlaybackRate(float playback_rate) {
- data_source_->SetPlaybackRate(playback_rate);
-}
-
-scoped_refptr<DemuxerStream> ShellDemuxer::GetStream(
- media::DemuxerStream::Type type) {
- if (type == DemuxerStream::AUDIO) {
- return audio_demuxer_stream_;
- } else if (type == DemuxerStream::VIDEO) {
- return video_demuxer_stream_;
- } else {
- DLOG(WARNING) << "unsupported stream type requested";
- }
- return NULL;
-}
-
-base::TimeDelta ShellDemuxer::GetStartTime() const {
- // we always assume a start time of 0
- return base::TimeDelta();
-}
-
-const AudioDecoderConfig& ShellDemuxer::AudioConfig() {
- return parser_->AudioConfig();
-}
-
-const VideoDecoderConfig& ShellDemuxer::VideoConfig() {
- return parser_->VideoConfig();
-}
-
-bool ShellDemuxer::MessageLoopBelongsToCurrentThread() const {
- return message_loop_->BelongsToCurrentThread();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_demuxer.h b/src/media/filters/shell_demuxer.h
deleted file mode 100644
index 7fc381e..0000000
--- a/src/media/filters/shell_demuxer.h
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_DEMUXER_H_
-#define MEDIA_FILTERS_SHELL_DEMUXER_H_
-
-#include <deque>
-#include <map>
-#include <vector>
-
-#include "base/message_loop.h"
-#include "base/threading/thread.h"
-#include "media/base/demuxer.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/ranges.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/filters/shell_parser.h"
-
-namespace media {
-
-class DecoderBuffer;
-class ShellDemuxer;
-
-class ShellDemuxerStream : public DemuxerStream {
- public:
- ShellDemuxerStream(ShellDemuxer* demuxer, Type type);
-
- // DemuxerStream implementation
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
- virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
- virtual Type type() OVERRIDE;
- virtual void EnableBitstreamConverter() OVERRIDE;
- virtual bool StreamWasEncrypted() const OVERRIDE;
-
- // Functions used by ShellDemuxer
- Ranges<base::TimeDelta> GetBufferedRanges();
- void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer);
- void FlushBuffers();
- void Stop();
- base::TimeDelta GetLastBufferTimestamp() const;
-
- private:
- // The Ranges object doesn't offer a complement object so we rebuild
- // enqueued ranges from the union of all of the buffers in the queue.
- // Call me whenever _removing_ data from buffer_queue_.
- void RebuildEnqueuedRanges_Locked();
-
- // non-owning pointer to avoid circular reference
- ShellDemuxer* demuxer_;
- Type type_;
-
- // Used to protect everything below.
- mutable base::Lock lock_;
- // Keeps track of all time ranges this object has seen since creation.
- // The demuxer uses these ranges to update the pipeline about what data
- // it has demuxed.
- Ranges<base::TimeDelta> buffered_ranges_;
- // The last timestamp of buffer enqueued. This is used in two places:
- // 1. Used with the timestamp of the current frame to calculate the
- // buffer range.
- // 2. Used by the demuxer to deteminate what type of frame to get next.
- base::TimeDelta last_buffer_timestamp_;
- bool stopped_;
-
- typedef std::deque<scoped_refptr<DecoderBuffer> > BufferQueue;
- BufferQueue buffer_queue_;
-
- typedef std::deque<ReadCB> ReadQueue;
- ReadQueue read_queue_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellDemuxerStream);
-};
-
-class MEDIA_EXPORT ShellDemuxer : public Demuxer {
- public:
- ShellDemuxer(const scoped_refptr<base::MessageLoopProxy>& message_loop,
- DataSource* data_source);
- virtual ~ShellDemuxer();
-
- // Demuxer implementation.
- virtual void Initialize(DemuxerHost* host,
- const PipelineStatusCB& status_cb) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE;
- virtual void OnAudioRendererDisabled() OVERRIDE;
- virtual void SetPlaybackRate(float playback_rate) OVERRIDE;
- virtual scoped_refptr<DemuxerStream> GetStream(
- DemuxerStream::Type type) OVERRIDE;
- virtual base::TimeDelta GetStartTime() const OVERRIDE;
-
- // TODO: Consider move the following functions to private section.
-
- // Issues a task to the demuxer to identify the next buffer of provided type
- // in the stream, allocate memory to contain that buffer, download the bytes
- // in to it, and enqueue the data in the appropriate demuxer stream.
- void Request(DemuxerStream::Type type);
-
- // The DemuxerStream objects ask their parent ShellDemuxer stream class
- // for these configuration data rather than duplicating in the child classes
- const AudioDecoderConfig& AudioConfig();
- const VideoDecoderConfig& VideoConfig();
-
- // Provide access to ShellDemuxerStream.
- bool MessageLoopBelongsToCurrentThread() const;
-
- // Callback from ShellBufferFactory
- void BufferAllocated(scoped_refptr<DecoderBuffer> buffer);
-
- private:
- void ParseConfigDone(const PipelineStatusCB& status_cb,
- PipelineStatus status);
- void DataSourceStopped(const base::Closure& callback);
-
- // methods that perform blocking I/O, and are therefore run on the
- // blocking_thread_
- // download enough of the stream to parse the configuration. returns
- // false on error.
- PipelineStatus ParseConfigBlocking(const PipelineStatusCB& status_cb);
- void RequestTask(DemuxerStream::Type type);
- void DownloadTask(scoped_refptr<DecoderBuffer> buffer);
- void IssueNextRequestTask();
- void SeekTask(base::TimeDelta time, const PipelineStatusCB& cb);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- DemuxerHost* host_;
-
- // Thread on which all blocking operations are executed.
- base::Thread blocking_thread_;
- DataSource* data_source_;
- scoped_refptr<ShellDataSourceReader> reader_;
-
- bool stopped_;
- bool flushing_;
-
- scoped_refptr<ShellDemuxerStream> audio_demuxer_stream_;
- scoped_refptr<ShellDemuxerStream> video_demuxer_stream_;
- scoped_refptr<ShellParser> parser_;
-
- scoped_refptr<ShellAU> requested_au_;
- bool audio_reached_eos_;
- bool video_reached_eos_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_DEMUXER_H_
diff --git a/src/media/filters/shell_ffmpeg.cc b/src/media/filters/shell_ffmpeg.cc
deleted file mode 100644
index f18e6bd..0000000
--- a/src/media/filters/shell_ffmpeg.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_ffmpeg.h"
-
-namespace media {
-
-namespace {
-bool ffmpeg_initialized = false;
-} // namespace
-
-void EnsureFfmpegInitialized() {
- if (!ffmpeg_initialized) {
- av_register_all();
- ffmpeg_initialized = true;
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_ffmpeg.h b/src/media/filters/shell_ffmpeg.h
deleted file mode 100644
index fd6d47d..0000000
--- a/src/media/filters/shell_ffmpeg.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_FFMPEG_H_
-#define MEDIA_FILTERS_SHELL_FFMPEG_H_
-
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libavresample/avresample.h>
-#include <libavutil/avutil.h>
-#include <libavutil/imgutils.h>
-#include <libavutil/opt.h>
-} // extern "C"
-
-namespace media {
-
-void EnsureFfmpegInitialized();
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_FFMPEG_H_
diff --git a/src/media/filters/shell_flv_parser.cc b/src/media/filters/shell_flv_parser.cc
deleted file mode 100644
index 4492a49..0000000
--- a/src/media/filters/shell_flv_parser.cc
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_flv_parser.h"
-
-#include <inttypes.h>
-#include <limits>
-
-#include "base/stringprintf.h"
-#include "media/base/endian_util.h"
-
-namespace media {
-
-// "FLV" as hex ASCII codes
-static const uint32 kFLV = 0x00464c56;
-
-// FLV configuration, such as the AVCConfigRecord and the AudioSpecificConfig,
-// should proceed any actual encoded data, and should be in the top of the file.
-// This constant describes how far into the file we're willing to traverse
-// without encountering metadata or encoded video keyframe data before giving
-// up.
-static const uint64 kMetadataMaxBytes = 4 * 1024 * 1024;
-
-static const uint8 kAudioTagType = 8;
-static const uint8 kVideoTagType = 9;
-static const uint8 kScriptDataObjectTagType = 18;
-
-// size of standard FLV tag
-static const int kTagSize = 11;
-// To limit reads we download a bit of extra data in flash headers to make
-// sure we get all of the tag information we might need, allowing us
-// to download the actual encoded data directly into a decoder buffer with
-// proper alignment on a subsequent read. This value is calculated as the sum of
-// the FLV tag size, the VIDEODATA tag size, and the AVCVIDEOPACKET tag size,
-// which is the maximum amount of tag data that we will need to parse before
-// download encoded A/V data.
-static const int kTagDownloadSize = kTagSize + 1 + 4;
-// these constants describe total tag sizes for AAC/AVC addendums to tags
-static const int kAudioTagSize = 2;
-static const int kVideoTagSize = 5;
-
-// FLV AUDIODATA tag constants
-static const uint8 kSoundFormatTypeAAC = 10;
-// FLV AACAUDIODATA tag constants
-static const uint8 kAACPacketTypeSequence = 0;
-static const uint8 kAACPacketTypeRaw = 1;
-// FLV VIDEODATA tag constants
-static const uint8 kCodecIDAVC = 7;
-// FLV AVCVIDEODATA tag constants
-static const uint8 kAVCPacketTypeSequenceHeader = 0;
-static const uint8 kAVCPacketTypeNALU = 1;
-// Unused:
-// static const uint8 kAVCPacketTypeEndOfSequence = 2;
-
-// SCRIPTDATA parsing constants
-static const uint8 kAMF0NumberType = 0x00;
-static const int kAMF0NumberLength = 9;
-
-// static
-PipelineStatus ShellFLVParser::Construct(
- scoped_refptr<ShellDataSourceReader> reader,
- const uint8* construction_header,
- scoped_refptr<ShellParser>* parser) {
- DCHECK(parser);
- *parser = NULL;
-
- // look for "FLV" string at top of file, mask off LSB
- uint32 FLV = endian_util::load_uint32_big_endian(construction_header) >> 8;
- if (FLV != kFLV) {
- // Not an flv.
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
- // Check for availability of both an audio and video stream. Audio stream is
- // third bit, video stream is first bit in 5th byte of file header
- if ((construction_header[4] & 0x05) != 0x05) {
- return DEMUXER_ERROR_NO_SUPPORTED_STREAMS;
- }
- // offset of first data tag in stream is next 4 bytes
- uint32 data_offset =
- endian_util::load_uint32_big_endian(construction_header + 5);
- // add four bytes to skip over PreviousTagSize0
- data_offset += 4;
-
- // construct an FLV parser
- *parser = new ShellFLVParser(reader, data_offset);
- return PIPELINE_OK;
-}
-
-ShellFLVParser::ShellFLVParser(scoped_refptr<ShellDataSourceReader> reader,
- uint32 tag_start_offset)
- : ShellAVCParser(reader),
- tag_offset_(tag_start_offset),
- at_end_of_file_(false) {}
-
-ShellFLVParser::~ShellFLVParser() {}
-
-bool ShellFLVParser::ParseConfig() {
- // traverse file until we either reach the limit of bytes we're willing to
- // parse of config info or we've encountered actual keyframe video data.
- while (tag_offset_ < kMetadataMaxBytes && time_to_byte_map_.size() == 0) {
- if (!ParseNextTag()) {
- return false;
- }
- }
-
- if (duration_.InMilliseconds() == 0) {
- return false;
- }
-
- // We may have a valid duration by now and the reader may know the
- // length of the file in bytes, see if we can extrapolate a bitrate from
- // this.
- if (duration_ != kInfiniteDuration() && reader_->FileSize() > 0) {
- bits_per_second_ = (uint32)(
- ((reader_->FileSize() * 8000ULL) / (duration_.InMilliseconds())));
- }
-
- return true;
-}
-
-scoped_refptr<ShellAU> ShellFLVParser::GetNextAU(DemuxerStream::Type type) {
- if (type == DemuxerStream::AUDIO) {
- return GetNextAudioAU();
- } else if (type == DemuxerStream::VIDEO) {
- return GetNextVideoAU();
- } else {
- NOTREACHED();
- }
- return NULL;
-}
-
-// seeking an flv:
-// 1) finding nearest video keyframe before timestamp:
-// a) If we are seeking in to an area we have already parsed then we
-// will find the bounding keyframe.
-// b) If not, we parse the FLV until a) is true.
-// 2) set tag_offset_ to the byte offset of the keyframe found in 1)
-bool ShellFLVParser::SeekTo(base::TimeDelta timestamp) {
- // convert timestamp to millisecond FLV timestamp
- uint32 timestamp_flv = (uint32)timestamp.InMilliseconds();
- bool found_upper_bound = false;
- uint64 seek_byte_offset = tag_offset_;
- uint32 seek_timestamp = 0;
- // upper_bound returns iterator of first element in container with key > arg
- TimeToByteMap::iterator keyframe_in_map =
- time_to_byte_map_.upper_bound(timestamp_flv);
- // this is case 1a), or keyframe is last keyframe before EOS,
- // or map is empty (error state)
- if (keyframe_in_map == time_to_byte_map_.end()) {
- // is map empty? This is an error case, we should always have found a
- // keyframe during ParseConfig()
- if (time_to_byte_map_.size() == 0) {
- NOTREACHED() << "empty time to byte map on FLV seek";
- return false;
- } else {
- // start at last keyframe in the map and parse from there
- seek_byte_offset = time_to_byte_map_.rbegin()->second;
- }
- } else {
- found_upper_bound = true;
- // it's possible timestamp <= first keyframe in map, in which case we
- // use the first keyframe in map.
- if (keyframe_in_map != time_to_byte_map_.begin()) {
- keyframe_in_map--;
- }
- seek_byte_offset = keyframe_in_map->second;
- seek_timestamp = keyframe_in_map->first;
- }
- // if seek has changed our position in the file jump there now
- if (seek_byte_offset != tag_offset_) {
- JumpParserTo(seek_byte_offset);
- }
- // if found_upper_bound is still false we are in case 1b), parse ahead until
- // we encounter an upper bound or an eof
- while (!found_upper_bound && !at_end_of_file_) {
- // save highest keyframe in file in case it becomes the one we want
- seek_byte_offset = time_to_byte_map_.rbegin()->second;
- seek_timestamp = time_to_byte_map_.rbegin()->first;
- // parse next tag in the file
- if (!ParseNextTag()) {
- return false;
- }
- // check last keyframe timestamp, if it's greater than our target timestamp
- // we can stop
- found_upper_bound = (time_to_byte_map_.rbegin()->first > timestamp_flv);
- }
- // make sure we have done step 2), jump parser to new keyframe
- if (seek_byte_offset != tag_offset_) {
- JumpParserTo(seek_byte_offset);
- }
- DLOG(INFO) << base::StringPrintf("flv parser seeking to timestamp: %" PRId64
- " chose keyframe at %d",
- timestamp.InMilliseconds(), seek_timestamp);
- return true;
-}
-
-scoped_refptr<ShellAU> ShellFLVParser::GetNextAudioAU() {
- // As audio timestamps are supposed to increase monotonically we need
- // only 2 to calculate a duration.
- while (next_audio_aus_.size() < 2) {
- if (!ParseNextTag()) {
- return NULL;
- }
- }
- // There should always be 2 AUs in the queue now, even if they are both EOS.
- DCHECK_GE(next_audio_aus_.size(), 2);
-
- // Extract first AU in queue
- scoped_refptr<ShellAU> au(next_audio_aus_.front());
- next_audio_aus_.pop_front();
- // Next timestamp should be greater than ours, if not something is very funny
- // with this FLV and we won't be able to calculate duration.
- if (next_audio_aus_.front()->GetTimestamp() >= au->GetTimestamp()) {
- au->SetDuration(next_audio_aus_.front()->GetTimestamp() -
- au->GetTimestamp());
- } else {
- DLOG(ERROR) << "out of order audio timestamps encountered on FLV parsing.";
- }
- return au;
-}
-
-scoped_refptr<ShellAU> ShellFLVParser::GetNextVideoAU() {
- while (next_video_aus_.empty()) {
- if (!ParseNextTag()) {
- return NULL;
- }
- }
- // extract next video AU
- scoped_refptr<ShellAU> au(next_video_aus_.front());
- next_video_aus_.pop_front();
-
- return au;
-}
-
-//
-// byte layout of an FLVTAG is:
-// field | type | comment
-// ------------------+--------+---------
-// previous tag size | uint32 | we skip past these when parsing last tag
-// tag type | uint8 | parsing starts here. describes tag type
-// tag data size | uint24 | size of tag data payload (everything after this)
-// timestamp | uint24 | lower 24 bits of timestamp in milliseconds
-// timestamp ext | uint8 | upper 8 bits of timestamp in milliseconds
-// stream id | uint24 | always 0
-//
-bool ShellFLVParser::ParseNextTag() {
- uint8 tag_buffer[kTagDownloadSize];
-
- if (at_end_of_file_) {
- return false;
- }
-
- // get previous tag size and header for next one
- int bytes_read =
- reader_->BlockingRead(tag_offset_, kTagDownloadSize, tag_buffer);
-
- // if that was the last tag in the stream detect the EOS and return. This
- // is where normal termination of an FLV stream will occur.
- if (bytes_read < kTagDownloadSize) {
- at_end_of_file_ = true;
- // Normal termination of an FLV. Enqueue EOS AUs in both streams.
- next_video_aus_.push_back(ShellAU::CreateEndOfStreamAU(
- DemuxerStream::VIDEO, video_track_duration_));
- next_audio_aus_.push_back(ShellAU::CreateEndOfStreamAU(
- DemuxerStream::AUDIO, audio_track_duration_));
- return true;
- }
-
- // extract the tag data size from the tag header as uint24
- // this is size of attached data field only not including this header
- // but including the audio and video sub-headers
- uint32 tag_data_size =
- endian_util::load_uint32_big_endian(tag_buffer + 1) >> 8;
-
- // extract timestamp, wonky byte order comes from the standard
- int32 timestamp = tag_buffer[4] << 16 | tag_buffer[5] << 8 | tag_buffer[6] |
- tag_buffer[7] << 24;
-
- // choose which tag type to parse
- bool parse_result = true;
- uint8* tag_body = tag_buffer + kTagSize;
- switch (tag_buffer[0]) {
- case kAudioTagType:
- parse_result = ParseAudioDataTag(tag_body, tag_data_size, timestamp);
- break;
-
- case kVideoTagType:
- parse_result = ParseVideoDataTag(tag_body, tag_data_size, timestamp);
- break;
-
- case kScriptDataObjectTagType:
- parse_result =
- ParseScriptDataObjectTag(tag_body, tag_data_size, timestamp);
- break;
-
- default:
- DLOG(WARNING) << base::StringPrintf("unsupported FLV TagType %d",
- tag_buffer[0]);
- break;
- }
-
- // advance read pointer to next tag header
- tag_offset_ += kTagSize + tag_data_size + 4;
- return parse_result;
-}
-
-// FLV AUDIODATA tags are packed into a single byte, bit layout is:
-// aaaabbcd
-// aaaa: 4 bits format enum, AAC is 10 decimal
-// bb: 2 bits sample rate enum, AAC is always 3 decimal (44 KHz)
-// c: 1 bit sound size, 0 means 8 bit, 1 means 16 bit, AAC is always 1
-// d: 1 bit sound type, 0 means mono, 1 means stereo, AAC is always 1
-// if this is an AACAUDIODATA tag the next byte in the sequence 0 if
-// this is an AudioSpecificConfig tag or 1 if it is raw AAC frame data.
-//
-// * NOTE that FLV standard defines fixed values for sample rate, bit
-// width, and channel count for AAC samples but the AudioSpecificConfig may
-// define those values differently and is authoritative, so we ignore the
-// FLV-provided config values.
-bool ShellFLVParser::ParseAudioDataTag(uint8* tag,
- uint32 size,
- uint32 timestamp) {
- // Smallest meaningful size for an audio data tag is 4 bytes, one for the
- // AUDIODATA tag, one for the AACAUDIODATA tag. and minimum 2 bytes of data.
- if (size < kAudioTagSize + 2) {
- return false;
- }
- // we only support parsing AAC audio data tags
- if (((tag[0] >> 4) & 0x0f) != kSoundFormatTypeAAC) {
- return false;
- }
- // now see if this is a config packet or a data packet
- if (tag[1] == kAACPacketTypeSequence) { // audio config info
- // AudioSpecificConfig records can be longer than two bytes but we extract
- // everything we need from the first two bytes, positioned here at index 2
- // and 3 in the tag buffer
- ParseAudioSpecificConfig(tag[2], tag[3]);
- } else if (tag[1] == kAACPacketTypeRaw) { // raw AAC audio
- // this is audio data, check timestamp
- base::TimeDelta ts = base::TimeDelta::FromMilliseconds(timestamp);
- if (ts > audio_track_duration_) {
- audio_track_duration_ = ts;
- }
- // build the AU
- size_t prepend_size = CalculatePrependSize(DemuxerStream::AUDIO, true);
- scoped_refptr<ShellAU> au = ShellAU::CreateAudioAU(
- tag_offset_ + kTagSize + kAudioTagSize, size - kAudioTagSize,
- prepend_size, true, ts, kInfiniteDuration(), this);
- next_audio_aus_.push_back(au);
- }
-
- return true;
-}
-
-// FLV VIDEODATA tags are packed into a single byte, bit layout is:
-// aaaabbbb
-// aaaa: 4 bits frame type enum, 1 is AVC keyframe, 2 is AVC inter-frame
-// bbbb: 4 bits codecID, 7 is AVC
-// if this is an AVCVIDEOPACKET tag the next 4 bytes comprise the AVCVIDEOPACKET
-// tag header:
-// field | type | comment
-// ------------------+--------+---------
-// AVCPacketType | uint8 | 0 is config, 1 is data, 2 is EOS (ignored)
-// CompositionTime | int24 | signed time offset, add to FLV timestamp for pts
-//
-// NOTE that FLV video data is always presented in decode order and
-// CompositionTime is not entirely reliable for determining pts, as some
-// encoders always set it to zero.
-bool ShellFLVParser::ParseVideoDataTag(uint8* tag,
- uint32 size,
- uint32 timestamp) {
- // need at least 5 bytes of tag data, one for the VIDEODATA tag and 4 for
- // the AVCVIDEODATA tag that should always follow it
- if (size < kVideoTagSize) {
- return false;
- }
- // check for AVC format
- if ((tag[0] & 0x0f) != kCodecIDAVC) {
- return false;
- }
- // determine packet type
- if (tag[1] == kAVCPacketTypeSequenceHeader) { // video config info
- // AVC config record, download and parse
- return DownloadAndParseAVCConfigRecord(
- tag_offset_ + kTagSize + kVideoTagSize, size);
- } else if (tag[1] == kAVCPacketTypeNALU) { // raw AVC data
- // should we add this to our keyframe map?
- bool is_keyframe = (tag[0] & 0xf0) == 0x10;
- // TODO: when we add support for seeking, make sure these numbers are
- // consistent with the numbers provided by the time-to-byte manifest.
- if (is_keyframe) {
- time_to_byte_map_[timestamp] = tag_offset_;
- }
- // extract 24-bit composition time offset in big-endian for this frame
- int32 composition_time_offset = tag[2] * 65536 + tag[3] * 256 + tag[4];
- // calculate pts from flv timestamp and cts
- uint32 pts = timestamp + composition_time_offset;
- // FLV standard says that there can be multiple AVC NALUs packed here, so
- // we iterate through the tag data payload and enqueue byte offsets for
- // each NALU we encounter. The NALUs are packed by size counter that is
- // nal_header_size_ bytes long followed by the NALU of that size.
- uint32 avc_data_size = size - kVideoTagSize;
- uint32 avc_tag_offset = 0;
- base::TimeDelta ts = base::TimeDelta::FromMilliseconds(pts);
- if (ts > video_track_duration_) {
- video_track_duration_ = ts;
- }
-
- size_t prepend_size =
- CalculatePrependSize(DemuxerStream::VIDEO, is_keyframe);
- scoped_refptr<ShellAU> au = ShellAU::CreateVideoAU(
- tag_offset_ + kTagSize + kVideoTagSize + avc_tag_offset, avc_data_size,
- prepend_size, nal_header_size_, is_keyframe, ts, kInfiniteDuration(),
- this);
- // enqueue data tag
- next_video_aus_.push_back(au);
- }
- return true;
-}
-
-// FLV SCRIPTDATA tags are in serialized typically in Action Message Format 0 as
-// a collection of key/value pairs terminated by a special code. We only wish to
-// parse the duration and byterate from the scriptdata so we use a very light
-// weight parser in ExtractAMF0Number();
-bool ShellFLVParser::ParseScriptDataObjectTag(uint8* tag,
- uint32 size,
- uint32 timestamp) {
- scoped_refptr<ShellScopedArray> script_buffer =
- ShellBufferFactory::Instance()->AllocateArray(size);
- if (!script_buffer || !script_buffer->Get()) {
- return false;
- }
- int bytes_read =
- reader_->BlockingRead(tag_offset_ + kTagSize, size, script_buffer->Get());
- DCHECK_LE(size, static_cast<uint32>(std::numeric_limits<int32>::max()));
- if (bytes_read < static_cast<int>(size)) {
- return false;
- }
- // Attempt to extract the duration from the FLV metadata.
- double duration_seconds = 0;
- if (!ExtractAMF0Number(script_buffer, "duration", &duration_seconds)) {
- // might be worth trying to parse this as AMF3?
- return false;
- }
- duration_ = base::TimeDelta::FromMicroseconds(
- duration_seconds * base::Time::kMicrosecondsPerSecond);
-
- // Try for the byterate too, but this is nonfatal if we can't get it.
- double byterate = 0;
- if (ExtractAMF0Number(script_buffer, "totaldatarate", &byterate)) {
- bits_per_second_ = (uint32)(byterate * 8.0);
- }
-
- return true;
-}
-
-// The SCRIPTDATA tag contains a list of ordered pairs of AMF0 strings followed
-// by an arbitrary AMF0 object. Typically there's one object of interest with
-// string name 'onMetaData' followed by an anonymous object. In any event we
-// will scan the buffer looking only for the provided string, verify the next
-// bytes in the stream describe an AMF0 number, extract and return it.
-// TODO: Replace this (brittle) code with a proper AMF0 parser.
-bool ShellFLVParser::ExtractAMF0Number(scoped_refptr<ShellScopedArray> amf0,
- const char* name,
- double* number_out) {
- DCHECK(number_out);
- // the string will be proceeded by a u16 big-endian string length
- uint16 name_length = strlen(name);
- // there's lots of nonprinting characters and zeros in amf0, so we'll need
- // to search for the string using our own method
- int match_offset = 0;
- int name_offset = 0;
- // the last index in the buffer we could extract a string followed by Number
- int search_length = amf0->Size() - (name_length + kAMF0NumberLength);
- uint8* search_buffer = amf0->Get();
- while (match_offset <= search_length && name_offset < name_length) {
- if (search_buffer[match_offset] == name[name_offset]) {
- name_offset++; // advance our substring pointer in the event of a match
- } else {
- name_offset = 0; // reset our substring pointer on a miss
- }
- match_offset++; // always advance our larger string pointer
- }
- // If we got a match name_offset will be pointing past the end of the search
- // string and match_offset will be pointing to valid memory with room to
- // extract a Number
- if ((name_offset == name_length) &&
- (match_offset <= amf0->Size() - kAMF0NumberLength)) {
- // make sure the first byte matches the number type code
- if (search_buffer[match_offset] != kAMF0NumberType) {
- return false;
- }
- // advance pointer past the number type to the number itself
- match_offset++;
- // load big-endian double as uint, then cast to correct type
- uint64 num_as_uint =
- endian_util::load_uint64_big_endian(search_buffer + match_offset);
- *number_out = *((double*)(&num_as_uint));
- return true;
- }
- return false;
-}
-
-void ShellFLVParser::JumpParserTo(uint64 byte_offset) {
- next_video_aus_.clear();
- next_audio_aus_.clear();
- at_end_of_file_ = false;
- tag_offset_ = byte_offset;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_flv_parser.h b/src/media/filters/shell_flv_parser.h
deleted file mode 100644
index 1f512c0..0000000
--- a/src/media/filters/shell_flv_parser.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_FLV_PARSER_H_
-#define MEDIA_FILTERS_SHELL_FLV_PARSER_H_
-
-#include <map>
-#include <list>
-
-#include "media/base/shell_buffer_factory.h"
-#include "media/filters/shell_avc_parser.h"
-
-namespace media {
-
-class ShellFLVParser : public ShellAVCParser {
- public:
- // Attempts to make sense of the provided bytes of the top of a file as an
- // flv, and if it does make sense returns PIPELINE_OK and |*parser| contains a
- // ShellFLVParser initialized with some basic state. If it doesn't make sense
- // this returns an error status and |*parser| contains NULL.
- static PipelineStatus Construct(scoped_refptr<ShellDataSourceReader> reader,
- const uint8* construction_header,
- scoped_refptr<ShellParser>* parser);
- ShellFLVParser(scoped_refptr<ShellDataSourceReader> reader,
- uint32 tag_start_offset);
- virtual ~ShellFLVParser();
-
- // === ShellParser Implementation
- virtual bool ParseConfig() OVERRIDE;
- virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) OVERRIDE;
- virtual bool SeekTo(base::TimeDelta timestamp) OVERRIDE;
-
- protected:
- scoped_refptr<ShellAU> GetNextAudioAU();
- scoped_refptr<ShellAU> GetNextVideoAU();
-
- // Advance by one tag through the FLV. If encountering a keyframe, update the
- // time-to-byte map. If a regular video or audio data tag, update the next tag
- // structures. Otherwise it will download and parse the tag as it must contain
- // video metadata. Returns false on fatal error. Will call one of the Parse
- // helper methods defined below.
- bool ParseNextTag();
- bool ParseAudioDataTag(uint8* tag, uint32 size, uint32 timestamp);
- bool ParseVideoDataTag(uint8* tag, uint32 size, uint32 timestamp);
- bool ParseScriptDataObjectTag(uint8* tag, uint32 size, uint32 timestamp);
-
- // SCRIPTDATAOBJECT parsing
- bool ExtractAMF0Number(scoped_refptr<ShellScopedArray> amf0,
- const char* name,
- double* number_out);
-
- // flush internal parsing state and move tag_offset_ to the provided argument.
- void JumpParserTo(uint64 byte_offset);
-
- // The byte position in the stream of the tag parser. Between calls to
- // ParseNextTag() should point at the start of the next FLV tag in the file.
- uint64 tag_offset_;
-
- // Stores a map of video keyframe times to byte offsets in the FLV file. At
- // peak keyframe rates of 1 per second of video, and 16 bytes per entry
- // this map will consume approximately 1 MB of memory for 18 hours
- // of video worst-case. We build the data structure while traversing the
- // FLV tag-to-tag. The stream positions point at the start of the FLV tag
- // just like the entry conditions for tag_offset_ in ParseNextTag().
- typedef std::map<uint32, uint64> TimeToByteMap;
- TimeToByteMap time_to_byte_map_;
-
- // We maintain a record of data tags we have parsed headers for but not
- // downloaded the actual byte contents of.
- typedef std::list<scoped_refptr<ShellAU> > AUList;
- AUList next_video_aus_;
- AUList next_audio_aus_;
-
- // When true, all subsequent reads to ParseNextTag() will enqueue EOS AUs
- // in both audio and video AU queues.
- bool at_end_of_file_;
-
- base::TimeDelta audio_track_duration_;
- base::TimeDelta video_track_duration_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ShellFLVParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_FLV_PARSER_H_
diff --git a/src/media/filters/shell_mp4_map.cc b/src/media/filters/shell_mp4_map.cc
deleted file mode 100644
index 1d8b8d6..0000000
--- a/src/media/filters/shell_mp4_map.cc
+++ /dev/null
@@ -1,1153 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_mp4_map.h"
-
-#include "base/stringprintf.h"
-#include "media/base/endian_util.h"
-#include "media/filters/shell_mp4_parser.h"
-
-namespace media {
-
-// ==== TableCache =============================================================
-
-ShellMP4Map::TableCache::TableCache(uint64 table_offset,
- uint32 entry_count,
- uint32 entry_size,
- uint32 cache_size_entries,
- scoped_refptr<ShellDataSourceReader> reader)
- : entry_size_(entry_size),
- entry_count_(entry_count),
- cache_size_entries_(cache_size_entries),
- table_offset_(table_offset),
- reader_(reader),
- cache_first_entry_number_(-1),
- cache_entry_count_(0) {}
-
-uint8* ShellMP4Map::TableCache::GetBytesAtEntry(uint32 entry_number) {
- // don't fetch the unfetchable
- if (entry_number >= entry_count_) {
- return NULL;
- }
- // this query within valid range for the current cache table?
- if (entry_number < cache_first_entry_number_ ||
- entry_number >= cache_first_entry_number_ + cache_entry_count_) {
- // Calculate first entry in table keeping cache size alignment in table.
- // Always cache one more entry as in stss we need to use the first entry
- // of the next cache slot as an upper bound.
- cache_entry_count_ = cache_size_entries_ + 1;
- cache_first_entry_number_ =
- (entry_number / cache_size_entries_) * cache_size_entries_;
- // see if we have exceeded our table bounds
- if (cache_first_entry_number_ + cache_entry_count_ > entry_count_) {
- cache_entry_count_ = entry_count_ - cache_first_entry_number_;
- }
- // drop old data to allow ShellBufferFactory to defrag
- cache_ = NULL;
- int bytes_to_read = cache_entry_count_ * entry_size_;
- cache_ = ShellBufferFactory::Instance()->AllocateArray(bytes_to_read);
- if (!cache_ || !cache_->Get()) {
- cache_entry_count_ = 0;
- return NULL;
- }
- uint64 file_offset =
- table_offset_ + (cache_first_entry_number_ * entry_size_);
- int bytes_read =
- reader_->BlockingRead(file_offset, bytes_to_read, cache_->Get());
- if (bytes_read < bytes_to_read) {
- cache_entry_count_ = 0;
- return NULL;
- }
- }
- // cache is assumed to be valid and to contain the entry from here on
- DCHECK(cache_->Get());
- DCHECK_GE(entry_number, cache_first_entry_number_);
- DCHECK_LT(entry_number, cache_first_entry_number_ + cache_entry_count_);
-
- uint32 cache_offset = entry_number - cache_first_entry_number_;
- return cache_->Get() + (cache_offset * entry_size_);
-}
-
-bool ShellMP4Map::TableCache::ReadU32Entry(uint32 entry_number, uint32* entry) {
- if (uint8* data = GetBytesAtEntry(entry_number)) {
- *entry = endian_util::load_uint32_big_endian(data);
- return true;
- }
-
- return false;
-}
-
-bool ShellMP4Map::TableCache::ReadU32PairEntry(uint32 entry_number,
- uint32* first,
- uint32* second) {
- if (uint8* data = GetBytesAtEntry(entry_number)) {
- if (first)
- *first = endian_util::load_uint32_big_endian(data);
- if (second)
- *second = endian_util::load_uint32_big_endian(data + 4);
- return true;
- }
-
- return false;
-}
-
-bool ShellMP4Map::TableCache::ReadU32EntryIntoU64(uint32 entry_number,
- uint64* entry) {
- if (uint8* data = GetBytesAtEntry(entry_number)) {
- *entry = endian_util::load_uint32_big_endian(data);
- return true;
- }
-
- return false;
-}
-
-bool ShellMP4Map::TableCache::ReadU64Entry(uint32 entry_number, uint64* entry) {
- if (uint8* data = GetBytesAtEntry(entry_number)) {
- *entry = endian_util::load_uint64_big_endian(data);
- return true;
- }
-
- return false;
-}
-
-// ==== ShellMP4Map ============================================================
-
-// atom | name | size | description, (*) means optional table
-// -----+-----------------------+------+----------------------------------------
-// co64 | chunk offset (64-bit) | 8 | per-chunk list of chunk file offsets
-// ctts | composition offset | 8 | (*) run-length sample number to cts
-// stco | chunk offset (32-bit) | 4 | per-chunk list of chunk file offsets
-// stsc | sample-to-chunk | 12 | chunk number to samples per chunk
-// stss | sync sample | 4 | (*) list of keyframe sample numbers
-// stts | time-to-sample | 8 | run-length sample number to duration
-// stsz | sample size | 4 | per-sample list of sample sizes
-
-ShellMP4Map::ShellMP4Map(scoped_refptr<ShellDataSourceReader> reader)
- : reader_(reader),
- current_chunk_sample_(0),
- next_chunk_sample_(0),
- current_chunk_offset_(0),
- highest_valid_sample_number_(UINT32_MAX),
- ctts_first_sample_(0),
- ctts_sample_offset_(0),
- ctts_next_first_sample_(0),
- ctts_table_index_(0),
- stsc_first_chunk_(0),
- stsc_first_chunk_sample_(0),
- stsc_samples_per_chunk_(0),
- stsc_next_first_chunk_(0),
- stsc_next_first_chunk_sample_(0),
- stsc_table_index_(0),
- stss_last_keyframe_(0),
- stss_next_keyframe_(0),
- stss_table_index_(0),
- stts_first_sample_(0),
- stts_first_sample_time_(0),
- stts_sample_duration_(0),
- stts_next_first_sample_(0),
- stts_next_first_sample_time_(0),
- stts_table_index_(0),
- stsz_default_size_(0) {}
-
-bool ShellMP4Map::IsComplete() {
- // all required table pointers must be valid for map to function
- return (co64_ || stco_) && stsc_ && stts_ && (stsz_ || stsz_default_size_);
-}
-
-// The sample size is a lookup in the stsz table, which is indexed per sample
-// number.
-bool ShellMP4Map::GetSize(uint32 sample_number, uint32& size_out) {
- DCHECK(stsz_ || stsz_default_size_);
-
- if (sample_number > highest_valid_sample_number_) {
- return false;
- }
-
- if (stsz_default_size_) {
- size_out = stsz_default_size_;
- return true;
- }
-
- return stsz_->ReadU32Entry(sample_number, &size_out);
-}
-
-// We first must integrate the stsc table to find the chunk number that the
-// sample resides in, and the first sample number in that chunk. We look up that
-// chunk offset from the stco or co64, which are indexed by chunk number. We
-// then use the stsz to sum samples to the byte offset with that chunk. The sum
-// of the chunk offset and the byte offset within the chunk is the offset of
-// the sample.
-bool ShellMP4Map::GetOffset(uint32 sample_number, uint64& offset_out) {
- DCHECK(stsc_);
- DCHECK(stco_ || co64_);
- DCHECK(stsz_ || stsz_default_size_);
-
- if (sample_number > highest_valid_sample_number_) {
- return false;
- }
-
- // check for sequential access of sample numbers within the same chunk
- if (sample_number < current_chunk_sample_ ||
- sample_number >= next_chunk_sample_) {
- // integrate through stsc until we find the chunk range containing sample
- if (!stsc_AdvanceToSample(sample_number)) {
- return false;
- }
- // make sure stsc advance did its job correctly
- DCHECK_GE(sample_number, stsc_first_chunk_sample_);
-
- // calculate chunk number based on chunk sample size for this range
- uint32 sample_offset = sample_number - stsc_first_chunk_sample_;
- uint32 chunk_range_offset = sample_offset / stsc_samples_per_chunk_;
- uint32 chunk_number = stsc_first_chunk_ + chunk_range_offset;
- // should be within the range of chunks with this sample size
- DCHECK_LT(chunk_number, stsc_next_first_chunk_);
- // update first sample number contained within this chunk
- current_chunk_sample_ = stsc_first_chunk_sample_ +
- (chunk_range_offset * stsc_samples_per_chunk_);
- // update first sample number of next chunk
- next_chunk_sample_ = current_chunk_sample_ + stsc_samples_per_chunk_;
- // find offset of this chunk within the file from co64/stco
- if (co64_) {
- if (!co64_->ReadU64Entry(chunk_number, ¤t_chunk_offset_))
- return false;
- } else if (!stco_->ReadU32EntryIntoU64(chunk_number,
- ¤t_chunk_offset_)) {
- return false;
- }
- }
-
- // at this point we should have sample_number within the range of our chunk
- // offset summation saved state
- DCHECK_LE(current_chunk_sample_, sample_number);
- DCHECK_LT(sample_number, next_chunk_sample_);
-
- if (stsz_default_size_ > 0) {
- current_chunk_offset_ +=
- (sample_number - current_chunk_sample_) * stsz_default_size_;
- current_chunk_sample_ = sample_number;
- } else {
- // sum sample sizes within chunk to get to byte offset of sample
- while (current_chunk_sample_ < sample_number) {
- uint32 sample_size = 0;
- if (!GetSize(current_chunk_sample_, sample_size)) {
- return false;
- }
- current_chunk_offset_ += sample_size;
- current_chunk_sample_++;
- }
- }
-
- offset_out = current_chunk_offset_;
- return true;
-}
-
-// Given a current sample number we integrate through the stts to find the
-// duration of the current sample, and at the same time integrate through the
-// durations to find the dts of that sample number. We then integrate sample
-// numbers through the ctts to find the composition time offset, which we add to
-// the dts to return the pts.
-bool ShellMP4Map::GetTimestamp(uint32 sample_number, uint64& timestamp_out) {
- if (sample_number > highest_valid_sample_number_) {
- return false;
- }
-
- if (!stts_AdvanceToSample(sample_number)) {
- return false;
- }
- DCHECK_LT(sample_number, stts_next_first_sample_);
- DCHECK_GE(sample_number, stts_first_sample_);
- uint64 dts = stts_first_sample_time_ +
- (sample_number - stts_first_sample_) * stts_sample_duration_;
- if (ctts_) {
- if (!ctts_AdvanceToSample(sample_number)) {
- return false;
- }
- DCHECK_LT(sample_number, ctts_next_first_sample_);
- DCHECK_GE(sample_number, ctts_first_sample_);
- }
- timestamp_out = dts + ctts_sample_offset_;
- return true;
-}
-
-// Sum through the stts to find the duration of the given sample_number.
-bool ShellMP4Map::GetDuration(uint32 sample_number, uint32& duration_out) {
- if (sample_number > highest_valid_sample_number_) {
- return false;
- }
-
- if (!stts_AdvanceToSample(sample_number)) {
- return false;
- }
- DCHECK_LT(sample_number, stts_next_first_sample_);
- DCHECK_GE(sample_number, stts_first_sample_);
- duration_out = stts_sample_duration_;
- return true;
-}
-
-bool ShellMP4Map::GetIsKeyframe(uint32 sample_number, bool& is_keyframe_out) {
- if (sample_number > highest_valid_sample_number_) {
- return false;
- }
-
- // no stts means every frame is a keyframe
- if (!stss_) {
- is_keyframe_out = true;
- return true;
- }
-
- // check for keyframe match on either range value
- if (sample_number == stss_next_keyframe_) {
- is_keyframe_out = true;
- return stss_AdvanceStep();
- } else if (sample_number == stss_last_keyframe_) {
- is_keyframe_out = true;
- return true;
- }
-
- // this could be for a much earlier sample number, check if we are within
- // current range of sample numbers
- if (sample_number < stss_last_keyframe_ ||
- sample_number > stss_next_keyframe_) {
- // search for containing entry
- if (!stss_FindNearestKeyframe(sample_number)) {
- return false;
- }
- }
- // sample number must be in range of keyframe states
- DCHECK_GE(sample_number, stss_last_keyframe_);
- DCHECK_LT(sample_number, stss_next_keyframe_);
- // stss_FindNearestKeyframe returns exact matches to
- // sample_number in the stss_last_keyframe_ variable, so
- // we check that for equality
- is_keyframe_out = (sample_number == stss_last_keyframe_);
-
- return true;
-}
-
-bool ShellMP4Map::IsEOS(uint32 sample_number) {
- return (sample_number > highest_valid_sample_number_);
-}
-
-// First look up the sample number for the provided timestamp by integrating
-// timestamps through the stts. Then do a binary search on the stss to find the
-// keyframe nearest that sample number.
-bool ShellMP4Map::GetKeyframe(uint64 timestamp, uint32& sample_out) {
- // Advance stts to the provided timestamp range
- if (!stts_AdvanceToTime(timestamp)) {
- return false;
- }
- // ensure we got the correct sample duration range
- DCHECK_LT(timestamp, stts_next_first_sample_time_);
- DCHECK_GE(timestamp, stts_first_sample_time_);
- // calculate sample number containing this timestamp
- uint64 time_offset_within_range = timestamp - stts_first_sample_time_;
- uint32 sample_number =
- stts_first_sample_ + (time_offset_within_range / stts_sample_duration_);
-
- // TODO: ctts?
-
- // binary search on stts to find nearest keyframe beneath this sample number
- if (stss_) {
- if (!stss_FindNearestKeyframe(sample_number)) {
- return false;
- }
- sample_out = stss_last_keyframe_;
- } else {
- // an absent stts means every frame is a key frame, we can provide sample
- // directly.
- sample_out = sample_number;
- }
- return true;
-}
-
-// Set up map state and load first part of table, or entire table if it is small
-// enough, for each of the supporated atoms.
-bool ShellMP4Map::SetAtom(uint32 four_cc,
- uint64 offset,
- uint64 size,
- uint32 cache_size_entries,
- const uint8* atom) {
- // All map atoms are variable-length tables starting with 4 bytes of
- // version/flag info followed by a uint32 indicating the number of items in
- // table. The stsz atom bucks tradition by putting an optional default value
- // at index 4.
- uint32 count = 0;
- uint64 table_offset = offset + 8;
- if (four_cc == kAtomType_stsz) {
- if (size < 12) {
- return false;
- }
- stsz_default_size_ = endian_util::load_uint32_big_endian(atom + 4);
- count = endian_util::load_uint32_big_endian(atom + 8);
- highest_valid_sample_number_ =
- std::min(count - 1, highest_valid_sample_number_);
- // if a non-zero default size is provided don't bother loading the table
- if (stsz_default_size_) {
- stsz_ = NULL;
- return true;
- }
-
- table_offset += 4;
- } else {
- if (size < 8) {
- return false;
- }
- count = endian_util::load_uint32_big_endian(atom + 4);
- }
-
- // if cache_size_entries is 0 we are to cache the entire table
- if (cache_size_entries == 0) {
- cache_size_entries = count;
- }
-
- bool atom_init = false;
- // initialize the appropriate table cache dependent on table type
- switch (four_cc) {
- case kAtomType_co64:
- co64_ = new TableCache(table_offset, count, kEntrySize_co64,
- cache_size_entries, reader_);
- if (co64_)
- atom_init = co64_Init();
- break;
-
- case kAtomType_ctts:
- ctts_ = new TableCache(table_offset, count, kEntrySize_ctts,
- cache_size_entries, reader_);
- if (ctts_)
- atom_init = ctts_Init();
- break;
-
- case kAtomType_stco:
- stco_ = new TableCache(table_offset, count, kEntrySize_stco,
- cache_size_entries, reader_);
- if (stco_)
- atom_init = stco_Init();
- break;
-
- case kAtomType_stsc:
- stsc_ = new TableCache(table_offset, count, kEntrySize_stsc,
- cache_size_entries, reader_);
- if (stsc_)
- atom_init = stsc_Init();
- break;
-
- case kAtomType_stss:
- stss_ = new TableCache(table_offset, count, kEntrySize_stss,
- cache_size_entries, reader_);
- if (stss_)
- atom_init = stss_Init();
- break;
-
- case kAtomType_stts:
- stts_ = new TableCache(table_offset, count, kEntrySize_stts,
- cache_size_entries, reader_);
- if (stts_)
- atom_init = stts_Init();
- break;
-
- case kAtomType_stsz:
- stsz_ = new TableCache(table_offset, count, kEntrySize_stsz,
- cache_size_entries, reader_);
- if (stsz_)
- atom_init = stsz_Init();
- break;
-
- default:
- NOTREACHED() << "unknown atom type provided to mp4 map";
- break;
- }
-
- return atom_init;
-}
-
-bool ShellMP4Map::co64_Init() {
- DCHECK(co64_);
- // load offset of first chunk into current_chunk_offset_
- if (co64_->GetEntryCount() > 0) {
- // can drop any stco table already allocated
- stco_ = NULL;
- // load initial value of current_chunk_offset_ for 0th chunk
- return co64_->ReadU64Entry(0, ¤t_chunk_offset_);
- }
-
- co64_ = NULL;
-
- return true;
-}
-
-// The ctts table has the following per-entry layout:
-// uint32 sample count
-// uint32 composition offset in ticks
-//
-bool ShellMP4Map::ctts_Init() {
- DCHECK(ctts_);
- // get cache segment vector to reserve table entries in advance
- int cache_segments =
- (ctts_->GetEntryCount() / ctts_->GetCacheSizeEntries()) + 1;
- ctts_samples_.reserve(cache_segments);
- if (ctts_->GetEntryCount() > 0) {
- // save the start of the first table integration at 0
- ctts_samples_.push_back(0);
- ctts_table_index_ = 0;
- ctts_first_sample_ = 0;
- // load first entry in table, to start integration
- return ctts_->ReadU32PairEntry(0, &ctts_next_first_sample_,
- &ctts_sample_offset_);
- }
- // drop empty ctts_ table
- ctts_ = NULL;
-
- return true;
-}
-
-// To find the composition offset of a given sample number we must integrate
-// through the ctts to find the range of samples containing sample_number. Note
-// that the ctts is an optional table.
-bool ShellMP4Map::ctts_AdvanceToSample(uint32 sample_number) {
- // ctts table is optional, so treat not having one as non-fatal
- if (!ctts_) {
- return true;
- }
- // sample number could be before our saved first sample, meaning we've
- // gone backward in sample numbers and will need to restart integration at
- // the nearest saved sample count starting at a cache entry
- if (sample_number < ctts_first_sample_) {
- if (!ctts_SlipCacheToSample(sample_number, 0)) {
- return false;
- }
- }
-
- // sample_number could also be ahead of our current range, for example when
- // seeking forward. See if we've calculated these values ahead of us before,
- // and if we can slip forward to them
- int next_cache_index = (ctts_table_index_ / ctts_->GetCacheSizeEntries()) + 1;
- if ((next_cache_index < ctts_samples_.size()) &&
- (sample_number >= ctts_samples_[next_cache_index])) {
- if (!ctts_SlipCacheToSample(sample_number, next_cache_index)) {
- return false;
- }
- }
-
- // perform integration until sample number is within correct ctts range
- while (ctts_next_first_sample_ <= sample_number) {
- // next first sample is now our the first sample
- ctts_first_sample_ = ctts_next_first_sample_;
- // advance to next entry in table
- ctts_table_index_++;
- // If this would be a new cache entry, keep a record of integration up
- // to this point so we don't have to start from 0 on seeking back
- if (!(ctts_table_index_ % ctts_->GetCacheSizeEntries())) {
- int cache_index = ctts_table_index_ / ctts_->GetCacheSizeEntries();
- // check that this is our first time with these data
- if (cache_index == ctts_samples_.size()) {
- ctts_samples_.push_back(ctts_first_sample_);
- }
- // our integration at this point should always match any stored record
- DCHECK_EQ(ctts_first_sample_, ctts_samples_[cache_index]);
- }
-
- if (ctts_table_index_ < ctts_->GetEntryCount()) {
- // load the sample count to determine next first sample
- uint32 sample_count;
- if (!ctts_->ReadU32PairEntry(ctts_table_index_, &sample_count,
- &ctts_sample_offset_))
- return false;
- ctts_next_first_sample_ = ctts_first_sample_ + sample_count;
- } else {
- // This means that the last entry in the table specified a sample range
- // that this sample number has exceeded, and so the ctts of this sample
- // number is undefined. While not a fatal error it's kind of a weird
- // state, we set the offset back to zero and extend the next_first_sample
- // to infinity
- DLOG(WARNING) << base::StringPrintf(
- "out of range sample number %d in ctts, last valid sample number: %d",
- sample_number, ctts_next_first_sample_);
- ctts_sample_offset_ = 0;
- ctts_next_first_sample_ = UINT32_MAX;
- break;
- }
- }
- return true;
-}
-
-bool ShellMP4Map::ctts_SlipCacheToSample(uint32 sample_number,
- int starting_cache_index) {
- DCHECK_LT(starting_cache_index, ctts_samples_.size());
- int cache_index = starting_cache_index;
- for (; cache_index + 1 < ctts_samples_.size(); cache_index++) {
- if (sample_number < ctts_samples_[cache_index + 1]) {
- break;
- }
- }
- ctts_first_sample_ = ctts_samples_[cache_index];
- ctts_table_index_ = cache_index * ctts_->GetCacheSizeEntries();
- // read sample count and duration to set next values
- uint32 sample_count;
- if (!ctts_->ReadU32PairEntry(ctts_table_index_, &sample_count,
- &ctts_sample_offset_))
- return false;
- ctts_next_first_sample_ = ctts_first_sample_ + sample_count;
- return true;
-}
-
-bool ShellMP4Map::stco_Init() {
- DCHECK(stco_);
- // load offset of first chunk into current_chunk_offset_
- if (stco_->GetEntryCount() > 0) {
- co64_ = NULL;
- return stco_->ReadU32EntryIntoU64(0, ¤t_chunk_offset_);
- }
-
- stco_ = NULL;
-
- return true;
-}
-
-// The stsc table has the following per-entry layout:
-// uint32 first chunk number with this sample count
-// uint32 samples-per-chunk
-// uint32 sample description id (unused)
-bool ShellMP4Map::stsc_Init() {
- DCHECK(stsc_);
- // set up vector to correct final size
- int cache_segments =
- (stsc_->GetEntryCount() / stsc_->GetCacheSizeEntries()) + 1;
- stsc_sample_sums_.reserve(cache_segments);
- // there must always be at least 1 entry in a valid stsc table
- if (stsc_->GetEntryCount() > 0) {
- stsc_first_chunk_ = 0;
- stsc_first_chunk_sample_ = 0;
- // first cached entry is always 0
- stsc_sample_sums_.push_back(0);
- if (!stsc_->ReadU32PairEntry(0, NULL, &stsc_samples_per_chunk_)) {
- stsc_ = NULL;
- return false;
- }
- // look up next first chunk at next index in table
- if (stsc_->GetEntryCount() > 1) {
- if (!stsc_->ReadU32PairEntry(1, &stsc_next_first_chunk_, NULL)) {
- stsc_ = NULL;
- return false;
- }
- --stsc_next_first_chunk_;
- stsc_next_first_chunk_sample_ =
- stsc_next_first_chunk_ * stsc_samples_per_chunk_;
- } else {
- // every chunk in the file has the sample sample count, set next first
- // chunk to highest valid chunk number.
- stsc_next_first_chunk_ = UINT32_MAX;
- stsc_next_first_chunk_sample_ = UINT32_MAX;
- }
- stsc_table_index_ = 0;
-
- // since we known the size of the first chunk we can set next_chunk_sample_
- next_chunk_sample_ = stsc_samples_per_chunk_;
-
- } else {
- stsc_ = NULL;
- }
-
- return true;
-}
-
-// To find the chunk number of an abritrary sample we have to sum the
-// samples-per-chunk value multiplied by the number of chunks with that sample
-// count until the sum exceeds the sample number, then calculate the chunk
-// number from that range of per-sample chunk sizes. Since this map is meant
-// to be consumed incrementally and with minimal memory consumption we calculate
-// this integration step only when needed, and save results for each cached
-// piece of the table, to avoid having to recalculate needed data.
-bool ShellMP4Map::stsc_AdvanceToSample(uint32 sample_number) {
- DCHECK(stsc_);
- // sample_number could be before first chunk, meaning that we are seeking
- // backwards and have left the current chunk. Find the closest part of the
- // cached table and integrate forward from there.
- if (sample_number < stsc_first_chunk_sample_) {
- if (!stsc_SlipCacheToSample(sample_number, 0)) {
- return false;
- }
- }
-
- // sample_number could also be well head of our current piece of the
- // cache, so see if we can re-use any previously calculated summations to
- // skip to the nearest cache entry
- int next_cache_index = (stsc_table_index_ / stsc_->GetCacheSizeEntries()) + 1;
- if ((next_cache_index < stsc_sample_sums_.size()) &&
- (sample_number >= stsc_sample_sums_[next_cache_index])) {
- if (!stsc_SlipCacheToSample(sample_number, next_cache_index)) {
- return false;
- }
- }
-
- // Integrate through each table entry until we find sample_number in range
- while (stsc_next_first_chunk_sample_ <= sample_number) {
- // advance to next chunk sample range
- stsc_first_chunk_sample_ = stsc_next_first_chunk_sample_;
- // our next_first_chunk is now our first chunk
- stsc_first_chunk_ = stsc_next_first_chunk_;
- // advance to next entry in table
- stsc_table_index_++;
- // if we've advanced to a new segment of the cache, update the saved
- // integration values
- if (!(stsc_table_index_ % stsc_->GetCacheSizeEntries())) {
- int cache_index = stsc_table_index_ / stsc_->GetCacheSizeEntries();
- // check that this is our first time with these data
- if (cache_index == stsc_sample_sums_.size()) {
- stsc_sample_sums_.push_back(stsc_first_chunk_sample_);
- }
- // our integration at this point should always match any stored record
- DCHECK_EQ(stsc_first_chunk_sample_, stsc_sample_sums_[cache_index]);
- }
- if (stsc_table_index_ < stsc_->GetEntryCount()) {
- // look up our new sample rate
- if (!stsc_->ReadU32PairEntry(stsc_table_index_, NULL,
- &stsc_samples_per_chunk_)) {
- return false;
- }
- // we need to look up next table entry to determine next first chunk
- if (stsc_table_index_ + 1 < stsc_->GetEntryCount()) {
- // look up next first chunk
- if (!stsc_->ReadU32PairEntry(stsc_table_index_ + 1,
- &stsc_next_first_chunk_, NULL)) {
- return false;
- }
- --stsc_next_first_chunk_;
- // carry sum of first_samples forward to next chunk range
- stsc_next_first_chunk_sample_ +=
- (stsc_next_first_chunk_ - stsc_first_chunk_) *
- stsc_samples_per_chunk_;
- } else {
- // this is the normal place to encounter the end of the chunk table.
- // set the next chunk to the highest valid chunk number
- stsc_next_first_chunk_ = UINT32_MAX;
- stsc_next_first_chunk_sample_ = UINT32_MAX;
- }
- } else {
- // We should normally encounter the end of the chunk table on lookup
- // of the next_first_chunk_ within the if clause associated with this
- // else. Something has gone wrong.
- NOTREACHED();
- return false;
- }
- }
- return true;
-}
-
-bool ShellMP4Map::stsc_SlipCacheToSample(uint32 sample_number,
- int starting_cache_index) {
- DCHECK_LT(starting_cache_index, stsc_sample_sums_.size());
- // look through old sample sums for the first entry that exceeds sample
- // sample_number, we want the entry right before that
- int cache_index = starting_cache_index;
- for (; cache_index + 1 < stsc_sample_sums_.size(); cache_index++) {
- if (sample_number < stsc_sample_sums_[cache_index + 1]) {
- break;
- }
- }
- // jump to new spot in table
- stsc_first_chunk_sample_ = stsc_sample_sums_[cache_index];
- stsc_table_index_ = cache_index * stsc_->GetCacheSizeEntries();
- if (!stsc_->ReadU32PairEntry(stsc_table_index_, &stsc_first_chunk_,
- &stsc_samples_per_chunk_)) {
- return false;
- }
- // load current and next values
- --stsc_first_chunk_;
- if (stsc_table_index_ + 1 < stsc_->GetEntryCount()) {
- if (!stsc_->ReadU32PairEntry(stsc_table_index_ + 1, &stsc_next_first_chunk_,
- NULL)) {
- return false;
- }
- --stsc_next_first_chunk_;
- stsc_next_first_chunk_sample_ =
- stsc_first_chunk_sample_ +
- ((stsc_next_first_chunk_ - stsc_first_chunk_) *
- stsc_samples_per_chunk_);
- } else {
- // We seem to have cached an entry 1 entry before end of the table, and
- // are seeking to a region contained in that last entry in the table.
- stsc_next_first_chunk_ = UINT32_MAX;
- stsc_next_first_chunk_sample_ = UINT32_MAX;
- }
- return true;
-}
-
-// stss is a list of sample numbers that are keyframes.
-bool ShellMP4Map::stss_Init() {
- int cache_segments =
- (stss_->GetEntryCount() / stss_->GetCacheSizeEntries()) + 1;
- stss_keyframes_.reserve(cache_segments);
- // empty stss means every frame is a keyframe, same as not
- // providing one
- if (stss_->GetEntryCount() > 0) {
- // identify first keyframe from first entry in stss
- if (!stss_->ReadU32Entry(0, &stss_last_keyframe_)) {
- stss_ = NULL;
- return false;
- }
- --stss_last_keyframe_;
- stss_keyframes_.push_back(stss_last_keyframe_);
- stss_next_keyframe_ = stss_last_keyframe_;
- stss_table_index_ = 0;
- } else {
- stss_ = NULL;
- }
-
- return true;
-}
-
-// advance by one table entry through stss, updating cache if necessary
-bool ShellMP4Map::stss_AdvanceStep() {
- DCHECK(stss_);
- stss_last_keyframe_ = stss_next_keyframe_;
- stss_table_index_++;
- if (stss_table_index_ < stss_->GetEntryCount()) {
- if (!stss_->ReadU32Entry(stss_table_index_, &stss_next_keyframe_)) {
- return false;
- }
- --stss_next_keyframe_;
- if (!(stss_table_index_ % stss_->GetCacheSizeEntries())) {
- int cache_index = stss_table_index_ / stss_->GetCacheSizeEntries();
- // only add if this is the first time we've encountered this number
- if (cache_index == stss_keyframes_.size()) {
- stss_keyframes_.push_back(stss_next_keyframe_);
- }
- DCHECK_EQ(stss_next_keyframe_, stss_keyframes_[cache_index]);
- }
- } else {
- stss_next_keyframe_ = UINT32_MAX;
- }
- return true;
-}
-
-bool ShellMP4Map::stss_FindNearestKeyframe(uint32 sample_number) {
- DCHECK(stss_);
- // it is assumed that there's at least one cache entry created by
- // stss_Init();
- DCHECK_GT(stss_keyframes_.size(), 0);
- int cache_entry_number = stss_keyframes_.size() - 1;
- int total_cache_entries =
- (stss_->GetEntryCount() + stss_->GetCacheSizeEntries() - 1) /
- stss_->GetCacheSizeEntries();
- // if there's more than one cache entry we can search the cached
- // entries for the entry containing our keyframe, otherwise we skip
- // directly to the binary search of the single cached entry
- if (total_cache_entries > 1) {
- // if the sample number resides within the range of cached entries
- // we search those to find right table cache entry to load
- if (sample_number < stss_keyframes_[cache_entry_number]) {
- int lower_bound = 0;
- int upper_bound = stss_keyframes_.size();
- // binary search to find range
- while (lower_bound <= upper_bound) {
- cache_entry_number = lower_bound + ((upper_bound - lower_bound) / 2);
- if (sample_number < stss_keyframes_[cache_entry_number]) {
- upper_bound = cache_entry_number - 1;
- } else { // sample_number >= stss_keyframes_[cache_entry_number]
- // if we are at end of list or next cache entry is higher than sample
- // number we consider it a match
- if (cache_entry_number == stss_keyframes_.size() - 1 ||
- sample_number < stss_keyframes_[cache_entry_number + 1]) {
- break;
- }
- lower_bound = cache_entry_number + 1;
- }
- }
- }
- // We've gotten as close as we can using the cached values and must handle
- // two cases. (a) is that we know that sample_number is contained in the
- // cache_entry_number, because we know that:
- // stts_keyframes_[cache_entry_number] <= sample_number <
- // stts_keyframes_[cache_entry_number + 1]
- // (b) is that we only know:
- // stts_keyframes_[stts_keyframes_.size() - 1] <= sample_number
- // because we have not cached an upper bound to sample_number.
- // First step is to make (b) in to (a) by advancing through cache entries
- // until last table entry in cache > sample_number or until we arrive
- // at the cache entry in the table.
- while ((cache_entry_number == stss_keyframes_.size() - 1) &&
- cache_entry_number < total_cache_entries - 1) {
- // Use the first key frame in next cache as upper bound.
- int next_cached_entry_number =
- (cache_entry_number + 1) * stss_->GetCacheSizeEntries();
- uint32 next_cached_keyframe;
- if (!stss_->ReadU32Entry(next_cached_entry_number,
- &next_cached_keyframe)) {
- return false;
- }
- --next_cached_keyframe;
- // if this keyframe is higher than our sample number we're in the right
- // table, stop
- if (sample_number < next_cached_keyframe) {
- break;
- }
- // ok, we need to look in to the next cache entry, advance
- cache_entry_number++;
- int first_table_entry_number =
- cache_entry_number * stss_->GetCacheSizeEntries();
- uint32 first_keyframe_in_cache_entry;
- if (!stss_->ReadU32Entry(first_table_entry_number,
- &first_keyframe_in_cache_entry)) {
- return false;
- }
- --first_keyframe_in_cache_entry;
- // save first entry in keyframe cache
- stss_keyframes_.push_back(first_keyframe_in_cache_entry);
- }
- // make sure we have an upper bound
- if (cache_entry_number != total_cache_entries - 1 &&
- cache_entry_number == stss_keyframes_.size() - 1) {
- int next_cached_entry_number =
- ((cache_entry_number + 1) * stss_->GetCacheSizeEntries());
- uint32 next_cached_keyframe;
- if (!stss_->ReadU32Entry(next_cached_entry_number,
- &next_cached_keyframe)) {
- return false;
- }
- --next_cached_keyframe;
- stss_keyframes_.push_back(next_cached_keyframe);
- }
- // ok, now we assume we are in state (a), and that we're either
- // at the end of the table or within the cache entry bounds for our
- // sample number
- DCHECK(stss_keyframes_[cache_entry_number] <= sample_number &&
- (cache_entry_number == total_cache_entries - 1 ||
- sample_number < stss_keyframes_[cache_entry_number + 1]));
- }
- // binary search within stss cache entry for keyframes bounding sample_number
- int lower_bound = cache_entry_number * stss_->GetCacheSizeEntries();
- int upper_bound = std::min(lower_bound + stss_->GetCacheSizeEntries(),
- stss_->GetEntryCount());
-
- while (lower_bound <= upper_bound) {
- stss_table_index_ = lower_bound + ((upper_bound - lower_bound) / 2);
- if (!stss_->ReadU32Entry(stss_table_index_, &stss_last_keyframe_)) {
- return false;
- }
- --stss_last_keyframe_;
- if (sample_number < stss_last_keyframe_) {
- upper_bound = stss_table_index_ - 1;
- } else { // sample_number >= last_keyframe
- lower_bound = stss_table_index_ + 1;
- // if this is the last entry in the table, we can stop here.
- if (lower_bound == stss_->GetEntryCount()) {
- stss_next_keyframe_ = UINT32_MAX;
- break;
- }
- // load next entry in table, see if we actually found the upper bound
- if (!stss_->ReadU32Entry(lower_bound, &stss_next_keyframe_)) {
- return false;
- }
- --stss_next_keyframe_;
- if (sample_number < stss_next_keyframe_) {
- stss_table_index_ = lower_bound;
- break;
- }
- }
- }
- return sample_number >= stss_last_keyframe_ &&
- sample_number < stss_next_keyframe_;
-}
-
-// The stts table has the following per-entry layout:
-// uint32 sample count - number of sequential samples with this duration
-// uint32 sample duration - duration in ticks of this sample range
-bool ShellMP4Map::stts_Init() {
- int cache_segments =
- (stts_->GetEntryCount() / stts_->GetCacheSizeEntries()) + 1;
- stts_samples_.reserve(cache_segments);
- stts_timestamps_.reserve(cache_segments);
- // need at least one entry in valid stts
- if (stts_->GetEntryCount() > 0) {
- // integration starts at 0 for both cache entries
- stts_samples_.push_back(0);
- stts_timestamps_.push_back(0);
- if (!stts_->ReadU32PairEntry(0, &stts_next_first_sample_,
- &stts_sample_duration_)) {
- stts_ = NULL;
- return false;
- }
- stts_first_sample_ = 0;
- stts_first_sample_time_ = 0;
- stts_next_first_sample_time_ =
- stts_next_first_sample_ * stts_sample_duration_;
- stts_table_index_ = 0;
- } else {
- stts_ = NULL;
- }
-
- return true;
-}
-
-bool ShellMP4Map::stts_AdvanceToSample(uint32 sample_number) {
- DCHECK(stts_);
- // sample_number could be before our current sample range, in which case
- // we skip to the nearest table entry before sample_number and integrate
- // forward to the sample_number again.
- if (sample_number < stts_first_sample_) {
- if (!stts_SlipCacheToSample(sample_number, 0)) {
- return false;
- }
- }
-
- // sample number could also be well ahead of this cache segment, if we've
- // previously calculated summations ahead let's skip to the correct one
- int next_cache_index = (stts_table_index_ / stts_->GetCacheSizeEntries()) + 1;
- if ((next_cache_index < stts_samples_.size()) &&
- (sample_number >= stts_samples_[next_cache_index])) {
- if (!stts_SlipCacheToSample(sample_number, next_cache_index)) {
- return false;
- }
- }
-
- // integrate through the stts until sample_number is within current range
- while (stts_next_first_sample_ <= sample_number) {
- if (!stts_IntegrateStep()) {
- return false;
- }
- }
- return true;
-}
-
-// Move our integration steps to a previously saved entry in the cache tables.
-// Searches linearly through the vector of old cached values, so can accept a
-// starting index to do the search from.
-bool ShellMP4Map::stts_SlipCacheToSample(uint32 sample_number,
- int starting_cache_index) {
- DCHECK_LT(starting_cache_index, stts_samples_.size());
- int cache_index = starting_cache_index;
- for (; cache_index + 1 < stts_samples_.size(); cache_index++) {
- if (sample_number < stts_samples_[cache_index + 1]) {
- break;
- }
- }
- stts_first_sample_ = stts_samples_[cache_index];
- stts_first_sample_time_ = stts_timestamps_[cache_index];
- stts_table_index_ = cache_index * stts_->GetCacheSizeEntries();
- uint32 sample_count;
- // read sample count and duration to set next values
- if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count,
- &stts_sample_duration_)) {
- return false;
- }
- stts_next_first_sample_ = stts_first_sample_ + sample_count;
- stts_next_first_sample_time_ =
- stts_first_sample_time_ + (sample_count * stts_sample_duration_);
- return true;
-}
-
-bool ShellMP4Map::stts_AdvanceToTime(uint64 timestamp) {
- DCHECK(stts_);
-
- if (timestamp < stts_first_sample_time_) {
- if (!stts_SlipCacheToTime(timestamp, 0)) {
- return false;
- }
- }
-
- // sample number could also be well ahead of this cache segment, if we've
- // previously calculated summations ahead let's skip to the correct one
- int next_cache_index = (stts_table_index_ / stts_->GetCacheSizeEntries()) + 1;
- if ((next_cache_index < stts_timestamps_.size()) &&
- (timestamp >= stts_timestamps_[next_cache_index])) {
- if (!stts_SlipCacheToTime(timestamp, next_cache_index)) {
- return false;
- }
- }
-
- // integrate through the stts until sample_number is within current range
- while (stts_next_first_sample_time_ <= timestamp) {
- if (!stts_IntegrateStep()) {
- return false;
- }
- }
- return true;
-}
-
-bool ShellMP4Map::stts_IntegrateStep() {
- // advance time to next sample range
- uint32 range_size = stts_next_first_sample_ - stts_first_sample_;
- stts_first_sample_time_ += (range_size * stts_sample_duration_);
- // advance sample counter to next range
- stts_first_sample_ = stts_next_first_sample_;
- // bump table counter to next entry
- stts_table_index_++;
- // see if we just crossed a cache boundary and should cache results
- if (!(stts_table_index_ % stts_->GetCacheSizeEntries())) {
- int cache_index = stts_table_index_ / stts_->GetCacheSizeEntries();
- // check that this is our first time with these data
- if (cache_index == stts_samples_.size()) {
- // both tables should always grow together
- DCHECK_EQ(stts_samples_.size(), stts_timestamps_.size());
- stts_samples_.push_back(stts_first_sample_);
- stts_timestamps_.push_back(stts_first_sample_time_);
- }
- // our integration at this point should always match any stored record
- DCHECK_EQ(stts_first_sample_, stts_samples_[cache_index]);
- DCHECK_EQ(stts_first_sample_time_, stts_timestamps_[cache_index]);
- }
- if (stts_table_index_ < stts_->GetEntryCount()) {
- // load next entry data
- uint32 sample_count;
- if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count,
- &stts_sample_duration_)) {
- return false;
- }
- // calculate next sample number from range size
- stts_next_first_sample_ = stts_first_sample_ + sample_count;
- // and load duration of this sample range
- stts_next_first_sample_time_ =
- stts_first_sample_time_ + (sample_count * stts_sample_duration_);
- } else {
- // We've gone beyond the range defined by the last entry in the stts.
- // this is an error.
- highest_valid_sample_number_ =
- std::min(highest_valid_sample_number_, stts_first_sample_ - 1);
- return false;
- }
- return true;
-}
-
-bool ShellMP4Map::stts_SlipCacheToTime(uint64 timestamp,
- int starting_cache_index) {
- DCHECK_LT(starting_cache_index, stts_timestamps_.size());
- int cache_index = starting_cache_index;
- for (; cache_index + 1 < stts_timestamps_.size(); cache_index++) {
- if (timestamp < stts_timestamps_[cache_index + 1]) {
- break;
- }
- }
- stts_first_sample_ = stts_samples_[cache_index];
- stts_first_sample_time_ = stts_timestamps_[cache_index];
- stts_table_index_ = cache_index * stts_->GetCacheSizeEntries();
- // read sample count and duration to set next values
- uint32 sample_count;
- if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count,
- &stts_sample_duration_)) {
- return false;
- }
- stts_next_first_sample_ = stts_first_sample_ + sample_count;
- stts_next_first_sample_time_ =
- stts_first_sample_time_ + (sample_count * stts_sample_duration_);
- return true;
-}
-
-bool ShellMP4Map::stsz_Init() {
- return stsz_->GetBytesAtEntry(0) != NULL;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_mp4_map.h b/src/media/filters/shell_mp4_map.h
deleted file mode 100644
index c1c9212..0000000
--- a/src/media/filters/shell_mp4_map.h
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_MP4_MAP_H_
-#define MEDIA_FILTERS_SHELL_MP4_MAP_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_data_source_reader.h"
-
-namespace media {
-
-// per-atom sizes of individual table entries
-static const int kEntrySize_co64 = 8;
-static const int kEntrySize_ctts = 8;
-static const int kEntrySize_stco = 4;
-static const int kEntrySize_stts = 8;
-static const int kEntrySize_stsc = 12;
-static const int kEntrySize_stss = 4;
-static const int kEntrySize_stsz = 4;
-
-// Utility class to parse the various subatoms of the stbl mp4 atom and use
-// them to provide byte offsets, sizes, and timestamps of a mp4 atom while
-// reusing memory issued by ShellBufferFactory. The caching design benefits
-// from, but does not require, sequential access in sample numbers.
-class ShellMP4Map : public base::RefCountedThreadSafe<ShellMP4Map> {
- public:
- explicit ShellMP4Map(scoped_refptr<ShellDataSourceReader> reader);
-
- bool IsComplete();
-
- // All Get() methods return true on success and save their values by
- // reference in the latter argument.
- bool GetSize(uint32 sample_number, uint32& size_out);
- bool GetOffset(uint32 sample_number, uint64& offset_out);
- // all time values in *ticks* as defined by the mp4 container
- bool GetTimestamp(uint32 sample_number, uint64& timestamp_out);
- bool GetDuration(uint32 sample_number, uint32& duration_out);
- bool GetIsKeyframe(uint32 sample_number, bool& is_keyframe_out);
- // Used to determine if the failure reported by any of the above methods
- // is due to EOS or other (fatal) error. The length of a mp4 file in samples
- // may not be known until iterating through almost the entire map, in the
- // case of a default sample size (rare in compressed media)
- bool IsEOS(uint32 sample_number);
-
- // Returns the keyframe sample number nearest the provided timestamp
- bool GetKeyframe(uint64 timestamp, uint32& sample_out);
-
- // pass 0 as cache_size_entries to force caching of the entire map.
- bool SetAtom(uint32 four_cc, // fourCC code ascii code as big-endian uint32
- uint64 offset, // offset of atom body in file
- uint64 size, // total size of atom in bytes
- uint32 cache_size_entries, // num of entries to cache in memory
- const uint8* atom); // pointer to atom body start
-
- private:
- bool co64_Init();
-
- bool ctts_Init();
- // advance the ctts cache and integration state to contain sample number.
- bool ctts_AdvanceToSample(uint32 sample_number);
- bool ctts_SlipCacheToSample(uint32 sample_number, int starting_cache_index);
-
- bool stco_Init();
-
- bool stsc_Init();
- // advance the stsc cache and integration state to contain sample number.
- bool stsc_AdvanceToSample(uint32 sample_number);
- // re-use previously calculated sums to jump through the table to get to the
- // nearest cache entry that contains given sample number. Starts the search
- // from the starting_cache_index.
- bool stsc_SlipCacheToSample(uint32 sample_number, int starting_cache_index);
-
- bool stss_Init();
- // step through table by one table entry, return false on error
- bool stss_AdvanceStep();
- // search for nearest keyframe, update state to contain it
- bool stss_FindNearestKeyframe(uint32 sample_number);
-
- bool stts_Init();
- bool stts_AdvanceToSample(uint32 sample_number);
- bool stts_SlipCacheToSample(uint32 sample_number, int starting_cache_index);
- bool stts_AdvanceToTime(uint64 timestamp);
- bool stts_SlipCacheToTime(uint64 timestamp, int starting_cache_index);
- // step through the stts table by one table entry, return false on error
- bool stts_IntegrateStep();
-
- bool stsz_Init();
-
- // TableCache manages the caching of each atom table in a separate instance.
- // As each atom has a different per-entry byte size, and may want different
- // caching behavior based on consumption rate of entries and the overall size
- // of the table, it allows each atom to use its own policy for caching.
- // To keep things relatively simple it always keep a table of size of the
- // minimum of cache_size_entries or entry_count in memory, and that cached
- // table is always aligned with the full table in cache_size_entries, so
- // that the position in the cache is trivially calculated from
- // entry_number % cache_size_entries, and the cache index is similarly
- // caculated from entry_number / cache_size_entries.
- class TableCache : public base::RefCountedThreadSafe<TableCache> {
- public:
- TableCache(uint64 table_offset, // byte offset of start of table in file
- uint32 entry_count, // number of entries in table
- uint32 entry_size, // size in bytes of each entry in table
- uint32 cache_size_entries, // number of entries to cache in mem
- scoped_refptr<ShellDataSourceReader> reader); // reader to use
-
- // The following Read* functions all read values in big endian.
- bool ReadU32Entry(uint32 entry_number, uint32* entry);
- bool ReadU32PairEntry(uint32 entry_number, uint32* first, uint32* second);
- bool ReadU32EntryIntoU64(uint32 entry_number, uint64* entry);
- bool ReadU64Entry(uint32 entry_number, uint64* entry);
-
- uint8* GetBytesAtEntry(uint32 entry_number);
-
- // how many entries total in the table?
- inline uint32 GetEntryCount() const { return entry_count_; }
- // how many entries are we caching in memory at once?
- inline uint32 GetCacheSizeEntries() const { return cache_size_entries_; }
-
- private:
- friend class base::RefCountedThreadSafe<TableCache>;
- uint32 entry_size_; // size of entry in bytes
- uint32 entry_count_; // size of table in entries
- uint32 cache_size_entries_; // max number of entries to fit in memory
- uint64 table_offset_; // offset of table in stream
- scoped_refptr<ShellDataSourceReader> reader_; // means to read more table
-
- // current cache state
- scoped_refptr<ShellScopedArray> cache_; // the cached part of the table
- uint32 cache_first_entry_number_; // first table entry number in cache
- uint32 cache_entry_count_; // number of valid entries in cache
- };
-
- scoped_refptr<ShellDataSourceReader> reader_;
-
- // current integration state for GetOffset(), we save the sum of sample sizes
- // within the current chunk.
- uint32 current_chunk_sample_; // sample number last included in summation
- uint32 next_chunk_sample_; // first sample number of next chunk
- uint64 current_chunk_offset_; // file byte offset of current_chunk_sample_
-
- // Can be set by a stsz entry count but an stsz may provide a default size,
- // in which case this number may not be known until itegration through
- // the ctts, or stts has completed. In the event that one of those tables
- // ends at a lower number than the others this number will be amended
- // to return the lower number.
- uint32 highest_valid_sample_number_;
-
- // ==== c064 - per-chunk list of file offsets (64-bit)
- scoped_refptr<TableCache> co64_;
-
- // ==== ctts - run-length sample number to composition time offset
- scoped_refptr<TableCache> ctts_;
- uint32 ctts_first_sample_;
- uint32 ctts_sample_offset_;
- uint32 ctts_next_first_sample_;
- uint32 ctts_table_index_;
- std::vector<uint32> ctts_samples_;
-
- // ==== stco - per-chunk list of chunk file offsets (32-bit)
- scoped_refptr<TableCache> stco_;
-
- // ==== stsc - chunk-number to samples-per-chunk
- scoped_refptr<TableCache> stsc_;
- uint32 stsc_first_chunk_; // first chunk of the current sample size range
- uint32 stsc_first_chunk_sample_; // sum of samples of all prev chunk ranges
- uint32 stsc_samples_per_chunk_; // current samples-per-chunk in this range
- uint32 stsc_next_first_chunk_; // the chunk number the next region begins in
- uint32 stsc_next_first_chunk_sample_; // sample number next region begins in
- uint32 stsc_table_index_; // the index in the table of the current range
- std::vector<uint32> stsc_sample_sums_; // saved sums of cache segments
-
- // ==== stss - list of keyframe sample numbers
- scoped_refptr<TableCache> stss_;
- uint32 stss_last_keyframe_;
- uint32 stss_next_keyframe_;
- uint32 stss_table_index_; // index of stss_next_keyframe_ in table
- std::vector<uint32> stss_keyframes_;
-
- // ==== stts - run-length sample number to time duration
- scoped_refptr<TableCache> stts_;
- uint32 stts_first_sample_; // first sample of the current duration range
- uint64 stts_first_sample_time_; // sum of all durations of previous ranges
- uint32 stts_sample_duration_; // current duration of samples in this range
- uint32 stts_next_first_sample_; // first sample number of next range
- uint64 stts_next_first_sample_time_; // first timestamp of next range
- uint32 stts_table_index_; // index in the table of the next entry
- std::vector<uint32> stts_samples_;
- std::vector<uint64> stts_timestamps_;
-
- // ==== stsz - per-sample list of sample sizes
- scoped_refptr<TableCache> stsz_;
- uint32 stsz_default_size_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_MP4_MAP_H_
diff --git a/src/media/filters/shell_mp4_map_unittest.cc b/src/media/filters/shell_mp4_map_unittest.cc
deleted file mode 100644
index 5fca23c..0000000
--- a/src/media/filters/shell_mp4_map_unittest.cc
+++ /dev/null
@@ -1,1130 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_mp4_map.h"
-
-#include <stdlib.h> // for rand and srand
-
-#include <algorithm> // for std::min
-#include <set>
-#include <sstream>
-#include <vector>
-
-#include "lb_platform.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/mock_shell_data_source_reader.h"
-#include "media/filters/shell_mp4_parser.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::LB::Platform::load_uint32_big_endian;
-using ::LB::Platform::store_uint32_big_endian;
-using ::LB::Platform::store_uint64_big_endian;
-
-using ::media::kAtomType_co64;
-using ::media::kAtomType_ctts;
-using ::media::kAtomType_stco;
-using ::media::kAtomType_stsc;
-using ::media::kAtomType_stss;
-using ::media::kAtomType_stsz;
-using ::media::kAtomType_stts;
-using ::media::kEntrySize_co64;
-using ::media::kEntrySize_ctts;
-using ::media::kEntrySize_stco;
-using ::media::kEntrySize_stsc;
-using ::media::kEntrySize_stss;
-using ::media::kEntrySize_stsz;
-using ::media::kEntrySize_stts;
-using ::media::MockShellDataSourceReader;
-using ::media::ShellBufferFactory;
-using ::media::ShellMP4Map;
-
-using ::testing::_;
-using ::testing::AllOf;
-using ::testing::AnyNumber;
-using ::testing::DoAll;
-using ::testing::Ge;
-using ::testing::Invoke;
-using ::testing::Lt;
-using ::testing::Return;
-using ::testing::SetArrayArgument;
-
-namespace {
-
-int RandomRange(int min, int max) {
- return min + rand() % (max - min + 1);
-}
-
-// Data structure represent a sample inside stbl. It has redundant data for
-// easy access.
-struct Sample {
- bool is_key_frame;
- int size;
- int offset;
- int chunk_index;
- int dts_duration;
- int dts;
- int cts;
-};
-
-typedef std::vector<Sample> SampleVector;
-
-class SampleTable {
- public:
- // All ranges are inclusive at both ends.
- // Set range of composition timestamp to [0, 0] to disable ctts.
- SampleTable(unsigned int seed,
- int num_of_samples,
- int min_sample_size,
- int max_sample_size,
- int min_samples_per_chunk,
- int max_samples_per_chunk,
- int min_key_frame_gap,
- int max_key_frame_gap,
- int min_sample_decode_timestamp_offset,
- int max_sample_decode_timestamp_offset,
- int min_sample_composition_timestamp_offset,
- int max_sample_composition_timestamp_offset)
- : read_count_(0), read_bytes_(0) {
- srand(seed);
- CHECK_GT(num_of_samples, 0);
- CHECK_GT(min_sample_size, 0);
- CHECK_LE(min_sample_size, max_sample_size);
- CHECK_GT(min_samples_per_chunk, 0);
- CHECK_LE(min_samples_per_chunk, max_samples_per_chunk);
- CHECK_GT(min_key_frame_gap, 0);
- CHECK_LE(min_key_frame_gap, max_key_frame_gap);
- CHECK_GT(min_sample_decode_timestamp_offset, 0);
- CHECK_LE(min_sample_decode_timestamp_offset,
- max_sample_decode_timestamp_offset);
- CHECK_LE(min_sample_composition_timestamp_offset,
- max_sample_composition_timestamp_offset);
-
- samples_.resize(num_of_samples);
-
- int remaining_sample_in_chunk = 0;
- int current_chunk_index = -1;
- int next_key_frame = 0;
-
- for (int i = 0; i < num_of_samples; ++i) {
- samples_[i].size = RandomRange(min_sample_size, max_sample_size);
- samples_[i].offset =
- i == 0 ? rand() : samples_[i - 1].offset + samples_[i - 1].size;
- samples_[i].dts =
- i == 0 ? 0 : samples_[i - 1].dts + samples_[i - 1].dts_duration;
- samples_[i].dts_duration =
- RandomRange(min_sample_decode_timestamp_offset,
- max_sample_decode_timestamp_offset);
- ;
- samples_[i].cts = samples_[i].dts +
- RandomRange(min_sample_composition_timestamp_offset,
- max_sample_composition_timestamp_offset);
- if (!remaining_sample_in_chunk) {
- ++current_chunk_index;
- remaining_sample_in_chunk =
- RandomRange(min_samples_per_chunk, max_samples_per_chunk);
- }
-
- if (i >= next_key_frame) {
- samples_[i].is_key_frame = true;
- next_key_frame += RandomRange(min_key_frame_gap, max_key_frame_gap);
- } else {
- samples_[i].is_key_frame = false;
- }
-
- samples_[i].chunk_index = current_chunk_index;
- --remaining_sample_in_chunk;
- }
-
- PopulateBoxes();
- }
-
- int64 GetBoxOffset(uint32 atom_type) const {
- switch (atom_type) {
- case kAtomType_stsz:
- return stsz_offset_;
- case kAtomType_stco:
- return stco_offset_;
- case kAtomType_co64:
- return co64_offset_;
- case kAtomType_stsc:
- return stsc_offset_;
- case kAtomType_ctts:
- return ctts_offset_;
- case kAtomType_stts:
- return stts_offset_;
- case kAtomType_stss:
- return stss_offset_;
- default:
- NOTREACHED();
- return 0;
- }
- }
-
- int64 GetBoxSize(uint32 atom_type) const {
- switch (atom_type) {
- case kAtomType_stsz:
- return stsz_.size();
- case kAtomType_stco:
- return stco_.size();
- case kAtomType_co64:
- return co64_.size();
- case kAtomType_stsc:
- return stsc_.size();
- case kAtomType_ctts:
- return ctts_.size();
- case kAtomType_stts:
- return stts_.size();
- case kAtomType_stss:
- return stss_.size();
- default:
- NOTREACHED();
- return 0;
- }
- }
-
- const uint8_t* GetBoxData(uint32 atom_type) const {
- return &combined_[0] + GetBoxOffset(atom_type) - file_offset_;
- }
-
- size_t sample_count() const { return samples_.size(); }
- const Sample& sample(int i) const { return samples_.at(i); }
-
- size_t keyframe_count() const { return (stss_.size() - 8) / kEntrySize_stss; }
-
- int BlockingRead(int64 position, int size, uint8* data) {
- CHECK_GE(position, file_offset_);
- CHECK_LE(position + size, file_offset_ + combined_.size());
- uint32 offset = position - file_offset_;
- memcpy(data, &combined_[0] + offset, size);
- ++read_count_;
- read_bytes_ += size;
- return size;
- }
-
- void ClearReadStatistics() {
- read_count_ = 0;
- read_bytes_ = 0;
- }
-
- int read_count() const { return read_count_; }
- int read_bytes() const { return read_bytes_; }
-
- void Dump() const {
- std::stringstream ss;
- uint32 boxes[] = {kAtomType_stsz, kAtomType_stco, kAtomType_co64,
- kAtomType_stsc, kAtomType_ctts, kAtomType_stts,
- kAtomType_stss};
- const char* names[] = {"stsz", "stco", "co64", "stsc",
- "ctts", "stts", "stss"};
- for (uint32 i = 0; i < sizeof(boxes) / sizeof(*boxes); ++i) {
- ss << "\n======================== " << names[i]
- << " ========================\n";
- int64 size = GetBoxSize(boxes[i]);
- const uint8_t* data = GetBoxData(boxes[i]);
- for (int64 j = 0; j < size; ++j) {
- ss << static_cast<unsigned int>(data[j]) << ' ';
- if (j != 0 && j % 32 == 0)
- ss << '\n';
- }
- }
- LOG(INFO) << ss.str();
- }
-
- private:
- SampleVector samples_;
-
- int64 file_offset_;
- int64 stsz_offset_;
- int64 stco_offset_;
- int64 co64_offset_;
- int64 stsc_offset_;
- int64 ctts_offset_;
- int64 stts_offset_;
- int64 stss_offset_;
-
- std::vector<uint8_t> combined_;
- std::vector<uint8_t> stsz_;
- std::vector<uint8_t> stco_;
- std::vector<uint8_t> co64_;
- std::vector<uint8_t> stsc_;
- std::vector<uint8_t> ctts_;
- std::vector<uint8_t> stts_;
- std::vector<uint8_t> stss_;
-
- int read_count_;
- int read_bytes_;
-
- static void inc_uint32_big_endian(uint8_t* data) {
- uint32 value = load_uint32_big_endian(data);
- store_uint32_big_endian(value + 1, data);
- }
-
- void PopulateBoxes() {
- CHECK(!samples_.empty());
- bool all_key_frames = true;
- bool all_ctts_offset_is_zero = true;
- bool all_sample_has_same_size = true;
-
- for (SampleVector::const_iterator iter = samples_.begin();
- iter != samples_.end(); ++iter) {
- if (!iter->is_key_frame)
- all_key_frames = false;
- if (iter->dts != iter->cts)
- all_ctts_offset_is_zero = false;
- if (iter->size != samples_[0].size)
- all_sample_has_same_size = false;
- }
-
- // populate the stsz box: 4 bytes flags + 4 bytes default size
- // + 4 bytes count + size table if default size is 0
- if (all_sample_has_same_size) {
- stsz_.resize(12);
- store_uint32_big_endian(samples_[0].size, &stsz_[4]);
- } else {
- stsz_.resize(samples_.size() * kEntrySize_stsz + 12);
- store_uint32_big_endian(0, &stsz_[4]);
-
- uint8_t* table_offset = &stsz_[12];
-
- for (SampleVector::const_iterator iter = samples_.begin();
- iter != samples_.end(); ++iter) {
- store_uint32_big_endian(iter->size, table_offset);
- table_offset += kEntrySize_stsz;
- }
- }
- store_uint32_big_endian(samples_.size(), &stsz_[8]);
-
- // populate stco, co64 and stsc
- // stco = 4 bytes count + (4 bytes offset)*
- // co64 = 4 bytes count + (8 bytes offset)*
- // stsc = 4 bytes count + (4 bytes chunk index + 4 bytes sample per chunk
- // + 4 bytes sample description index)*
- stco_.resize(8);
- co64_.resize(8);
- stsc_.resize(8);
- uint32 chunk_offset = samples_[0].offset;
- int current_chunk_index = -1;
- for (SampleVector::const_iterator iter = samples_.begin();
- iter != samples_.end(); ++iter) {
- if (current_chunk_index != iter->chunk_index) {
- current_chunk_index = iter->chunk_index;
- stco_.resize(stco_.size() + kEntrySize_stco);
- store_uint32_big_endian(chunk_offset,
- &stco_[stco_.size() - kEntrySize_stco]);
- co64_.resize(co64_.size() + kEntrySize_co64);
- store_uint64_big_endian(chunk_offset,
- &co64_[co64_.size() - kEntrySize_co64]);
- stsc_.resize(stsc_.size() + kEntrySize_stsc);
- store_uint32_big_endian(current_chunk_index + 1, // start from 1
- &stsc_[stsc_.size() - kEntrySize_stsc]);
- }
- inc_uint32_big_endian(&stsc_[stsc_.size() - kEntrySize_stsc + 4]);
- chunk_offset += iter->size;
- }
- store_uint32_big_endian((stco_.size() - 8) / kEntrySize_stco, &stco_[4]);
- store_uint32_big_endian((co64_.size() - 8) / kEntrySize_co64, &co64_[4]);
- store_uint32_big_endian((stsc_.size() - 8) / kEntrySize_stsc, &stsc_[4]);
-
- // populate stts and ctts.
- // stts = 4 bytes count + (4 bytes sample count + 4 bytes sample delta)*
- // ctts = 4 bytes count + (4 bytes sample count + 4 bytes sample offset)*
- // the offset is to stts.
- stts_.resize(8);
- ctts_.resize(all_ctts_offset_is_zero ? 0 : 8);
- int32 last_stts_duration = -1;
- int32 last_ctts_offset = samples_[0].cts - samples_[0].dts - 1;
-
- for (size_t i = 0; i < samples_.size(); ++i) {
- int32 ctts_offset = samples_[i].cts - samples_[i].dts;
- if (last_stts_duration != samples_[i].dts_duration) {
- stts_.resize(stts_.size() + kEntrySize_stts);
- last_stts_duration = samples_[i].dts_duration;
- store_uint32_big_endian(last_stts_duration, &stts_[stts_.size() - 4]);
- }
- inc_uint32_big_endian(&stts_[stts_.size() - 8]);
- if (!all_ctts_offset_is_zero) {
- if (last_ctts_offset != ctts_offset) {
- ctts_.resize(ctts_.size() + kEntrySize_ctts);
- store_uint32_big_endian(ctts_offset, &ctts_[ctts_.size() - 4]);
- last_ctts_offset = ctts_offset;
- }
- inc_uint32_big_endian(&ctts_[ctts_.size() - 8]);
- }
- }
- store_uint32_big_endian((stts_.size() - 8) / kEntrySize_stts, &stts_[4]);
- if (!all_ctts_offset_is_zero)
- store_uint32_big_endian((ctts_.size() - 8) / kEntrySize_ctts, &ctts_[4]);
-
- // populate stss box
- // stss = 4 bytes count + (4 bytes sample index)*
- if (!all_key_frames) {
- stss_.resize(8);
- for (size_t i = 0; i < samples_.size(); ++i) {
- if (samples_[i].is_key_frame) {
- stss_.resize(stss_.size() + kEntrySize_stss);
- store_uint32_big_endian(i + 1,
- &stss_[stss_.size() - kEntrySize_stss]);
- }
- }
- store_uint32_big_endian((stss_.size() - 8) / kEntrySize_stss, &stss_[4]);
- }
-
- const int kGarbageSize = 1024;
- std::vector<uint8_t> garbage;
- garbage.reserve(kGarbageSize);
- for (int i = 0; i < kGarbageSize; ++i)
- garbage.push_back(RandomRange(0xef, 0xfe));
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), stsz_.begin(), stsz_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), stco_.begin(), stco_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), co64_.begin(), co64_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), stsc_.begin(), stsc_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), ctts_.begin(), ctts_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), stts_.begin(), stts_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
- combined_.insert(combined_.end(), stss_.begin(), stss_.end());
- combined_.insert(combined_.end(), garbage.begin(), garbage.end());
-
- file_offset_ = abs(rand());
- stsz_offset_ = file_offset_ + kGarbageSize;
- stco_offset_ = stsz_offset_ + stsz_.size() + kGarbageSize;
- co64_offset_ = stco_offset_ + stco_.size() + kGarbageSize;
- stsc_offset_ = co64_offset_ + co64_.size() + kGarbageSize;
- ctts_offset_ = stsc_offset_ + stsc_.size() + kGarbageSize;
- stts_offset_ = ctts_offset_ + ctts_.size() + kGarbageSize;
- stss_offset_ = stts_offset_ + stts_.size() + kGarbageSize;
- }
-};
-
-class ShellMP4MapTest : public testing::Test {
- protected:
- ShellMP4MapTest() {
- // we create and destroy buffer factory after each test to detect any
- // leaked reference-counted objects or pending callbacks
- ShellBufferFactory::Initialize();
- // make a new mock reader
- reader_ = new ::testing::NiceMock<MockShellDataSourceReader>();
- // make a new map with a mock reader.
- map_ = new ShellMP4Map(reader_);
- }
-
- virtual ~ShellMP4MapTest() {
- // wipe out the map or ShellBufferFactory may complain of unfreed allocs
- DCHECK(map_->HasOneRef());
- map_ = NULL;
-
- reader_->Stop(base::Closure());
- DCHECK(reader_->HasOneRef());
- reader_ = NULL;
-
- ShellBufferFactory::Terminate();
- }
-
- void ResetMap() { map_ = new ShellMP4Map(reader_); }
-
- void CreateTestSampleTable(unsigned int seed,
- int num_of_samples,
- int min_sample_size,
- int max_sample_size,
- int min_samples_per_chunk,
- int max_samples_per_chunk,
- int min_key_frame_gap,
- int max_key_frame_gap,
- int min_sample_decode_timestamp_offset,
- int max_sample_decode_timestamp_offset,
- int min_sample_composition_timestamp_offset,
- int max_sample_composition_timestamp_offset) {
- sample_table_.reset(new SampleTable(
- seed, num_of_samples, min_sample_size, max_sample_size,
- min_samples_per_chunk, max_samples_per_chunk, min_key_frame_gap,
- max_key_frame_gap, min_sample_decode_timestamp_offset,
- max_sample_decode_timestamp_offset,
- min_sample_composition_timestamp_offset,
- max_sample_composition_timestamp_offset));
- ON_CALL(*reader_, BlockingRead(_, _, _))
- .WillByDefault(Invoke(sample_table_.get(), &SampleTable::BlockingRead));
- }
-
- void SetTestTable(uint32 four_cc, uint32 cache_size_entries) {
- map_->SetAtom(four_cc, sample_table_->GetBoxOffset(four_cc),
- sample_table_->GetBoxSize(four_cc), cache_size_entries,
- sample_table_->GetBoxData(four_cc));
- }
-
- const Sample& GetTestSample(uint32 sample_number) const {
- return sample_table_->sample(sample_number);
- }
-
- // ==== Test Fixture Members
- scoped_refptr<ShellMP4Map> map_;
- scoped_refptr<MockShellDataSourceReader> reader_;
- scoped_ptr<SampleTable> sample_table_;
-};
-
-// ==== SetAtom() Tests ========================================================
-/*
-TEST_F(ShellMP4MapTest, SetAtomWithZeroDefaultSize) {
- // SetAtom() should fail with a zero default size on an stsc.
- NOTIMPLEMENTED();
-}
-*/
-// ==== GetSize() Tests ========================================================
-
-TEST_F(ShellMP4MapTest, GetSizeWithDefaultSize) {
- CreateTestSampleTable(100, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
- 10, 20);
- sample_table_->ClearReadStatistics();
-
- for (int i = 13; i < 21; ++i) {
- ResetMap();
- SetTestTable(kAtomType_stsz, i);
-
- uint32 returned_size;
- ASSERT_TRUE(map_->GetSize(0, returned_size));
- ASSERT_EQ(returned_size, 0xb0df00d);
- ASSERT_FALSE(map_->GetSize(2000, returned_size));
- ASSERT_TRUE(map_->GetSize(2, returned_size));
- ASSERT_EQ(returned_size, 0xb0df00d);
- ASSERT_TRUE(map_->GetSize(120, returned_size));
- ASSERT_EQ(returned_size, 0xb0df00d);
- }
-
- ASSERT_EQ(sample_table_->read_count(), 0);
-}
-
-TEST_F(ShellMP4MapTest, GetSizeIterationWithHugeCache) {
- for (int max_sample_size = 10; max_sample_size < 20; ++max_sample_size) {
- CreateTestSampleTable(200 + max_sample_size, 1000, 10, max_sample_size, 5,
- 10, 5, 10, 10, 20, 10, 20);
- for (int i = 1500; i < 10000; i = i * 2 + 1) {
- ResetMap();
- sample_table_->ClearReadStatistics();
- SetTestTable(kAtomType_stsz, i);
-
- for (uint32 j = 0; j < sample_table_->sample_count(); j++) {
- uint32 map_reported_size = 0;
- ASSERT_TRUE(map_->GetSize(j, map_reported_size));
- uint32 table_size = GetTestSample(j).size;
- // reported size should match table size
- ASSERT_EQ(map_reported_size, table_size);
- }
-
- // call to a sample past the size of the table should fail
- uint32 failed_size = 0;
- ASSERT_FALSE(map_->GetSize(sample_table_->sample_count(), failed_size));
- ASSERT_LE(sample_table_->read_count(), 1);
- ASSERT_LE(sample_table_->read_bytes(),
- sample_table_->GetBoxSize(kAtomType_stsz));
- }
- }
-}
-
-TEST_F(ShellMP4MapTest, GetSizeIterationTinyCache) {
- for (int max_sample_size = 10; max_sample_size < 20; ++max_sample_size) {
- CreateTestSampleTable(300 + max_sample_size, 1000, 10, max_sample_size, 5,
- 10, 5, 10, 10, 20, 10, 20);
- for (int i = 5; i < 12; ++i) {
- ResetMap();
- SetTestTable(kAtomType_stsz, i);
- sample_table_->ClearReadStatistics();
- for (uint32 j = 0; j < sample_table_->sample_count(); j++) {
- uint32 map_reported_size = 0;
- ASSERT_TRUE(map_->GetSize(j, map_reported_size));
- uint32 table_size = GetTestSample(j).size;
- ASSERT_EQ(map_reported_size, table_size);
- }
- ASSERT_LE(sample_table_->read_count(),
- sample_table_->sample_count() / i + 1);
- if (sample_table_->read_count())
- ASSERT_LE(sample_table_->read_bytes() / sample_table_->read_count(),
- (i + 1) * kEntrySize_stsz);
- // call to sample past the table size should still faile
- uint32 failed_size = 0;
- ASSERT_FALSE(map_->GetSize(sample_table_->sample_count(), failed_size));
- }
- }
-}
-
-TEST_F(ShellMP4MapTest, GetSizeRandomAccess) {
- CreateTestSampleTable(101, 2000, 20, 24, 5, 10, 5, 10, 10, 20, 10, 20);
- for (int i = 24; i < 27; ++i) {
- ResetMap();
- SetTestTable(kAtomType_stsz, i);
- sample_table_->ClearReadStatistics();
- // test first sample query somewhere later in the table, sample 105
- uint32 map_reported_size = 0;
- ASSERT_TRUE(map_->GetSize(i * 4 + 5, map_reported_size));
- uint32 table_size = GetTestSample(i * 4 + 5).size;
- ASSERT_EQ(map_reported_size, table_size);
- ASSERT_EQ(sample_table_->read_count(), 1);
- ASSERT_LE(sample_table_->read_bytes(), (i + 1) * kEntrySize_stsz);
-
- // now jump back to sample 0
- sample_table_->ClearReadStatistics();
- ASSERT_TRUE(map_->GetSize(0, map_reported_size));
- table_size = GetTestSample(0).size;
- ASSERT_EQ(map_reported_size, table_size);
- ASSERT_EQ(sample_table_->read_count(), 1);
- ASSERT_LE(sample_table_->read_bytes(), (i + 1) * kEntrySize_stsz);
-
- // now seek well past the end, this query should fail but not break
- // subsequent queries or issue a recache
- ASSERT_FALSE(
- map_->GetSize(sample_table_->sample_count(), map_reported_size));
-
- // a query back within the first table should not cause recache
- ASSERT_TRUE(map_->GetSize(10, map_reported_size));
- table_size = GetTestSample(10).size;
- ASSERT_EQ(map_reported_size, table_size);
-
- // check sample queries right on cache boundaries out-of-order
- sample_table_->ClearReadStatistics();
- ASSERT_TRUE(map_->GetSize(2 * i, map_reported_size));
- table_size = GetTestSample(2 * i).size;
- ASSERT_EQ(map_reported_size, table_size);
- ASSERT_EQ(sample_table_->read_count(), 1);
- ASSERT_TRUE(map_->GetSize(2 * i - 1, map_reported_size));
- table_size = GetTestSample(2 * i - 1).size;
- ASSERT_EQ(map_reported_size, table_size);
- ASSERT_TRUE(map_->GetSize(2 * i - 2, map_reported_size));
- table_size = GetTestSample(2 * i - 2).size;
- ASSERT_EQ(map_reported_size, table_size);
- ASSERT_EQ(sample_table_->read_count(), 2);
- }
-}
-
-// ==== GetOffset() Tests ======================================================
-
-TEST_F(ShellMP4MapTest, GetOffsetIterationHugeCache) {
- for (int coindex = 0; coindex < 2; ++coindex) {
- CreateTestSampleTable(102 + coindex, 1000, 20, 25, 5, 10, 5, 10, 10, 20, 10,
- 20);
- ResetMap();
- SetTestTable(kAtomType_stsz, 1000);
- SetTestTable(kAtomType_stsc, 1000);
- SetTestTable(coindex ? kAtomType_stco : kAtomType_co64, 1000);
-
- // no expectations on reader_, all tables should now be in memory
- for (uint32 i = 0; i < sample_table_->sample_count(); ++i) {
- uint64 map_reported_offset = 0;
- ASSERT_TRUE(map_->GetOffset(i, map_reported_offset));
- uint64 table_offset = GetTestSample(i).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
-
- // calls to sample numbers outside file range should fail non-fatally
- uint64 failed_offset;
- ASSERT_FALSE(map_->GetOffset(sample_table_->sample_count(), failed_offset));
- }
-}
-
-TEST_F(ShellMP4MapTest, GetOffsetIterationTinyCache) {
- for (int coindex = 0; coindex < 2; ++coindex) {
- CreateTestSampleTable(103, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- for (int i = 1; i < 12; ++i) {
- ResetMap();
- SetTestTable(kAtomType_stsz, i);
- SetTestTable(kAtomType_stsc, i);
- SetTestTable(coindex ? kAtomType_stco : kAtomType_co64, i);
-
- // iterate through all samples in range
- for (uint32 j = 0; j < sample_table_->sample_count(); j += 2) {
- uint64 map_reported_offset = 0;
- ASSERT_TRUE(map_->GetOffset(j, map_reported_offset));
- uint64 table_offset = GetTestSample(j).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
-
- // calls to sample numbers outside file range should fail non-fatally
- uint64 failed_offset;
- ASSERT_FALSE(
- map_->GetOffset(sample_table_->sample_count(), failed_offset));
- }
- }
-}
-
-// Random access within cache should just result in correct re-integration
-// through the stsc.
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessHugeCache) {
- for (int coindex = 0; coindex < 2; ++coindex) {
- CreateTestSampleTable(104, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stsz, 300);
- SetTestTable(kAtomType_stsc, 300);
- SetTestTable(coindex ? kAtomType_stco : kAtomType_co64, 300);
-
- for (int i = 0; i < 1000; ++i) {
- uint32 sample_number = rand() % sample_table_->sample_count();
- uint64 map_reported_offset = 0;
- ASSERT_TRUE(map_->GetOffset(sample_number, map_reported_offset));
- uint64 table_offset = GetTestSample(sample_number).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
- }
-}
-
-// Random access across cache boundaries should not break computation of
-// offsets.
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessTinyCache) {
- for (int coindex = 0; coindex < 2; ++coindex) {
- CreateTestSampleTable(105, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stsz, 7);
- SetTestTable(kAtomType_stsc, 7);
- SetTestTable(coindex ? kAtomType_stco : kAtomType_co64, 7);
-
- // calls to sample numbers outside file range should fail non-fatally
- uint64 failed_offset;
- ASSERT_FALSE(map_->GetOffset(sample_table_->sample_count(), failed_offset));
-
- // second sample in the file
- uint32 sample_number = 1;
- uint64 map_reported_offset = 0;
- ASSERT_TRUE(map_->GetOffset(sample_number, map_reported_offset));
- uint64 table_offset = GetTestSample(sample_number).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
-
- for (int i = 1; i < 15; ++i) {
- ResetMap();
- SetTestTable(kAtomType_stsz, 7);
- SetTestTable(kAtomType_stsc, 7);
- SetTestTable(kAtomType_stco, 7);
-
- sample_number = sample_table_->sample_count() - i;
- ASSERT_TRUE(map_->GetOffset(sample_number, map_reported_offset));
- table_offset = GetTestSample(sample_number).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
-
- sample_number--;
- ASSERT_TRUE(map_->GetOffset(sample_number, map_reported_offset));
- table_offset = GetTestSample(sample_number).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
-
- // now iterate through a few samples in the middle
- sample_number /= 2;
- for (int j = 0; j < 40; j++) {
- ASSERT_TRUE(map_->GetOffset(sample_number + j, map_reported_offset));
- table_offset = GetTestSample(sample_number + j).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
-
- // now iterate backwards from the same starting point
- for (int j = 0; j < 40; j++) {
- ASSERT_TRUE(map_->GetOffset(sample_number - j, map_reported_offset));
- table_offset = GetTestSample(sample_number - j).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
- }
- }
-}
-
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessWithDefaultSize) {
- for (int coindex = 0; coindex < 2; ++coindex) {
- CreateTestSampleTable(106, 300, 20, 20, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stsz, 7);
- SetTestTable(kAtomType_stsc, 7);
- SetTestTable(coindex ? kAtomType_stco : kAtomType_co64, 7);
-
- // Calculating offset of an out-of-range sample should still return an
- // error.
- uint64 map_reported_offset = 0;
- ASSERT_FALSE(map_->GetOffset(sample_table_->sample_count() + 2,
- map_reported_offset));
-
- // First sample in file should still work, though.
- ASSERT_TRUE(map_->GetOffset(0, map_reported_offset));
- uint64 table_offset = GetTestSample(0).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
-
- // Last sample should also work.
- ASSERT_TRUE(map_->GetOffset(sample_table_->sample_count() - 1,
- map_reported_offset));
- table_offset = GetTestSample(sample_table_->sample_count() - 1).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
-
- // Skip by 3 through the file a few times
- for (int i = 0; i < sample_table_->sample_count(); ++i) {
- int sample_index = (i * 3) % sample_table_->sample_count();
- ASSERT_TRUE(map_->GetOffset(sample_index, map_reported_offset));
- table_offset = GetTestSample(sample_index).offset;
- ASSERT_EQ(map_reported_offset, table_offset);
- }
- }
-}
-
-// ==== GetDuration() Tests ====================================================
-
-TEST_F(ShellMP4MapTest, GetDurationIteration) {
- CreateTestSampleTable(107, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 2);
-
- for (uint32 i = 0; i < sample_table_->sample_count(); ++i) {
- uint32 map_reported_duration = 0;
- ASSERT_TRUE(map_->GetDuration(i, map_reported_duration));
- uint32 table_duration = GetTestSample(i).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
- }
-
- // entries past end of table should fail
- uint32 failed_duration = 0;
- ASSERT_FALSE(
- map_->GetDuration(sample_table_->sample_count(), failed_duration));
-}
-
-TEST_F(ShellMP4MapTest, GetDurationRandomAccess) {
- CreateTestSampleTable(108, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 3);
-
- // first sample in table
- uint32 map_reported_duration = 0;
- ASSERT_TRUE(map_->GetDuration(0, map_reported_duration));
- uint32 table_duration = GetTestSample(0).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
-
- // last sample in table
- ASSERT_TRUE(map_->GetDuration(sample_table_->sample_count() - 1,
- map_reported_duration));
- table_duration =
- GetTestSample(sample_table_->sample_count() - 1).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
-
- // sample just past end should fail
- ASSERT_FALSE(
- map_->GetDuration(sample_table_->sample_count(), map_reported_duration));
-
- // but shouldn't break other sample lookups
- ASSERT_TRUE(map_->GetDuration(2, map_reported_duration));
- table_duration = GetTestSample(2).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
-
- // now iterate backwards through entire table back to first sample
- for (int i = sample_table_->sample_count() - 1; i >= 1; i--) {
- ASSERT_TRUE(map_->GetDuration(i, map_reported_duration));
- table_duration = GetTestSample(i).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
- }
-}
-
-// ==== GetTimestamp() Tests ===================================================
-
-TEST_F(ShellMP4MapTest, GetTimestampIterationNoCompositionTime) {
- CreateTestSampleTable(109, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 7);
-
- for (uint32 i = 0; i < sample_table_->sample_count(); ++i) {
- uint64 map_reported_timestamp = 0;
- ASSERT_TRUE(map_->GetTimestamp(i, map_reported_timestamp));
- uint64 table_timestamp = GetTestSample(i).dts;
- ASSERT_EQ(map_reported_timestamp, table_timestamp);
- }
-
- // entries past end of table should fail
- uint64 failed_timestamp = 0;
- ASSERT_FALSE(
- map_->GetTimestamp(sample_table_->sample_count(), failed_timestamp));
-}
-
-TEST_F(ShellMP4MapTest, GetTimestampRandomAccessNoCompositionTime) {
- CreateTestSampleTable(110, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 10);
-
- // skip by sevens through the file, seven times
- for (int i = 0; i < sample_table_->sample_count(); ++i) {
- uint32 sample_number = (i * 7) % sample_table_->sample_count();
- uint64 map_reported_timestamp = 0;
- ASSERT_TRUE(map_->GetTimestamp(sample_number, map_reported_timestamp));
- uint64 table_timestamp = GetTestSample(sample_number).dts;
- ASSERT_EQ(map_reported_timestamp, table_timestamp);
- }
-
- // check a failed entry
- uint64 failed_timestamp = 0;
- ASSERT_FALSE(
- map_->GetTimestamp(sample_table_->sample_count() * 2, failed_timestamp));
-
- // should still be able to recover with valid input, this time skip by 21s
- // backward through the file 21 times
- for (int i = sample_table_->sample_count() - 1; i >= 0; i--) {
- uint32 sample_number = (i * 21) % sample_table_->sample_count();
- uint64 map_reported_timestamp = 0;
- ASSERT_TRUE(map_->GetTimestamp(sample_number, map_reported_timestamp));
- uint64 table_timestamp = GetTestSample(sample_number).dts;
- ASSERT_EQ(map_reported_timestamp, table_timestamp);
- }
-}
-
-TEST_F(ShellMP4MapTest, GetTimestampIteration) {
- CreateTestSampleTable(111, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- for (int i = 1; i < 20; ++i) {
- ResetMap();
- SetTestTable(kAtomType_ctts, i);
- SetTestTable(kAtomType_stts, i);
-
- for (int j = 0; j < sample_table_->sample_count(); ++j) {
- uint64 map_reported_timestamp = 0;
- ASSERT_TRUE(map_->GetTimestamp(j, map_reported_timestamp));
- uint64 table_timestamp = GetTestSample(j).cts;
- ASSERT_EQ(map_reported_timestamp, table_timestamp);
-
- uint32 map_reported_duration = 0;
- ASSERT_TRUE(map_->GetDuration(j, map_reported_duration));
- uint32 table_duration = GetTestSample(j).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
- }
- }
-}
-
-TEST_F(ShellMP4MapTest, GetTimestampRandomAccess) {
- CreateTestSampleTable(112, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- for (int i = 1; i < 20; ++i) {
- ResetMap();
- SetTestTable(kAtomType_ctts, i);
- SetTestTable(kAtomType_stts, i);
-
- for (int j = 0; j < 100; ++j) {
- uint32 sample_number = rand() % sample_table_->sample_count();
- uint64 map_reported_timestamp = 0;
- ASSERT_TRUE(map_->GetTimestamp(sample_number, map_reported_timestamp));
- uint64 table_timestamp = GetTestSample(sample_number).cts;
- ASSERT_EQ(map_reported_timestamp, table_timestamp);
-
- uint32 map_reported_duration = 0;
- ASSERT_TRUE(map_->GetDuration(sample_number, map_reported_duration));
- uint32 table_duration = GetTestSample(sample_number).dts_duration;
- ASSERT_EQ(map_reported_duration, table_duration);
- }
- }
-}
-
-// ==== GetIsKeyframe() Tests ==================================================
-
-// the map should consider every valid sample number a keyframe without an stss
-TEST_F(ShellMP4MapTest, GetIsKeyframeNoKeyframeTable) {
- ResetMap();
- bool is_keyframe_out = false;
- ASSERT_TRUE(map_->GetIsKeyframe(100, is_keyframe_out));
- ASSERT_TRUE(is_keyframe_out);
-
- is_keyframe_out = false;
- ASSERT_TRUE(map_->GetIsKeyframe(5, is_keyframe_out));
- ASSERT_TRUE(is_keyframe_out);
-
- for (int i = 17; i < 174; i += 3) {
- is_keyframe_out = false;
- ASSERT_TRUE(map_->GetIsKeyframe(i, is_keyframe_out));
- ASSERT_TRUE(is_keyframe_out);
- }
-}
-
-TEST_F(ShellMP4MapTest, GetIsKeyframeIteration) {
- CreateTestSampleTable(113, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
- 10, 20);
- ResetMap();
- sample_table_->ClearReadStatistics();
- SetTestTable(kAtomType_stss, sample_table_->keyframe_count() / 2 + 5);
-
- for (uint32 i = 0; i < sample_table_->sample_count(); ++i) {
- bool map_is_keyframe_out = false;
- ASSERT_TRUE(map_->GetIsKeyframe(i, map_is_keyframe_out));
- bool table_is_keyframe = GetTestSample(i).is_key_frame;
- ASSERT_EQ(map_is_keyframe_out, table_is_keyframe);
- }
-}
-
-TEST_F(ShellMP4MapTest, GetIsKeyframeRandomAccess) {
- CreateTestSampleTable(114, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
- 10, 20);
- ResetMap();
- sample_table_->ClearReadStatistics();
- SetTestTable(kAtomType_stss, sample_table_->keyframe_count() / 2 + 5);
-
- // pick a keyframe about halfway
- uint32 sample_number = sample_table_->sample_count() / 2;
- while (!GetTestSample(sample_number).is_key_frame)
- ++sample_number;
- // sample one past it should not be a keyframe
- bool map_is_keyframe_out = false;
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number + 1, map_is_keyframe_out));
- ASSERT_FALSE(map_is_keyframe_out);
- // sample one before keyframe should not be a keyframe either
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number - 1, map_is_keyframe_out));
- ASSERT_FALSE(map_is_keyframe_out);
- // however it should be a keyframe
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number, map_is_keyframe_out));
- ASSERT_TRUE(map_is_keyframe_out);
-
- // first keyframe
- sample_number = 0;
- while (!GetTestSample(sample_number).is_key_frame)
- ++sample_number;
- // next sample should not be a keyframe
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number + 1, map_is_keyframe_out));
- ASSERT_FALSE(map_is_keyframe_out);
- // but it should be
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number, map_is_keyframe_out));
- ASSERT_TRUE(map_is_keyframe_out);
-
- // iterate backwards from end of file to beginning
- for (int i = sample_table_->sample_count() - 1; i >= 0; --i) {
- ASSERT_TRUE(map_->GetIsKeyframe(i, map_is_keyframe_out));
- ASSERT_EQ(map_is_keyframe_out, GetTestSample(i).is_key_frame);
- }
-
- // iterate backwards through keyframes only
- for (int i = sample_table_->sample_count() - 1; i >= 0; --i) {
- if (GetTestSample(i).is_key_frame) {
- ASSERT_TRUE(map_->GetIsKeyframe(i, map_is_keyframe_out));
- ASSERT_TRUE(map_is_keyframe_out);
- }
- }
-
- // iterate forwards but skip all keyframes
- for (int i = sample_table_->sample_count() - 1; i >= 0; --i) {
- if (!GetTestSample(i).is_key_frame) {
- ASSERT_TRUE(map_->GetIsKeyframe(i, map_is_keyframe_out));
- ASSERT_FALSE(map_is_keyframe_out);
- }
- }
-
- ResetMap();
- sample_table_->ClearReadStatistics();
- SetTestTable(kAtomType_stss, 7);
-
- // random access
- for (int i = 0; i < 1000; ++i) {
- sample_number = rand() % sample_table_->sample_count();
- ASSERT_TRUE(map_->GetIsKeyframe(sample_number, map_is_keyframe_out));
- ASSERT_EQ(map_is_keyframe_out, GetTestSample(sample_number).is_key_frame);
- }
-}
-
-// ==== GetKeyframe() Tests ====================================================
-
-// every frame should be returned as a keyframe. This tests if our computation
-// of timestamps => sample numbers is equivalent to sample numbers => timestamps
-TEST_F(ShellMP4MapTest, GetKeyframeNoKeyframeTableIteration) {
- CreateTestSampleTable(115, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 7);
-
- for (int i = 0; i < sample_table_->sample_count(); ++i) {
- // get actual timestamp and duration of this sample
- uint64 sample_timestamp = GetTestSample(i).dts;
- uint32 sample_duration = GetTestSample(i).dts_duration;
- // add a bit of time to sample timestamp, but keep time within this frame
- sample_timestamp += i % sample_duration;
- uint32 map_keyframe = 0;
- ASSERT_TRUE(map_->GetKeyframe(sample_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, i);
- }
-}
-
-TEST_F(ShellMP4MapTest, GetKeyframeNoKeyframeTableRandomAccess) {
- CreateTestSampleTable(116, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stts, 5);
-
- // backwards through the middle third of samples
- for (int i = (sample_table_->sample_count() * 2) / 3;
- i >= sample_table_->sample_count() / 3; --i) {
- uint64 sample_timestamp = GetTestSample(i).dts;
- uint32 sample_duration = GetTestSample(i).dts_duration;
- sample_timestamp += sample_duration - 1 - (i % sample_duration);
- uint32 map_keyframe = 0;
- ASSERT_TRUE(map_->GetKeyframe(sample_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, i);
- }
-
- // highest valid timestamp in file
- uint64 highest_timestamp =
- GetTestSample(sample_table_->sample_count() - 1).dts;
- highest_timestamp +=
- GetTestSample(sample_table_->sample_count() - 1).dts_duration - 1;
- uint32 map_keyframe = 0;
- ASSERT_TRUE(map_->GetKeyframe(highest_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, sample_table_->sample_count() - 1);
-
- // lowest valid timestamp in file
- ASSERT_TRUE(map_->GetKeyframe(0, map_keyframe));
- ASSERT_EQ(map_keyframe, 0);
-
- // should fail on higher timestamps
- ASSERT_FALSE(map_->GetKeyframe(highest_timestamp + 1, map_keyframe));
-}
-
-// GetKeyframe is not normally called iteratively, so we test random access
-TEST_F(ShellMP4MapTest, GetKeyframe) {
- CreateTestSampleTable(117, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
- ResetMap();
- SetTestTable(kAtomType_stss, 3);
- SetTestTable(kAtomType_stts, 7);
-
- // find first keyframe in file, should be first frame
- uint32 map_keyframe = 0;
- ASSERT_TRUE(map_->GetKeyframe(0, map_keyframe));
- ASSERT_EQ(map_keyframe, 0);
-
- // find a first quarter keyframe in file
- uint32 qtr_keyframe = sample_table_->sample_count() / 4;
- while (!GetTestSample(qtr_keyframe).is_key_frame)
- ++qtr_keyframe;
- uint32 next_keyframe = qtr_keyframe + 1;
- while (!GetTestSample(next_keyframe).is_key_frame)
- ++next_keyframe;
- uint32 prev_keyframe = qtr_keyframe - 1;
- while (!GetTestSample(prev_keyframe).is_key_frame)
- --prev_keyframe;
- uint32 last_keyframe = sample_table_->sample_count() - 1;
- while (!GetTestSample(last_keyframe).is_key_frame)
- --last_keyframe;
- // midway between this keyframe and the next one
- uint32 test_frame = qtr_keyframe + ((next_keyframe - qtr_keyframe) / 2);
- // get time for this frame
- uint64 test_frame_timestamp = GetTestSample(test_frame).dts;
- // get duration for this frame
- uint32 test_frame_duration = GetTestSample(test_frame).dts_duration;
- // midway through this frame
- test_frame_timestamp += test_frame_duration / 2;
- // find lower bound keyframe, should be qtr_keyframe
- ASSERT_TRUE(map_->GetKeyframe(test_frame_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, qtr_keyframe);
-
- // timestamp one tick before qtr_keyframe should find previous keyframe
- test_frame_timestamp = GetTestSample(qtr_keyframe).dts - 1;
- ASSERT_TRUE(map_->GetKeyframe(test_frame_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, prev_keyframe);
-
- // very highest timestamp in file should return last keyframe
- uint64 highest_timestamp =
- GetTestSample(sample_table_->sample_count() - 1).dts;
- highest_timestamp +=
- GetTestSample(sample_table_->sample_count() - 1).dts_duration - 1;
- ASSERT_TRUE(map_->GetKeyframe(highest_timestamp, map_keyframe));
- ASSERT_EQ(map_keyframe, last_keyframe);
-}
-
-} // namespace
diff --git a/src/media/filters/shell_mp4_parser.cc b/src/media/filters/shell_mp4_parser.cc
deleted file mode 100644
index 0360858..0000000
--- a/src/media/filters/shell_mp4_parser.cc
+++ /dev/null
@@ -1,732 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_mp4_parser.h"
-
-#include <inttypes.h>
-#include <limits>
-
-#include "base/stringprintf.h"
-#include "media/base/endian_util.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/mp4/es_descriptor.h"
-
-#if SHELL_MP4_PARSER_DUMP_ATOMS
-#include <string>
-extern const std::string* global_game_content_path;
-#endif
-
-namespace media {
-
-// how many bytes to skip within an avc1 before the config atoms start?
-static const int kSkipBytes_avc1 = 78;
-
-// what's the smallest meaningful mp4 atom size?
-static const int kAtomMinSize = 8;
-
-// Full Box has a one byte version and three bytes flags after the header
-static const int kFullBoxHeaderAndFlagSize = 4;
-
-// how much to download of an hdlr to get the trak type?
-static const int kDesiredBytes_hdlr = 12;
-static const uint32 kAudioSubtype_hdlr_soun = 0x736f756e;
-static const uint32 kVideoSubtype_hdlr_vide = 0x76696465;
-
-// how much to download of an mp4a to determine version number?
-static const int kDesiredBytes_mp4a = 2;
-// how big is the mp4a atom before optional extension atoms?
-static const int kTotalSize_mp4a_v0 = 28;
-static const int kTotalSize_mp4a_v1 = 44;
-static const int kTotalSize_mp4a_v2 = 64;
-
-// how many bytes should we download from an mdhd?
-static const int kDesiredBytes_mdhd = 20;
-
-// how many bytes should we download from an mvhd?
-static const int kDesiredBytes_mvhd = 20;
-
-// how many bytes to skip within an stsd before the config atoms start?
-static const int kSkipBytes_stsd = 8;
-
-// use average values of mp4 metadata tables plus 2 standard deviations to
-// hold most metadata atoms entirely in memory.
-static const int kMapTableAtomCacheEntries_stsz = 260591 / kEntrySize_stsz;
-static const int kMapTableAtomCacheEntries_stco = 22859 / kEntrySize_stco;
-static const int kMapTableAtomCacheEntries_stss = 3786 / kEntrySize_stss;
-static const int kMapTableAtomCacheEntries_stts = 164915 / kEntrySize_stts;
-static const int kMapTableAtomCacheEntries_stsc = 32199 / kEntrySize_stsc;
-static const int kMapTableAtomCacheEntries_co64 = 740212 / kEntrySize_co64;
-static const int kMapTableAtomCacheEntries_ctts = 51543 / kEntrySize_ctts;
-
-// static
-PipelineStatus ShellMP4Parser::Construct(
- scoped_refptr<ShellDataSourceReader> reader,
- const uint8* construction_header,
- scoped_refptr<ShellParser>* parser) {
- DCHECK(parser);
- *parser = NULL;
-
- // detect mp4 stream by looking for ftyp atom at top of file
- uint32 ftyp = endian_util::load_uint32_big_endian(construction_header + 4);
- if (ftyp != kAtomType_ftyp) {
- // not an mp4
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
-
- // first 4 bytes will be the size of the ftyp atom
- uint32 ftyp_atom_size =
- endian_util::load_uint32_big_endian(construction_header);
- if (ftyp_atom_size < kAtomMinSize) {
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
-
- // construct new mp4 parser
- *parser = new ShellMP4Parser(reader, ftyp_atom_size);
- return PIPELINE_OK;
-}
-
-ShellMP4Parser::ShellMP4Parser(scoped_refptr<ShellDataSourceReader> reader,
- uint32 ftyp_atom_size)
- : ShellAVCParser(reader),
- atom_offset_(ftyp_atom_size), // start at next atom, skipping over ftyp
- current_trak_is_video_(false),
- current_trak_is_audio_(false),
- current_trak_time_scale_(0),
- video_time_scale_hz_(0),
- audio_time_scale_hz_(0),
- audio_map_(new ShellMP4Map(reader)),
- video_map_(new ShellMP4Map(reader)),
- audio_sample_(0),
- video_sample_(0),
- first_audio_hole_ticks_(0),
- first_audio_hole_(base::TimeDelta::FromSeconds(0)) {}
-
-ShellMP4Parser::~ShellMP4Parser() {}
-
-// For MP4 we traverse the file's atom structure attempting to find the audio
-// and video configuration information and the locations in the file of the
-// various stbl subatoms which detail the position of the audio and video
-// NALUs in the file. As some of the stbl subatoms can be quite large we cache
-// a fixed maximum quantity of each stbl subatom and update the cache only on
-// miss.
-bool ShellMP4Parser::ParseConfig() {
- while (!IsConfigComplete() || !audio_map_->IsComplete() ||
- !video_map_->IsComplete()) {
- if (!ParseNextAtom()) {
- return false;
- }
- }
- return true;
-}
-
-scoped_refptr<ShellAU> ShellMP4Parser::GetNextAU(DemuxerStream::Type type) {
- uint32 size = 0;
- uint32 duration_ticks = 0;
- uint64 timestamp_ticks = 0;
- uint64 offset = 0;
- bool is_keyframe = false;
- base::TimeDelta timestamp;
- base::TimeDelta duration;
- if (type == DemuxerStream::AUDIO) {
- if (audio_time_scale_hz_ == 0) {
- DLOG(ERROR) << "|audio_time_scale_hz_| cannot be 0.";
- return NULL;
- }
- if (!audio_map_->GetSize(audio_sample_, size) ||
- !audio_map_->GetOffset(audio_sample_, offset) ||
- !audio_map_->GetDuration(audio_sample_, duration_ticks) ||
- !audio_map_->GetTimestamp(audio_sample_, timestamp_ticks)) {
- // determine if EOS or error
- if (audio_map_->IsEOS(audio_sample_)) {
- return ShellAU::CreateEndOfStreamAU(DemuxerStream::AUDIO,
- audio_track_duration_);
- } else {
- DLOG(ERROR) << "parsed bad audio AU";
- return NULL;
- }
- }
- // all aac frames are random-access, so all are keyframes
- is_keyframe = true;
- audio_sample_++;
- timestamp = TicksToTime(timestamp_ticks, audio_time_scale_hz_);
- duration = TicksToTime(duration_ticks, audio_time_scale_hz_);
-
- // It would be very unusual to encounter non-contiguous audio
- // in an mp4, but you never know. Make sure this timestamp is
- // contiguous in ticks from the last one
- if (first_audio_hole_ticks_ == timestamp_ticks) {
- // Much of the audio stack assumes that audio timestamps are
- // contiguous. While the timestamps coming out of the map are
- // normally continuous, they are on a different time scale. Due
- // to roundoff error in conversion the timestamps produced may
- // be discontinuous. To correct this we correct the timestamp
- // to the one the system is expecting for continuity, then modify
- // the duration by the negative of that same (small) value,
- // so as to not accumulate roundoff error over time.
- base::TimeDelta time_difference = timestamp - first_audio_hole_;
- timestamp = first_audio_hole_;
- duration += time_difference;
- first_audio_hole_ = timestamp + duration;
- first_audio_hole_ticks_ += duration_ticks;
- } else {
- DLOG(WARNING) << "parsed non-contiguous mp4 audio timestamp";
- // reset hole tracking past gap
- first_audio_hole_ticks_ = timestamp_ticks + duration_ticks;
- first_audio_hole_ = timestamp + duration;
- }
- } else if (type == DemuxerStream::VIDEO) {
- if (video_time_scale_hz_ == 0) {
- DLOG(ERROR) << "|video_time_scale_hz_| cannot be 0.";
- return NULL;
- }
- if (!video_map_->GetSize(video_sample_, size) ||
- !video_map_->GetOffset(video_sample_, offset) ||
- !video_map_->GetDuration(video_sample_, duration_ticks) ||
- !video_map_->GetTimestamp(video_sample_, timestamp_ticks) ||
- !video_map_->GetIsKeyframe(video_sample_, is_keyframe)) {
- if (video_map_->IsEOS(video_sample_)) {
- return ShellAU::CreateEndOfStreamAU(DemuxerStream::VIDEO,
- video_track_duration_);
- } else {
- DLOG(ERROR) << "parsed bad video AU";
- return NULL;
- }
- }
- video_sample_++;
- timestamp = TicksToTime(timestamp_ticks, video_time_scale_hz_);
- duration = TicksToTime(duration_ticks, video_time_scale_hz_);
- // due to b-frames it's much more likely we'll encounter discontinuous
- // video buffers. As a result we add a small duration to each video
- // buffer, equal in value to one full tick at the video timescale. The
- // showing of video frames is actually keyed on the audio clock, so this
- // shouldn't create too much jitter in the output.
- duration += one_video_tick_;
- } else {
- NOTREACHED() << "unsupported stream type";
- return NULL;
- }
-
- size_t prepend_size = CalculatePrependSize(type, is_keyframe);
-
- if (type == DemuxerStream::AUDIO)
- return ShellAU::CreateAudioAU(offset, size, prepend_size, is_keyframe,
- timestamp, duration, this);
- return ShellAU::CreateVideoAU(offset, size, prepend_size, nal_header_size_,
- is_keyframe, timestamp, duration, this);
-}
-
-bool ShellMP4Parser::SeekTo(base::TimeDelta timestamp) {
- if (audio_time_scale_hz_ == 0 || video_time_scale_hz_ == 0) {
- DLOG_IF(ERROR, audio_time_scale_hz_ == 0)
- << "|audio_time_scale_hz_| cannot be 0.";
- DLOG_IF(ERROR, video_time_scale_hz_ == 0)
- << "|video_time_scale_hz_| cannot be 0.";
- return false;
- }
-
- // get video timestamp in video time units
- uint64 video_ticks = TimeToTicks(timestamp, video_time_scale_hz_);
- // find nearest keyframe from map, make it our next video sample
- if (!video_map_->GetKeyframe(video_ticks, video_sample_)) {
- return false;
- }
- // get the timestamp for this video keyframe
- uint64 video_keyframe_time_ticks = 0;
- if (!video_map_->GetTimestamp(video_sample_, video_keyframe_time_ticks)) {
- return false;
- }
- base::TimeDelta video_keyframe_time =
- TicksToTime(video_keyframe_time_ticks, video_time_scale_hz_);
- // find the closest audio frame that bounds that timestamp
- uint64 audio_ticks = TimeToTicks(video_keyframe_time, audio_time_scale_hz_);
- if (!audio_map_->GetKeyframe(audio_ticks, audio_sample_)) {
- return false;
- }
- DLOG(INFO) << base::StringPrintf(
- "seeking to timestamp: %" PRId64 ", video sample: %d, audio sample: %d",
- timestamp.InMilliseconds(), video_sample_, audio_sample_);
- // cheat our buffer continuity system
- if (!audio_map_->GetTimestamp(audio_sample_, first_audio_hole_ticks_)) {
- return false;
- }
- first_audio_hole_ =
- TicksToTime(first_audio_hole_ticks_, audio_time_scale_hz_);
- return true;
-}
-
-// parse the atom starting at atom_offset_, update appropriate internal state,
-// return false on fatal error. General structure of an MP4 atom is:
-// field | type | comment
-// ------------------+--------+---------
-// atom size | uint32 | if 0 means "rest of file", if 1 means extended
-// fourCC code | ASCII | four-byte ASCII code we treat as uint32
-// extended size | uint64 | optional size field, only here if atom size is 1
-// <--- rest of atom body starts here
-bool ShellMP4Parser::ParseNextAtom() {
- uint8 atom[kAtomDownload];
- int bytes_read = reader_->BlockingRead(atom_offset_, kAtomDownload, atom);
- if (bytes_read < kAtomDownload) {
- return false;
- }
- // first 4 bytes are size of atom uint32
- uint64 atom_size = (uint64)endian_util::load_uint32_big_endian(atom);
- // normally atom body starts just past fourCC code
- uint32 atom_body = kAtomMinSize;
- // if 1 we need to load the extended size which will be appended just past
- // the fourCC code
- if (atom_size == 1) {
- atom_size = endian_util::load_uint64_big_endian(atom + 8);
- // advance atom_body past the 8 bytes of size we just parsed
- atom_body += 8;
- } else if (atom_size == 0) {
- // calculate size of this atom from remainder of file
- DCHECK_LE(atom_offset_,
- static_cast<uint64>(std::numeric_limits<int64>::max()));
- if (reader_->FileSize() > static_cast<int64>(atom_offset_)) {
- atom_size = reader_->FileSize() - atom_offset_;
- }
- }
- // atom sizes also include the size of the start of the atom, so sanity-check
- // the size we just parsed against the number of bytes we needed to parse it
- if (atom_size < atom_body) {
- DLOG(WARNING) << base::StringPrintf("atom size: %" PRId64
- " less than min body size %d",
- atom_size, atom_body);
- return false;
- }
-
- // extract fourCC code as big-endian uint32
- uint32 four_cc = endian_util::load_uint32_big_endian(atom + 4);
- DLOG(INFO) << base::StringPrintf("four_cc: %c%c%c%c", atom[4], atom[5],
- atom[6], atom[7]);
-
-#if SHELL_MP4_PARSER_DUMP_ATOMS
- DumpAtomToDisk(four_cc, atom_size, atom_offset_);
-#endif
-
- // advance read pointer to atom body
- atom_offset_ += atom_body;
- // adjust size of body of atom from size of header
- uint64 atom_data_size = atom_size - atom_body;
-
- bool atom_parse_success = true;
-
- // We use 95% certainty intervals for video metadata atom sizes. The map
- // is written to handle larger atom sizes but having to recache metadata
- // increases latencies on things like seeks.
- int map_table_atom_cache_entries = 0;
-
- // now take appropriate action based on atom type
- switch (four_cc) {
- // avc1 atoms are contained within stsd atoms and carry their own
- // configuration baggage load, which we skip over and parse the atoms
- // within, normally an avcC atom.
- case kAtomType_avc1:
- atom_offset_ += kSkipBytes_avc1;
- break;
-
- // avcC atoms contain the AVCConfigRecord, our video configuration info
- case kAtomType_avcC:
- atom_parse_success =
- DownloadAndParseAVCConfigRecord(atom_offset_, atom_data_size);
- if (atom_parse_success)
- atom_offset_ += atom_data_size;
- break;
-
- // esds atoms contain actually usable audio configuration info for AAC.
- case kAtomType_esds:
- return ParseMP4_esds(atom_data_size);
-
- // can tell us if mdia and mdhd atoms relate to audio or video metadata
- case kAtomType_hdlr:
- return ParseMP4_hdlr(atom_data_size, atom + atom_body);
-
- // provides a duration and a timescale unique to a given track
- case kAtomType_mdhd:
- return ParseMP4_mdhd(atom_data_size, atom + atom_body);
-
- // mp4a atoms contain audio configuration info, but we only want to know
- // which version it is so we can skip to the esds, which we must be present
- // when using AAC
- case kAtomType_mp4a:
- return ParseMP4_mp4a(atom_data_size, atom + atom_body);
-
- // movie header atom contains track duration and time unit scale, we trust
- // these data as the authoritative duration data for the mp4
- case kAtomType_mvhd:
- return ParseMP4_mvhd(atom_data_size, atom + atom_body);
-
- // stsd atoms may contain avc1 atoms, which themselves may contain avcC
- // atoms, which contain actually usable configuration information. skip to
- // subatom.
- case kAtomType_stsd:
- atom_offset_ += kSkipBytes_stsd;
- break;
-
- // We're very much interested in the contents of the trak container atom,
- // blow away state that we may have been keeping about any prior trak
- // atoms we've parsed.
- case kAtomType_trak:
- current_trak_is_video_ = false;
- current_trak_is_audio_ = false;
- break;
-
- // if one of the stbl subatoms add it to the appropriate audio or video map
- // and then advance past it.
- case kAtomType_co64:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_co64;
- break;
-
- case kAtomType_ctts:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_ctts;
- break;
-
- case kAtomType_stco:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_stco;
- break;
-
- case kAtomType_stts:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_stts;
- break;
-
- case kAtomType_stsc:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_stsc;
- break;
-
- case kAtomType_stss:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_stss;
- break;
-
- case kAtomType_stsz:
- map_table_atom_cache_entries = kMapTableAtomCacheEntries_stsz;
- break;
-
- // these are container atoms, so we dont want to advance past the header
- // as we are interested in their contents. Parsing them is trivial
- // as all they are is a size header and a fourCC type tag, which we've
- // already parsed and advanced past.
- case kAtomType_mdia:
- case kAtomType_minf:
- case kAtomType_moov:
- case kAtomType_stbl:
- // no-op
- break;
-
- // known atom types that we wish to just skip past the body without warning
- case kAtomType_dinf:
- case kAtomType_dref:
- case kAtomType_smhd:
- case kAtomType_tkhd:
- case kAtomType_vmhd:
- atom_offset_ += atom_data_size;
- break;
-
- // parse functions are assumed to advance read_position_ themselves,
- // as we are flattening a tree of atoms so that the atom_size we parsed
- // this time, if it's a container, may not have been entirely consumed
- // in this single call. However for unsupported atoms we just skip them
- // entirely, meaning we will skip their contents too.
- default:
- atom_offset_ += atom_data_size;
- DLOG(INFO) << base::StringPrintf(
- "skipping unsupported MP4 atom: %c%c%c%c", atom[4], atom[5], atom[6],
- atom[7]);
- break;
- }
-
- if (map_table_atom_cache_entries > 0) {
- if (current_trak_is_video_) {
- atom_parse_success =
- video_map_->SetAtom(four_cc, atom_offset_, atom_data_size,
- map_table_atom_cache_entries, atom + atom_body);
- } else if (current_trak_is_audio_) {
- atom_parse_success =
- audio_map_->SetAtom(four_cc, atom_offset_, atom_data_size,
- map_table_atom_cache_entries, atom + atom_body);
- }
- atom_offset_ += atom_data_size;
- }
-
- if (!atom_parse_success) {
- DLOG(ERROR) << base::StringPrintf("Unable to parse MP4 atom: %c%c%c%c",
- atom[4], atom[5], atom[6], atom[7]);
- }
-
- return atom_parse_success;
-}
-
-#if SHELL_MP4_PARSER_DUMP_ATOMS
-
-void ShellMP4Parser::DumpAtomToDisk(uint32 four_cc,
- uint32 atom_size,
- uint64 atom_offset) {
- // download entire atom into buffer
- scoped_refptr<ShellScopedArray> scoped_buffer =
- ShellBufferFactory::Instance()->AllocateArray(atom_size);
- uint8* buffer = scoped_buffer->Get();
- int bytes_read = reader_->BlockingRead(atom_offset, atom_size, buffer);
- DCHECK_EQ(bytes_read, atom_size);
- // calculate file and table names
- std::string av_prefix_file;
- if (current_trak_is_video_) {
- av_prefix_file = "/mp4_video_atom_";
- } else if (current_trak_is_audio_) {
- av_prefix_file = "/mp4_audio_atom_";
- } else {
- av_prefix_file = "/mp4_atom_";
- }
- std::string atom_name = base::StringPrintf(
- "%c%c%c%c", (char)(four_cc >> 24), (char)(four_cc >> 16),
- (char)(four_cc >> 8), (char)four_cc);
- // build path
- std::string path = base::StringPrintf(
- "%s%s%s_%lld.txt", global_game_content_path->c_str(),
- av_prefix_file.c_str(), atom_name.c_str(), atom_offset);
- // get file for writing
- FILE* atom_file = fopen(path.c_str(), "w");
- DCHECK(atom_file);
- // 13 bytes per line matches 80-column rule with indenting :)
- for (int i = 0; i < atom_size; i += 13) {
- std::string atom_chars;
- // do whole lines at a time
- if (atom_size - i > 13) {
- atom_chars = base::StringPrintf(
- " 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, "
- "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n",
- buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4],
- buffer[i + 5], buffer[i + 6], buffer[i + 7], buffer[i + 8],
- buffer[i + 9], buffer[i + 10], buffer[i + 11], buffer[i + 12]);
- } else {
- atom_chars = " "; // start string with indentation
- // do last line one char at a time
- for (int j = i; j < atom_size - 1; j++) {
- atom_chars += base::StringPrintf("0x%02x, ", buffer[j]);
- }
- // final char gets no comma and a newline
- atom_chars += base::StringPrintf("0x%02x\n", buffer[atom_size - 1]);
- }
- // save line
- fwrite(atom_chars.c_str(), 1, atom_chars.length(), atom_file);
- }
- // close file
- fclose(atom_file);
-}
-
-#endif
-
-bool ShellMP4Parser::ParseMP4_esds(uint64 atom_data_size) {
- if (atom_data_size < kFullBoxHeaderAndFlagSize) {
- DLOG(WARNING) << base::StringPrintf(
- "esds box should at least be %d bytes but now it is %" PRId64 " bytes",
- kFullBoxHeaderAndFlagSize, atom_data_size);
- return false;
- }
-
- uint64 esds_offset = atom_offset_ + kFullBoxHeaderAndFlagSize;
- uint64 esds_size = atom_data_size - kFullBoxHeaderAndFlagSize;
- // we'll need to download entire esds, allocate buffer for it
- scoped_refptr<ShellScopedArray> esds_storage =
- ShellBufferFactory::Instance()->AllocateArray(esds_size);
- uint8* esds = NULL;
- if (!esds_storage || !(esds = esds_storage->Get())) {
- DLOG(WARNING) << base::StringPrintf(
- "unable to allocate esds temp array of %" PRId64 " bytes", esds_size);
- return false;
- }
- // download esds
- int bytes_read = reader_->BlockingRead(esds_offset, esds_size, esds);
- if (bytes_read < esds_size) {
- DLOG(WARNING) << "failed to download esds";
- return false;
- }
- mp4::ESDescriptor es_descriptor;
- std::vector<uint8> data(esds, esds + esds_size);
- if (es_descriptor.Parse(data)) {
- const std::vector<uint8>& dsi = es_descriptor.decoder_specific_info();
- if (dsi.size() >= 2) {
- ParseAudioSpecificConfig(dsi[0], dsi[1]);
- atom_offset_ += atom_data_size;
- return true;
- }
- DLOG(WARNING) << "esds audio specific config shorter than 2 bytes";
- } else {
- DLOG(WARNING) << "error in parse esds box";
- }
-
- return false;
-}
-
-bool ShellMP4Parser::ParseMP4_hdlr(uint64 atom_data_size, uint8* hdlr) {
- // ensure we're downloading enough of the hdlr to parse
- DCHECK_LE(kDesiredBytes_hdlr + 16, kAtomDownload);
- // sanity-check for minimum size
- if (atom_data_size < kDesiredBytes_hdlr) {
- DLOG(WARNING) << base::StringPrintf("bad size %" PRId64 " on hdlr",
- atom_data_size);
- return false;
- }
- // last 4 bytes of the 12 we need are an ascii code for the trak type, we
- // want 'vide' for video or 'soun' for audio. ignore the rest.
- uint32 hdlr_subtype = endian_util::load_uint32_big_endian(hdlr + 8);
- // update state flags
- current_trak_is_video_ = (hdlr_subtype == kVideoSubtype_hdlr_vide);
- current_trak_is_audio_ = (hdlr_subtype == kAudioSubtype_hdlr_soun);
- // save a time scale if pending
- if (current_trak_time_scale_ > 0 && current_trak_is_video_) {
- video_time_scale_hz_ = current_trak_time_scale_;
- current_trak_time_scale_ = 0;
- video_track_duration_ = current_trak_duration_;
- one_video_tick_ =
- base::TimeDelta::FromMicroseconds(1000000 / video_time_scale_hz_);
- }
- if (current_trak_time_scale_ > 0 && current_trak_is_audio_) {
- audio_time_scale_hz_ = current_trak_time_scale_;
- current_trak_time_scale_ = 0;
- audio_track_duration_ = current_trak_duration_;
- }
- // skip rest of atom
- atom_offset_ += atom_data_size;
- return true;
-}
-
-bool ShellMP4Parser::ParseMP4_mdhd(uint64 atom_data_size, uint8* mdhd) {
- DCHECK_LE(kDesiredBytes_mdhd + 16, kAtomDownload);
- if (atom_data_size < kDesiredBytes_mdhd) {
- DLOG(WARNING) << base::StringPrintf("bad size %" PRId64 " on mdhd",
- atom_data_size);
- return false;
- }
- uint32 time_scale = endian_util::load_uint32_big_endian(mdhd + 12);
- if(time_scale == 0) {
- DLOG(WARNING) << "got 0 time scale for mvhd";
- return false;
- }
- // double-check track duration, it may be different from the movie duration
- uint32 track_duration_ticks = endian_util::load_uint32_big_endian(mdhd + 16);
- base::TimeDelta track_duration =
- TicksToTime(track_duration_ticks, time_scale);
- if (track_duration > duration_) {
- DLOG(WARNING) << base::StringPrintf("mdhd has longer duration: %" PRId64
- " ms than old value: %" PRId64 " ms.",
- track_duration.InMicroseconds(),
- duration_.InMicroseconds());
- duration_ = track_duration;
- }
- if (current_trak_is_video_) {
- video_time_scale_hz_ = time_scale;
- current_trak_time_scale_ = 0;
- video_track_duration_ = track_duration;
- one_video_tick_ =
- base::TimeDelta::FromMicroseconds(1000000 / video_time_scale_hz_);
- } else if (current_trak_is_audio_) {
- audio_time_scale_hz_ = time_scale;
- current_trak_time_scale_ = 0;
- audio_track_duration_ = track_duration;
- } else {
- // it's possible we will encounter the mdhd before we encounter the hdlr,
- // in that event we save the time scale value until we know.
- current_trak_time_scale_ = time_scale;
- current_trak_duration_ = track_duration;
- }
- atom_offset_ += atom_data_size;
- return true;
-}
-
-bool ShellMP4Parser::ParseMP4_mp4a(uint64 atom_data_size, uint8* mp4a) {
- DCHECK_LE(kDesiredBytes_mp4a + 16, kAtomDownload);
- // we only need the first two bytes of the header, which details the version
- // number of this atom, which tells us the size of the rest of the header,
- // telling us how much we should skip to get to the extension contents.
- if (atom_data_size < kDesiredBytes_mp4a) {
- DLOG(WARNING) << base::StringPrintf("bad size %" PRId64 " on mp4a",
- atom_data_size);
- return false;
- }
- uint16 mp4a_version = endian_util::load_uint16_big_endian(mp4a);
- switch (mp4a_version) {
- case 0:
- atom_offset_ += kTotalSize_mp4a_v0;
- return true;
-
- case 1:
- atom_offset_ += kTotalSize_mp4a_v1;
- return true;
-
- case 2:
- atom_offset_ += kTotalSize_mp4a_v2;
- return true;
-
- default:
- // unknown mp4a atom version, parse failure
- DLOG(ERROR) << base::StringPrintf("parsed bad mp4a version %d",
- mp4a_version);
- return false;
- }
-}
-
-// partial layout of mvhd header is:
-// offset | name | size in bytes
-// -------+-------------------+---------------
-// 0 | version | 1 (ignored)
-// 1 | flags | 3 (ignored)
-// 4 | creation time | 4 (ignored)
-// 8 | modification time | 4 (ignored)
-// 12 | time scale | 4
-// 16 | duration: | 4
-//
-bool ShellMP4Parser::ParseMP4_mvhd(uint64 atom_data_size, uint8* mvhd) {
- DCHECK_LE(kDesiredBytes_mvhd + 16, kAtomDownload);
- // it should be at least long enough for us to extract the parts we want
- if (atom_data_size < kDesiredBytes_mvhd) {
- DLOG(WARNING) << base::StringPrintf("bad size %" PRId64 " on mvhd",
- atom_data_size);
- return false;
- }
- uint32 time_scale_hz = endian_util::load_uint32_big_endian(mvhd + 12);
- if (time_scale_hz == 0) {
- DLOG(WARNING) << "got 0 time scale for mvhd";
- return false;
- }
- // duration is in units of the time scale we just extracted
- uint64 duration_ticks = endian_util::load_uint32_big_endian(mvhd + 16);
- // calculate actual duration from that and the time scale
- duration_ = TicksToTime(duration_ticks, time_scale_hz);
- // advance read position
- atom_offset_ += atom_data_size;
- return true;
-}
-
-base::TimeDelta ShellMP4Parser::TicksToTime(uint64 ticks,
- uint32 time_scale_hz) {
- DCHECK_NE(time_scale_hz, 0);
-
- if (time_scale_hz == 0) {
- return base::TimeDelta::FromSeconds(0);
- }
- return base::TimeDelta::FromMicroseconds((ticks * 1000000ULL) /
- time_scale_hz);
-}
-
-uint64 ShellMP4Parser::TimeToTicks(base::TimeDelta time, uint32 time_scale_hz) {
- DCHECK_NE(time_scale_hz, 0);
-
- if (time_scale_hz == 0) {
- return 0;
- }
- return (time.InMicroseconds() * time_scale_hz) / 1000000ULL;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_mp4_parser.h b/src/media/filters/shell_mp4_parser.h
deleted file mode 100644
index bf7ea68..0000000
--- a/src/media/filters/shell_mp4_parser.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_MP4_PARSER_H_
-#define MEDIA_FILTERS_SHELL_MP4_PARSER_H_
-
-#include "media/filters/shell_avc_parser.h"
-#include "media/filters/shell_mp4_map.h"
-
-// If true the parser will save every atom it parses to disk. Note that since
-// the parser is lazy and only parses what it needs to build the map and know
-// the required video config information it is likely to not build a complete
-// atom table for a given file.
-#define SHELL_MP4_PARSER_DUMP_ATOMS 0
-
-namespace media {
-
-// How many bytes to download from the start of the atom? Should be large
-// enough that we can extract all the data we need from the atom without
-// second download (typically), but no larger. This is currently set at 16
-// bytes for the 8 byte header + optional 8 byte size extension plus 20 bytes
-// for the needed values within an mvhd header. We leave this is the header so
-// that ShellMP4Map can re-use,
-static const int kAtomDownload = 36;
-
-// mp4 atom fourCC codes as big-endian unsigned ints
-static const uint32 kAtomType_avc1 = 0x61766331; // skip in to subatom
-static const uint32 kAtomType_avcC = 0x61766343; // download and parse
-static const uint32 kAtomType_co64 = 0x636f3634; // cache in table
-static const uint32 kAtomType_ctts = 0x63747473; // cache in table
-static const uint32 kAtomType_dinf = 0x64696e66; // skip whole atom
-static const uint32 kAtomType_dref = 0x64726566; // skip whole atom
-static const uint32 kAtomType_esds = 0x65736473; // download and parse
-static const uint32 kAtomType_ftyp = 0x66747970; // top of the file only
-static const uint32 kAtomType_hdlr = 0x68646c72; // parse first 12 bytes
-static const uint32 kAtomType_mdhd = 0x6d646864; // parse first 20 bytes
-static const uint32 kAtomType_mdia = 0x6d646961; // container atom, no-op
-static const uint32 kAtomType_minf = 0x6d696e66; // container atom, no-op
-static const uint32 kAtomType_moov = 0x6d6f6f76; // container atom, no-op
-static const uint32 kAtomType_mp4a = 0x6d703461; // parse first 10 bytes
-static const uint32 kAtomType_mvhd = 0x6d766864; // parse first 20 bytes
-static const uint32 kAtomType_smhd = 0x736d6862; // skip whole atom
-static const uint32 kAtomType_stbl = 0x7374626c; // container atom, no-op
-static const uint32 kAtomType_stco = 0x7374636f; // cache in table
-static const uint32 kAtomType_stts = 0x73747473; // cache in table
-static const uint32 kAtomType_stsc = 0x73747363; // cache in table
-static const uint32 kAtomType_stsd = 0x73747364; // skip in to subatom
-static const uint32 kAtomType_stss = 0x73747373; // cache in table
-static const uint32 kAtomType_stsz = 0x7374737a; // cache in table
-static const uint32 kAtomType_trak = 0x7472616b; // container atom, no-op
-static const uint32 kAtomType_tkhd = 0x746b6864; // skip whole atom
-static const uint32 kAtomType_vmhd = 0x766d6864; // skip whole atom
-// TODO: mp4v!!
-
-class ShellMP4Parser : public ShellAVCParser {
- public:
- // Attempts to make sense of the provided bytes of the top of a file as an
- // flv, and if it does make sense returns PIPELINE_OK and |*parser| contains a
- // ShellMP4Parser initialized with some basic state. If it doesn't make sense
- // this returns an error status and |*parser| contains NULL.
- static PipelineStatus Construct(scoped_refptr<ShellDataSourceReader> reader,
- const uint8* construction_header,
- scoped_refptr<ShellParser>* parser);
- ShellMP4Parser(scoped_refptr<ShellDataSourceReader> reader,
- uint32 ftyp_atom_size);
- virtual ~ShellMP4Parser();
-
- // === ShellParser implementation
- virtual bool ParseConfig() OVERRIDE;
- virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) OVERRIDE;
- virtual bool SeekTo(base::TimeDelta timestamp) OVERRIDE;
-
- private:
- bool ParseNextAtom();
- bool ParseMP4_esds(uint64 atom_data_size);
- bool ParseMP4_hdlr(uint64 atom_data_size, uint8* hdlr);
- bool ParseMP4_mdhd(uint64 atom_data_size, uint8* mdhd);
- bool ParseMP4_mp4a(uint64 atom_data_size, uint8* mp4a);
- bool ParseMP4_mvhd(uint64 atom_data_size, uint8* mvhd);
- base::TimeDelta TicksToTime(uint64 ticks, uint32 time_scale_hz);
- uint64 TimeToTicks(base::TimeDelta time, uint32 time_scale_hz);
-
-#if SHELL_MP4_PARSER_DUMP_ATOMS
- void DumpAtomToDisk(uint32 four_cc, uint32 atom_size, uint64 atom_offset);
-#endif
-
- uint64 atom_offset_;
- bool current_trak_is_video_;
- bool current_trak_is_audio_;
- uint32 current_trak_time_scale_;
- base::TimeDelta current_trak_duration_;
- uint32 video_time_scale_hz_;
- base::TimeDelta one_video_tick_;
- uint32 audio_time_scale_hz_;
- base::TimeDelta audio_track_duration_;
- base::TimeDelta video_track_duration_;
- scoped_refptr<ShellMP4Map> audio_map_;
- scoped_refptr<ShellMP4Map> video_map_;
- uint32 audio_sample_;
- uint32 video_sample_;
- // for keeping buffers continuous across time scales
- uint64 first_audio_hole_ticks_;
- base::TimeDelta first_audio_hole_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_MP4_PARSER_H_
diff --git a/src/media/filters/shell_parser.cc b/src/media/filters/shell_parser.cc
deleted file mode 100644
index f1d4958..0000000
--- a/src/media/filters/shell_parser.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_parser.h"
-
-#include "base/logging.h"
-#include "media/filters/shell_flv_parser.h"
-#include "media/filters/shell_mp4_parser.h"
-
-namespace media {
-
-// ==== ShellParser ============================================================
-
-// how many bytes to download of the file to determine type?
-const int ShellParser::kInitialHeaderSize = 9;
-
-// static
-PipelineStatus ShellParser::Construct(
- scoped_refptr<ShellDataSourceReader> reader,
- scoped_refptr<ShellParser>* parser) {
- DCHECK(parser);
- *parser = NULL;
-
- // download first 16 bytes of stream to determine file type and extract basic
- // container-specific stream configuration information
- uint8 header[kInitialHeaderSize];
- int bytes_read = reader->BlockingRead(0, kInitialHeaderSize, header);
- if (bytes_read != kInitialHeaderSize) {
- return DEMUXER_ERROR_COULD_NOT_PARSE;
- }
-
- // attempt to construct mp4 parser from this header
- PipelineStatus status = ShellMP4Parser::Construct(reader, header, parser);
- if (status == PIPELINE_OK) {
- return status;
- }
- // ok, attempt FLV
- return ShellFLVParser::Construct(reader, header, parser);
-}
-
-ShellParser::ShellParser(scoped_refptr<ShellDataSourceReader> reader)
- : reader_(reader), duration_(kInfiniteDuration()), bits_per_second_(0) {}
-
-ShellParser::~ShellParser() {}
-
-bool ShellParser::IsConfigComplete() {
- return (video_config_.IsValidConfig()) && (audio_config_.IsValidConfig()) &&
- (duration_ != kInfiniteDuration());
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_parser.h b/src/media/filters/shell_parser.h
deleted file mode 100644
index 6d349b0..0000000
--- a/src/media/filters/shell_parser.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_PARSER_H_
-#define MEDIA_FILTERS_SHELL_PARSER_H_
-
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_data_source_reader.h"
-#include "media/base/video_decoder_config.h"
-#include "media/filters/shell_au.h"
-
-namespace media {
-
-// abstract base class to define a stream parser interface used by ShellDemuxer.
-class ShellParser : public base::RefCountedThreadSafe<ShellParser> {
- public:
- static const int kInitialHeaderSize;
- // Determine stream type, construct appropriate parser object, and returns
- // PIPELINE_OK on success or error code.
- static PipelineStatus Construct(scoped_refptr<ShellDataSourceReader> reader,
- scoped_refptr<ShellParser>* parser);
- explicit ShellParser(scoped_refptr<ShellDataSourceReader> reader);
-
- // Seek through the file looking for audio and video configuration info,
- // saving as much config state as is possible. Should try to be fast but this
- // may result in the downloading of MB of data. Returns false on fatal error.
- virtual bool ParseConfig() = 0;
-
- // Returns a populated, valid AU indicating the needed information for
- // downloding and decoding the next access unit in the stream, or NULL on
- // fatal error. On success this advances the respective audio or video cursor
- // to the next AU.
- virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) = 0;
- // Write the appropriate prepend header for the supplied au into the supplied
- // buffer. Return false on error.
- virtual bool Prepend(scoped_refptr<ShellAU> au,
- scoped_refptr<DecoderBuffer> buffer) = 0;
- // Advance internal state to provided timestamp. Return false on error.
- virtual bool SeekTo(base::TimeDelta timestamp) = 0;
-
- // ======= config state methods, values should be set by ParseConfig()
- // Returns true if all of the required variables defined below are valid.
- // BitsPerSecond() is optional.
- virtual bool IsConfigComplete();
- // time-duration of file, may return kInfiniteDuration() if unknown
- virtual base::TimeDelta Duration() { return duration_; }
- // bits per second of media, if known, otherwise 0
- virtual uint32 BitsPerSecond() { return bits_per_second_; }
- virtual const AudioDecoderConfig& AudioConfig() { return audio_config_; }
- virtual const VideoDecoderConfig& VideoConfig() { return video_config_; }
-
- protected:
- // only allow RefCountedThreadSafe to delete us
- friend class base::RefCountedThreadSafe<ShellParser>;
- virtual ~ShellParser();
- scoped_refptr<ShellDataSourceReader> reader_;
- AudioDecoderConfig audio_config_;
- VideoDecoderConfig video_config_;
- base::TimeDelta duration_;
- uint32 bits_per_second_;
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_PARSER_H_
diff --git a/src/media/filters/shell_raw_audio_decoder_linux.cc b/src/media/filters/shell_raw_audio_decoder_linux.cc
deleted file mode 100644
index 40785c5..0000000
--- a/src/media/filters/shell_raw_audio_decoder_linux.cc
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_raw_audio_decoder_linux.h"
-
-#include <memory.h>
-
-#include <list>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/decoder_buffer_pool.h"
-#include "media/filters/shell_ffmpeg.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-namespace {
-
-const size_t kSampleSizeInBytes = sizeof(float);
-
-struct QueuedAudioBuffer {
- // AudioDecoder::Status status; // status is used to represent decode errors
- scoped_refptr<DecoderBuffer> buffer;
-};
-
-bool IsEndOfStream(int result,
- int decoded_size,
- scoped_refptr<DecoderBuffer> buffer) {
- return result == 0 && decoded_size == 0 && buffer->IsEndOfStream();
-}
-
-void ResampleToInterleavedFloat(int source_sample_format,
- int channel_layout,
- int sample_rate,
- int samples_per_channel,
- uint8** input_buffer,
- uint8* output_buffer) {
- AVAudioResampleContext* context = avresample_alloc_context();
- DCHECK(context);
-
- av_opt_set_int(context, "in_channel_layout", channel_layout, 0);
- av_opt_set_int(context, "out_channel_layout", channel_layout, 0);
- av_opt_set_int(context, "in_sample_rate", sample_rate, 0);
- av_opt_set_int(context, "out_sample_rate", sample_rate, 0);
- av_opt_set_int(context, "in_sample_fmt", source_sample_format, 0);
- av_opt_set_int(context, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
- av_opt_set_int(context, "internal_sample_fmt", source_sample_format, 0);
-
- int result = avresample_open(context);
- DCHECK(!result);
-
- int samples_resampled =
- avresample_convert(context, &output_buffer, 1024, samples_per_channel,
- input_buffer, 0, samples_per_channel);
- DCHECK_EQ(samples_resampled, samples_per_channel);
-
- avresample_close(context);
- av_free(context);
-}
-
-class ShellRawAudioDecoderLinux : public ShellRawAudioDecoder {
- public:
- ShellRawAudioDecoderLinux();
- ~ShellRawAudioDecoderLinux() OVERRIDE;
-
- int GetBytesPerSample() const OVERRIDE { return kSampleSizeInBytes; }
- // When the input buffer is not NULL, it can be a normal buffer or an EOS
- // buffer. In this case the function will return the decoded buffer if there
- // is any.
- // The input buffer can be NULL, in this case the function will return a
- // queued buffer if there is any or return NULL if there is no queued buffer.
- void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decoder_cb) OVERRIDE;
- bool Flush() OVERRIDE;
- bool UpdateConfig(const AudioDecoderConfig& config) OVERRIDE;
-
- private:
- void ReleaseResource();
- void ResetTimestampState();
- void RunDecodeLoop(const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append);
-
- DecoderBufferPool decoder_buffer_pool_;
-
- AVCodecContext* codec_context_;
- AVFrame* av_frame_;
-
- // Decoded audio format.
- int bits_per_channel_;
- ChannelLayout channel_layout_;
- int samples_per_second_;
-
- // Used for computing output timestamps.
- scoped_ptr<AudioTimestampHelper> output_timestamp_helper_;
- int bytes_per_frame_;
- base::TimeDelta last_input_timestamp_;
-
- // Since multiple frames may be decoded from the same packet we need to
- // queue them up and hand them out as we receive Read() calls.
- std::list<QueuedAudioBuffer> queued_audio_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellRawAudioDecoderLinux);
-};
-
-ShellRawAudioDecoderLinux::ShellRawAudioDecoderLinux()
- : decoder_buffer_pool_(GetBytesPerSample()),
- codec_context_(NULL),
- av_frame_(NULL),
- bits_per_channel_(0),
- channel_layout_(CHANNEL_LAYOUT_NONE),
- samples_per_second_(0),
- bytes_per_frame_(0),
- last_input_timestamp_(kNoTimestamp()) {
- EnsureFfmpegInitialized();
-}
-
-ShellRawAudioDecoderLinux::~ShellRawAudioDecoderLinux() {
- ReleaseResource();
-}
-
-void ShellRawAudioDecoderLinux::Decode(
- const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decoder_cb) {
- if (buffer && !buffer->IsEndOfStream()) {
- if (last_input_timestamp_ == kNoTimestamp()) {
- last_input_timestamp_ = buffer->GetTimestamp();
- } else if (buffer->GetTimestamp() != kNoTimestamp()) {
- DCHECK_GE(buffer->GetTimestamp().ToInternalValue(),
- last_input_timestamp_.ToInternalValue());
- last_input_timestamp_ = buffer->GetTimestamp();
- }
- }
-
- if (buffer && queued_audio_.empty())
- RunDecodeLoop(buffer, false);
-
- if (queued_audio_.empty()) {
- decoder_cb.Run(NEED_MORE_DATA, NULL);
- return;
- }
- scoped_refptr<DecoderBuffer> result = queued_audio_.front().buffer;
- queued_audio_.pop_front();
- decoder_cb.Run(BUFFER_DECODED, result);
-}
-
-bool ShellRawAudioDecoderLinux::Flush() {
- avcodec_flush_buffers(codec_context_);
- ResetTimestampState();
- queued_audio_.clear();
-
- return true;
-}
-
-bool ShellRawAudioDecoderLinux::UpdateConfig(const AudioDecoderConfig& config) {
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid audio stream -"
- << " codec: " << config.codec()
- << " channel layout: " << config.channel_layout()
- << " bits per channel: " << config.bits_per_channel()
- << " samples per second: " << config.samples_per_second();
- return false;
- }
-
- if (codec_context_ && (bits_per_channel_ != config.bits_per_channel() ||
- channel_layout_ != config.channel_layout() ||
- samples_per_second_ != config.samples_per_second())) {
- DVLOG(1) << "Unsupported config change :";
- DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_ << " -> "
- << config.bits_per_channel();
- DVLOG(1) << "\tchannel_layout : " << channel_layout_ << " -> "
- << config.channel_layout();
- DVLOG(1) << "\tsample_rate : " << samples_per_second_ << " -> "
- << config.samples_per_second();
- return false;
- }
-
- ReleaseResource();
-
- codec_context_ = avcodec_alloc_context3(NULL);
- DCHECK(codec_context_);
- codec_context_->codec_type = AVMEDIA_TYPE_AUDIO;
- codec_context_->codec_id = CODEC_ID_AAC;
- // request_sample_fmt is set by us, but sample_fmt is set by the decoder.
- codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT; // interleaved float
-
- codec_context_->channels =
- ChannelLayoutToChannelCount(config.channel_layout());
- codec_context_->sample_rate = config.samples_per_second();
-
- if (config.extra_data()) {
- codec_context_->extradata_size = config.extra_data_size();
- codec_context_->extradata = reinterpret_cast<uint8_t*>(
- av_malloc(config.extra_data_size() + FF_INPUT_BUFFER_PADDING_SIZE));
- memcpy(codec_context_->extradata, config.extra_data(),
- config.extra_data_size());
- memset(codec_context_->extradata + config.extra_data_size(), '\0',
- FF_INPUT_BUFFER_PADDING_SIZE);
- } else {
- codec_context_->extradata = NULL;
- codec_context_->extradata_size = 0;
- }
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- DCHECK(codec);
-
- int rv = avcodec_open2(codec_context_, codec, NULL);
- DCHECK_GE(rv, 0);
- if (rv < 0) {
- DLOG(ERROR) << "Unable to open codec, result = " << rv;
- return false;
- }
-
- // Ensure avcodec_open2() respected our format request.
- if (codec_context_->sample_fmt != codec_context_->request_sample_fmt) {
- DLOG(INFO) << "Unable to configure a supported sample format,"
- << " sample_fmt = " << codec_context_->sample_fmt
- << " instead of " << codec_context_->request_sample_fmt
- << ". Use libavresample to resample the decoded result to FLT";
- DLOG(INFO) << "Supported formats:";
- const AVSampleFormat* fmt;
- for (fmt = codec_context_->codec->sample_fmts; *fmt != -1; ++fmt) {
- DLOG(INFO) << " " << *fmt << " (" << av_get_sample_fmt_name(*fmt) << ")";
- }
- }
-
- av_frame_ = avcodec_alloc_frame();
- DCHECK(av_frame_);
-
- bits_per_channel_ = config.bits_per_channel();
- channel_layout_ = config.channel_layout();
- samples_per_second_ = config.samples_per_second();
- output_timestamp_helper_.reset(new AudioTimestampHelper(
- kSampleSizeInBytes, config.samples_per_second()));
-
- ResetTimestampState();
-
- return true;
-}
-
-void ShellRawAudioDecoderLinux::ReleaseResource() {
- if (codec_context_) {
- av_free(codec_context_->extradata);
- avcodec_close(codec_context_);
- av_free(codec_context_);
- codec_context_ = NULL;
- }
- if (av_frame_) {
- av_free(av_frame_);
- av_frame_ = NULL;
- }
-}
-
-void ShellRawAudioDecoderLinux::ResetTimestampState() {
- output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
- last_input_timestamp_ = kNoTimestamp();
-}
-
-void ShellRawAudioDecoderLinux::RunDecodeLoop(
- const scoped_refptr<DecoderBuffer>& input,
- bool skip_eos_append) {
- AVPacket packet;
- av_init_packet(&packet);
- packet.data = input->GetWritableData();
- packet.size = input->GetDataSize();
-
- do {
- avcodec_get_frame_defaults(av_frame_);
- int frame_decoded = 0;
- int result = avcodec_decode_audio4(codec_context_, av_frame_,
- &frame_decoded, &packet);
- DCHECK_GE(result, 0);
-
- // Update packet size and data pointer in case we need to call the decoder
- // with the remaining bytes from this packet.
- packet.size -= result;
- packet.data += result;
-
- if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
- !input->IsEndOfStream()) {
- DCHECK_NE(input->GetTimestamp().ToInternalValue(),
- kNoTimestamp().ToInternalValue());
- output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp());
- }
-
- const uint8* decoded_audio_data = NULL;
- int decoded_audio_size = 0;
- if (frame_decoded) {
- decoded_audio_data = av_frame_->data[0];
- decoded_audio_size = av_samples_get_buffer_size(
- NULL, codec_context_->channels, av_frame_->nb_samples,
- codec_context_->sample_fmt, 1);
- }
-
- scoped_refptr<DecoderBuffer> output;
-
- if (decoded_audio_size > 0) {
- // Copy the audio samples into an output buffer.
- int buffer_size = kSampleSizeInBytes * mp4::AAC::kFramesPerAccessUnit *
- codec_context_->channels;
- output = decoder_buffer_pool_.Allocate(buffer_size);
- DCHECK(output);
- // Interleave the planar samples to conform to the general decoder
- // requirement. This should eventually be lifted.
- ResampleToInterleavedFloat(
- codec_context_->sample_fmt, codec_context_->channel_layout,
- samples_per_second_, mp4::AAC::kFramesPerAccessUnit,
- av_frame_->extended_data,
- reinterpret_cast<uint8*>(output->GetWritableData()));
- output->SetTimestamp(output_timestamp_helper_->GetTimestamp());
- output->SetDuration(
- output_timestamp_helper_->GetDuration(decoded_audio_size));
- output_timestamp_helper_->AddBytes(decoded_audio_size /
- codec_context_->channels);
- } else if (IsEndOfStream(result, decoded_audio_size, input) &&
- !skip_eos_append) {
- DCHECK_EQ(packet.size, 0);
- // Create an end of stream output buffer.
- output = DecoderBuffer::CreateEOSBuffer(kNoTimestamp());
- }
-
- if (output) {
- QueuedAudioBuffer queue_entry = {output};
- queued_audio_.push_back(queue_entry);
- }
-
- // TODO: update statistics.
- } while (packet.size > 0);
-}
-
-} // namespace
-
-scoped_ptr<ShellRawAudioDecoder> CreateShellRawAudioDecoderLinux(
- const AudioDecoderConfig& config) {
- scoped_ptr<ShellRawAudioDecoder> decoder(new ShellRawAudioDecoderLinux);
- if (!decoder->UpdateConfig(config)) {
- decoder.reset();
- }
- return decoder.Pass();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_raw_audio_decoder_linux.h b/src/media/filters/shell_raw_audio_decoder_linux.h
deleted file mode 100644
index 9c314ad..0000000
--- a/src/media/filters/shell_raw_audio_decoder_linux.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_LINUX_H_
-#define MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_LINUX_H_
-
-#include "media/filters/shell_audio_decoder_impl.h"
-
-namespace media {
-
-scoped_ptr<ShellRawAudioDecoder> CreateShellRawAudioDecoderLinux(
- const AudioDecoderConfig& config);
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_LINUX_H_
diff --git a/src/media/filters/shell_raw_audio_decoder_stub.cc b/src/media/filters/shell_raw_audio_decoder_stub.cc
deleted file mode 100644
index 6231cc2..0000000
--- a/src/media/filters/shell_raw_audio_decoder_stub.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_raw_audio_decoder_stub.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer_pool.h"
-#include "media/mp4/aac.h"
-
-namespace media {
-
-namespace {
-
-const size_t kSampleSizeInBytes = sizeof(float);
-
-class ShellRawAudioDecoderStub : public ShellRawAudioDecoder {
- public:
- ShellRawAudioDecoderStub();
-
- int GetBytesPerSample() const OVERRIDE { return kSampleSizeInBytes; }
- void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decoder_cb) OVERRIDE;
- bool Flush() OVERRIDE;
- bool UpdateConfig(const AudioDecoderConfig& config) OVERRIDE;
-
- private:
- DecoderBufferPool decoder_buffer_pool_;
- size_t decoded_buffer_size_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellRawAudioDecoderStub);
-};
-
-ShellRawAudioDecoderStub::ShellRawAudioDecoderStub()
- : decoder_buffer_pool_(GetBytesPerSample()), decoded_buffer_size_(0) {}
-
-void ShellRawAudioDecoderStub::Decode(
- const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decoder_cb) {
- DCHECK_NE(decoded_buffer_size_, 0);
- DCHECK(buffer);
- if (buffer->IsEndOfStream()) {
- decoder_cb.Run(BUFFER_DECODED, buffer);
- return;
- }
- scoped_refptr<DecoderBuffer> decoded_buffer =
- decoder_buffer_pool_.Allocate(decoded_buffer_size_);
- decoded_buffer->SetTimestamp(buffer->GetTimestamp());
- decoder_cb.Run(BUFFER_DECODED, decoded_buffer);
-}
-
-bool ShellRawAudioDecoderStub::Flush() {
- return true;
-}
-
-bool ShellRawAudioDecoderStub::UpdateConfig(const AudioDecoderConfig& config) {
- DCHECK(config.IsValidConfig() && config.codec() == kCodecAAC);
- ChannelLayout channel_layout = config.channel_layout();
- int channel_count = ChannelLayoutToChannelCount(channel_layout);
- if (channel_count != 1 && channel_count != 2 && channel_count != 6 &&
- channel_count != 8) {
- return false;
- }
-
- decoded_buffer_size_ =
- kSampleSizeInBytes * channel_count * mp4::AAC::kFramesPerAccessUnit;
- return true;
-}
-
-} // namespace
-
-scoped_ptr<ShellRawAudioDecoder> CreateShellRawAudioDecoderStub(
- const AudioDecoderConfig& config) {
- scoped_ptr<ShellRawAudioDecoder> decoder(new ShellRawAudioDecoderStub);
- if (!decoder->UpdateConfig(config)) {
- decoder.reset();
- }
- return decoder.Pass();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_raw_audio_decoder_stub.h b/src/media/filters/shell_raw_audio_decoder_stub.h
deleted file mode 100644
index f779e02..0000000
--- a/src/media/filters/shell_raw_audio_decoder_stub.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_STUB_H_
-#define MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_STUB_H_
-
-#include "media/filters/shell_audio_decoder_impl.h"
-
-namespace media {
-
-scoped_ptr<ShellRawAudioDecoder> CreateShellRawAudioDecoderStub(
- const AudioDecoderConfig& config);
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_RAW_AUDIO_DECODER_STUB_H_
diff --git a/src/media/filters/shell_raw_video_decoder_linux.cc b/src/media/filters/shell_raw_video_decoder_linux.cc
deleted file mode 100644
index 703ac46..0000000
--- a/src/media/filters/shell_raw_video_decoder_linux.cc
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_raw_video_decoder_linux.h"
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/video_util.h"
-#include "media/filters/shell_ffmpeg.h"
-
-namespace media {
-
-using base::TimeDelta;
-typedef ShellVideoDataAllocator::FrameBuffer FrameBuffer;
-typedef ShellVideoDataAllocator::YV12Param YV12Param;
-
-namespace {
-
-// FFmpeg requires its decoding buffers to align with platform alignment. It
-// mentions inside
-// http://ffmpeg.org/doxygen/trunk/structAVFrame.html#aa52bfc6605f6a3059a0c3226cc0f6567
-// that the alignment on most modern desktop systems are 16 or 32. We use 64 to
-// be safe.
-const int kPlatformAlignment = 32;
-
-size_t AlignUp(size_t size, int alignment) {
- DCHECK_EQ(alignment & (alignment - 1), 0);
- return (size + alignment - 1) & ~(alignment - 1);
-}
-
-size_t GetYV12SizeInBytes(int32 width, int32 height) {
- return width * height * 3 / 2;
-}
-
-VideoFrame::Format PixelFormatToVideoFormat(PixelFormat pixel_format) {
- switch (pixel_format) {
- case PIX_FMT_YUV420P:
- return VideoFrame::YV12;
- case PIX_FMT_YUVJ420P:
- return VideoFrame::YV12;
- default:
- DLOG(ERROR) << "Unsupported PixelFormat: " << pixel_format;
- }
- return VideoFrame::INVALID;
-}
-
-int VideoCodecProfileToProfileID(VideoCodecProfile profile) {
- switch (profile) {
- case H264PROFILE_BASELINE:
- return FF_PROFILE_H264_BASELINE;
- case H264PROFILE_MAIN:
- return FF_PROFILE_H264_MAIN;
- case H264PROFILE_EXTENDED:
- return FF_PROFILE_H264_EXTENDED;
- case H264PROFILE_HIGH:
- return FF_PROFILE_H264_HIGH;
- case H264PROFILE_HIGH10PROFILE:
- return FF_PROFILE_H264_HIGH_10;
- case H264PROFILE_HIGH422PROFILE:
- return FF_PROFILE_H264_HIGH_422;
- case H264PROFILE_HIGH444PREDICTIVEPROFILE:
- return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
- default:
- DLOG(ERROR) << "Unknown VideoCodecProfile: " << profile;
- }
- return FF_PROFILE_UNKNOWN;
-}
-
-PixelFormat VideoFormatToPixelFormat(VideoFrame::Format video_format) {
- switch (video_format) {
- case VideoFrame::YV12:
- return PIX_FMT_YUV420P;
- default:
- DLOG(ERROR) << "Unsupported VideoFrame::Format: " << video_format;
- }
- return PIX_FMT_NONE;
-}
-
-void CopyColorPlane(const uint8* src,
- int32 width_in_bytes,
- int32 pitch,
- int32 height,
- uint8* dest) {
- while (height > 0) {
- memcpy(dest, src, width_in_bytes);
- src += pitch;
- dest += width_in_bytes;
- --height;
- }
-}
-
-// Convert a frame buffer whose pitch might be different than its width to a
-// frame buffer whose pitch is the same as its width. This involves line by
-// line copy of all three color planes.
-scoped_refptr<FrameBuffer> ConvertFrameBuffer(
- const scoped_refptr<FrameBuffer>& src,
- int32 width,
- int32 height) {
- DCHECK(width % 2 == 0) << "Invalid width " << width;
- DCHECK(height % 2 == 0) << "Invalid height " << height;
-
- ShellVideoDataAllocator* allocator =
- ShellMediaPlatform::Instance()->GetVideoDataAllocator();
- DCHECK(allocator);
- size_t yv12_frame_size = GetYV12SizeInBytes(width, height);
- scoped_refptr<FrameBuffer> dest =
- allocator->AllocateFrameBuffer(yv12_frame_size, kPlatformAlignment);
-
- // Get the src pitch and height as what we did in GetVideoBuffer().
- int32 src_pitch = AlignUp(width, kPlatformAlignment * 2);
- int32 src_height = AlignUp(height, kPlatformAlignment * 2);
- DCHECK_LE(GetYV12SizeInBytes(src_pitch, src_height), src->size());
- DCHECK_LE(GetYV12SizeInBytes(width, height), dest->size());
-
- uint8* src_data = src->data();
- uint8* dest_data = dest->data();
- CopyColorPlane(src_data, width, src_pitch, height, dest_data);
- src_data += src_pitch * src_height;
- dest_data += width * height;
- CopyColorPlane(src_data, width / 2, src_pitch / 2, height / 2, dest_data);
- src_data += src_pitch / 2 * src_height / 2;
- dest_data += width / 2 * height / 2;
- CopyColorPlane(src_data, width / 2, src_pitch / 2, height / 2, dest_data);
- return dest;
-}
-
-// TODO: Make this decoder handle decoder errors. Now it assumes
-// that the input stream is always correct.
-class ShellRawVideoDecoderLinux : public ShellRawVideoDecoder {
- public:
- explicit ShellRawVideoDecoderLinux(ShellVideoDataAllocator* allocator);
- ~ShellRawVideoDecoderLinux();
-
- virtual void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) OVERRIDE;
- virtual bool Flush() OVERRIDE;
- virtual bool UpdateConfig(const VideoDecoderConfig& config) OVERRIDE;
-
- private:
- void ReleaseResource();
- static int GetVideoBuffer(AVCodecContext* codec_context, AVFrame* frame);
- static void ReleaseVideoBuffer(AVCodecContext*, AVFrame* frame);
-
- ShellVideoDataAllocator* allocator_;
- AVCodecContext* codec_context_;
- AVFrame* av_frame_;
- gfx::Size natural_size_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellRawVideoDecoderLinux);
-};
-
-ShellRawVideoDecoderLinux::ShellRawVideoDecoderLinux(
- ShellVideoDataAllocator* allocator)
- : allocator_(allocator), codec_context_(NULL), av_frame_(NULL) {
- EnsureFfmpegInitialized();
-}
-
-ShellRawVideoDecoderLinux::~ShellRawVideoDecoderLinux() {
- ReleaseResource();
-}
-
-void ShellRawVideoDecoderLinux::Decode(
- const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) {
- DCHECK(buffer);
- DCHECK(!decode_cb.is_null());
-
- AVPacket packet;
- av_init_packet(&packet);
- avcodec_get_frame_defaults(av_frame_);
- packet.data = buffer->GetWritableData();
- packet.size = buffer->GetDataSize();
- packet.pts = buffer->GetTimestamp().InMilliseconds();
-
- int frame_decoded = 0;
- int result =
- avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, &packet);
- DCHECK_GE(result, 0);
- if (frame_decoded == 0) {
- decode_cb.Run(NEED_MORE_DATA, NULL);
- return;
- }
-
- // TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675
- // The decoder is in a bad state and not decoding correctly.
- // Checking for NULL avoids a crash in CopyPlane().
- if (!av_frame_->data[VideoFrame::kYPlane] ||
- !av_frame_->data[VideoFrame::kUPlane] ||
- !av_frame_->data[VideoFrame::kVPlane]) {
- DLOG(ERROR) << "Video frame was produced yet has invalid frame data.";
- decode_cb.Run(FATAL_ERROR, NULL);
- return;
- }
-
- if (!av_frame_->opaque) {
- DLOG(ERROR) << "VideoFrame object associated with frame data not set.";
- decode_cb.Run(FATAL_ERROR, NULL);
- return;
- }
-
- scoped_refptr<FrameBuffer> frame_buffer =
- static_cast<FrameBuffer*>(av_frame_->opaque);
- DCHECK(frame_buffer);
-
- TimeDelta timestamp = TimeDelta::FromMilliseconds(av_frame_->pkt_pts);
- // TODO: Currently the Linux egl backend doesn't support texture
- // with pitch different than its width. So we create a new video frame whose
- // visible size is the same as its coded size to ensure that the underlying
- // texture has the same pitch as its width. This makes code complex, though
- // it doesn't necessary mean that the new code is slower as we have to make a
- // copy anyway to avoid using the frame buffer while Ffmpeg is still using it.
- // It is worth revisiting if we are going to release Linux as a production
- // platform.
- YV12Param param(av_frame_->width, av_frame_->height,
- gfx::Rect(av_frame_->width, av_frame_->height),
- frame_buffer->data());
- // We have to make a copy of the frame buffer as the frame buffer retrieved
- // from |av_frame_->opaque| may still be used by Ffmpeg.
- size_t yv12_frame_size =
- GetYV12SizeInBytes(av_frame_->width, av_frame_->height);
- scoped_refptr<FrameBuffer> converted_frame_buffer =
- ConvertFrameBuffer(frame_buffer, av_frame_->width, av_frame_->height);
- scoped_refptr<VideoFrame> frame =
- allocator_->CreateYV12Frame(converted_frame_buffer, param, timestamp);
-
- decode_cb.Run(FRAME_DECODED, frame);
-}
-
-bool ShellRawVideoDecoderLinux::Flush() {
- avcodec_flush_buffers(codec_context_);
-
- return true;
-}
-
-bool ShellRawVideoDecoderLinux::UpdateConfig(const VideoDecoderConfig& config) {
- ReleaseResource();
-
- natural_size_ = config.natural_size();
-
- codec_context_ = avcodec_alloc_context3(NULL);
- DCHECK(codec_context_);
- codec_context_->codec_type = AVMEDIA_TYPE_VIDEO;
- codec_context_->codec_id = CODEC_ID_H264;
- codec_context_->profile = VideoCodecProfileToProfileID(config.profile());
- codec_context_->coded_width = config.coded_size().width();
- codec_context_->coded_height = config.coded_size().height();
- codec_context_->pix_fmt = VideoFormatToPixelFormat(config.format());
-
- codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
- codec_context_->thread_count = 2;
- codec_context_->opaque = this;
- codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
- codec_context_->get_buffer = GetVideoBuffer;
- codec_context_->release_buffer = ReleaseVideoBuffer;
-
- if (config.extra_data()) {
- codec_context_->extradata_size = config.extra_data_size();
- codec_context_->extradata = reinterpret_cast<uint8_t*>(
- av_malloc(config.extra_data_size() + FF_INPUT_BUFFER_PADDING_SIZE));
- memcpy(codec_context_->extradata, config.extra_data(),
- config.extra_data_size());
- memset(codec_context_->extradata + config.extra_data_size(), '\0',
- FF_INPUT_BUFFER_PADDING_SIZE);
- } else {
- codec_context_->extradata = NULL;
- codec_context_->extradata_size = 0;
- }
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- DCHECK(codec);
- int rv = avcodec_open2(codec_context_, codec, NULL);
- DCHECK_GE(rv, 0);
- if (rv < 0) {
- DLOG(ERROR) << "Unable to open codec, result = " << rv;
- return false;
- }
-
- av_frame_ = avcodec_alloc_frame();
- DCHECK(av_frame_);
-
- return true;
-}
-
-void ShellRawVideoDecoderLinux::ReleaseResource() {
- if (codec_context_) {
- av_free(codec_context_->extradata);
- avcodec_close(codec_context_);
- av_free(codec_context_);
- codec_context_ = NULL;
- }
- if (av_frame_) {
- av_free(av_frame_);
- av_frame_ = NULL;
- }
-}
-
-int ShellRawVideoDecoderLinux::GetVideoBuffer(AVCodecContext* codec_context,
- AVFrame* frame) {
- VideoFrame::Format format = PixelFormatToVideoFormat(codec_context->pix_fmt);
- if (format == VideoFrame::INVALID)
- return AVERROR(EINVAL);
- DCHECK_EQ(format, VideoFrame::YV12);
-
- gfx::Size size(codec_context->width, codec_context->height);
- int ret = av_image_check_size(size.width(), size.height(), 0, NULL);
- if (ret < 0) {
- return ret;
- }
-
- if (!VideoFrame::IsValidConfig(format, size, gfx::Rect(size), size)) {
- return AVERROR(EINVAL);
- }
-
- // Align to kPlatformAlignment * 2 as we will divide the y_stride by 2 for u
- // and v planes.
- size_t y_stride = AlignUp(size.width(), kPlatformAlignment * 2);
- size_t uv_stride = y_stride / 2;
- size_t aligned_height = AlignUp(size.height(), kPlatformAlignment * 2);
- scoped_refptr<FrameBuffer> frame_buffer = allocator_->AllocateFrameBuffer(
- GetYV12SizeInBytes(y_stride, aligned_height), kPlatformAlignment);
-
- // y plane
- frame->base[0] = frame_buffer->data();
- frame->data[0] = frame->base[0];
- frame->linesize[0] = y_stride;
- // u plane
- frame->base[1] = frame->base[0] + y_stride * aligned_height;
- frame->data[1] = frame->base[1];
- memset(frame->data[1], 0, uv_stride * (aligned_height / 2));
- frame->linesize[1] = uv_stride;
- // v plane
- frame->base[2] = frame->base[1] + uv_stride * aligned_height / 2;
- frame->data[2] = frame->base[2];
- memset(frame->data[2], 0, uv_stride * (aligned_height / 2));
- frame->linesize[2] = uv_stride;
-
- frame->opaque = NULL;
- frame_buffer.swap(reinterpret_cast<FrameBuffer**>(&frame->opaque));
- frame->type = FF_BUFFER_TYPE_USER;
- frame->pkt_pts =
- codec_context->pkt ? codec_context->pkt->pts : AV_NOPTS_VALUE;
- frame->width = codec_context->width;
- frame->height = codec_context->height;
- frame->format = codec_context->pix_fmt;
-
- return 0;
-}
-
-void ShellRawVideoDecoderLinux::ReleaseVideoBuffer(AVCodecContext*,
- AVFrame* frame) {
- scoped_refptr<FrameBuffer> frame_buffer;
- frame_buffer.swap(reinterpret_cast<FrameBuffer**>(&frame->opaque));
-
- // The FFmpeg API expects us to zero the data pointers in
- // this callback
- memset(frame->data, 0, sizeof(frame->data));
- frame->opaque = NULL;
-}
-
-} // namespace
-
-scoped_ptr<ShellRawVideoDecoder> CreateShellRawVideoDecoderLinux(
- ShellVideoDataAllocator* allocator,
- const VideoDecoderConfig& config,
- Decryptor* decryptor,
- bool was_encrypted) {
- DCHECK(allocator);
- DCHECK_EQ(config.codec(), kCodecH264) << "Video codec " << config.codec()
- << " is not supported.";
- scoped_ptr<ShellRawVideoDecoder> decoder(
- new ShellRawVideoDecoderLinux(allocator));
- if (decoder->UpdateConfig(config))
- return decoder.Pass();
- return scoped_ptr<ShellRawVideoDecoder>();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_raw_video_decoder_linux.h b/src/media/filters/shell_raw_video_decoder_linux.h
deleted file mode 100644
index f364223..0000000
--- a/src/media/filters/shell_raw_video_decoder_linux.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_LINUX_H_
-#define MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_LINUX_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "media/base/shell_video_data_allocator.h"
-#include "media/base/video_decoder_config.h"
-#include "media/filters/shell_video_decoder_impl.h"
-
-namespace media {
-
-scoped_ptr<ShellRawVideoDecoder> CreateShellRawVideoDecoderLinux(
- ShellVideoDataAllocator* allocator,
- const VideoDecoderConfig& config,
- Decryptor* decryptor,
- bool was_encrypted);
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_LINUX_H_
diff --git a/src/media/filters/shell_raw_video_decoder_stub.cc b/src/media/filters/shell_raw_video_decoder_stub.cc
deleted file mode 100644
index 333cdec..0000000
--- a/src/media/filters/shell_raw_video_decoder_stub.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_raw_video_decoder_stub.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-namespace {
-
-typedef ShellVideoDataAllocator::FrameBuffer FrameBuffer;
-typedef ShellVideoDataAllocator::YV12Param YV12Param;
-
-class ShellRawVideoDecoderStub : public ShellRawVideoDecoder {
- public:
- explicit ShellRawVideoDecoderStub(ShellVideoDataAllocator* allocator)
- : allocator_(allocator) {}
- void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) OVERRIDE;
- bool Flush() OVERRIDE;
- bool UpdateConfig(const VideoDecoderConfig& config) OVERRIDE;
-
- private:
- ShellVideoDataAllocator* allocator_;
- gfx::Size natural_size_;
-
- DISALLOW_COPY_AND_ASSIGN(ShellRawVideoDecoderStub);
-};
-
-void ShellRawVideoDecoderStub::Decode(
- const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) {
- if (buffer->IsEndOfStream()) {
- decode_cb.Run(FRAME_DECODED, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- size_t yuv_size = natural_size_.width() * natural_size_.height() * 3 / 2;
- scoped_refptr<FrameBuffer> frame_buffer =
- allocator_->AllocateFrameBuffer(yuv_size, 1);
- YV12Param param(natural_size_.width(), natural_size_.height(),
- gfx::Rect(natural_size_), frame_buffer->data());
- scoped_refptr<VideoFrame> frame =
- allocator_->CreateYV12Frame(frame_buffer, param, buffer->GetTimestamp());
- decode_cb.Run(FRAME_DECODED, frame);
-}
-
-bool ShellRawVideoDecoderStub::Flush() {
- return true;
-}
-
-bool ShellRawVideoDecoderStub::UpdateConfig(const VideoDecoderConfig& config) {
- natural_size_ = config.natural_size();
- return true;
-}
-
-} // namespace
-
-scoped_ptr<ShellRawVideoDecoder> CreateShellRawVideoDecoderStub(
- ShellVideoDataAllocator* allocator,
- const VideoDecoderConfig& config,
- Decryptor* decryptor,
- bool was_encrypted) {
- DCHECK(allocator);
- scoped_ptr<ShellRawVideoDecoder> decoder(
- new ShellRawVideoDecoderStub(allocator));
- if (decoder->UpdateConfig(config))
- return decoder.Pass();
- return scoped_ptr<ShellRawVideoDecoder>();
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_raw_video_decoder_stub.h b/src/media/filters/shell_raw_video_decoder_stub.h
deleted file mode 100644
index c9beadb..0000000
--- a/src/media/filters/shell_raw_video_decoder_stub.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_STUB_H_
-#define MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_STUB_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "media/base/shell_video_data_allocator.h"
-#include "media/filters/shell_video_decoder_impl.h"
-
-namespace media {
-
-scoped_ptr<ShellRawVideoDecoder> CreateShellRawVideoDecoderStub(
- ShellVideoDataAllocator* allocator,
- const VideoDecoderConfig& config,
- Decryptor* decryptor,
- bool was_encrypted);
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_RAW_VIDEO_DECODER_STUB_H_
diff --git a/src/media/filters/shell_rbsp_stream.cc b/src/media/filters/shell_rbsp_stream.cc
deleted file mode 100644
index 7402862..0000000
--- a/src/media/filters/shell_rbsp_stream.cc
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_rbsp_stream.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-ShellRBSPStream::ShellRBSPStream(const uint8* nalu_buffer,
- size_t nalu_buffer_size)
- : nalu_buffer_(nalu_buffer),
- nalu_buffer_size_(nalu_buffer_size),
- nalu_buffer_byte_offset_(0),
- current_nalu_byte_(0),
- number_consecutive_zeros_(0),
- rbsp_bit_offset_(0) {}
-
-// read unsigned Exp-Golomb coded integer, ISO 14496-10 Section 9.1
-bool ShellRBSPStream::ReadUEV(uint32& uev_out) {
- int leading_zero_bits = -1;
- for (uint8 b = 0; b == 0; leading_zero_bits++) {
- if (!ReadRBSPBit(b)) {
- return false;
- }
- }
- // we can only fit 31 bits of Exp-Golomb coded data into a 32-bit number
- if (leading_zero_bits >= 32) {
- return false;
- }
- uint32 result = (1 << leading_zero_bits) - 1;
- uint32 remainder = 0;
- if (!ReadBits(leading_zero_bits, remainder)) {
- return false;
- }
- result += remainder;
- uev_out = result;
- return true;
-}
-
-// read signed Exp-Golomb coded integer, ISO 14496-10 Section 9.1
-bool ShellRBSPStream::ReadSEV(int32& sev_out) {
- // we start off by reading an unsigned Exp-Golomb coded number
- uint32 uev = 0;
- if (!ReadUEV(uev)) {
- return false;
- }
- // the LSb in this number is treated as the inverted sign bit
- bool is_negative = !(uev & 1);
- int32 result = (int32)((uev + 1) >> 1);
- if (is_negative) {
- result *= -1;
- }
- sev_out = result;
- return true;
-}
-
-// read and return up to 32 bits, filling from the right, meaning that
-// ReadBits(17) on a stream of all 1s would return 0x01ffff
-bool ShellRBSPStream::ReadBits(size_t bits, uint32& bits_out) {
- if (bits > 32) {
- return false;
- }
- if (bits == 0) {
- return true;
- }
- uint32 result = 0;
- size_t bytes = bits >> 3;
- // read bytes first
- for (int i = 0; i < bytes; i++) {
- uint8 new_byte = 0;
- if (!ReadRBSPByte(new_byte)) {
- return false;
- }
- result = result << 8;
- result = result | (uint32)new_byte;
- }
- // scoot any leftover bits in
- bits = bits % 8;
- for (int i = 0; i < bits; i++) {
- uint8 new_bit = 0;
- if (!ReadRBSPBit(new_bit)) {
- return false;
- }
- result = result << 1;
- result = result | (uint32)new_bit;
- }
- bits_out = result;
- return true;
-}
-
-// jump over bytes in the RBSP stream
-bool ShellRBSPStream::SkipBytes(size_t bytes) {
- for (int i = 0; i < bytes; ++i) {
- if (!ConsumeNALUByte()) {
- return false;
- }
- }
- return true;
-}
-
-// jump over bits in the RBSP stream
-bool ShellRBSPStream::SkipBits(size_t bits) {
- // skip bytes first
- size_t bytes = bits >> 3;
- if (bytes > 0) {
- if (!SkipBytes(bytes)) {
- return false;
- }
- }
- // mask off byte skips
- bits = bits & 7;
- // if no bits left to skip just return
- if (bits == 0) {
- return true;
- }
- // obey the convention that if our bit offset is 0 we haven't loaded the
- // current byte, extract it from NALU stream as we are going to advance
- // the bit cursor in to it (or potentially past it)
- if (rbsp_bit_offset_ == 0) {
- if (!ConsumeNALUByte()) {
- return false;
- }
- }
- // add to our bit offset
- rbsp_bit_offset_ += bits;
- // if we jumped in to the next byte advance the NALU stream, respecting the
- // convention that if we're at 8 bits stay on the current byte
- if (rbsp_bit_offset_ >= 9) {
- if (!ConsumeNALUByte()) {
- return false;
- }
- }
- rbsp_bit_offset_ = rbsp_bit_offset_ % 8;
- return true;
-}
-
-// advance by one byte through the NALU buffer, respecting the encoding of
-// 00 00 03 => 00 00. Updates the state of current_nalu_byte_ to the new value.
-bool ShellRBSPStream::ConsumeNALUByte() {
- if (nalu_buffer_byte_offset_ >= nalu_buffer_size_) {
- return false;
- }
- current_nalu_byte_ = nalu_buffer_[nalu_buffer_byte_offset_];
- if (current_nalu_byte_ == 0x03 && number_consecutive_zeros_ >= 2) {
- ++nalu_buffer_byte_offset_;
- current_nalu_byte_ = nalu_buffer_[nalu_buffer_byte_offset_];
- number_consecutive_zeros_ = 0;
- }
-
- if (current_nalu_byte_ == 0) {
- ++number_consecutive_zeros_;
- } else {
- number_consecutive_zeros_ = 0;
- }
- ++nalu_buffer_byte_offset_;
- return true;
-}
-
-// return single bit in the LSb from the RBSP stream. Bits are read from MSb
-// to LSb in the stream.
-bool ShellRBSPStream::ReadRBSPBit(uint8& bit_out) {
- // check to see if we need to consume a fresh byte
- if (rbsp_bit_offset_ == 0) {
- if (!ConsumeNALUByte()) {
- return false;
- }
- }
- // since we read from MSb to LSb in stream we shift right
- uint8 bit = (current_nalu_byte_ >> (7 - rbsp_bit_offset_)) & 1;
- // increment bit offset
- rbsp_bit_offset_ = (rbsp_bit_offset_ + 1) % 8;
- bit_out = bit;
- return true;
-}
-
-bool ShellRBSPStream::ReadRBSPByte(uint8& byte_out) {
- // fast path for byte-aligned access
- if (rbsp_bit_offset_ == 0) {
- if (!ConsumeNALUByte()) {
- return false;
- }
- byte_out = current_nalu_byte_;
- return true;
- }
- // at least some of the bits in the current byte will be included in this
- // next byte, absorb them
- uint8 upper_part = current_nalu_byte_;
- // read next byte from stream
- if (!ConsumeNALUByte()) {
- return false;
- }
- // form the byte from the two bytes
- byte_out = (upper_part << rbsp_bit_offset_) |
- (current_nalu_byte_ >> (8 - rbsp_bit_offset_));
- return true;
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_rbsp_stream.h b/src/media/filters/shell_rbsp_stream.h
deleted file mode 100644
index 36fd4eb..0000000
--- a/src/media/filters/shell_rbsp_stream.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
-#define MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
-
-#include "base/basictypes.h"
-
-namespace media {
-
-// ISO 14496-10 describes a byte encoding format for NALUs (network abstraction
-// layer units) and rules to convert it into a RBSP stream, which is the format
-// that some other atoms are defined. This class takes a non-owning reference
-// to a buffer and extract various types from the stream while silently
-// consuming the extra encoding bytes and advancing a bit stream pointer.
-class ShellRBSPStream {
- public:
- // NON-OWNING pointer to buffer. It is assumed the client will dispose of
- // this buffer.
- ShellRBSPStream(const uint8* nalu_buffer, size_t nalu_buffer_size);
- // all Read/Skip methods return the value by reference and return true
- // on success, false on read error/EOB. Once the object has returned
- // false the consistency of the data is not guaranteed.
- // read unsigned Exp-Golomb coded integer, ISO 14496-10 Section 9.1
- bool ReadUEV(uint32& uev_out);
- // read signed Exp-Golomb coded integer, ISO 14496-10 Section 9.1
- bool ReadSEV(int32& sev_out);
- // read and return up to 32 bits, filling from the right, meaning that
- // ReadBits(17) on a stream of all 1s would return 0x01ffff
- bool ReadBits(size_t bits, uint32& bits_out);
- bool ReadByte(uint8& byte_out) { return ReadRBSPByte(byte_out); }
- bool ReadBit(uint8& bit_out) { return ReadRBSPBit(bit_out); }
- // jump over bytes in the RBSP stream
- bool SkipBytes(size_t bytes);
- // jump over bits in the RBSP stream
- bool SkipBits(size_t bits);
-
- private:
- // advance by one byte through the NALU buffer, respecting the encoding of
- // 00 00 03 => 00 00. Updates the state of current_nalu_byte_ to the new
- // value.
- // returns fale if we have moved past the end of the buffer.
- bool ConsumeNALUByte();
- // return single bit in the LSb from the RBSP stream. Bits are read from MSb
- // to LSb in the stream.
- bool ReadRBSPBit(uint8& bit_out);
- bool ReadRBSPByte(uint8& byte_out);
-
- const uint8* nalu_buffer_;
- size_t nalu_buffer_size_;
- size_t nalu_buffer_byte_offset_;
- uint8 current_nalu_byte_;
- int number_consecutive_zeros_;
- // location of rbsp bit cursor within current_nalu_byte_
- size_t rbsp_bit_offset_;
-};
-};
-
-#endif // MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
diff --git a/src/media/filters/shell_rbsp_stream_unittest.cc b/src/media/filters/shell_rbsp_stream_unittest.cc
deleted file mode 100644
index 048ddad..0000000
--- a/src/media/filters/shell_rbsp_stream_unittest.cc
+++ /dev/null
@@ -1,635 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/filters/shell_rbsp_stream.h"
-
-#include <list>
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/stringprintf.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class ShellRBSPStreamTest : public testing::Test {
- protected:
- ShellRBSPStreamTest() {}
-
- virtual ~ShellRBSPStreamTest() {}
-
- // Given num encode the value in signed exp-golomb syntax and push
- // the value on the provided bitlist
- void EncodeSEV(int32 num, std::list<bool>& bits) {
- bool is_negative = (num < 0);
- uint32 unum = 0;
- if (is_negative) {
- unum = (uint32)(num * -1);
- } else {
- unum = (uint32)num;
- }
- // multiply unsigned value by 2
- unum = unum << 1;
- // subtract one from the positive values
- if (!is_negative) {
- --unum;
- }
- // encode the resulting uev
- EncodeUEV(unum, bits);
- }
-
- // Given num encode the value in unsigned exp-golomb syntax and push
- // the value on to the provided bitlist
- void EncodeUEV(uint32 num, std::list<bool>& bits) {
- // find largest (2^pow) - 1 smaller than num
- uint32 pow = 31;
- uint32 base = 0x7fffffff;
- while (base > num) {
- pow--;
- base = base >> 1;
- }
- // encoding calls for pow leading zeros, followed by a 1, followed
- // by pow digits of the input number - ((2^pow) - 1).
- // we move from MSb to LSb, so start by pushing back the leading 0s
- for (int i = 0; i < pow; i++) {
- bits.push_back(false);
- }
- // now push the separating one
- bits.push_back(true);
- // and now pow bits of the remainder bitfield MSb to LSb
- uint32 remainder = num - base;
- for (int i = pow - 1; i >= 0; --i) {
- bits.push_back((remainder >> i) & 0x01);
- }
- }
-
- // after building a bitlist in various fun ways call this method to
- // create a buffer on the heap that can be passed to ShellRBSPStream
- // for deserialization.
- scoped_array<uint8> SerializeToBuffer(const std::list<bool>& bitlist,
- bool add_sequence_bytes,
- size_t& buffer_size_out) {
- // start by building a list of bytes, so we can add the
- // 00 00 => 00 00 03 sequence bytes
- std::list<uint8> bytelist;
- uint8 push_byte = 0;
- uint32 bit_counter = 0;
- for (std::list<bool>::const_iterator it = bitlist.begin();
- it != bitlist.end(); ++it) {
- bit_counter++;
- push_byte = push_byte << 1;
- if (*it) {
- push_byte |= 1;
- }
- if (!(bit_counter % 8)) {
- bytelist.push_back(push_byte);
- push_byte = 0;
- }
- }
- // push any remaining bits on as the final byte
- if (bit_counter % 8) {
- bytelist.push_back(push_byte << (8 - (bit_counter % 8)));
- }
- // if we should add sequence bytes we iterate through the new
- // byte list looking for 00 00 and inserting a 03 after each.
- if (add_sequence_bytes) {
- int num_zeros = 0;
- for (std::list<uint8>::iterator it = bytelist.begin();
- it != bytelist.end(); ++it) {
- // if we just passed two sequential zeros insert a 03
- if (num_zeros == 2) {
- bytelist.insert(it, 0x03);
- // reset the counter
- num_zeros = 0;
- }
- if (*it == 0) {
- ++num_zeros;
- } else {
- num_zeros = 0;
- }
- }
- } else {
- // we will need to detect any naturally ocurring 00 00 03s
- // and protect them from removal of the 03, by inserting a
- // second 03
- int num_zeros = 0;
- for (std::list<uint8>::iterator it = bytelist.begin();
- it != bytelist.end(); ++it) {
- if ((num_zeros >= 2) && (*it == 0x03)) {
- bytelist.insert(it, 0x03);
- }
- if (*it == 0) {
- ++num_zeros;
- } else {
- num_zeros = 0;
- }
- }
- }
- // alright we can make the final output buffer
- scoped_array<uint8> buf(new uint8[bytelist.size()]);
- int index = 0;
- for (std::list<uint8>::iterator it = bytelist.begin(); it != bytelist.end();
- it++) {
- buf[index] = *it;
- index++;
- }
- buffer_size_out = bytelist.size();
- return buf.Pass();
- }
-};
-
-TEST_F(ShellRBSPStreamTest, ReadUEV) {
- std::list<bool> fibbits;
- // encode first 47 Fibonacci numbers
- uint32 f_n_minus_2 = 0;
- EncodeUEV(f_n_minus_2, fibbits);
- uint32 f_n_minus_1 = 1;
- EncodeUEV(f_n_minus_1, fibbits);
- for (int i = 2; i < 47; i++) {
- uint32 f_n = f_n_minus_1 + f_n_minus_2;
- EncodeUEV(f_n, fibbits);
- // update values
- f_n_minus_2 = f_n_minus_1;
- f_n_minus_1 = f_n;
- }
- // convert to buffer
- size_t fib_buffer_size = 0;
- scoped_array<uint8> fib_buffer =
- SerializeToBuffer(fibbits, true, fib_buffer_size);
- size_t fib_buffer_no_sequence_size;
- scoped_array<uint8> fib_buffer_no_sequence =
- SerializeToBuffer(fibbits, false, fib_buffer_no_sequence_size);
- ShellRBSPStream fib_stream(fib_buffer.get(), fib_buffer_size);
- ShellRBSPStream fib_stream_no_sequence(fib_buffer_no_sequence.get(),
- fib_buffer_no_sequence_size);
- // deserialize the same sequence from both buffers
- uint32 uev = 0;
- uint32 uev_n = 0;
- f_n_minus_2 = 0;
- ASSERT_TRUE(fib_stream.ReadUEV(uev));
- ASSERT_EQ(uev, f_n_minus_2);
- ASSERT_TRUE(fib_stream_no_sequence.ReadUEV(uev_n));
- ASSERT_EQ(uev_n, f_n_minus_2);
-
- f_n_minus_1 = 1;
- ASSERT_TRUE(fib_stream.ReadUEV(uev));
- ASSERT_EQ(uev, f_n_minus_1);
- ASSERT_TRUE(fib_stream_no_sequence.ReadUEV(uev_n));
- ASSERT_EQ(uev_n, f_n_minus_1);
-
- for (int i = 2; i < 47; i++) {
- uint32 f_n = f_n_minus_1 + f_n_minus_2;
- ASSERT_TRUE(fib_stream.ReadUEV(uev));
- ASSERT_EQ(uev, f_n);
- ASSERT_TRUE(fib_stream_no_sequence.ReadUEV(uev_n));
- ASSERT_EQ(uev_n, f_n);
- f_n_minus_2 = f_n_minus_1;
- f_n_minus_1 = f_n;
- }
- // subsequent call to ReadUEV should fail
- ASSERT_FALSE(fib_stream.ReadUEV(uev));
- ASSERT_FALSE(fib_stream_no_sequence.ReadUEV(uev_n));
-}
-
-TEST_F(ShellRBSPStreamTest, ReadSEV) {
- std::list<bool> lucasbits;
- // encode first 44 Lucas numbers with alternating sign
- int32 l_n_minus_2 = 1;
- EncodeSEV(l_n_minus_2, lucasbits);
- int32 l_n_minus_1 = 2;
- EncodeSEV(-l_n_minus_1, lucasbits);
- for (int i = 2; i < 44; ++i) {
- int32 l_n = l_n_minus_1 + l_n_minus_2;
- if (i % 2) {
- EncodeSEV(-l_n, lucasbits);
- } else {
- EncodeSEV(l_n, lucasbits);
- }
- l_n_minus_2 = l_n_minus_1;
- l_n_minus_1 = l_n;
- }
- // convert to buffers
- size_t lucas_seq_buffer_size = 0;
- scoped_array<uint8> lucas_seq_buffer =
- SerializeToBuffer(lucasbits, true, lucas_seq_buffer_size);
- size_t lucas_deseq_buffer_size = 0;
- scoped_array<uint8> lucas_deseq_buffer =
- SerializeToBuffer(lucasbits, false, lucas_deseq_buffer_size);
- ShellRBSPStream lucas_seq_stream(lucas_seq_buffer.get(),
- lucas_seq_buffer_size);
- ShellRBSPStream lucas_deseq_stream(lucas_deseq_buffer.get(),
- lucas_deseq_buffer_size);
- l_n_minus_2 = 1;
- l_n_minus_1 = 2;
- int32 sev = 0;
- int32 sev_n = 0;
- ASSERT_TRUE(lucas_seq_stream.ReadSEV(sev));
- ASSERT_EQ(sev, 1);
- ASSERT_TRUE(lucas_deseq_stream.ReadSEV(sev_n));
- ASSERT_EQ(sev_n, 1);
- ASSERT_TRUE(lucas_seq_stream.ReadSEV(sev));
- ASSERT_EQ(sev, -2);
- ASSERT_TRUE(lucas_deseq_stream.ReadSEV(sev_n));
- ASSERT_EQ(sev_n, -2);
- for (int i = 2; i < 44; ++i) {
- int32 l_n = l_n_minus_1 + l_n_minus_2;
- ASSERT_TRUE(lucas_seq_stream.ReadSEV(sev));
- ASSERT_TRUE(lucas_deseq_stream.ReadSEV(sev_n));
- if (i % 2) {
- ASSERT_EQ(-sev, l_n);
- ASSERT_EQ(-sev_n, l_n);
- } else {
- ASSERT_EQ(sev, l_n);
- ASSERT_EQ(sev_n, l_n);
- }
- l_n_minus_2 = l_n_minus_1;
- l_n_minus_1 = l_n;
- }
- // subsequent calls to ReadSEV should fail
- ASSERT_FALSE(lucas_seq_stream.ReadSEV(sev));
- ASSERT_FALSE(lucas_deseq_stream.ReadSEV(sev_n));
-}
-
-static const uint8 kTestRBSPExpGolombTooBig[] = {
- // 15 leading zeros, should be fine
- // 0000000000000001010101010101010
- // = 2^15 - 1 + read_bits(010101010101010)
- // = 32768 - 1 + 10922 = 43689 unsigned, 21845 signed
- // 0000 0000 0000 0001 0101 0101 0101 010+0 (first 0 of next number)
- 0x00, 0x01, 0x55, 0x54,
- // 31 leading zeros, should be fine
- // 000000000000000000000000000000010000000000000000000000000000001
- // = 2^31 - 1 + 1 = 2147483648 unsigned, -1073741824 signed
- // 0 appended on to last byte
- // 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000 0000 0000
- 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
- // 0000 01+00 (first 2 zeros of next number)
- 0x04,
- // 32 leading zeros, should not be ok
- // 00000000000000000000000000000000111111111111111111111111111111111
- // = 2^32 - 1 + 2^32 = 2^33 - 1 = 8589934591
- // 00 appended on to last byte
- // 0000 0000 0000 0000 0000 0000 0000 0011 1111 1111 1111 1111 1111 1111
- 0x00, 0x00, 0x00, 0x03, 0x03, 0xff, 0xff, 0xff,
- // 1111 111+0 (to complete the byte)
- 0xfe};
-
-TEST_F(ShellRBSPStreamTest, ReadUEVTooLarge) {
- // construct a stream from the supplied test data
- ShellRBSPStream uev_too_big(kTestRBSPExpGolombTooBig,
- sizeof(kTestRBSPExpGolombTooBig));
- // first call should succeed
- uint32 uev = 0;
- ASSERT_TRUE(uev_too_big.ReadUEV(uev));
- ASSERT_EQ(uev, 43689);
- // as should the second call
- ASSERT_TRUE(uev_too_big.ReadUEV(uev));
- ASSERT_EQ(uev, 2147483648u);
- // third should fail
- ASSERT_FALSE(uev_too_big.ReadUEV(uev));
-}
-
-TEST_F(ShellRBSPStreamTest, ReadSEVTooLarge) {
- // construct a stream from the supplied test data
- ShellRBSPStream sev_too_big(kTestRBSPExpGolombTooBig,
- sizeof(kTestRBSPExpGolombTooBig));
- // first call should succeed
- int32 sev = 0;
- ASSERT_TRUE(sev_too_big.ReadSEV(sev));
- ASSERT_EQ(sev, 21845);
- // as should the second call
- ASSERT_TRUE(sev_too_big.ReadSEV(sev));
- ASSERT_EQ(sev, -1073741824);
- // third should fail
- ASSERT_FALSE(sev_too_big.ReadSEV(sev));
-}
-
-TEST_F(ShellRBSPStreamTest, ReadBit) {
- std::list<bool> padded_ones;
- // build a bitfield of 1 padded by n zeros, for n in range[0, 1024]
- for (int i = 0; i < 1024; i++) {
- for (int j = 0; j < i; j++) {
- padded_ones.push_back(false);
- }
- padded_ones.push_back(true);
- }
- // build the buffer with sequence bits and without
- size_t sequence_buff_size = 0;
- scoped_array<uint8> sequence_buff =
- SerializeToBuffer(padded_ones, true, sequence_buff_size);
- ShellRBSPStream seq_stream(sequence_buff.get(), sequence_buff_size);
-
- size_t desequence_buff_size = 0;
- scoped_array<uint8> desequence_buff =
- SerializeToBuffer(padded_ones, false, desequence_buff_size);
- ShellRBSPStream deseq_stream(desequence_buff.get(), desequence_buff_size);
- for (std::list<bool>::iterator it = padded_ones.begin();
- it != padded_ones.end(); ++it) {
- uint8 bit = 0;
- ASSERT_TRUE(seq_stream.ReadBit(bit));
- ASSERT_EQ(*it, bit);
- uint8 deseq_bit = 0;
- ASSERT_TRUE(deseq_stream.ReadBit(deseq_bit));
- ASSERT_EQ(*it, deseq_bit);
- }
-
- // there should be less than a byte in the either stream
- uint8 fail_byte = 0;
- ASSERT_FALSE(seq_stream.ReadByte(fail_byte));
- ASSERT_FALSE(deseq_stream.ReadByte(fail_byte));
-}
-
-TEST_F(ShellRBSPStreamTest, ReadByte) {
- // build a field of 16 x (0xaa byte followed by 0 bit)
- std::list<bool> aa_field;
- for (int i = 0; i < 16; ++i) {
- for (int j = 0; j < 8; ++j) {
- aa_field.push_back(!(j % 2));
- }
- aa_field.push_back(false);
- }
- // deseqbuff will be identical due to dense packing of 01 pattern
- size_t aabuff_size = 0;
- scoped_array<uint8> aabuff = SerializeToBuffer(aa_field, true, aabuff_size);
- ShellRBSPStream aa_stream(aabuff.get(), aabuff_size);
- for (int i = 0; i < 16; ++i) {
- uint8 aa = 0;
- ASSERT_TRUE(aa_stream.ReadByte(aa));
- ASSERT_EQ(aa, 0xaa);
- // read the zero separator bit
- uint8 zero = 0;
- ASSERT_TRUE(aa_stream.ReadBit(zero));
- ASSERT_EQ(zero, 0);
- }
-
- // build a field of 24 x (1 bit, 4 bytes of 0, one 03 byte, 4 bytes of 0)
- std::list<bool> zero_field;
- for (int i = 0; i < 24; ++i) {
- zero_field.push_back(true);
- for (int j = 0; j < 32; ++j) {
- zero_field.push_back(false);
- }
- zero_field.push_back(false);
- zero_field.push_back(false);
- zero_field.push_back(false);
- zero_field.push_back(false);
- zero_field.push_back(false);
- zero_field.push_back(false);
- zero_field.push_back(true);
- zero_field.push_back(true);
- for (int j = 0; j < 32; ++j) {
- zero_field.push_back(false);
- }
- }
- size_t zseqbuff_size = 0;
- scoped_array<uint8> zseqbuff =
- SerializeToBuffer(zero_field, true, zseqbuff_size);
- ShellRBSPStream zseq_stream(zseqbuff.get(), zseqbuff_size);
- size_t zdseqbuff_size = 0;
- scoped_array<uint8> zdseqbuff =
- SerializeToBuffer(zero_field, false, zdseqbuff_size);
- ShellRBSPStream zdseq_stream(zdseqbuff.get(), zdseqbuff_size);
- for (int i = 0; i < 24; ++i) {
- // read the leading 1 bit
- uint8 seq_bit = 0;
- ASSERT_TRUE(zseq_stream.ReadBit(seq_bit));
- ASSERT_EQ(seq_bit, 1);
- uint8 dseq_bit = 0;
- ASSERT_TRUE(zdseq_stream.ReadBit(dseq_bit));
- ASSERT_EQ(dseq_bit, 1);
- // read 4 zeros
- uint8 seq_byte = 0;
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- uint8 dseq_byte = 0;
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- // read the 3
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0x03);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0x03);
- // read the remaining 4 zeros
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zseq_stream.ReadByte(seq_byte));
- ASSERT_EQ(seq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- ASSERT_TRUE(zdseq_stream.ReadByte(dseq_byte));
- ASSERT_EQ(dseq_byte, 0);
- }
-}
-
-TEST_F(ShellRBSPStreamTest, ReadBits) {
- // test the assertion in the ReadBits comment, as it had a bug :)
- std::list<bool> seventeen_ones;
- for (int i = 0; i < 17; ++i) {
- seventeen_ones.push_back(true);
- }
- size_t seventeen_ones_size = 0;
- scoped_array<uint8> seventeen_ones_buff =
- SerializeToBuffer(seventeen_ones, false, seventeen_ones_size);
- ShellRBSPStream seventeen_ones_stream(seventeen_ones_buff.get(),
- seventeen_ones_size);
- uint32 seventeen_ones_word = 0;
- ASSERT_TRUE(seventeen_ones_stream.ReadBits(17, seventeen_ones_word));
- ASSERT_EQ(seventeen_ones_word, 0x0001ffff);
-
- // serialize all powers of two from 2^0 to 2^31
- std::list<bool> pows;
- for (int i = 0; i < 32; ++i) {
- pows.push_back(true);
- for (int j = 0; j < i; ++j) {
- pows.push_back(false);
- }
- }
- size_t pows_size = 0;
- scoped_array<uint8> pows_buff = SerializeToBuffer(pows, true, pows_size);
- ShellRBSPStream pows_stream(pows_buff.get(), pows_size);
- // ReadBits(0) should succeed and not modify the value of the ref output or
- // internal bit iterator
- uint32 dont_touch = 0xfeedfeed;
- ASSERT_TRUE(pows_stream.ReadBits(0, dont_touch));
- ASSERT_EQ(dont_touch, 0xfeedfeed);
- // compare deserializations
- for (int i = 0; i < 32; ++i) {
- uint32 bits = 0;
- ASSERT_TRUE(pows_stream.ReadBits(i + 1, bits));
- ASSERT_EQ(bits, (uint32)(1 << i));
- }
-}
-
-TEST_F(ShellRBSPStreamTest, SkipBytes) {
- // serialize all nine-bit values from zero to 512
- std::list<bool> nines;
- for (int i = 0; i < 512; ++i) {
- for (int j = 8; j >= 0; --j) {
- nines.push_back((i >> j) & 1);
- }
- }
- size_t nines_size = 0;
- scoped_array<uint8> nines_buff = SerializeToBuffer(nines, true, nines_size);
- size_t nines_deseq_size = 0;
- scoped_array<uint8> nines_deseq_buff =
- SerializeToBuffer(nines, false, nines_deseq_size);
- ShellRBSPStream nines_stream(nines_buff.get(), nines_size);
- ShellRBSPStream nines_deseq_stream(nines_deseq_buff.get(), nines_deseq_size);
- // iterate through streams, skipping in one and reading in the other, always
- // comparing values.
- for (int i = 0; i < 512; ++i) {
- if (i % 2) {
- ASSERT_TRUE(nines_stream.SkipBytes(1));
- uint8 bit = 0;
- ASSERT_TRUE(nines_stream.ReadBit(bit));
- uint32 ninebits = 0;
- ASSERT_TRUE(nines_deseq_stream.ReadBits(9, ninebits));
- ASSERT_EQ(ninebits, i);
- ASSERT_EQ(ninebits & 1, bit);
- } else {
- ASSERT_TRUE(nines_deseq_stream.SkipBytes(1));
- uint8 bit = 0;
- ASSERT_TRUE(nines_deseq_stream.ReadBit(bit));
- uint32 ninebits = 0;
- ASSERT_TRUE(nines_stream.ReadBits(9, ninebits));
- ASSERT_EQ(ninebits, i);
- ASSERT_EQ(ninebits & 1, bit);
- }
- }
- // 1 true bit followed by 1 byte with 1, followed by 1 true bit, then 2 bytes
- // with 2, followed by 1 bit, then 3 bytes with 3, etc up to 256
- std::list<bool> run_length;
- for (int i = 0; i < 256; ++i) {
- for (int j = 0; j < i; ++j) {
- for (int k = 7; k >= 0; --k) {
- run_length.push_back((i >> k) & 1);
- }
- }
- run_length.push_back(true);
- }
- size_t run_length_size = 0;
- scoped_array<uint8> run_length_buff =
- SerializeToBuffer(run_length, true, run_length_size);
- size_t run_length_deseq_size = 0;
- scoped_array<uint8> run_length_deseq_buff =
- SerializeToBuffer(run_length, false, run_length_deseq_size);
- ShellRBSPStream run_length_stream(run_length_buff.get(), run_length_size);
- ShellRBSPStream run_length_deseq_stream(run_length_deseq_buff.get(),
- run_length_deseq_size);
- // read first bit, skip first byte from each stream, read next bit
- uint8 bit = 0;
- ASSERT_TRUE(run_length_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- bit = 0;
- ASSERT_TRUE(run_length_deseq_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- ASSERT_TRUE(run_length_stream.SkipBytes(1));
- ASSERT_TRUE(run_length_deseq_stream.SkipBytes(1));
- bit = 0;
- ASSERT_TRUE(run_length_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- bit = 0;
- ASSERT_TRUE(run_length_deseq_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
-
- for (int i = 2; i < 256; ++i) {
- // read first byte in seq stream, make sure it matches value
- uint8 byte = 0;
- ASSERT_TRUE(run_length_stream.ReadByte(byte));
- ASSERT_EQ(byte, i);
- // skip the rest of the byte field
- ASSERT_TRUE(run_length_stream.SkipBytes(i - 1));
- bit = 0;
- // read the separating one bit
- ASSERT_TRUE(run_length_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- // read last byte in deseq stream, so skip bytes first
- ASSERT_TRUE(run_length_deseq_stream.SkipBytes(i - 1));
- byte = 0;
- ASSERT_TRUE(run_length_deseq_stream.ReadByte(byte));
- ASSERT_EQ(byte, i);
- // read the separating one bit
- bit = 0;
- ASSERT_TRUE(run_length_deseq_stream.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- }
-
- // further skips should fail
- ASSERT_FALSE(run_length_stream.SkipBytes(1));
- ASSERT_FALSE(run_length_deseq_stream.SkipBytes(1));
-}
-
-TEST_F(ShellRBSPStreamTest, SkipBits) {
- std::list<bool> one_ohs;
- // encode one 1, followed by one zero, followed by 2 1s, followed by 2 zeros,
- // etc
- for (int i = 1; i <= 64; ++i) {
- for (int j = 0; j < i; ++j) {
- one_ohs.push_back(true);
- }
- for (int j = 0; j < i; ++j) {
- one_ohs.push_back(false);
- }
- }
- size_t skip_ones_size = 0;
- scoped_array<uint8> skip_ones_buff =
- SerializeToBuffer(one_ohs, true, skip_ones_size);
- size_t skip_ohs_size = 0;
- scoped_array<uint8> skip_ohs_buff =
- SerializeToBuffer(one_ohs, false, skip_ohs_size);
- ShellRBSPStream skip_ones(skip_ones_buff.get(), skip_ones_size);
- ShellRBSPStream skip_ohs(skip_ohs_buff.get(), skip_ohs_size);
- for (int i = 1; i < 64; ++i) {
- // skip the ones
- ASSERT_TRUE(skip_ones.SkipBits(i));
- // read the ones from the zeros stream
- for (int j = 0; j < i; ++j) {
- uint8 bit = 0;
- ASSERT_TRUE(skip_ohs.ReadBit(bit));
- ASSERT_EQ(bit, 1);
- }
- // skip the ohs
- ASSERT_TRUE(skip_ohs.SkipBits(i));
- // read the ohs from the ones stream
- for (int j = 0; j < i; ++j) {
- uint8 bit = 0;
- ASSERT_TRUE(skip_ones.ReadBit(bit));
- ASSERT_EQ(bit, 0);
- }
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_video_decoder_impl.cc b/src/media/filters/shell_video_decoder_impl.cc
deleted file mode 100644
index 96e24b7..0000000
--- a/src/media/filters/shell_video_decoder_impl.cc
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "media/filters/shell_video_decoder_impl.h"
-
-#include <limits.h> // for ULLONG_MAX
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/debug/trace_event.h"
-#include "base/logging.h"
-#include "build/build_config.h" // Must come before OS_STARBOARD.
-#include "media/base/bind_to_loop.h"
-#include "media/base/pipeline_status.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_media_statistics.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#endif // defined(OS_STARBOARD)
-
-namespace media {
-
-//==============================================================================
-// ShellVideoDecoderImpl
-//
-
-ShellVideoDecoderImpl::ShellVideoDecoderImpl(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- ShellRawVideoDecoderFactory* raw_video_decoder_factory)
- : state_(kUninitialized),
- media_pipeline_message_loop_(message_loop),
- raw_video_decoder_factory_(raw_video_decoder_factory),
- decoder_thread_("Video Decoder") {
- DCHECK(raw_video_decoder_factory_);
-}
-
-ShellVideoDecoderImpl::~ShellVideoDecoderImpl() {}
-
-void ShellVideoDecoderImpl::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::Initialize()");
- DCHECK(!decoder_thread_.IsRunning());
- DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread());
- // check for no already attached stream, valid input stream, and save it
- DCHECK(!demuxer_stream_);
-
- if (!stream) {
- status_cb.Run(PIPELINE_ERROR_DECODE);
- return;
- }
- demuxer_stream_ = stream;
-
- VideoDecoderConfig decoder_config;
- decoder_config.CopyFrom(demuxer_stream_->video_decoder_config());
- LOG(INFO) << "Configuration at Start: "
- << decoder_config.AsHumanReadableString();
-
- raw_decoder_ = raw_video_decoder_factory_->Create(
- decoder_config, demuxer_stream_->GetDecryptor(),
- demuxer_stream_->StreamWasEncrypted());
-
- if (!raw_decoder_) {
- status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_CODEC, decoder_config.codec());
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_WIDTH,
- decoder_config.natural_size().width());
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_HEIGHT,
- decoder_config.natural_size().height());
-
- base::Thread::Options options;
-
-#if defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE)
- options.stack_size = SB_MEDIA_THREAD_STACK_SIZE;
-#endif // defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE)
-
- if (decoder_thread_.StartWithOptions(options)) {
- state_ = kNormal;
- status_cb.Run(PIPELINE_OK);
- } else {
- status_cb.Run(PIPELINE_ERROR_DECODE);
- }
-}
-
-void ShellVideoDecoderImpl::Read(const ReadCB& read_cb) {
- TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DoRead()");
- if (!decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()) {
- decoder_thread_.message_loop_proxy()->PostTask(
- FROM_HERE, base::Bind(&ShellVideoDecoderImpl::Read, this, read_cb));
- return;
- }
-
- DCHECK(!read_cb.is_null());
- DCHECK(read_cb_.is_null()) << "overlapping reads not supported";
- DCHECK_NE(state_, kUninitialized);
-
- read_cb_ = BindToLoop(media_pipeline_message_loop_, read_cb);
-
- // if an error has occurred, return error
- if (state_ == kShellDecodeError) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- return;
- }
-
- // if decoding is done return empty frame
- if (state_ == kDecodeFinished) {
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- if (buffer_to_decode_) {
- DecodeBuffer(buffer_to_decode_);
- } else if (state_ == kFlushCodec) {
- DecodeBuffer(eof_buffer_);
- } else {
- ReadFromDemuxerStream(); // Kick off a read
- }
-}
-
-void ShellVideoDecoderImpl::ReadFromDemuxerStream() {
- DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread());
- DCHECK_NE(state_, kDecodeFinished);
-
- media_pipeline_message_loop_->PostTask(
- FROM_HERE, base::Bind(&DemuxerStream::Read, demuxer_stream_,
- BindToCurrentLoop(base::Bind(
- &ShellVideoDecoderImpl::BufferReady, this))));
-}
-
-void ShellVideoDecoderImpl::BufferReady(
- DemuxerStream::Status demuxer_status,
- const scoped_refptr<DecoderBuffer>& buffer) {
- DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread());
- // must either be Ok and have a buffer or not Ok and no buffer
- DCHECK_EQ(demuxer_status != DemuxerStream::kOk, !buffer) << demuxer_status;
-
- if (state_ == kUninitialized) {
- // Stop has been called before BufferReady is posted. read_cb_ should be
- // called and cleared inside Stop().
- DCHECK(read_cb_.is_null());
- return;
- }
-
- if (state_ == kShellDecodeError) {
- DLOG(WARNING) << "read returned but decoder is in error state";
- return;
- }
-
- // if we deferred reset based on a pending read, process that reset now
- // after returning an empty frame
- if (!reset_cb_.is_null()) {
- DoReset();
- return;
- }
-
- if (demuxer_status == DemuxerStream::kConfigChanged) {
- VideoDecoderConfig decoder_config;
- decoder_config.CopyFrom(demuxer_stream_->video_decoder_config());
- LOG(INFO) << "Configuration Changed: "
- << decoder_config.AsHumanReadableString();
- // One side effect of asking for the video configuration is that
- // the MediaSource demuxer stack uses that request to determine
- // that the video decoder has updated its configuration.
- // We therefore must ask for the updated video configuration
- // before requesting any data, or the MediaSource stack will
- // assert.
- if (!raw_decoder_->UpdateConfig(decoder_config)) {
- DLOG(ERROR) << "Error Reconfig H264 decoder";
- DecoderFatalError();
- return;
- }
-
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_CODEC, decoder_config.codec());
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_WIDTH,
- decoder_config.natural_size().width());
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_HEIGHT,
- decoder_config.natural_size().height());
-
- ReadFromDemuxerStream();
- return;
- }
-
- // if stream is aborted service this and any pending reads with
- // empty frames
- if (demuxer_status == DemuxerStream::kAborted) {
- if (!read_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- }
- return;
- }
-
- DCHECK(buffer);
- if (!buffer) {
- DecoderFatalError();
- return;
- }
-
- // at this point demuxerstream state should be OK
- DCHECK_EQ(demuxer_status, DemuxerStream::kOk);
-
- // Decode this one
- DecodeBuffer(buffer);
-}
-
-void ShellVideoDecoderImpl::DecodeBuffer(
- const scoped_refptr<DecoderBuffer>& buffer) {
- TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DecodeBuffer()");
- SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_DECODE);
-
- DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread());
- DCHECK_NE(state_, kUninitialized);
- DCHECK_NE(state_, kDecodeFinished);
- DCHECK(buffer);
- if (buffer_to_decode_)
- DCHECK_EQ(buffer_to_decode_, buffer);
-
- buffer_to_decode_ = buffer;
-
- // if we deferred reset based on a pending read, process that reset now
- // after returning an empty frame
- if (!reset_cb_.is_null()) {
- DoReset();
- return;
- }
-
- // if we've encountered an EOS buffer then attempt no more reads from upstream
- // empty queue and prepare to transition to kDecodeFinished.
- if (buffer->IsEndOfStream()) {
- TRACE_EVENT0("media_stack",
- "ShellVideoDecoderImpl::DecodeBuffer() EOS received");
- eof_buffer_ = buffer;
- // We pipeline reads, so it is possible that we will receive more than one
- // read callback with EOS after the first
- if (state_ == kNormal) {
- state_ = kFlushCodec;
- }
- }
-
- ShellRawVideoDecoder::DecodeCB decode_cb =
- base::Bind(&ShellVideoDecoderImpl::DecodeCallback, this);
- raw_decoder_->Decode(buffer, BindToCurrentLoop(decode_cb));
-}
-
-void ShellVideoDecoderImpl::DecodeCallback(
- ShellRawVideoDecoder::DecodeStatus status,
- const scoped_refptr<VideoFrame>& frame) {
- DCHECK(buffer_to_decode_);
-
- if (!reset_cb_.is_null()) {
- DoReset();
- return;
- }
-
- if (status == ShellRawVideoDecoder::RETRY_WITH_SAME_BUFFER) {
- if (frame) {
- base::ResetAndReturn(&read_cb_).Run(kOk, frame);
- } else {
- decoder_thread_.message_loop_proxy()->PostTask(
- FROM_HERE, base::Bind(&ShellVideoDecoderImpl::DecodeBuffer, this,
- buffer_to_decode_));
- }
- return;
- }
-
- buffer_to_decode_ = NULL;
-
- if (status == ShellRawVideoDecoder::FRAME_DECODED) {
- TRACE_EVENT1("media_stack", "ShellVideoDecoderImpl frame decoded",
- "timestamp", frame->GetTimestamp().InMicroseconds());
- DCHECK(frame);
- base::ResetAndReturn(&read_cb_).Run(kOk, frame);
- return;
- }
-
- if (status == ShellRawVideoDecoder::NEED_MORE_DATA) {
- DCHECK(!frame);
- if (state_ == kFlushCodec) {
- state_ = kDecodeFinished;
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
- return;
- }
-
- ReadFromDemuxerStream();
- return;
- }
-
- if (status == ShellRawVideoDecoder::FATAL_ERROR) {
- DecoderFatalError();
- return;
- }
-
- NOTREACHED();
-}
-
-void ShellVideoDecoderImpl::Reset(const base::Closure& closure) {
- TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::Reset()");
- if (!decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()) {
- decoder_thread_.message_loop_proxy()->PostTask(
- FROM_HERE, base::Bind(&ShellVideoDecoderImpl::Reset, this, closure));
- return;
- }
-
- reset_cb_ = BindToLoop(media_pipeline_message_loop_, closure);
-
- // Defer the reset if a read is pending.
- if (!read_cb_.is_null())
- return;
-
- DoReset();
-}
-
-void ShellVideoDecoderImpl::DoReset() {
- DCHECK(!reset_cb_.is_null());
- DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread());
-
- // service any pending read call with a NULL buffer
- if (!read_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
- }
-
- if (!raw_decoder_->Flush()) {
- DLOG(ERROR) << "Error Flush Decoder";
- DecoderFatalError();
- }
-
- if (state_ != kShellDecodeError)
- state_ = kNormal;
- eof_buffer_ = NULL;
- buffer_to_decode_ = NULL;
-
- base::ResetAndReturn(&reset_cb_).Run();
-}
-
-void ShellVideoDecoderImpl::DecoderFatalError() {
- TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DecoderFatalError()");
- DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread());
- // fatal error within the decoder
- DLOG(ERROR) << "fatal video decoder error.";
- // service any read callbacks with error
- if (!read_cb_.is_null()) {
- base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
- }
- // terminate playback
- state_ = kShellDecodeError;
-}
-
-void ShellVideoDecoderImpl::Stop(const base::Closure& closure) {
- DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread());
-
- decoder_thread_.Stop();
- raw_decoder_.reset(NULL);
-
- // terminate playback
- state_ = kUninitialized;
-
- if (!read_cb_.is_null())
- base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
-
- closure.Run();
-}
-
-void ShellVideoDecoderImpl::NearlyUnderflow() {
- DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread());
- if (raw_decoder_) {
- raw_decoder_->NearlyUnderflow();
- }
-}
-
-void ShellVideoDecoderImpl::HaveEnoughFrames() {
- DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread());
- if (raw_decoder_) {
- raw_decoder_->HaveEnoughFrames();
- }
-}
-
-} // namespace media
diff --git a/src/media/filters/shell_video_decoder_impl.h b/src/media/filters/shell_video_decoder_impl.h
deleted file mode 100644
index ba9e17d..0000000
--- a/src/media/filters/shell_video_decoder_impl.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef MEDIA_FILTERS_SHELL_VIDEO_DECODER_IMPL_H_
-#define MEDIA_FILTERS_SHELL_VIDEO_DECODER_IMPL_H_
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/threading/thread.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/video_decoder.h"
-#include "media/base/video_decoder_config.h"
-
-namespace media {
-
-class ShellRawVideoDecoder {
- public:
- enum DecodeStatus {
- FRAME_DECODED, // Successfully decoded a frame.
- NEED_MORE_DATA, // Need more data to decode the next frame.
- RETRY_WITH_SAME_BUFFER, // Retry later with the same input. Note that in
- // this case the decoder may still return a valid
- // buffered frame.
- FATAL_ERROR // Decoder encounters fatal error, abort playback.
- };
- typedef media::DecoderBuffer DecoderBuffer;
- typedef media::VideoDecoderConfig VideoDecoderConfig;
- typedef media::VideoFrame VideoFrame;
- typedef base::Callback<void(DecodeStatus, const scoped_refptr<VideoFrame>&)>
- DecodeCB;
-
- ShellRawVideoDecoder() {}
- virtual ~ShellRawVideoDecoder() {}
- virtual void Decode(const scoped_refptr<DecoderBuffer>& buffer,
- const DecodeCB& decode_cb) = 0;
- virtual bool Flush() = 0;
- virtual bool UpdateConfig(const VideoDecoderConfig& config) = 0;
- // See ShellVideoDecoder for more details about the following two functions.
- virtual void NearlyUnderflow() {}
- virtual void HaveEnoughFrames() {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ShellRawVideoDecoder);
-};
-
-class ShellRawVideoDecoderFactory {
- public:
- virtual ~ShellRawVideoDecoderFactory() {}
-
- virtual scoped_ptr<ShellRawVideoDecoder> Create(
- const VideoDecoderConfig& config,
- Decryptor* decryptor,
- bool was_encrypted) = 0;
-};
-
-class MEDIA_EXPORT ShellVideoDecoderImpl : public VideoDecoder {
- public:
- ShellVideoDecoderImpl(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- ShellRawVideoDecoderFactory* raw_video_decoder_factory);
- ~ShellVideoDecoderImpl() OVERRIDE;
-
- // ShellVideoDecoder implementation.
- void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- void Read(const ReadCB& read_cb) OVERRIDE;
- void Reset(const base::Closure& closure) OVERRIDE;
- void Stop(const base::Closure& closure) OVERRIDE;
- void NearlyUnderflow() OVERRIDE;
- void HaveEnoughFrames() OVERRIDE;
-
- private:
- enum DecoderState {
- kUninitialized,
- kNormal,
- kFlushCodec,
- kDecodeFinished,
- kShellDecodeError
- };
-
- void BufferReady(DemuxerStream::Status status,
- const scoped_refptr<DecoderBuffer>& buffer);
-
- // actually makes the call to the platform decoder to decode
- void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer);
- // the callback from the raw decoder indicates an operation has been finished.
- void DecodeCallback(ShellRawVideoDecoder::DecodeStatus status,
- const scoped_refptr<VideoFrame>& frame);
- // Posts a task to read from the demuxer stream.
- void ReadFromDemuxerStream();
- // Reset decoder and call |reset_cb_|.
- void DoReset();
- // called on decoder thread, fatal error on decode, we shut down decode
- void DecoderFatalError();
-
- DecoderState state_;
- scoped_refptr<base::MessageLoopProxy> media_pipeline_message_loop_;
- ShellRawVideoDecoderFactory* raw_video_decoder_factory_;
- scoped_refptr<DemuxerStream> demuxer_stream_;
- ReadCB read_cb_;
- base::Closure reset_cb_;
-
- scoped_ptr<ShellRawVideoDecoder> raw_decoder_;
-
- // TODO: ensure the demuxer can handle multiple EOS requests then remove this
- // hack.
- scoped_refptr<DecoderBuffer> eof_buffer_;
-
- scoped_refptr<DecoderBuffer> buffer_to_decode_;
-
- // All decoding tasks will be performed on this thread's message loop
- base::Thread decoder_thread_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ShellVideoDecoderImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SHELL_VIDEO_DECODER_IMPL_H_
diff --git a/src/media/filters/skcanvas_video_renderer.cc b/src/media/filters/skcanvas_video_renderer.cc
deleted file mode 100644
index 2b718de..0000000
--- a/src/media/filters/skcanvas_video_renderer.cc
+++ /dev/null
@@ -1,302 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/skcanvas_video_renderer.h"
-
-#include "base/logging.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-namespace media {
-
-void SkCanvasVideoRenderer::Paint(VideoFrame* video_frame, SkCanvas* canvas,
- const gfx::RectF& dest_rect, uint8_t alpha) {
- NOTREACHED();
-}
-
-} // namespace media
-
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-#include "media/base/video_frame.h"
-#include "media/base/yuv_convert.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkDevice.h"
-
-namespace media {
-
-static bool IsEitherYV12OrYV16(media::VideoFrame::Format format) {
- return format == media::VideoFrame::YV12 || format == media::VideoFrame::YV16;
-}
-
-static bool IsEitherYV12OrYV16OrNative(media::VideoFrame::Format format) {
- return IsEitherYV12OrYV16(format) ||
- format == media::VideoFrame::NATIVE_TEXTURE;
-}
-
-// CanFastPaint is a helper method to determine the conditions for fast
-// painting. The conditions are:
-// 1. No skew in canvas matrix.
-// 2. No flipping nor mirroring.
-// 3. Canvas has pixel format ARGB8888.
-// 4. Canvas is opaque.
-// 5. Frame format is YV12 or YV16.
-//
-// TODO(hclam): The fast paint method should support flipping and mirroring.
-// Disable the flipping and mirroring checks once we have it.
-static bool CanFastPaint(SkCanvas* canvas, uint8_t alpha,
- media::VideoFrame::Format format) {
- if (alpha != 0xFF || !IsEitherYV12OrYV16(format))
- return false;
-
- const SkMatrix& total_matrix = canvas->getTotalMatrix();
- // Perform the following checks here:
- // 1. Check for skewing factors of the transformation matrix. They should be
- // zero.
- // 2. Check for mirroring and flipping. Make sure they are greater than zero.
- if (SkScalarNearlyZero(total_matrix.getSkewX()) &&
- SkScalarNearlyZero(total_matrix.getSkewY()) &&
- total_matrix.getScaleX() > 0 &&
- total_matrix.getScaleY() > 0) {
- SkDevice* device = canvas->getDevice();
- const SkBitmap::Config config = device->config();
-
- if (config == SkBitmap::kARGB_8888_Config && device->isOpaque()) {
- return true;
- }
- }
-
- return false;
-}
-
-// Fast paint does YUV => RGB, scaling, blitting all in one step into the
-// canvas. It's not always safe and appropriate to perform fast paint.
-// CanFastPaint() is used to determine the conditions.
-static void FastPaint(
- const scoped_refptr<media::VideoFrame>& video_frame,
- SkCanvas* canvas,
- const SkRect& dest_rect) {
- DCHECK(IsEitherYV12OrYV16(video_frame->format())) << video_frame->format();
- DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane),
- video_frame->stride(media::VideoFrame::kVPlane));
-
- const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true);
- media::YUVType yuv_type = media::YV16;
- int y_shift = 0;
- if (video_frame->format() == media::VideoFrame::YV12) {
- yuv_type = media::YV12;
- y_shift = 1;
- }
-
- // Transform the destination rectangle to local coordinates.
- const SkMatrix& local_matrix = canvas->getTotalMatrix();
- SkRect local_dest_rect;
- local_matrix.mapRect(&local_dest_rect, dest_rect);
-
- // After projecting the destination rectangle to local coordinates, round
- // the projected rectangle to integer values, this will give us pixel values
- // of the rectangle.
- SkIRect local_dest_irect, local_dest_irect_saved;
- local_dest_rect.round(&local_dest_irect);
- local_dest_rect.round(&local_dest_irect_saved);
-
- // No point painting if the destination rect doesn't intersect with the
- // clip rect.
- if (!local_dest_irect.intersect(canvas->getTotalClip().getBounds()))
- return;
-
- // At this point |local_dest_irect| contains the rect that we should draw
- // to within the clipping rect.
-
- // Calculate the address for the top left corner of destination rect in
- // the canvas that we will draw to. The address is obtained by the base
- // address of the canvas shifted by "left" and "top" of the rect.
- uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) +
- local_dest_irect.fTop * bitmap.rowBytes() +
- local_dest_irect.fLeft * 4;
-
- // Project the clip rect to the original video frame, obtains the
- // dimensions of the projected clip rect, "left" and "top" of the rect.
- // The math here are all integer math so we won't have rounding error and
- // write outside of the canvas.
- // We have the assumptions of dest_rect.width() and dest_rect.height()
- // being non-zero, these are valid assumptions since finding intersection
- // above rejects empty rectangle so we just do a DCHECK here.
- DCHECK_NE(0, dest_rect.width());
- DCHECK_NE(0, dest_rect.height());
- size_t frame_clip_width = local_dest_irect.width() *
- video_frame->visible_rect().width() / local_dest_irect_saved.width();
- size_t frame_clip_height = local_dest_irect.height() *
- video_frame->visible_rect().height() / local_dest_irect_saved.height();
-
- // Project the "left" and "top" of the final destination rect to local
- // coordinates of the video frame, use these values to find the offsets
- // in the video frame to start reading.
- size_t frame_clip_left =
- video_frame->visible_rect().x() +
- (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) *
- video_frame->visible_rect().width() / local_dest_irect_saved.width();
- size_t frame_clip_top =
- video_frame->visible_rect().y() +
- (local_dest_irect.fTop - local_dest_irect_saved.fTop) *
- video_frame->visible_rect().height() / local_dest_irect_saved.height();
-
- // Use the "left" and "top" of the destination rect to locate the offset
- // in Y, U and V planes.
- size_t y_offset = (video_frame->stride(media::VideoFrame::kYPlane) *
- frame_clip_top) + frame_clip_left;
-
- // For format YV12, there is one U, V value per 2x2 block.
- // For format YV16, there is one U, V value per 2x1 block.
- size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
- (frame_clip_top >> y_shift)) + (frame_clip_left >> 1);
- uint8* frame_clip_y =
- video_frame->data(media::VideoFrame::kYPlane) + y_offset;
- uint8* frame_clip_u =
- video_frame->data(media::VideoFrame::kUPlane) + uv_offset;
- uint8* frame_clip_v =
- video_frame->data(media::VideoFrame::kVPlane) + uv_offset;
-
-// TODO: this class needs to die in a fire.
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- // TODO(hclam): do rotation and mirroring here.
- // TODO(fbarchard): switch filtering based on performance.
- bitmap.lockPixels();
- media::ScaleYUVToRGB32(frame_clip_y,
- frame_clip_u,
- frame_clip_v,
- dest_rect_pointer,
- frame_clip_width,
- frame_clip_height,
- local_dest_irect.width(),
- local_dest_irect.height(),
- video_frame->stride(media::VideoFrame::kYPlane),
- video_frame->stride(media::VideoFrame::kUPlane),
- bitmap.rowBytes(),
- yuv_type,
- media::ROTATE_0,
- media::FILTER_BILINEAR);
- bitmap.unlockPixels();
-#endif
-}
-
-// Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data.
-//
-// |bitmap| will be (re)allocated to match the dimensions of |video_frame|.
-static void ConvertVideoFrameToBitmap(
- const scoped_refptr<media::VideoFrame>& video_frame,
- SkBitmap* bitmap) {
- DCHECK(IsEitherYV12OrYV16OrNative(video_frame->format()))
- << video_frame->format();
- if (IsEitherYV12OrYV16(video_frame->format())) {
- DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane),
- video_frame->stride(media::VideoFrame::kVPlane));
- }
-
- // Check if |bitmap| needs to be (re)allocated.
- if (bitmap->isNull() ||
- bitmap->width() != video_frame->visible_rect().width() ||
- bitmap->height() != video_frame->visible_rect().height()) {
- bitmap->setConfig(SkBitmap::kARGB_8888_Config,
- video_frame->visible_rect().width(),
- video_frame->visible_rect().height());
- bitmap->allocPixels();
- bitmap->setIsVolatile(true);
- }
-
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- bitmap->lockPixels();
- if (IsEitherYV12OrYV16(video_frame->format())) {
- media::YUVType yuv_type = media::YV16;
- int y_shift = 0;
- if (video_frame->format() == media::VideoFrame::YV12) {
- yuv_type = media::YV12;
- y_shift = 1;
- }
-
- // Use the "left" and "top" of the destination rect to locate the offset
- // in Y, U and V planes.
- size_t y_offset = (video_frame->stride(media::VideoFrame::kYPlane) *
- video_frame->visible_rect().y()) +
- video_frame->visible_rect().x();
-
- // For format YV12, there is one U, V value per 2x2 block.
- // For format YV16, there is one U, V value per 2x1 block.
- size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
- (video_frame->visible_rect().y() >> y_shift)) +
- (video_frame->visible_rect().x() >> 1);
- uint8* frame_clip_y =
- video_frame->data(media::VideoFrame::kYPlane) + y_offset;
- uint8* frame_clip_u =
- video_frame->data(media::VideoFrame::kUPlane) + uv_offset;
- uint8* frame_clip_v =
- video_frame->data(media::VideoFrame::kVPlane) + uv_offset;
-
- media::ConvertYUVToRGB32(frame_clip_y,
- frame_clip_u,
- frame_clip_v,
- static_cast<uint8*>(bitmap->getPixels()),
- video_frame->visible_rect().width(),
- video_frame->visible_rect().height(),
- video_frame->stride(media::VideoFrame::kYPlane),
- video_frame->stride(media::VideoFrame::kUPlane),
- bitmap->rowBytes(),
- yuv_type);
- } else {
- DCHECK_EQ(video_frame->format(), media::VideoFrame::NATIVE_TEXTURE);
- video_frame->ReadPixelsFromNativeTexture(bitmap->getPixels());
- }
- bitmap->notifyPixelsChanged();
- bitmap->unlockPixels();
-#endif
-}
-
-SkCanvasVideoRenderer::SkCanvasVideoRenderer()
- : last_frame_timestamp_(media::kNoTimestamp()) {
-}
-
-SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {}
-
-void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::RectF& dest_rect,
- uint8_t alpha) {
- if (alpha == 0) {
- return;
- }
-
- SkRect dest;
- dest.set(dest_rect.x(), dest_rect.y(), dest_rect.right(), dest_rect.bottom());
-
- SkPaint paint;
- paint.setAlpha(alpha);
-
- // Paint black rectangle if there isn't a frame available or the
- // frame has an unexpected format.
- if (!video_frame || !IsEitherYV12OrYV16OrNative(video_frame->format())) {
- canvas->drawRect(dest, paint);
- return;
- }
-
- // Scale and convert to RGB in one step if we can.
- if (CanFastPaint(canvas, alpha, video_frame->format())) {
- FastPaint(video_frame, canvas, dest);
- return;
- }
-
- // Check if we should convert and update |last_frame_|.
- if (last_frame_.isNull() ||
- video_frame->GetTimestamp() != last_frame_timestamp_) {
- ConvertVideoFrameToBitmap(video_frame, &last_frame_);
- last_frame_timestamp_ = video_frame->GetTimestamp();
- }
-
- // Do a slower paint using |last_frame_|.
- paint.setFilterBitmap(true);
- canvas->drawBitmapRect(last_frame_, NULL, dest, &paint);
-}
-
-} // namespace media
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
diff --git a/src/media/filters/skcanvas_video_renderer.h b/src/media/filters/skcanvas_video_renderer.h
deleted file mode 100644
index 8dafcf3..0000000
--- a/src/media/filters/skcanvas_video_renderer.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_SKCANVAS_VIDEO_RENDERER_H_
-#define MEDIA_FILTERS_SKCANVAS_VIDEO_RENDERER_H_
-
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "ui/gfx/rect.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-
-class SkCanvas;
-
-namespace media {
-
-class VideoFrame;
-
-class MEDIA_EXPORT SkCanvasVideoRenderer {
- public:
- void Paint(VideoFrame* video_frame, SkCanvas* canvas,
- const gfx::RectF& dest_rect, uint8_t alpha);
-};
-
-} // namespace media
-
-#else // defined(__LB_SHELL__) || defined(COBALT)
-
-#include "third_party/skia/include/core/SkBitmap.h"
-
-class SkCanvas;
-
-namespace media {
-
-class VideoFrame;
-
-// Handles rendering of VideoFrames to SkCanvases, doing any necessary YUV
-// conversion and caching of resulting RGB bitmaps.
-class MEDIA_EXPORT SkCanvasVideoRenderer {
- public:
- SkCanvasVideoRenderer();
- ~SkCanvasVideoRenderer();
-
- // Paints |video_frame| on |canvas|, scaling the result to fit dimensions
- // specified by |dest_rect|.
- //
- // Black will be painted on |canvas| if |video_frame| is null.
- void Paint(media::VideoFrame* video_frame,
- SkCanvas* canvas,
- const gfx::RectF& dest_rect,
- uint8_t alpha);
-
- private:
- // An RGB bitmap and corresponding timestamp of the previously converted
- // video frame data.
- SkBitmap last_frame_;
- base::TimeDelta last_frame_timestamp_;
-
- DISALLOW_COPY_AND_ASSIGN(SkCanvasVideoRenderer);
-};
-
-} // namespace media
-
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
-#endif // MEDIA_FILTERS_SKCANVAS_VIDEO_RENDERER_H_
diff --git a/src/media/filters/skcanvas_video_renderer_unittest.cc b/src/media/filters/skcanvas_video_renderer_unittest.cc
deleted file mode 100644
index a50f266..0000000
--- a/src/media/filters/skcanvas_video_renderer_unittest.cc
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/base/video_frame.h"
-#include "media/base/video_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkDevice.h"
-#include "media/filters/skcanvas_video_renderer.h"
-
-using media::VideoFrame;
-
-namespace media {
-
-static const int kWidth = 320;
-static const int kHeight = 240;
-static const gfx::Rect kNaturalRect(0, 0, kWidth, kHeight);
-
-// Helper for filling a |canvas| with a solid |color|.
-void FillCanvas(SkCanvas* canvas, SkColor color) {
- const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true);
- bitmap.lockPixels();
- bitmap.eraseColor(color);
- bitmap.unlockPixels();
-}
-
-// Helper for returning the color of a solid |canvas|.
-SkColor GetColorAt(SkCanvas* canvas, int x, int y) {
- const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(false);
- bitmap.lockPixels();
- SkColor c = bitmap.getColor(x, y);
- bitmap.unlockPixels();
- return c;
-}
-
-SkColor GetColor(SkCanvas* canvas) {
- return GetColorAt(canvas, 0, 0);
-}
-
-class SkCanvasVideoRendererTest : public testing::Test {
- public:
- enum Color {
- kNone,
- kRed,
- kGreen,
- kBlue,
- };
-
- SkCanvasVideoRendererTest();
- virtual ~SkCanvasVideoRendererTest();
-
- // Paints to |canvas| using |renderer_| without any frame data.
- void PaintWithoutFrame(SkCanvas* canvas);
-
- // Paints the |video_frame| to the |canvas| using |renderer_|, setting the
- // color of |video_frame| to |color| first.
- void Paint(VideoFrame* video_frame, SkCanvas* canvas, Color color);
-
- // Getters for various frame sizes.
- VideoFrame* natural_frame() { return natural_frame_; }
- VideoFrame* larger_frame() { return larger_frame_; }
- VideoFrame* smaller_frame() { return smaller_frame_; }
- VideoFrame* cropped_frame() { return cropped_frame_; }
-
- // Getters for canvases that trigger the various painting paths.
- SkCanvas* fast_path_canvas() { return &fast_path_canvas_; }
- SkCanvas* slow_path_canvas() { return &slow_path_canvas_; }
-
- private:
- SkCanvasVideoRenderer renderer_;
-
- scoped_refptr<VideoFrame> natural_frame_;
- scoped_refptr<VideoFrame> larger_frame_;
- scoped_refptr<VideoFrame> smaller_frame_;
- scoped_refptr<VideoFrame> cropped_frame_;
-
- SkDevice fast_path_device_;
- SkCanvas fast_path_canvas_;
- SkDevice slow_path_device_;
- SkCanvas slow_path_canvas_;
-
- DISALLOW_COPY_AND_ASSIGN(SkCanvasVideoRendererTest);
-};
-
-SkCanvasVideoRendererTest::SkCanvasVideoRendererTest()
- : natural_frame_(VideoFrame::CreateBlackFrame(gfx::Size(kWidth, kHeight))),
- larger_frame_(VideoFrame::CreateBlackFrame(
- gfx::Size(kWidth * 2, kHeight * 2))),
- smaller_frame_(VideoFrame::CreateBlackFrame(
- gfx::Size(kWidth / 2, kHeight / 2))),
- cropped_frame_(VideoFrame::CreateFrame(
- VideoFrame::YV12,
- gfx::Size(16, 16),
- gfx::Rect(6, 6, 8, 6),
- gfx::Size(8, 6),
- base::TimeDelta::FromMilliseconds(4))),
- fast_path_device_(SkBitmap::kARGB_8888_Config, kWidth, kHeight, true),
- fast_path_canvas_(&fast_path_device_),
- slow_path_device_(SkBitmap::kARGB_8888_Config, kWidth, kHeight, false),
- slow_path_canvas_(&slow_path_device_) {
- // Give each frame a unique timestamp.
- natural_frame_->SetTimestamp(base::TimeDelta::FromMilliseconds(1));
- larger_frame_->SetTimestamp(base::TimeDelta::FromMilliseconds(2));
- smaller_frame_->SetTimestamp(base::TimeDelta::FromMilliseconds(3));
-
- // Make sure the cropped video frame's aspect ratio matches the output device.
- // Update cropped_frame_'s crop dimensions if this is not the case.
- EXPECT_EQ(cropped_frame()->natural_size().width() * kHeight,
- cropped_frame()->natural_size().height() * kWidth);
-
- // Fill in the cropped frame's entire data with colors:
- //
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // Bl Bl Bl Bl Bl Bl Bl Bl R R R R R R R R
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- // G G G G G G G G B B B B B B B B
- //
- // The visible crop of the frame (as set by its visible_rect_) has contents:
- //
- // Bl Bl R R R R R R
- // Bl Bl R R R R R R
- // G G B B B B B B
- // G G B B B B B B
- // G G B B B B B B
- // G G B B B B B B
- //
- // Each color region in the cropped frame is on a 2x2 block granularity, to
- // avoid sharing UV samples between regions.
-
- static const uint8 cropped_y_plane[] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 76, 76, 76, 76, 76,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- 149, 149, 149, 149, 149, 149, 149, 149, 29, 29, 29, 29, 29, 29, 29, 29,
- };
-
- static const uint8 cropped_u_plane[] = {
- 128, 128, 128, 128, 84, 84, 84, 84,
- 128, 128, 128, 128, 84, 84, 84, 84,
- 128, 128, 128, 128, 84, 84, 84, 84,
- 128, 128, 128, 128, 84, 84, 84, 84,
- 43, 43, 43, 43, 255, 255, 255, 255,
- 43, 43, 43, 43, 255, 255, 255, 255,
- 43, 43, 43, 43, 255, 255, 255, 255,
- 43, 43, 43, 43, 255, 255, 255, 255,
- };
- static const uint8 cropped_v_plane[] = {
- 128, 128, 128, 128, 255, 255, 255, 255,
- 128, 128, 128, 128, 255, 255, 255, 255,
- 128, 128, 128, 128, 255, 255, 255, 255,
- 128, 128, 128, 128, 255, 255, 255, 255,
- 21, 21, 21, 21, 107, 107, 107, 107,
- 21, 21, 21, 21, 107, 107, 107, 107,
- 21, 21, 21, 21, 107, 107, 107, 107,
- 21, 21, 21, 21, 107, 107, 107, 107,
- };
-
- media::CopyYPlane(cropped_y_plane, 16, 16, cropped_frame());
- media::CopyUPlane(cropped_u_plane, 8, 8, cropped_frame());
- media::CopyVPlane(cropped_v_plane, 8, 8, cropped_frame());
-}
-
-SkCanvasVideoRendererTest::~SkCanvasVideoRendererTest() {}
-
-void SkCanvasVideoRendererTest::PaintWithoutFrame(SkCanvas* canvas) {
- renderer_.Paint(NULL, canvas, kNaturalRect, 0xFF);
-}
-
-void SkCanvasVideoRendererTest::Paint(VideoFrame* video_frame,
- SkCanvas* canvas,
- Color color) {
- switch (color) {
- case kNone:
- break;
- case kRed:
- media::FillYUV(video_frame, 76, 84, 255);
- break;
- case kGreen:
- media::FillYUV(video_frame, 149, 43, 21);
- break;
- case kBlue:
- media::FillYUV(video_frame, 29, 255, 107);
- break;
- }
- renderer_.Paint(video_frame, canvas, kNaturalRect, 0xFF);
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_NoFrame) {
- // Test that black gets painted over canvas.
- FillCanvas(fast_path_canvas(), SK_ColorRED);
- PaintWithoutFrame(fast_path_canvas());
- EXPECT_EQ(SK_ColorBLACK, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_NoFrame) {
- // Test that black gets painted over canvas.
- FillCanvas(slow_path_canvas(), SK_ColorRED);
- PaintWithoutFrame(slow_path_canvas());
- EXPECT_EQ(SK_ColorBLACK, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_Natural) {
- Paint(natural_frame(), fast_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_Natural) {
- Paint(natural_frame(), slow_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_Larger) {
- Paint(natural_frame(), fast_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(fast_path_canvas()));
-
- Paint(larger_frame(), fast_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorBLUE, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_Larger) {
- Paint(natural_frame(), slow_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-
- Paint(larger_frame(), slow_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorBLUE, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_Smaller) {
- Paint(natural_frame(), fast_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(fast_path_canvas()));
-
- Paint(smaller_frame(), fast_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorBLUE, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_Smaller) {
- Paint(natural_frame(), slow_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-
- Paint(smaller_frame(), slow_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorBLUE, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_NoTimestamp) {
- VideoFrame* video_frame = natural_frame();
- video_frame->SetTimestamp(media::kNoTimestamp());
- Paint(video_frame, fast_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_NoTimestamp) {
- VideoFrame* video_frame = natural_frame();
- video_frame->SetTimestamp(media::kNoTimestamp());
- Paint(video_frame, slow_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_SameVideoFrame) {
- Paint(natural_frame(), fast_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(fast_path_canvas()));
-
- // Fast paints always get painted to the canvas.
- Paint(natural_frame(), fast_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorBLUE, GetColor(fast_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_SameVideoFrame) {
- Paint(natural_frame(), slow_path_canvas(), kRed);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-
- // Slow paints can get cached, expect the old color value.
- Paint(natural_frame(), slow_path_canvas(), kBlue);
- EXPECT_EQ(SK_ColorRED, GetColor(slow_path_canvas()));
-}
-
-TEST_F(SkCanvasVideoRendererTest, FastPaint_CroppedFrame) {
- Paint(cropped_frame(), fast_path_canvas(), kNone);
- // Check the corners.
- EXPECT_EQ(SK_ColorBLACK, GetColorAt(fast_path_canvas(), 0, 0));
- EXPECT_EQ(SK_ColorRED, GetColorAt(fast_path_canvas(), kWidth - 1, 0));
- EXPECT_EQ(SK_ColorGREEN, GetColorAt(fast_path_canvas(), 0, kHeight - 1));
- EXPECT_EQ(SK_ColorBLUE, GetColorAt(fast_path_canvas(), kWidth - 1,
- kHeight - 1));
- // Check the interior along the border between color regions. Note that we're
- // bilinearly upscaling, so we'll need to take care to pick sample points that
- // are just outside the "zone of resampling".
- // TODO(sheu): commenting out two checks due to http://crbug.com/158462.
-#if 0
- EXPECT_EQ(SK_ColorBLACK, GetColorAt(fast_path_canvas(), kWidth * 1 / 8 - 1,
- kHeight * 1 / 6 - 1));
-#endif
- EXPECT_EQ(SK_ColorRED, GetColorAt(fast_path_canvas(), kWidth * 3 / 8,
- kHeight * 1 / 6 - 1));
-#if 0
- EXPECT_EQ(SK_ColorGREEN, GetColorAt(fast_path_canvas(), kWidth * 1 / 8 - 1,
- kHeight * 3 / 6));
-#endif
- EXPECT_EQ(SK_ColorBLUE, GetColorAt(fast_path_canvas(), kWidth * 3 / 8,
- kHeight * 3 / 6));
-}
-
-TEST_F(SkCanvasVideoRendererTest, SlowPaint_CroppedFrame) {
- Paint(cropped_frame(), slow_path_canvas(), kNone);
- // Check the corners.
- EXPECT_EQ(SK_ColorBLACK, GetColorAt(slow_path_canvas(), 0, 0));
- EXPECT_EQ(SK_ColorRED, GetColorAt(slow_path_canvas(), kWidth - 1, 0));
- EXPECT_EQ(SK_ColorGREEN, GetColorAt(slow_path_canvas(), 0, kHeight - 1));
- EXPECT_EQ(SK_ColorBLUE, GetColorAt(slow_path_canvas(), kWidth - 1,
- kHeight - 1));
- // Check the interior along the border between color regions. Note that we're
- // bilinearly upscaling, so we'll need to take care to pick sample points that
- // are just outside the "zone of resampling".
- EXPECT_EQ(SK_ColorBLACK, GetColorAt(slow_path_canvas(), kWidth * 1 / 8 - 1,
- kHeight * 1 / 6 - 1));
- EXPECT_EQ(SK_ColorRED, GetColorAt(slow_path_canvas(), kWidth * 3 / 8,
- kHeight * 1 / 6 - 1));
- EXPECT_EQ(SK_ColorGREEN, GetColorAt(slow_path_canvas(), kWidth * 1 / 8 - 1,
- kHeight * 3 / 6));
- EXPECT_EQ(SK_ColorBLUE, GetColorAt(slow_path_canvas(), kWidth * 3 / 8,
- kHeight * 3 / 6));
-}
-
-} // namespace media
diff --git a/src/media/filters/source_buffer_stream.cc b/src/media/filters/source_buffer_stream.cc
deleted file mode 100644
index dfd1596..0000000
--- a/src/media/filters/source_buffer_stream.cc
+++ /dev/null
@@ -1,1730 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/source_buffer_stream.h"
-
-#include <algorithm>
-#include <map>
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/stl_util.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/shell_media_platform.h"
-#endif
-
-namespace media {
-
-// Helper class representing a range of buffered data. All buffers in a
-// SourceBufferRange are ordered sequentially in presentation order with no
-// gaps.
-class SourceBufferRange {
- public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
-
- // Returns the maximum distance in time between any buffer seen in this
- // stream. Used to estimate the duration of a buffer if its duration is not
- // known.
- typedef base::Callback<base::TimeDelta()> InterbufferDistanceCB;
-
- // Creates a source buffer range with |new_buffers|. |new_buffers| cannot be
- // empty and the front of |new_buffers| must be a keyframe.
- // |media_segment_start_time| refers to the starting timestamp for the media
- // segment to which these buffers belong.
- SourceBufferRange(const BufferQueue& new_buffers,
- base::TimeDelta media_segment_start_time,
- const InterbufferDistanceCB& interbuffer_distance_cb);
-
- // Appends |buffers| to the end of the range and updates |keyframe_map_| as
- // it encounters new keyframes. Assumes |buffers| belongs at the end of the
- // range.
- void AppendBuffersToEnd(const BufferQueue& buffers);
- bool CanAppendBuffersToEnd(const BufferQueue& buffers) const;
-
- // Appends the buffers from |range| into this range.
- // The first buffer in |range| must come directly after the last buffer
- // in this range.
- // If |transfer_current_position| is true, |range|'s |next_buffer_index_|
- // is transfered to this SourceBufferRange.
- void AppendRangeToEnd(const SourceBufferRange& range,
- bool transfer_current_position);
- bool CanAppendRangeToEnd(const SourceBufferRange& range) const;
-
- // Updates |next_buffer_index_| to point to the Buffer containing |timestamp|.
- // Assumes |timestamp| is valid and in this range.
- void Seek(base::TimeDelta timestamp);
-
- // Updates |next_buffer_index_| to point to next keyframe after or equal to
- // |timestamp|.
- void SeekAheadTo(base::TimeDelta timestamp);
-
- // Updates |next_buffer_index_| to point to next keyframe strictly after
- // |timestamp|.
- void SeekAheadPast(base::TimeDelta timestamp);
-
- // Seeks to the beginning of the range.
- void SeekToStart();
-
- // Finds the next keyframe from |buffers_| after |timestamp| (or at
- // |timestamp| if |is_exclusive| is false) and creates and returns a new
- // SourceBufferRange with the buffers from that keyframe onward.
- // The buffers in the new SourceBufferRange are moved out of this range. If
- // there is no keyframe after |timestamp|, SplitRange() returns null and this
- // range is unmodified.
- SourceBufferRange* SplitRange(base::TimeDelta timestamp, bool is_exclusive);
-
- // Deletes the buffers from this range starting at |timestamp|, exclusive if
- // |is_exclusive| is true, inclusive otherwise.
- // Resets |next_buffer_index_| if the buffer at |next_buffer_index_| was
- // deleted, and deletes the |keyframe_map_| entries for the buffers that
- // were removed.
- // |deleted_buffers| contains the buffers that were deleted from this range,
- // starting at the buffer that had been at |next_buffer_index_|.
- // Returns true if the |next_buffer_index_| is reset. Note that this method
- // may return true even if it does not add any buffers to |deleted_buffers|.
- // This indicates that the range had not buffered |next_buffer_index_|, but
- // a buffer at that position would have been deleted.
- bool TruncateAt(base::TimeDelta timestamp,
- BufferQueue* deleted_buffers, bool is_exclusive);
- // Deletes all buffers in range.
- bool DeleteAll(BufferQueue* deleted_buffers);
-
- // Deletes a GOP from the front or back of the range and moves these
- // buffers into |deleted_buffers|. Returns the number of bytes deleted from
- // the range (i.e. the size in bytes of |deleted_buffers|).
- int DeleteGOPFromFront(BufferQueue* deleted_buffers);
- int DeleteGOPFromBack(BufferQueue* deleted_buffers);
-
- // Indicates whether the GOP at the beginning or end of the range contains the
- // next buffer position.
- bool FirstGOPContainsNextBufferPosition() const;
- bool LastGOPContainsNextBufferPosition() const;
-
- // Updates |out_buffer| with the next buffer in presentation order. Seek()
- // must be called before calls to GetNextBuffer(), and buffers are returned
- // in order from the last call to Seek(). Returns true if |out_buffer| is
- // filled with a valid buffer, false if there is not enough data to fulfill
- // the request.
- bool GetNextBuffer(scoped_refptr<StreamParserBuffer>* out_buffer);
- bool HasNextBuffer() const;
-
- // Returns the config ID for the buffer that will be returned by
- // GetNextBuffer().
- int GetNextConfigId() const;
-
- // Returns true if the range knows the position of the next buffer it should
- // return, i.e. it has been Seek()ed. This does not necessarily mean that it
- // has the next buffer yet.
- bool HasNextBufferPosition() const;
-
- // Resets this range to an "unseeked" state.
- void ResetNextBufferPosition();
-
- // Returns the timestamp of the next buffer that will be returned from
- // GetNextBuffer(), or kNoTimestamp() if the timestamp is unknown.
- base::TimeDelta GetNextTimestamp() const;
-
- // Returns the start timestamp of the range.
- base::TimeDelta GetStartTimestamp() const;
-
- // Returns the timestamp of the last buffer in the range.
- base::TimeDelta GetEndTimestamp() const;
-
- // Returns the timestamp for the end of the buffered region in this range.
- // This is an approximation if the duration for the last buffer in the range
- // is unset.
- base::TimeDelta GetBufferedEndTimestamp() const;
-
- // Returns whether a buffer with a starting timestamp of |timestamp| would
- // belong in this range. This includes a buffer that would be appended to
- // the end of the range.
- bool BelongsToRange(base::TimeDelta timestamp) const;
-
- // Returns true if the range has enough data to seek to the specified
- // |timestamp|, false otherwise.
- bool CanSeekTo(base::TimeDelta timestamp) const;
-
- // Returns true if this range's buffered timespan completely overlaps the
- // buffered timespan of |range|.
- bool CompletelyOverlaps(const SourceBufferRange& range) const;
-
- // Returns true if the end of this range contains buffers that overlaps with
- // the beginning of |range|.
- bool EndOverlaps(const SourceBufferRange& range) const;
-
- // Returns true if |timestamp| is the timestamp of the next buffer in
- // sequence after |buffer|, false otherwise.
- bool IsNextInSequence(
- const scoped_refptr<media::StreamParserBuffer>& buffer,
- base::TimeDelta timestamp) const;
-
- int size_in_bytes() const { return size_in_bytes_; }
-
- private:
- typedef std::map<base::TimeDelta, int> KeyframeMap;
-
- // Seeks the range to the next keyframe after |timestamp|. If
- // |skip_given_timestamp| is true, the seek will go to a keyframe with a
- // timestamp strictly greater than |timestamp|.
- void SeekAhead(base::TimeDelta timestamp, bool skip_given_timestamp);
-
- // Returns an iterator in |buffers_| pointing to the buffer at |timestamp|.
- // If |skip_given_timestamp| is true, this returns the first buffer with
- // timestamp greater than |timestamp|.
- BufferQueue::iterator GetBufferItrAt(
- base::TimeDelta timestamp, bool skip_given_timestamp);
-
- // Returns an iterator in |keyframe_map_| pointing to the next keyframe after
- // |timestamp|. If |skip_given_timestamp| is true, this returns the first
- // keyframe with a timestamp strictly greater than |timestamp|.
- KeyframeMap::iterator GetFirstKeyframeAt(
- base::TimeDelta timestamp, bool skip_given_timestamp);
-
- // Returns an iterator in |keyframe_map_| pointing to the first keyframe
- // before or at |timestamp|.
- KeyframeMap::iterator GetFirstKeyframeBefore(base::TimeDelta timestamp);
-
- // Helper method to delete buffers in |buffers_| starting at
- // |starting_point|, an iterator in |buffers_|.
- bool TruncateAt(const BufferQueue::iterator& starting_point,
- BufferQueue* deleted_buffers);
-
- // Frees the buffers in |buffers_| from [|start_point|,|ending_point|) and
- // updates the |size_in_bytes_| accordingly. Does not update |keyframe_map_|.
- void FreeBufferRange(const BufferQueue::iterator& starting_point,
- const BufferQueue::iterator& ending_point);
-
- // Returns the distance in time estimating how far from the beginning or end
- // of this range a buffer can be to considered in the range.
- base::TimeDelta GetFudgeRoom() const;
-
- // Returns the approximate duration of a buffer in this range.
- base::TimeDelta GetApproximateDuration() const;
-
- // An ordered list of buffers in this range.
- BufferQueue buffers_;
-
- // Maps keyframe timestamps to its index position in |buffers_|.
- KeyframeMap keyframe_map_;
-
- // Index base of all positions in |keyframe_map_|. In other words, the
- // real position of entry |k| of |keyframe_map_| in the range is:
- // keyframe_map_[k] - keyframe_map_index_base_
- int keyframe_map_index_base_;
-
- // Index into |buffers_| for the next buffer to be returned by
- // GetNextBuffer(), set to -1 before Seek().
- int next_buffer_index_;
-
- // True if the range needs to wait for the next keyframe to be appended before
- // returning buffers from GetNextBuffer().
- bool waiting_for_keyframe_;
-
- // If |waiting_for_keyframe_| is true, this range will wait for the next
- // keyframe with timestamp >= |next_keyframe_timestamp_|.
- base::TimeDelta next_keyframe_timestamp_;
-
- // If the first buffer in this range is the beginning of a media segment,
- // |media_segment_start_time_| is the time when the media segment begins.
- // |media_segment_start_time_| may be <= the timestamp of the first buffer in
- // |buffers_|. |media_segment_start_time_| is kNoTimestamp() if this range
- // does not start at the beginning of a media segment, which can only happen
- // garbage collection or after an end overlap that results in a split range
- // (we don't have a way of knowing the media segment timestamp for the new
- // range).
- base::TimeDelta media_segment_start_time_;
-
- // Called to get the largest interbuffer distance seen so far in the stream.
- InterbufferDistanceCB interbuffer_distance_cb_;
-
- // Stores the amount of memory taken up by the data in |buffers_|.
- int size_in_bytes_;
-
- DISALLOW_COPY_AND_ASSIGN(SourceBufferRange);
-};
-
-} // namespace media
-
-// Helper method that returns true if |ranges| is sorted in increasing order,
-// false otherwise.
-static bool IsRangeListSorted(
- const std::list<media::SourceBufferRange*>& ranges) {
- base::TimeDelta prev = media::kNoTimestamp();
- for (std::list<media::SourceBufferRange*>::const_iterator itr =
- ranges.begin(); itr != ranges.end(); ++itr) {
- if (prev != media::kNoTimestamp() && prev >= (*itr)->GetStartTimestamp())
- return false;
- prev = (*itr)->GetEndTimestamp();
- }
- return true;
-}
-
-// Comparison function for two Buffers based on timestamp.
-static bool BufferComparator(
- const scoped_refptr<media::StreamParserBuffer>& first,
- const scoped_refptr<media::StreamParserBuffer>& second) {
- return first->GetDecodeTimestamp() < second->GetDecodeTimestamp();
-}
-
-// Returns an estimate of how far from the beginning or end of a range a buffer
-// can be to still be considered in the range, given the |approximate_duration|
-// of a buffer in the stream.
-static base::TimeDelta ComputeFudgeRoom(base::TimeDelta approximate_duration) {
- // Because we do not know exactly when is the next timestamp, any buffer
- // that starts within 2x the approximate duration of a buffer is considered
- // within this range.
- return 2 * approximate_duration;
-}
-
-// An arbitrarily-chosen number to estimate the duration of a buffer if none
-// is set and there's not enough information to get a better estimate.
-static int kDefaultBufferDurationInMs = 125;
-
-// The amount of time the beginning of the buffered data can differ from the
-// start time in order to still be considered the start of stream.
-static base::TimeDelta kSeekToStartFudgeRoom() {
- return base::TimeDelta::FromMilliseconds(1000);
-}
-
-// The maximum amount of data in bytes the stream will keep in memory.
-// 12MB: approximately 5 minutes of 320Kbps content.
-// 150MB: approximately 5 minutes of 4Mbps content.
-#if !defined(__LB_SHELL__) || defined(COBALT)
-static const int kDefaultAudioMemoryLimit = 12 * 1024 * 1024;
-static const int kDefaultVideoMemoryLimit = 150 * 1024 * 1024;
-#endif
-
-namespace media {
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-SourceBufferStream::SourceBufferStream(
- const AudioDecoderConfig& audio_config,
- const LogCB& log_cb)
- : log_cb_(log_cb),
- current_config_index_(0),
- append_config_index_(0),
- seek_pending_(false),
- seek_buffer_timestamp_(kNoTimestamp()),
- selected_range_(NULL),
- media_segment_start_time_(kNoTimestamp()),
- range_for_next_append_(ranges_.end()),
- new_media_segment_(false),
- last_buffer_timestamp_(kNoTimestamp()),
- max_interbuffer_distance_(kNoTimestamp()),
-#if defined(__LB_SHELL__) || defined(COBALT)
- memory_limit_(ShellMediaPlatform::Instance()->
- GetSourceBufferStreamAudioMemoryLimit()),
-#else // defined(__LB_SHELL__) || defined(COBALT)
- memory_limit_(kDefaultAudioMemoryLimit),
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- config_change_pending_(false) {
- DCHECK(audio_config.IsValidConfig());
- audio_configs_.push_back(new AudioDecoderConfig());
- audio_configs_.back()->CopyFrom(audio_config);
-}
-
-SourceBufferStream::SourceBufferStream(
- const VideoDecoderConfig& video_config,
- const LogCB& log_cb)
- : log_cb_(log_cb),
- current_config_index_(0),
- append_config_index_(0),
- seek_pending_(false),
- seek_buffer_timestamp_(kNoTimestamp()),
- selected_range_(NULL),
- media_segment_start_time_(kNoTimestamp()),
- range_for_next_append_(ranges_.end()),
- new_media_segment_(false),
- last_buffer_timestamp_(kNoTimestamp()),
- max_interbuffer_distance_(kNoTimestamp()),
-#if defined(__LB_SHELL__) || defined(COBALT)
- memory_limit_(ShellMediaPlatform::Instance()->
- GetSourceBufferStreamVideoMemoryLimit()),
-#else // defined(__LB_SHELL__) || defined(COBALT)
- memory_limit_(kDefaultVideoMemoryLimit),
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- config_change_pending_(false) {
- DCHECK(video_config.IsValidConfig());
- video_configs_.push_back(new VideoDecoderConfig());
- video_configs_.back()->CopyFrom(video_config);
-}
-#else
-SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config,
- const LogCB& log_cb)
- : log_cb_(log_cb),
- current_config_index_(0),
- append_config_index_(0),
- seek_pending_(false),
- seek_buffer_timestamp_(kNoTimestamp()),
- selected_range_(NULL),
- media_segment_start_time_(kNoTimestamp()),
- range_for_next_append_(ranges_.end()),
- new_media_segment_(false),
- last_buffer_timestamp_(kNoTimestamp()),
- max_interbuffer_distance_(kNoTimestamp()),
- memory_limit_(kDefaultAudioMemoryLimit),
- config_change_pending_(false) {
- DCHECK(audio_config.IsValidConfig());
- audio_configs_.push_back(new AudioDecoderConfig());
- audio_configs_.back()->CopyFrom(audio_config);
-}
-
-SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config,
- const LogCB& log_cb)
- : log_cb_(log_cb),
- current_config_index_(0),
- append_config_index_(0),
- seek_pending_(false),
- seek_buffer_timestamp_(kNoTimestamp()),
- selected_range_(NULL),
- media_segment_start_time_(kNoTimestamp()),
- range_for_next_append_(ranges_.end()),
- new_media_segment_(false),
- last_buffer_timestamp_(kNoTimestamp()),
- max_interbuffer_distance_(kNoTimestamp()),
- memory_limit_(kDefaultVideoMemoryLimit),
- config_change_pending_(false) {
- DCHECK(video_config.IsValidConfig());
- video_configs_.push_back(new VideoDecoderConfig());
- video_configs_.back()->CopyFrom(video_config);
-}
-#endif
-
-SourceBufferStream::~SourceBufferStream() {
- while (!ranges_.empty()) {
- delete ranges_.front();
- ranges_.pop_front();
- }
-
- STLDeleteElements(&audio_configs_);
- STLDeleteElements(&video_configs_);
-}
-
-void SourceBufferStream::OnNewMediaSegment(
- base::TimeDelta media_segment_start_time) {
- media_segment_start_time_ = media_segment_start_time;
- new_media_segment_ = true;
-
- RangeList::iterator last_range = range_for_next_append_;
- range_for_next_append_ = FindExistingRangeFor(media_segment_start_time);
-
- // Only reset |last_buffer_timestamp_| if this new media segment is not
- // adjacent to the previous media segment appended to the stream.
- if (range_for_next_append_ == ranges_.end() ||
- !AreAdjacentInSequence(
- last_buffer_timestamp_, media_segment_start_time)) {
- last_buffer_timestamp_ = kNoTimestamp();
- } else {
- DCHECK(last_range == range_for_next_append_);
- }
-}
-
-bool SourceBufferStream::Append(
- const SourceBufferStream::BufferQueue& buffers) {
- DCHECK(!buffers.empty());
- DCHECK(media_segment_start_time_ != kNoTimestamp());
-
- // Log if new media segment doesn't begin with a keyframe.
- if (new_media_segment_ && !buffers.front()->IsKeyframe()) {
- MEDIA_LOG(log_cb_) << "Media segment did not begin with keyframe.";
- }
-
- // Buffers within a media segment should be monotonically increasing.
- if (!IsMonotonicallyIncreasing(buffers)) {
- MEDIA_LOG(log_cb_) << "Buffers were not monotonically increasing.";
- return false;
- }
-
- if (media_segment_start_time_ < base::TimeDelta() ||
- buffers.front()->GetDecodeTimestamp() < base::TimeDelta()) {
- MEDIA_LOG(log_cb_)
- << "Cannot append a media segment with negative timestamps.";
- return false;
- }
-
- UpdateMaxInterbufferDistance(buffers);
- SetConfigIds(buffers);
-
- // Save a snapshot of stream state before range modifications are made.
- base::TimeDelta next_buffer_timestamp = GetNextBufferTimestamp();
- base::TimeDelta end_buffer_timestamp = GetEndBufferTimestamp();
-
- bool deleted_next_buffer = false;
- BufferQueue deleted_buffers;
-
- RangeList::iterator range_for_new_buffers = range_for_next_append_;
- // If there's a range for |buffers|, insert |buffers| accordingly. Otherwise,
- // create a new range with |buffers|.
- if (range_for_new_buffers != ranges_.end()) {
- InsertIntoExistingRange(range_for_new_buffers, buffers,
- &deleted_next_buffer, &deleted_buffers);
- } else {
- DCHECK(new_media_segment_);
-#if defined(__LB_SHELL__) || defined(COBALT)
- range_for_new_buffers =
- AddToRanges(new SourceBufferRange(
- buffers, media_segment_start_time_,
- base::Bind(&SourceBufferStream::GetMaxInterbufferDistance,
- base::Unretained(this))));
-#else
- range_for_new_buffers =
- AddToRanges(new SourceBufferRange(
- buffers, media_segment_start_time_,
- base::Bind(&SourceBufferStream::GetMaxInterbufferDistance,
- base::Unretained(this))));
-#endif
- }
-
- range_for_next_append_ = range_for_new_buffers;
- new_media_segment_ = false;
- last_buffer_timestamp_ = buffers.back()->GetDecodeTimestamp();
-
- // Resolve overlaps.
- ResolveCompleteOverlaps(
- range_for_new_buffers, &deleted_next_buffer, &deleted_buffers);
- ResolveEndOverlap(
- range_for_new_buffers, &deleted_next_buffer, &deleted_buffers);
- MergeWithAdjacentRangeIfNecessary(range_for_new_buffers);
-
- // Seek to try to fulfill a previous call to Seek().
- if (seek_pending_) {
- DCHECK(!selected_range_);
- DCHECK(!deleted_next_buffer);
- Seek(seek_buffer_timestamp_);
- }
-
- // Seek because the Append() has deleted the buffer that would have been
- // returned in the next call to GetNextBuffer().
- if (deleted_next_buffer) {
- DCHECK(!seek_pending_);
- SetSelectedRange(*range_for_new_buffers);
- if (next_buffer_timestamp != kNoTimestamp()) {
- // Seek ahead to the keyframe at or after |next_buffer_timestamp|, the
- // timestamp of the deleted next buffer.
- selected_range_->SeekAheadTo(next_buffer_timestamp);
- // Update track buffer with non-keyframe buffers leading up to the current
- // position of |selected_range_|.
- PruneTrackBuffer();
- if (!deleted_buffers.empty())
- UpdateTrackBuffer(deleted_buffers);
- } else {
- // If |next_buffer_timestamp| is kNoTimestamp(), it means the range
- // we've overlapped didn't actually have the next buffer buffered, and it
- // was waiting for the next buffer whose timestamp was greater than
- // |end_buffer_timestamp|. Seek the |selected_range_| to the next keyframe
- // after |end_buffer_timestamp|.
- DCHECK(track_buffer_.empty());
- DCHECK(deleted_buffers.empty());
- selected_range_->SeekAheadPast(end_buffer_timestamp);
- }
- }
-
- GarbageCollectIfNeeded();
-
- DCHECK(IsRangeListSorted(ranges_));
- DCHECK(OnlySelectedRangeIsSeeked());
- return true;
-}
-
-void SourceBufferStream::ResetSeekState() {
- SetSelectedRange(NULL);
- track_buffer_.clear();
- config_change_pending_ = false;
-}
-
-bool SourceBufferStream::ShouldSeekToStartOfBuffered(
- base::TimeDelta seek_timestamp) const {
- if (ranges_.empty())
- return false;
- base::TimeDelta beginning_of_buffered =
- ranges_.front()->GetStartTimestamp();
- return (seek_timestamp <= beginning_of_buffered &&
- beginning_of_buffered < kSeekToStartFudgeRoom());
-}
-
-bool SourceBufferStream::IsMonotonicallyIncreasing(
- const BufferQueue& buffers) const {
- DCHECK(!buffers.empty());
- base::TimeDelta prev_timestamp = last_buffer_timestamp_;
- for (BufferQueue::const_iterator itr = buffers.begin();
- itr != buffers.end(); ++itr) {
- base::TimeDelta current_timestamp = (*itr)->GetDecodeTimestamp();
- DCHECK(current_timestamp != kNoTimestamp());
-
- if (prev_timestamp != kNoTimestamp() && current_timestamp < prev_timestamp)
- return false;
-
- prev_timestamp = current_timestamp;
- }
- return true;
-}
-
-bool SourceBufferStream::OnlySelectedRangeIsSeeked() const {
- for (RangeList::const_iterator itr = ranges_.begin();
- itr != ranges_.end(); ++itr) {
- if ((*itr)->HasNextBufferPosition() && (*itr) != selected_range_)
- return false;
- }
- return !selected_range_ || selected_range_->HasNextBufferPosition();
-}
-
-void SourceBufferStream::UpdateMaxInterbufferDistance(
- const BufferQueue& buffers) {
- DCHECK(!buffers.empty());
- base::TimeDelta prev_timestamp = last_buffer_timestamp_;
- for (BufferQueue::const_iterator itr = buffers.begin();
- itr != buffers.end(); ++itr) {
- base::TimeDelta current_timestamp = (*itr)->GetDecodeTimestamp();
- DCHECK(current_timestamp != kNoTimestamp());
-
- base::TimeDelta interbuffer_distance;
- // WebMStreamParser in M25 doesn't guarantee that a duration is always set.
- if ((*itr)->GetDuration() != kNoTimestamp())
- interbuffer_distance = (*itr)->GetDuration();
-
- if (prev_timestamp != kNoTimestamp()) {
- interbuffer_distance =
- std::max(current_timestamp - prev_timestamp, interbuffer_distance);
- }
-
- if (interbuffer_distance > base::TimeDelta()) {
- if (max_interbuffer_distance_ == kNoTimestamp()) {
- max_interbuffer_distance_ = interbuffer_distance;
- } else {
- max_interbuffer_distance_ =
- std::max(max_interbuffer_distance_, interbuffer_distance);
- }
- }
- prev_timestamp = current_timestamp;
- }
-}
-
-void SourceBufferStream::SetConfigIds(const BufferQueue& buffers) {
- for (BufferQueue::const_iterator itr = buffers.begin();
- itr != buffers.end(); ++itr) {
- (*itr)->SetConfigId(append_config_index_);
- }
-}
-
-void SourceBufferStream::GarbageCollectIfNeeded() {
- // Compute size of |ranges_|.
- int ranges_size = 0;
- for (RangeList::iterator itr = ranges_.begin(); itr != ranges_.end(); ++itr)
- ranges_size += (*itr)->size_in_bytes();
-
- // Return if we're under or at the memory limit.
- if (ranges_size <= memory_limit_)
- return;
-
- LogMediaSourceTimeRanges("before garbage collect", GetBufferedTime());
-
- int bytes_to_free = ranges_size - memory_limit_;
-
- // Begin deleting from the front.
- int bytes_freed = FreeBuffers(bytes_to_free, false);
-
- LogMediaSourceTimeRanges("after garbage collect from front",
- GetBufferedTime());
-
- // Begin deleting from the back.
- if (bytes_to_free - bytes_freed > 0) {
- FreeBuffers(bytes_to_free - bytes_freed, true);
- LogMediaSourceTimeRanges("after garbage collect from back",
- GetBufferedTime());
- }
-}
-
-int SourceBufferStream::FreeBuffers(int total_bytes_to_free,
- bool reverse_direction) {
- DCHECK_GT(total_bytes_to_free, 0);
- int bytes_to_free = total_bytes_to_free;
- int bytes_freed = 0;
-
- // This range will save the last GOP appended to |range_for_next_append_|
- // if the buffers surrounding it get deleted during garbage collection.
- SourceBufferRange* new_range_for_append = NULL;
-
- while (!ranges_.empty() && bytes_to_free > 0) {
- SourceBufferRange* current_range = NULL;
- BufferQueue buffers;
- int bytes_deleted = 0;
-
- if (reverse_direction) {
- current_range = ranges_.back();
- if (current_range->LastGOPContainsNextBufferPosition()) {
- DCHECK_EQ(current_range, selected_range_);
- break;
- }
- bytes_deleted = current_range->DeleteGOPFromBack(&buffers);
- } else {
- current_range = ranges_.front();
- if (current_range->FirstGOPContainsNextBufferPosition()) {
- DCHECK_EQ(current_range, selected_range_);
- break;
- }
- bytes_deleted = current_range->DeleteGOPFromFront(&buffers);
- }
-
- // Check to see if we've just deleted the GOP that was last appended.
- if (buffers.back()->GetDecodeTimestamp() == last_buffer_timestamp_) {
- DCHECK(last_buffer_timestamp_ != kNoTimestamp());
- DCHECK(!new_range_for_append);
- // Create a new range containing these buffers.
-#if defined(__LB_SHELL__) || defined(COBALT)
- new_range_for_append = new SourceBufferRange(
- buffers, kNoTimestamp(),
- base::Bind(&SourceBufferStream::GetMaxInterbufferDistance,
- base::Unretained(this)));
-#else
- new_range_for_append = new SourceBufferRange(
- buffers, kNoTimestamp(),
- base::Bind(&SourceBufferStream::GetMaxInterbufferDistance,
- base::Unretained(this)));
-#endif
- range_for_next_append_ = ranges_.end();
- } else {
- bytes_to_free -= bytes_deleted;
- bytes_freed += bytes_deleted;
- }
-
- if (current_range->size_in_bytes() == 0) {
- DCHECK_NE(current_range, selected_range_);
- DCHECK(range_for_next_append_ == ranges_.end() ||
- *range_for_next_append_ != current_range);
- delete current_range;
- reverse_direction ? ranges_.pop_back() : ranges_.pop_front();
- }
- }
-
- // Insert |new_range_for_append| into |ranges_|, if applicable.
- if (new_range_for_append) {
- range_for_next_append_ = AddToRanges(new_range_for_append);
- DCHECK(range_for_next_append_ != ranges_.end());
-
- // Check to see if we need to merge |new_range_for_append| with the range
- // before or after it. |new_range_for_append| is created whenever the last
- // GOP appended is encountered, regardless of whether any buffers after it
- // are ultimately deleted. Merging is necessary if there were no buffers
- // (or very few buffers) deleted after creating |new_range_for_append|.
- if (range_for_next_append_ != ranges_.begin()) {
- RangeList::iterator range_before_next = range_for_next_append_;
- --range_before_next;
- MergeWithAdjacentRangeIfNecessary(range_before_next);
- }
- MergeWithAdjacentRangeIfNecessary(range_for_next_append_);
- }
- return bytes_freed;
-}
-
-void SourceBufferStream::InsertIntoExistingRange(
- const RangeList::iterator& range_for_new_buffers_itr,
- const BufferQueue& new_buffers,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers) {
- DCHECK(deleted_next_buffer);
- DCHECK(deleted_buffers);
-
- SourceBufferRange* range_for_new_buffers = *range_for_new_buffers_itr;
-
- if (last_buffer_timestamp_ != kNoTimestamp()) {
- // Clean up the old buffers between the last appended buffer and the
- // beginning of |new_buffers|.
- *deleted_next_buffer =
- DeleteBetween(
- range_for_new_buffers, last_buffer_timestamp_,
- new_buffers.front()->GetDecodeTimestamp(), true,
- deleted_buffers);
- }
-
- // If we cannot append the |new_buffers| to the end of the existing range,
- // this is either a start overlap or an middle overlap. Delete the buffers
- // that |new_buffers| overlaps.
- if (!range_for_new_buffers->CanAppendBuffersToEnd(new_buffers)) {
- *deleted_next_buffer |=
- DeleteBetween(
- range_for_new_buffers, new_buffers.front()->GetDecodeTimestamp(),
- new_buffers.back()->GetDecodeTimestamp(), false,
- deleted_buffers);
- }
-
- range_for_new_buffers->AppendBuffersToEnd(new_buffers);
-}
-
-bool SourceBufferStream::DeleteBetween(
- SourceBufferRange* range, base::TimeDelta start_timestamp,
- base::TimeDelta end_timestamp, bool is_range_exclusive,
- BufferQueue* deleted_buffers) {
- SourceBufferRange* new_next_range =
- range->SplitRange(end_timestamp, is_range_exclusive);
-
- if (new_next_range)
- AddToRanges(new_next_range);
-
- BufferQueue saved_buffers;
- bool deleted_next_buffer =
- range->TruncateAt(start_timestamp, &saved_buffers, is_range_exclusive);
-
- if (selected_range_ != range)
- return deleted_next_buffer;
-
- DCHECK(deleted_buffers->empty());
- *deleted_buffers = saved_buffers;
-
- // If the next buffer position has transferred to the split range, set the
- // selected range accordingly.
- if (new_next_range && new_next_range->HasNextBufferPosition()) {
- DCHECK(!range->HasNextBufferPosition());
- DCHECK(!deleted_next_buffer);
- SetSelectedRange(new_next_range);
- }
- return deleted_next_buffer;
-}
-
-bool SourceBufferStream::AreAdjacentInSequence(
- base::TimeDelta first_timestamp, base::TimeDelta second_timestamp) const {
- return first_timestamp < second_timestamp &&
- second_timestamp <=
- first_timestamp + ComputeFudgeRoom(GetMaxInterbufferDistance());
-}
-
-void SourceBufferStream::ResolveCompleteOverlaps(
- const RangeList::iterator& range_with_new_buffers_itr,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers) {
- DCHECK(deleted_next_buffer);
- DCHECK(deleted_buffers);
-
- SourceBufferRange* range_with_new_buffers = *range_with_new_buffers_itr;
- RangeList::iterator next_range_itr = range_with_new_buffers_itr;
- ++next_range_itr;
-
- while (next_range_itr != ranges_.end() &&
- range_with_new_buffers->CompletelyOverlaps(**next_range_itr)) {
- if (*next_range_itr == selected_range_) {
- DCHECK(!*deleted_next_buffer);
- *deleted_next_buffer = selected_range_->DeleteAll(deleted_buffers);
- DCHECK(*deleted_next_buffer);
- SetSelectedRange(NULL);
- }
- delete *next_range_itr;
- next_range_itr = ranges_.erase(next_range_itr);
- }
-}
-
-void SourceBufferStream::ResolveEndOverlap(
- const RangeList::iterator& range_with_new_buffers_itr,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers) {
- DCHECK(deleted_next_buffer);
- DCHECK(deleted_buffers);
-
- SourceBufferRange* range_with_new_buffers = *range_with_new_buffers_itr;
- RangeList::iterator next_range_itr = range_with_new_buffers_itr;
- ++next_range_itr;
-
- if (next_range_itr == ranges_.end() ||
- !range_with_new_buffers->EndOverlaps(**next_range_itr)) {
- return;
- }
-
- // Split the overlapped range after |range_with_new_buffers|'s last buffer
- // overlaps. Now |overlapped_range| contains only the buffers that do not
- // belong in |ranges_| anymore, and |new_next_range| contains buffers that
- // go after |range_with_new_buffers| (without overlap).
- scoped_ptr<SourceBufferRange> overlapped_range(*next_range_itr);
- next_range_itr = ranges_.erase(next_range_itr);
-
- SourceBufferRange* new_next_range =
- overlapped_range->SplitRange(
- range_with_new_buffers->GetEndTimestamp(), true);
-
- // If there were non-overlapped buffers, add the new range to |ranges_|.
- if (new_next_range)
- AddToRanges(new_next_range);
-
- // If we didn't overlap a selected range, return.
- if (selected_range_ != overlapped_range.get())
- return;
-
- // If the |overlapped_range| transfers its next buffer position to
- // |new_next_range|, make |new_next_range| the |selected_range_|.
- if (new_next_range && new_next_range->HasNextBufferPosition()) {
- DCHECK(!overlapped_range->HasNextBufferPosition());
- SetSelectedRange(new_next_range);
- return;
- }
-
- // Save the buffers in |overlapped_range|.
- DCHECK(!*deleted_next_buffer);
- DCHECK_EQ(overlapped_range.get(), selected_range_);
- *deleted_next_buffer = overlapped_range->DeleteAll(deleted_buffers);
- DCHECK(*deleted_next_buffer);
-
- // |overlapped_range| will be deleted, so set |selected_range_| to NULL.
- SetSelectedRange(NULL);
-}
-
-void SourceBufferStream::PruneTrackBuffer() {
- DCHECK(selected_range_);
- DCHECK(selected_range_->HasNextBufferPosition());
- base::TimeDelta next_timestamp = selected_range_->GetNextTimestamp();
-
- // If we don't have the next timestamp, we don't have anything to delete.
- if (next_timestamp == kNoTimestamp())
- return;
-
- while (!track_buffer_.empty() &&
- track_buffer_.back()->GetDecodeTimestamp() >= next_timestamp) {
- track_buffer_.pop_back();
- }
-}
-
-void SourceBufferStream::UpdateTrackBuffer(const BufferQueue& deleted_buffers) {
- DCHECK(!deleted_buffers.empty());
- DCHECK(selected_range_);
- DCHECK(selected_range_->HasNextBufferPosition());
-
- base::TimeDelta next_keyframe_timestamp = selected_range_->GetNextTimestamp();
- base::TimeDelta start_of_deleted =
- deleted_buffers.front()->GetDecodeTimestamp();
-
- // |deleted_buffers| should always come after the buffers in |track_buffer|.
- if (!track_buffer_.empty())
- DCHECK(track_buffer_.back()->GetDecodeTimestamp() < start_of_deleted);
-
- // If there is no gap between what was deleted and what was added, nothing
- // should be added to the track buffer.
- if (selected_range_->HasNextBuffer() &&
- next_keyframe_timestamp <= start_of_deleted) {
- return;
- }
-
- // If the |selected_range_| is ready to return data, fill the track buffer
- // with all buffers that come before |next_keyframe_timestamp| and return.
- if (selected_range_->HasNextBuffer()) {
- for (BufferQueue::const_iterator itr = deleted_buffers.begin();
- itr != deleted_buffers.end() &&
- (*itr)->GetDecodeTimestamp() < next_keyframe_timestamp; ++itr) {
- track_buffer_.push_back(*itr);
- }
- return;
- }
-
- // Otherwise, the |selected_range_| is not ready to return data, so add all
- // the deleted buffers into the |track_buffer_|.
- track_buffer_.insert(track_buffer_.end(),
- deleted_buffers.begin(), deleted_buffers.end());
-
- // See if the next range contains the keyframe after the end of the
- // |track_buffer_|, and if so, change |selected_range_|.
- RangeList::iterator next_range_itr = ++(GetSelectedRangeItr());
- if (next_range_itr == ranges_.end())
- return;
-
- (*next_range_itr)->SeekAheadPast(
- track_buffer_.back()->GetDecodeTimestamp());
-
- if (!(*next_range_itr)->HasNextBuffer())
- return;
-
- if (!selected_range_->IsNextInSequence(
- track_buffer_.back(), (*next_range_itr)->GetNextTimestamp())) {
- (*next_range_itr)->ResetNextBufferPosition();
- return;
- }
- SetSelectedRange(*next_range_itr);
-}
-
-void SourceBufferStream::MergeWithAdjacentRangeIfNecessary(
- const RangeList::iterator& range_with_new_buffers_itr) {
- SourceBufferRange* range_with_new_buffers = *range_with_new_buffers_itr;
- RangeList::iterator next_range_itr = range_with_new_buffers_itr;
- ++next_range_itr;
-
- if (next_range_itr != ranges_.end() &&
- range_with_new_buffers->CanAppendRangeToEnd(**next_range_itr)) {
- bool transfer_current_position = selected_range_ == *next_range_itr;
- range_with_new_buffers->AppendRangeToEnd(**next_range_itr,
- transfer_current_position);
- // Update |selected_range_| pointer if |range| has become selected after
- // merges.
- if (transfer_current_position)
- SetSelectedRange(range_with_new_buffers);
-
- if (next_range_itr == range_for_next_append_)
- range_for_next_append_ = range_with_new_buffers_itr;
-
- delete *next_range_itr;
- ranges_.erase(next_range_itr);
- }
-}
-
-void SourceBufferStream::Seek(base::TimeDelta timestamp) {
- DCHECK(timestamp >= base::TimeDelta());
- ResetSeekState();
-
- if (ShouldSeekToStartOfBuffered(timestamp)) {
- SetSelectedRange(ranges_.front());
- ranges_.front()->SeekToStart();
- seek_pending_ = false;
- seek_buffer_timestamp_ = GetNextBufferTimestamp();
- return;
- }
-
- seek_buffer_timestamp_ = timestamp;
- seek_pending_ = true;
-
- RangeList::iterator itr = ranges_.end();
- for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
- if ((*itr)->CanSeekTo(timestamp))
- break;
- }
-
- if (itr == ranges_.end())
- return;
-
- SetSelectedRange(*itr);
- selected_range_->Seek(timestamp);
- seek_pending_ = false;
- seek_buffer_timestamp_ = GetNextBufferTimestamp();
-}
-
-bool SourceBufferStream::IsSeekPending() const {
- return seek_pending_;
-}
-
-base::TimeDelta SourceBufferStream::GetSeekKeyframeTimestamp() const {
- DCHECK(!IsSeekPending());
- return seek_buffer_timestamp_;
-}
-
-void SourceBufferStream::OnSetDuration(base::TimeDelta duration) {
- RangeList::iterator itr = ranges_.end();
- for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
- if ((*itr)->GetEndTimestamp() > duration)
- break;
- }
- if (itr == ranges_.end())
- return;
-
- // Need to partially truncate this range.
- if ((*itr)->GetStartTimestamp() < duration) {
- bool deleted_seek_point = (*itr)->TruncateAt(duration, NULL, false);
- if (deleted_seek_point)
- ResetSeekState();
- ++itr;
- }
-
- // Delete all ranges that begin after |duration|.
- while (itr != ranges_.end()) {
- // If we're about to delete the selected range, also reset the seek state.
- DCHECK((*itr)->GetStartTimestamp() >= duration);
- if (*itr== selected_range_)
- ResetSeekState();
- delete *itr;
- itr = ranges_.erase(itr);
- }
-}
-
-SourceBufferStream::Status SourceBufferStream::GetNextBuffer(
- scoped_refptr<StreamParserBuffer>* out_buffer) {
- CHECK(!config_change_pending_);
-
- if (!track_buffer_.empty()) {
- DCHECK(selected_range_);
- if (track_buffer_.front()->GetConfigId() != current_config_index_) {
- config_change_pending_ = true;
- DVLOG(1) << "Config change (track buffer config ID does not match).";
- return kConfigChange;
- }
-
- *out_buffer = track_buffer_.front();
- track_buffer_.pop_front();
- return kSuccess;
- }
-
- if (!selected_range_ || !selected_range_->HasNextBuffer())
- return kNeedBuffer;
-
- if (selected_range_->GetNextConfigId() != current_config_index_) {
- config_change_pending_ = true;
- DVLOG(1) << "Config change (selected range config ID does not match).";
- return kConfigChange;
- }
-
- CHECK(selected_range_->GetNextBuffer(out_buffer));
- return kSuccess;
-}
-
-base::TimeDelta SourceBufferStream::GetNextBufferTimestamp() {
- if (!selected_range_) {
- DCHECK(track_buffer_.empty());
- return kNoTimestamp();
- }
-
- DCHECK(selected_range_->HasNextBufferPosition());
- if (!track_buffer_.empty())
- return track_buffer_.front()->GetDecodeTimestamp();
- return selected_range_->GetNextTimestamp();
-}
-
-base::TimeDelta SourceBufferStream::GetEndBufferTimestamp() {
- if (!selected_range_)
- return kNoTimestamp();
- return selected_range_->GetEndTimestamp();
-}
-
-SourceBufferStream::RangeList::iterator
-SourceBufferStream::FindExistingRangeFor(base::TimeDelta start_timestamp) {
- for (RangeList::iterator itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
- if ((*itr)->BelongsToRange(start_timestamp))
- return itr;
- }
- return ranges_.end();
-}
-
-SourceBufferStream::RangeList::iterator
-SourceBufferStream::AddToRanges(SourceBufferRange* new_range) {
- base::TimeDelta start_timestamp = new_range->GetStartTimestamp();
- RangeList::iterator itr = ranges_.end();
- for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
- if ((*itr)->GetStartTimestamp() > start_timestamp)
- break;
- }
- return ranges_.insert(itr, new_range);
-}
-
-SourceBufferStream::RangeList::iterator
-SourceBufferStream::GetSelectedRangeItr() {
- DCHECK(selected_range_);
- RangeList::iterator itr = ranges_.end();
- for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
- if (*itr == selected_range_)
- break;
- }
- DCHECK(itr != ranges_.end());
- return itr;
-}
-
-void SourceBufferStream::SetSelectedRange(SourceBufferRange* range) {
- if (selected_range_)
- selected_range_->ResetNextBufferPosition();
- selected_range_ = range;
-}
-
-Ranges<base::TimeDelta> SourceBufferStream::GetBufferedTime() const {
- Ranges<base::TimeDelta> ranges;
- for (RangeList::const_iterator itr = ranges_.begin();
- itr != ranges_.end(); ++itr) {
- ranges.Add((*itr)->GetStartTimestamp(), (*itr)->GetBufferedEndTimestamp());
- }
- return ranges;
-}
-
-bool SourceBufferStream::IsEndSelected() const {
- return ranges_.empty() || selected_range_ == ranges_.back();
-}
-
-const AudioDecoderConfig& SourceBufferStream::GetCurrentAudioDecoderConfig() {
- if (config_change_pending_)
- CompleteConfigChange();
- return *audio_configs_[current_config_index_];
-}
-
-const VideoDecoderConfig& SourceBufferStream::GetCurrentVideoDecoderConfig() {
- if (config_change_pending_)
- CompleteConfigChange();
- return *video_configs_[current_config_index_];
-}
-
-base::TimeDelta SourceBufferStream::GetMaxInterbufferDistance() const {
- if (max_interbuffer_distance_ == kNoTimestamp())
- return base::TimeDelta::FromMilliseconds(kDefaultBufferDurationInMs);
- return max_interbuffer_distance_;
-}
-
-bool SourceBufferStream::UpdateAudioConfig(const AudioDecoderConfig& config) {
- DCHECK(!audio_configs_.empty());
- DCHECK(video_configs_.empty());
- DVLOG(3) << "UpdateAudioConfig.";
-
- if (audio_configs_[0]->codec() != config.codec()) {
- MEDIA_LOG(log_cb_) << "Audio codec changes not allowed.";
- return false;
- }
-
- if (audio_configs_[0]->samples_per_second() != config.samples_per_second()) {
- MEDIA_LOG(log_cb_) << "Audio sample rate changes not allowed.";
- return false;
- }
-
- if (audio_configs_[0]->channel_layout() != config.channel_layout()) {
- MEDIA_LOG(log_cb_) << "Audio channel layout changes not allowed.";
- return false;
- }
-
- if (audio_configs_[0]->bits_per_channel() != config.bits_per_channel()) {
- MEDIA_LOG(log_cb_) << "Audio bits per channel changes not allowed.";
- return false;
- }
-
- if (audio_configs_[0]->is_encrypted() != config.is_encrypted()) {
- MEDIA_LOG(log_cb_) << "Audio encryption changes not allowed.";
- return false;
- }
-
- // Check to see if the new config matches an existing one.
- for (size_t i = 0; i < audio_configs_.size(); ++i) {
- if (config.Matches(*audio_configs_[i])) {
- append_config_index_ = i;
- return true;
- }
- }
-
- // No matches found so let's add this one to the list.
- append_config_index_ = audio_configs_.size();
- DVLOG(2) << "New audio config - index: " << append_config_index_;
- audio_configs_.resize(audio_configs_.size() + 1);
- audio_configs_[append_config_index_] = new AudioDecoderConfig();
- audio_configs_[append_config_index_]->CopyFrom(config);
- return true;
-}
-
-bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config) {
- DCHECK(!video_configs_.empty());
- DCHECK(audio_configs_.empty());
- DVLOG(3) << "UpdateVideoConfig.";
-
- if (video_configs_[0]->is_encrypted() != config.is_encrypted()) {
- MEDIA_LOG(log_cb_) << "Video Encryption changes not allowed.";
- return false;
- }
-
- if (video_configs_[0]->codec() != config.codec()) {
- MEDIA_LOG(log_cb_) << "Video codec changes not allowed.";
- return false;
- }
-
- if (video_configs_[0]->is_encrypted() != config.is_encrypted()) {
- MEDIA_LOG(log_cb_) << "Video encryption changes not allowed.";
- return false;
- }
-
- // Check to see if the new config matches an existing one.
- for (size_t i = 0; i < video_configs_.size(); ++i) {
- if (config.Matches(*video_configs_[i])) {
- append_config_index_ = i;
- return true;
- }
- }
-
- // No matches found so let's add this one to the list.
- append_config_index_ = video_configs_.size();
- DVLOG(2) << "New video config - index: " << append_config_index_;
- video_configs_.resize(video_configs_.size() + 1);
- video_configs_[append_config_index_] = new VideoDecoderConfig();
- video_configs_[append_config_index_]->CopyFrom(config);
- return true;
-}
-
-void SourceBufferStream::CompleteConfigChange() {
- config_change_pending_ = false;
-
- if (!track_buffer_.empty()) {
- current_config_index_ = track_buffer_.front()->GetConfigId();
- return;
- }
-
- if (selected_range_ && selected_range_->HasNextBuffer())
- current_config_index_ = selected_range_->GetNextConfigId();
-}
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-SourceBufferRange::SourceBufferRange(
- const BufferQueue& new_buffers, base::TimeDelta media_segment_start_time,
- const InterbufferDistanceCB& interbuffer_distance_cb)
- : keyframe_map_index_base_(0),
- next_buffer_index_(-1),
- waiting_for_keyframe_(false),
- next_keyframe_timestamp_(kNoTimestamp()),
- media_segment_start_time_(media_segment_start_time),
- interbuffer_distance_cb_(interbuffer_distance_cb),
- size_in_bytes_(0) {
- DCHECK(!new_buffers.empty());
- DCHECK(!interbuffer_distance_cb.is_null());
- AppendBuffersToEnd(new_buffers);
-}
-#else
-SourceBufferRange::SourceBufferRange(
- const BufferQueue& new_buffers, base::TimeDelta media_segment_start_time,
- const InterbufferDistanceCB& interbuffer_distance_cb)
- : keyframe_map_index_base_(0),
- next_buffer_index_(-1),
- waiting_for_keyframe_(false),
- next_keyframe_timestamp_(kNoTimestamp()),
- media_segment_start_time_(media_segment_start_time),
- interbuffer_distance_cb_(interbuffer_distance_cb),
- size_in_bytes_(0) {
- DCHECK(!new_buffers.empty());
- DCHECK(!interbuffer_distance_cb.is_null());
- AppendBuffersToEnd(new_buffers);
-}
-#endif
-
-void SourceBufferRange::AppendBuffersToEnd(const BufferQueue& new_buffers) {
- for (BufferQueue::const_iterator itr = new_buffers.begin();
- itr != new_buffers.end(); ++itr) {
- DCHECK((*itr)->GetDecodeTimestamp() != kNoTimestamp());
- buffers_.push_back(*itr);
- size_in_bytes_ += (*itr)->GetDataSize();
-
- if ((*itr)->IsKeyframe()) {
- keyframe_map_.insert(
- std::make_pair((*itr)->GetDecodeTimestamp(),
- static_cast<int>(buffers_.size()) - 1 +
- keyframe_map_index_base_));
-
- if (waiting_for_keyframe_ &&
- (*itr)->GetDecodeTimestamp() >= next_keyframe_timestamp_) {
- next_buffer_index_ = buffers_.size() - 1;
- next_keyframe_timestamp_ = kNoTimestamp();
- waiting_for_keyframe_ = false;
- }
- }
- }
-}
-
-void SourceBufferRange::Seek(base::TimeDelta timestamp) {
- DCHECK(CanSeekTo(timestamp));
- DCHECK(!keyframe_map_.empty());
-
- next_keyframe_timestamp_ = kNoTimestamp();
- waiting_for_keyframe_ = false;
-
- KeyframeMap::iterator result = GetFirstKeyframeBefore(timestamp);
- next_buffer_index_ = result->second - keyframe_map_index_base_;
- DCHECK_LT(next_buffer_index_, static_cast<int>(buffers_.size()));
-}
-
-void SourceBufferRange::SeekAheadTo(base::TimeDelta timestamp) {
- SeekAhead(timestamp, false);
-}
-
-void SourceBufferRange::SeekAheadPast(base::TimeDelta timestamp) {
- SeekAhead(timestamp, true);
-}
-
-void SourceBufferRange::SeekAhead(base::TimeDelta timestamp,
- bool skip_given_timestamp) {
- DCHECK(!keyframe_map_.empty());
-
- KeyframeMap::iterator result =
- GetFirstKeyframeAt(timestamp, skip_given_timestamp);
-
- // If there isn't a keyframe after |timestamp|, then seek to end and return
- // kNoTimestamp to signal such.
- if (result == keyframe_map_.end()) {
- waiting_for_keyframe_ = true;
- next_buffer_index_ = -1;
- next_keyframe_timestamp_ = timestamp;
- return;
- }
- next_buffer_index_ = result->second - keyframe_map_index_base_;
- DCHECK_LT(next_buffer_index_, static_cast<int>(buffers_.size()));
-}
-
-void SourceBufferRange::SeekToStart() {
- DCHECK(!buffers_.empty());
- next_buffer_index_ = 0;
-}
-
-SourceBufferRange* SourceBufferRange::SplitRange(
- base::TimeDelta timestamp, bool is_exclusive) {
- // Find the first keyframe after |timestamp|. If |is_exclusive|, do not
- // include keyframes at |timestamp|.
- KeyframeMap::iterator new_beginning_keyframe =
- GetFirstKeyframeAt(timestamp, is_exclusive);
-
- // If there is no keyframe after |timestamp|, we can't split the range.
- if (new_beginning_keyframe == keyframe_map_.end())
- return NULL;
-
- // Remove the data beginning at |keyframe_index| from |buffers_| and save it
- // into |removed_buffers|.
- int keyframe_index =
- new_beginning_keyframe->second - keyframe_map_index_base_;
- DCHECK_LT(keyframe_index, static_cast<int>(buffers_.size()));
- BufferQueue::iterator starting_point = buffers_.begin() + keyframe_index;
- BufferQueue removed_buffers(starting_point, buffers_.end());
- keyframe_map_.erase(new_beginning_keyframe, keyframe_map_.end());
- FreeBufferRange(starting_point, buffers_.end());
-
- // Create a new range with |removed_buffers|.
-#if defined(__LB_SHELL__) || defined(COBALT)
- SourceBufferRange* split_range =
- new SourceBufferRange(
- removed_buffers, kNoTimestamp(), interbuffer_distance_cb_);
-#else
- SourceBufferRange* split_range =
- new SourceBufferRange(
- removed_buffers, kNoTimestamp(), interbuffer_distance_cb_);
-#endif
-
- // If the next buffer position is now in |split_range|, update the state of
- // this range and |split_range| accordingly.
- if (next_buffer_index_ >= static_cast<int>(buffers_.size())) {
- DCHECK(!waiting_for_keyframe_);
- split_range->next_buffer_index_ = next_buffer_index_ - keyframe_index;
- ResetNextBufferPosition();
- } else if (waiting_for_keyframe_) {
- split_range->waiting_for_keyframe_ = true;
- split_range->next_keyframe_timestamp_ = next_keyframe_timestamp_;
- ResetNextBufferPosition();
- }
-
- return split_range;
-}
-
-SourceBufferRange::BufferQueue::iterator SourceBufferRange::GetBufferItrAt(
- base::TimeDelta timestamp, bool skip_given_timestamp) {
- // Need to make a dummy buffer with timestamp |timestamp| in order to search
- // the |buffers_| container.
- scoped_refptr<StreamParserBuffer> dummy_buffer =
- StreamParserBuffer::CopyFrom(NULL, 0, false);
- dummy_buffer->SetDecodeTimestamp(timestamp);
-
- if (skip_given_timestamp) {
- return std::upper_bound(
- buffers_.begin(), buffers_.end(), dummy_buffer, BufferComparator);
- }
- return std::lower_bound(
- buffers_.begin(), buffers_.end(), dummy_buffer, BufferComparator);
-}
-
-SourceBufferRange::KeyframeMap::iterator
-SourceBufferRange::GetFirstKeyframeAt(base::TimeDelta timestamp,
- bool skip_given_timestamp) {
- return skip_given_timestamp ?
- keyframe_map_.upper_bound(timestamp) :
- keyframe_map_.lower_bound(timestamp);
-}
-
-SourceBufferRange::KeyframeMap::iterator
-SourceBufferRange::GetFirstKeyframeBefore(base::TimeDelta timestamp) {
- KeyframeMap::iterator result = keyframe_map_.lower_bound(timestamp);
- // lower_bound() returns the first element >= |timestamp|, so we want the
- // previous element if it did not return the element exactly equal to
- // |timestamp|.
- if (result != keyframe_map_.begin() &&
- (result == keyframe_map_.end() || result->first != timestamp)) {
- --result;
- }
- return result;
-}
-
-bool SourceBufferRange::DeleteAll(BufferQueue* removed_buffers) {
- return TruncateAt(buffers_.begin(), removed_buffers);
-}
-
-bool SourceBufferRange::TruncateAt(
- base::TimeDelta timestamp, BufferQueue* removed_buffers,
- bool is_exclusive) {
- // Find the place in |buffers_| where we will begin deleting data.
- BufferQueue::iterator starting_point =
- GetBufferItrAt(timestamp, is_exclusive);
- return TruncateAt(starting_point, removed_buffers);
-}
-
-int SourceBufferRange::DeleteGOPFromFront(BufferQueue* deleted_buffers) {
- DCHECK(!FirstGOPContainsNextBufferPosition());
- DCHECK(deleted_buffers);
-
- int buffers_deleted = 0;
- int total_bytes_deleted = 0;
-
- KeyframeMap::iterator front = keyframe_map_.begin();
- DCHECK(front != keyframe_map_.end());
-
- // Delete the keyframe at the start of |keyframe_map_|.
- keyframe_map_.erase(front);
-
- // Now we need to delete all the buffers that depend on the keyframe we've
- // just deleted.
- int end_index = keyframe_map_.size() > 0 ?
- keyframe_map_.begin()->second - keyframe_map_index_base_ :
- buffers_.size();
-
- // Delete buffers from the beginning of the buffered range up until (but not
- // including) the next keyframe.
- for (int i = 0; i < end_index; i++) {
- int bytes_deleted = buffers_.front()->GetDataSize();
- size_in_bytes_ -= bytes_deleted;
- total_bytes_deleted += bytes_deleted;
- deleted_buffers->push_back(buffers_.front());
- buffers_.pop_front();
- ++buffers_deleted;
- }
-
- // Update |keyframe_map_index_base_| to account for the deleted buffers.
- keyframe_map_index_base_ += buffers_deleted;
-
- if (next_buffer_index_ > -1) {
- next_buffer_index_ -= buffers_deleted;
- DCHECK_GE(next_buffer_index_, 0);
- }
-
- // Invalidate media segment start time if we've deleted the first buffer of
- // the range.
- if (buffers_deleted > 0)
- media_segment_start_time_ = kNoTimestamp();
-
- return total_bytes_deleted;
-}
-
-int SourceBufferRange::DeleteGOPFromBack(BufferQueue* deleted_buffers) {
- DCHECK(!LastGOPContainsNextBufferPosition());
- DCHECK(deleted_buffers);
-
- // Remove the last GOP's keyframe from the |keyframe_map_|.
- KeyframeMap::iterator back = keyframe_map_.end();
- DCHECK_GT(keyframe_map_.size(), 0u);
- --back;
-
- // The index of the first buffer in the last GOP is equal to the new size of
- // |buffers_| after that GOP is deleted.
- size_t goal_size = back->second - keyframe_map_index_base_;
- keyframe_map_.erase(back);
-
- int total_bytes_deleted = 0;
- while (buffers_.size() != goal_size) {
- int bytes_deleted = buffers_.back()->GetDataSize();
- size_in_bytes_ -= bytes_deleted;
- total_bytes_deleted += bytes_deleted;
- // We're removing buffers from the back, so push each removed buffer to the
- // front of |deleted_buffers| so that |deleted_buffers| are in nondecreasing
- // order.
- deleted_buffers->push_front(buffers_.back());
- buffers_.pop_back();
- }
-
- return total_bytes_deleted;
-}
-
-bool SourceBufferRange::FirstGOPContainsNextBufferPosition() const {
- if (!HasNextBufferPosition())
- return false;
-
- // If there is only one GOP, it must contain the next buffer position.
- if (keyframe_map_.size() == 1u)
- return true;
-
- KeyframeMap::const_iterator second_gop = keyframe_map_.begin();
- ++second_gop;
- return !waiting_for_keyframe_ &&
- next_buffer_index_ < second_gop->second - keyframe_map_index_base_;
-}
-
-bool SourceBufferRange::LastGOPContainsNextBufferPosition() const {
- if (!HasNextBufferPosition())
- return false;
-
- // If there is only one GOP, it must contain the next buffer position.
- if (keyframe_map_.size() == 1u)
- return true;
-
- KeyframeMap::const_iterator last_gop = keyframe_map_.end();
- --last_gop;
- return waiting_for_keyframe_ ||
- last_gop->second - keyframe_map_index_base_ <= next_buffer_index_;
-}
-
-void SourceBufferRange::FreeBufferRange(
- const BufferQueue::iterator& starting_point,
- const BufferQueue::iterator& ending_point) {
- for (BufferQueue::iterator itr = starting_point;
- itr != ending_point; ++itr) {
- size_in_bytes_ -= (*itr)->GetDataSize();
- DCHECK_GE(size_in_bytes_, 0);
- }
- buffers_.erase(starting_point, ending_point);
-}
-
-bool SourceBufferRange::TruncateAt(
- const BufferQueue::iterator& starting_point, BufferQueue* removed_buffers) {
- DCHECK(!removed_buffers || removed_buffers->empty());
-
- // Return if we're not deleting anything.
- if (starting_point == buffers_.end())
- return false;
-
- // Reset the next buffer index if we will be deleting the buffer that's next
- // in sequence.
- bool removed_next_buffer = false;
- if (HasNextBufferPosition()) {
- base::TimeDelta next_buffer_timestamp = GetNextTimestamp();
- if (next_buffer_timestamp == kNoTimestamp() ||
- next_buffer_timestamp >= (*starting_point)->GetDecodeTimestamp()) {
- if (HasNextBuffer() && removed_buffers) {
- int starting_offset = starting_point - buffers_.begin();
- int next_buffer_offset = next_buffer_index_ - starting_offset;
- DCHECK_GE(next_buffer_offset, 0);
- BufferQueue saved(starting_point + next_buffer_offset, buffers_.end());
- removed_buffers->swap(saved);
- }
- ResetNextBufferPosition();
- removed_next_buffer = true;
- }
- }
-
- // Remove keyframes from |starting_point| onward.
- KeyframeMap::iterator starting_point_keyframe =
- keyframe_map_.lower_bound((*starting_point)->GetDecodeTimestamp());
- keyframe_map_.erase(starting_point_keyframe, keyframe_map_.end());
-
- // Remove everything from |starting_point| onward.
- FreeBufferRange(starting_point, buffers_.end());
- return removed_next_buffer;
-}
-
-bool SourceBufferRange::GetNextBuffer(
- scoped_refptr<StreamParserBuffer>* out_buffer) {
- if (!HasNextBuffer())
- return false;
-
- *out_buffer = buffers_.at(next_buffer_index_);
- next_buffer_index_++;
- return true;
-}
-
-bool SourceBufferRange::HasNextBuffer() const {
- return next_buffer_index_ >= 0 &&
- next_buffer_index_ < static_cast<int>(buffers_.size()) &&
- !waiting_for_keyframe_;
-}
-
-int SourceBufferRange::GetNextConfigId() const {
- DCHECK(HasNextBuffer());
- return buffers_.at(next_buffer_index_)->GetConfigId();
-}
-
-
-base::TimeDelta SourceBufferRange::GetNextTimestamp() const {
- DCHECK(!buffers_.empty());
- DCHECK(HasNextBufferPosition());
-
- if (waiting_for_keyframe_ ||
- next_buffer_index_ >= static_cast<int>(buffers_.size())) {
- return kNoTimestamp();
- }
-
- return buffers_.at(next_buffer_index_)->GetDecodeTimestamp();
-}
-
-bool SourceBufferRange::HasNextBufferPosition() const {
- return next_buffer_index_ >= 0 || waiting_for_keyframe_;
-}
-
-void SourceBufferRange::ResetNextBufferPosition() {
- next_buffer_index_ = -1;
- waiting_for_keyframe_ = false;
- next_keyframe_timestamp_ = kNoTimestamp();
-}
-
-void SourceBufferRange::AppendRangeToEnd(const SourceBufferRange& range,
- bool transfer_current_position) {
- DCHECK(CanAppendRangeToEnd(range));
- DCHECK(!buffers_.empty());
-
- if (transfer_current_position)
- next_buffer_index_ = range.next_buffer_index_ + buffers_.size();
-
- AppendBuffersToEnd(range.buffers_);
-}
-
-bool SourceBufferRange::CanAppendRangeToEnd(
- const SourceBufferRange& range) const {
- return CanAppendBuffersToEnd(range.buffers_);
-}
-
-bool SourceBufferRange::CanAppendBuffersToEnd(
- const BufferQueue& buffers) const {
- DCHECK(!buffers_.empty());
- return IsNextInSequence(buffers_.back(),
- buffers.front()->GetDecodeTimestamp());
-}
-
-bool SourceBufferRange::BelongsToRange(base::TimeDelta timestamp) const {
- DCHECK(!buffers_.empty());
-
- return (IsNextInSequence(buffers_.back(), timestamp) ||
- (GetStartTimestamp() <= timestamp && timestamp <= GetEndTimestamp()));
-}
-
-bool SourceBufferRange::CanSeekTo(base::TimeDelta timestamp) const {
- base::TimeDelta start_timestamp =
- std::max(base::TimeDelta(), GetStartTimestamp() - GetFudgeRoom());
- return !keyframe_map_.empty() && start_timestamp <= timestamp &&
- timestamp < GetBufferedEndTimestamp();
-}
-
-bool SourceBufferRange::CompletelyOverlaps(
- const SourceBufferRange& range) const {
- return GetStartTimestamp() <= range.GetStartTimestamp() &&
- GetEndTimestamp() >= range.GetEndTimestamp();
-}
-
-bool SourceBufferRange::EndOverlaps(const SourceBufferRange& range) const {
- return range.GetStartTimestamp() <= GetEndTimestamp() &&
- GetEndTimestamp() < range.GetEndTimestamp();
-}
-
-base::TimeDelta SourceBufferRange::GetStartTimestamp() const {
- DLOG_IF(WARNING, buffers_.empty()) << "|buffers_| cannot be empty.";
- base::TimeDelta start_timestamp = media_segment_start_time_;
- if (start_timestamp == kNoTimestamp())
- start_timestamp = buffers_.front()->GetDecodeTimestamp();
- return start_timestamp;
-}
-
-base::TimeDelta SourceBufferRange::GetEndTimestamp() const {
- DCHECK(!buffers_.empty());
- return buffers_.back()->GetDecodeTimestamp();
-}
-
-base::TimeDelta SourceBufferRange::GetBufferedEndTimestamp() const {
- DCHECK(!buffers_.empty());
- base::TimeDelta duration = buffers_.back()->GetDuration();
- if (duration == kNoTimestamp() || duration == base::TimeDelta())
- duration = GetApproximateDuration();
- return GetEndTimestamp() + duration;
-}
-
-bool SourceBufferRange::IsNextInSequence(
- const scoped_refptr<media::StreamParserBuffer>& buffer,
- base::TimeDelta timestamp) const {
- return buffer->GetDecodeTimestamp() < timestamp &&
- timestamp <= buffer->GetDecodeTimestamp() + GetFudgeRoom();
-}
-
-base::TimeDelta SourceBufferRange::GetFudgeRoom() const {
- return ComputeFudgeRoom(GetApproximateDuration());
-}
-
-base::TimeDelta SourceBufferRange::GetApproximateDuration() const {
- base::TimeDelta max_interbuffer_distance = interbuffer_distance_cb_.Run();
- DCHECK(max_interbuffer_distance != kNoTimestamp());
- return max_interbuffer_distance;
-}
-
-void LogMediaSourceTimeRanges(const std::string& desc,
- const Ranges<base::TimeDelta>& time_ranges) {
-#if LOG_MEDIA_SOURCE_ACTIVITIES
- LOG(INFO) << ("======== " + desc + " ========");
- for (size_t i = 0; i < time_ranges.size(); ++i)
- LOG(INFO) << "\t\tRange " << i << " : "
- << time_ranges.start(i).InMicroseconds() << " - "
- << time_ranges.end(i).InMicroseconds();
-#endif // LOG_MEDIA_SOURCE_ACTIVITIES
-}
-
-} // namespace media
diff --git a/src/media/filters/source_buffer_stream.h b/src/media/filters/source_buffer_stream.h
deleted file mode 100644
index babcb65..0000000
--- a/src/media/filters/source_buffer_stream.h
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// SourceBufferStream is a data structure that stores media Buffers in ranges.
-// Buffers can be appended out of presentation order. Buffers are retrieved by
-// seeking to the desired start point and calling GetNextBuffer(). Buffers are
-// returned in sequential presentation order.
-
-#ifndef MEDIA_FILTERS_SOURCE_BUFFER_STREAM_H_
-#define MEDIA_FILTERS_SOURCE_BUFFER_STREAM_H_
-
-#define LOG_MEDIA_SOURCE_ACTIVITIES 0
-
-#include <deque>
-#include <list>
-#include <utility>
-#include <vector>
-
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/base/ranges.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/video_decoder_config.h"
-
-namespace media {
-
-class SourceBufferRange;
-
-// See file-level comment for complete description.
-class MEDIA_EXPORT SourceBufferStream {
- public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
-
- // Status returned by GetNextBuffer().
- // kSuccess: Indicates that the next buffer was returned.
- // kNeedBuffer: Indicates that we need more data before a buffer can be
- // returned.
- // kConfigChange: Indicates that the next buffer requires a config change.
- enum Status {
- kSuccess,
- kNeedBuffer,
- kConfigChange,
- };
-
- SourceBufferStream(const AudioDecoderConfig& audio_config,
- const LogCB& log_cb);
- SourceBufferStream(const VideoDecoderConfig& video_config,
- const LogCB& log_cb);
-
- ~SourceBufferStream();
-
- // Signals that the next buffers appended are part of a new media segment
- // starting at |media_segment_start_time|.
- void OnNewMediaSegment(base::TimeDelta media_segment_start_time);
-
- // Add the |buffers| to the SourceBufferStream. Buffers within the queue are
- // expected to be in order, but multiple calls to Append() may add buffers out
- // of order or overlapping. Assumes all buffers within |buffers| are in
- // presentation order and are non-overlapping.
- // Returns true if Append() was successful, false if |buffers| are not added.
- // TODO(vrk): Implement garbage collection. (crbug.com/125070)
- bool Append(const BufferQueue& buffers);
-
- // Changes the SourceBufferStream's state so that it will start returning
- // buffers starting from the closest keyframe before |timestamp|.
- void Seek(base::TimeDelta timestamp);
-
- // Returns true if the SourceBufferStream has seeked to a time without
- // buffered data and is waiting for more data to be appended.
- bool IsSeekPending() const;
-
- // Returns the timestamp of the keyframe before the seek timestamp. Note that
- // this value is only valid (thus this function should only be called) when
- // IsSeekPending() returns false.
- base::TimeDelta GetSeekKeyframeTimestamp() const;
-
- // Notifies the SourceBufferStream that the media duration has been changed to
- // |duration| so it should drop any data past that point.
- void OnSetDuration(base::TimeDelta duration);
-
- // Fills |out_buffer| with a new buffer. Buffers are presented in order from
- // the last call to Seek(), or starting with the first buffer appended if
- // Seek() has not been called yet.
- // |out_buffer|'s timestamp may be earlier than the |timestamp| passed to
- // the last Seek() call.
- // Returns kSuccess if |out_buffer| is filled with a valid buffer, kNeedBuffer
- // if there is not enough data buffered to fulfill the request, and
- // kConfigChange if the next buffer requires a config change.
- Status GetNextBuffer(scoped_refptr<StreamParserBuffer>* out_buffer);
-
- // Returns a list of the buffered time ranges.
- Ranges<base::TimeDelta> GetBufferedTime() const;
-
- // Returns true if we don't have any ranges or the last range is selected.
- bool IsEndSelected() const;
-
- const AudioDecoderConfig& GetCurrentAudioDecoderConfig();
- const VideoDecoderConfig& GetCurrentVideoDecoderConfig();
-
- // Notifies this object that the audio config has changed and buffers in
- // future Append() calls should be associated with this new config.
- bool UpdateAudioConfig(const AudioDecoderConfig& config);
-
- // Notifies this object that the video config has changed and buffers in
- // future Append() calls should be associated with this new config.
- bool UpdateVideoConfig(const VideoDecoderConfig& config);
-
- // Returns the largest distance between two adjacent buffers in this stream,
- // or an estimate if no two adjacent buffers have been appended to the stream
- // yet.
- base::TimeDelta GetMaxInterbufferDistance() const;
-
- private:
- friend class SourceBufferStreamTest;
- typedef std::list<SourceBufferRange*> RangeList;
-
- void set_memory_limit(int memory_limit) { memory_limit_ = memory_limit; }
-
- // Frees up space if the SourceBufferStream is taking up too much memory.
- void GarbageCollectIfNeeded();
-
- // Attempts to delete approximately |total_bytes_to_free| amount of data
- // |ranges_|, starting at the front of |ranges_| and moving linearly forward
- // through the buffers. Deletes starting from the back if |reverse_direction|
- // is true. Returns the number of bytes freed.
- int FreeBuffers(int total_bytes_to_free, bool reverse_direction);
-
- // Appends |new_buffers| into |range_for_new_buffers_itr|, handling start and
- // end overlaps if necessary.
- // |deleted_next_buffer| is an output parameter that is true if the next
- // buffer that would have been returned from GetNextBuffer() was deleted
- // during this call.
- // |deleted_buffers| is an output parameter containing candidates for
- // |track_buffer_|.
- void InsertIntoExistingRange(
- const RangeList::iterator& range_for_new_buffers_itr,
- const BufferQueue& new_buffers,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers);
-
- // Resolve overlapping ranges such that no ranges overlap anymore.
- // |range_with_new_buffers_itr| points to the range that has newly appended
- // buffers.
- // |deleted_next_buffer| is an output parameter that is true if the next
- // buffer that would have been returned from GetNextBuffer() was deleted
- // during this call.
- // |deleted_buffers| is an output parameter containing candidates for
- // |track_buffer_|.
- void ResolveCompleteOverlaps(
- const RangeList::iterator& range_with_new_buffers_itr,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers);
- void ResolveEndOverlap(
- const RangeList::iterator& range_with_new_buffers_itr,
- bool* deleted_next_buffer, BufferQueue* deleted_buffers);
-
- // This method is a bit tricky to describe. When what would have been the
- // next buffer returned from |selected_range_| is overlapped by new data,
- // the |selected_range_| seeks forward to the next keyframe after (or at) the
- // next buffer timestamp and the overlapped buffers are deleted. But for
- // smooth playback between the old data to the new data's keyframe, some of
- // these |deleted_buffers| may be temporarily saved into |track_buffer_|.
- // UpdateTrackBuffer() takes these |deleted_buffers| and decides whether it
- // wants to save any buffers into |track_buffer_|.
- // TODO(vrk): This is a little crazy! Ideas for cleanup in crbug.com/129623.
- void UpdateTrackBuffer(const BufferQueue& deleted_buffers);
-
- // Removes buffers that come before |selected_range_|'s next buffer from the
- // |track_buffer_|.
- void PruneTrackBuffer();
-
- // Checks to see if |range_with_new_buffers_itr| can be merged with the range
- // next to it, and merges them if so.
- void MergeWithAdjacentRangeIfNecessary(
- const RangeList::iterator& range_with_new_buffers_itr);
-
- // Deletes the buffers between |start_timestamp|, |end_timestamp| from
- // |range|. Deletes between [start,end] if |is_range_exclusive| is true, or
- // (start,end) if |is_range_exclusive| is false.
- // Buffers are deleted in GOPs, so this method may delete buffers past
- // |end_timestamp| if the keyframe a buffer depends on was deleted.
- // Returns true if the |next_buffer_index_| is reset, and places the buffers
- // removed from the range starting at |next_buffer_index_| in
- // |deleted_buffers|.
- bool DeleteBetween(SourceBufferRange* range,
- base::TimeDelta start_timestamp,
- base::TimeDelta end_timestamp,
- bool is_range_exclusive,
- BufferQueue* deleted_buffers);
-
- // Returns true if |second_timestamp| is the timestamp of the next buffer in
- // sequence after |first_timestamp|, false otherwise.
- bool AreAdjacentInSequence(
- base::TimeDelta first_timestamp, base::TimeDelta second_timestamp) const;
-
- // Helper method that returns the timestamp for the next buffer that
- // |selected_range_| will return from GetNextBuffer() call, or kNoTimestamp()
- // if in between seeking (i.e. |selected_range_| is null).
- base::TimeDelta GetNextBufferTimestamp();
-
- // Returns the timestamp of the last buffer in the |selected_range_| or
- // kNoTimestamp() if |selected_range_| is null.
- base::TimeDelta GetEndBufferTimestamp();
-
- // Finds the range that should contain a media segment that begins with
- // |start_timestamp| and returns the iterator pointing to it. Returns
- // |ranges_.end()| if there's no such existing range.
- RangeList::iterator FindExistingRangeFor(base::TimeDelta start_timestamp);
-
- // Inserts |new_range| into |ranges_| preserving sorted order. Returns an
- // iterator in |ranges_| that points to |new_range|.
- RangeList::iterator AddToRanges(SourceBufferRange* new_range);
-
- // Returns an iterator that points to the place in |ranges_| where
- // |selected_range_| lives.
- RangeList::iterator GetSelectedRangeItr();
-
- // Sets the |selected_range_| to |range| and resets the next buffer position
- // for the previous |selected_range_|.
- void SetSelectedRange(SourceBufferRange* range);
-
- // Resets this stream back to an unseeked state.
- void ResetSeekState();
-
- // Returns true if |seek_timestamp| refers to the beginning of the first range
- // in |ranges_|, false otherwise or if |ranges_| is empty.
- bool ShouldSeekToStartOfBuffered(base::TimeDelta seek_timestamp) const;
-
- // Returns true if the timestamps of |buffers| are monotonically increasing
- // since the previous append to the media segment, false otherwise.
- bool IsMonotonicallyIncreasing(const BufferQueue& buffers) const;
-
- // Returns true if |selected_range_| is the only range in |ranges_| that
- // HasNextBufferPosition().
- bool OnlySelectedRangeIsSeeked() const;
-
- // Measures the distances between buffer timestamps and tracks the max.
- void UpdateMaxInterbufferDistance(const BufferQueue& buffers);
-
- // Sets the config ID for each buffer to |append_config_index_|.
- void SetConfigIds(const BufferQueue& buffers);
-
- // Called to complete a config change. Updates |current_config_index_| to
- // match the index of the next buffer. Calling this method causes
- // GetNextBuffer() to stop returning kConfigChange and start returning
- // kSuccess.
- void CompleteConfigChange();
-
- // Callback used to report error strings that can help the web developer
- // figure out what is wrong with the content.
- LogCB log_cb_;
-
- // List of disjoint buffered ranges, ordered by start time.
- RangeList ranges_;
-
- // Indicates which decoder config is being used by the decoder.
- // GetNextBuffer() is only allows to return buffers that have a
- // config ID that matches this index. If there is a mismatch then
- // it must signal that a config change is needed.
- int current_config_index_;
-
- // Indicates which decoder config to associate with new buffers
- // being appended. Each new buffer appended has its config ID set
- // to the value of this field.
- int append_config_index_;
-
- // Holds the audio/video configs for this stream. |current_config_index_|
- // and |append_config_index_| represent indexes into one of these vectors.
- std::vector<AudioDecoderConfig*> audio_configs_;
- std::vector<VideoDecoderConfig*> video_configs_;
-
- // True if more data needs to be appended before the Seek() can complete,
- // false if no Seek() has been requested or the Seek() is completed.
- bool seek_pending_;
-
- // The timestamp of the keyframe right before the seek timestamp.
- base::TimeDelta seek_keyframe_timestamp_;
-
- // Timestamp of the last request to Seek().
- base::TimeDelta seek_buffer_timestamp_;
-
- // Pointer to the seeked-to Range. This is the range from which
- // GetNextBuffer() calls are fulfilled after the |track_buffer_| has been
- // emptied.
- SourceBufferRange* selected_range_;
-
- // Queue of the next buffers to be returned from calls to GetNextBuffer(). If
- // |track_buffer_| is empty, return buffers from |selected_range_|.
- BufferQueue track_buffer_;
-
- // The start time of the current media segment being appended.
- base::TimeDelta media_segment_start_time_;
-
- // Points to the range containing the current media segment being appended.
- RangeList::iterator range_for_next_append_;
-
- // True when the next call to Append() begins a new media segment.
- bool new_media_segment_;
-
- // The timestamp of the last buffer appended to the media segment, set to
- // kNoTimestamp() if the beginning of the segment.
- base::TimeDelta last_buffer_timestamp_;
-
- // Stores the largest distance between two adjacent buffers in this stream.
- base::TimeDelta max_interbuffer_distance_;
-
- // The maximum amount of data in bytes the stream will keep in memory.
- int memory_limit_;
-
- // Indicates that a kConfigChanged status has been reported by GetNextBuffer()
- // and GetCurrentXXXDecoderConfig() must be called to update the current
- // config. GetNextBuffer() must not be called again until
- // GetCurrentXXXDecoderConfig() has been called.
- bool config_change_pending_;
-
- DISALLOW_COPY_AND_ASSIGN(SourceBufferStream);
-};
-
-void LogMediaSourceTimeRanges(const std::string& desc,
- const Ranges<base::TimeDelta>& time_ranges);
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_SOURCE_BUFFER_STREAM_H_
diff --git a/src/media/filters/source_buffer_stream_unittest.cc b/src/media/filters/source_buffer_stream_unittest.cc
deleted file mode 100644
index b4fa9ea..0000000
--- a/src/media/filters/source_buffer_stream_unittest.cc
+++ /dev/null
@@ -1,2589 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/source_buffer_stream.h"
-
-#include <string>
-
-#include "base/logging.h"
-#include "base/string_number_conversions.h"
-#include "base/string_split.h"
-#include "base/string_util.h"
-#include "media/base/data_buffer.h"
-#include "media/base/media_log.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-static const int kDefaultFramesPerSecond = 30;
-static const int kDefaultKeyframesPerSecond = 6;
-static const uint8 kDataA = 0x11;
-static const uint8 kDataB = 0x33;
-static const int kDataSize = 1;
-static const gfx::Size kCodedSize(320, 240);
-
-class SourceBufferStreamTest : public testing::Test {
- protected:
- SourceBufferStreamTest() {
- config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
- VideoFrame::YV12, kCodedSize, gfx::Rect(kCodedSize),
- kCodedSize, NULL, 0, false, false);
- stream_.reset(new SourceBufferStream(config_, LogCB()));
- SetStreamInfo(kDefaultFramesPerSecond, kDefaultKeyframesPerSecond);
- }
-
- void SetMemoryLimit(int buffers_of_data) {
- stream_->set_memory_limit(buffers_of_data * kDataSize);
- }
-
- void SetStreamInfo(int frames_per_second, int keyframes_per_second) {
- frames_per_second_ = frames_per_second;
- keyframes_per_second_ = keyframes_per_second;
- frame_duration_ = ConvertToFrameDuration(frames_per_second);
- }
-
- void NewSegmentAppend(int starting_position, int number_of_buffers) {
- AppendBuffers(starting_position, number_of_buffers, true,
- base::TimeDelta(), true, &kDataA, kDataSize);
- }
-
- void NewSegmentAppend(int starting_position, int number_of_buffers,
- const uint8* data) {
- AppendBuffers(starting_position, number_of_buffers, true,
- base::TimeDelta(), true, data, kDataSize);
- }
-
- void NewSegmentAppend_OffsetFirstBuffer(
- int starting_position, int number_of_buffers,
- base::TimeDelta first_buffer_offset) {
- AppendBuffers(starting_position, number_of_buffers, true,
- first_buffer_offset, true, &kDataA, kDataSize);
- }
-
- void NewSegmentAppend_ExpectFailure(
- int starting_position, int number_of_buffers) {
- AppendBuffers(starting_position, number_of_buffers, true,
- base::TimeDelta(), false, &kDataA, kDataSize);
- }
-
- void AppendBuffers(int starting_position, int number_of_buffers) {
- AppendBuffers(starting_position, number_of_buffers, false,
- base::TimeDelta(), true, &kDataA, kDataSize);
- }
-
- void AppendBuffers(int starting_position, int number_of_buffers,
- const uint8* data) {
- AppendBuffers(starting_position, number_of_buffers, false,
- base::TimeDelta(), true, data, kDataSize);
- }
-
- void NewSegmentAppend(const std::string& buffers_to_append) {
- AppendBuffers(buffers_to_append, true, false);
- }
-
- void AppendBuffers(const std::string& buffers_to_append) {
- AppendBuffers(buffers_to_append, false, false);
- }
-
- void NewSegmentAppendOneByOne(const std::string& buffers_to_append) {
- AppendBuffers(buffers_to_append, true, true);
- }
-
- void AppendBuffersOneByOne(const std::string& buffers_to_append) {
- AppendBuffers(buffers_to_append, false, true);
- }
-
- void Seek(int position) {
- stream_->Seek(position * frame_duration_);
- }
-
- void SeekToTimestamp(base::TimeDelta timestamp) {
- stream_->Seek(timestamp);
- }
-
- void CheckExpectedRanges(const std::string& expected) {
- Ranges<base::TimeDelta> r = stream_->GetBufferedTime();
-
- std::stringstream ss;
- ss << "{ ";
- for (size_t i = 0; i < r.size(); ++i) {
- int64 start = (r.start(i) / frame_duration_);
- int64 end = (r.end(i) / frame_duration_) - 1;
- ss << "[" << start << "," << end << ") ";
- }
- ss << "}";
- EXPECT_EQ(expected, ss.str());
- }
-
- void CheckExpectedRangesByTimestamp(const std::string& expected) {
- Ranges<base::TimeDelta> r = stream_->GetBufferedTime();
-
- std::stringstream ss;
- ss << "{ ";
- for (size_t i = 0; i < r.size(); ++i) {
- int64 start = r.start(i).InMilliseconds();
- int64 end = r.end(i).InMilliseconds();
- ss << "[" << start << "," << end << ") ";
- }
- ss << "}";
- EXPECT_EQ(expected, ss.str());
- }
-
- void CheckExpectedBuffers(
- int starting_position, int ending_position) {
- CheckExpectedBuffers(starting_position, ending_position, false, NULL, 0);
- }
-
- void CheckExpectedBuffers(
- int starting_position, int ending_position, bool expect_keyframe) {
- CheckExpectedBuffers(starting_position, ending_position, expect_keyframe,
- NULL, 0);
- }
-
- void CheckExpectedBuffers(
- int starting_position, int ending_position, const uint8* data) {
- CheckExpectedBuffers(starting_position, ending_position, false, data,
- kDataSize);
- }
-
- void CheckExpectedBuffers(
- int starting_position, int ending_position, const uint8* data,
- bool expect_keyframe) {
- CheckExpectedBuffers(starting_position, ending_position, expect_keyframe,
- data, kDataSize);
- }
-
- void CheckExpectedBuffers(
- int starting_position, int ending_position, bool expect_keyframe,
- const uint8* expected_data, int expected_size) {
- int current_position = starting_position;
- for (; current_position <= ending_position; current_position++) {
- scoped_refptr<StreamParserBuffer> buffer;
- SourceBufferStream::Status status = stream_->GetNextBuffer(&buffer);
-
- EXPECT_NE(status, SourceBufferStream::kConfigChange);
- if (status != SourceBufferStream::kSuccess)
- break;
-
- if (expect_keyframe && current_position == starting_position)
- EXPECT_TRUE(buffer->IsKeyframe());
-
- if (expected_data) {
- const uint8* actual_data = buffer->GetData();
- const int actual_size = buffer->GetDataSize();
- EXPECT_EQ(expected_size, actual_size);
- for (int i = 0; i < std::min(actual_size, expected_size); i++) {
- EXPECT_EQ(expected_data[i], actual_data[i]);
- }
- }
-
- EXPECT_EQ(buffer->GetDecodeTimestamp() / frame_duration_,
- current_position);
- }
-
- EXPECT_EQ(ending_position + 1, current_position);
- }
-
- void CheckExpectedBuffers(const std::string& expected) {
- std::vector<std::string> timestamps;
- base::SplitString(expected, ' ', ×tamps);
- for (size_t i = 0; i < timestamps.size(); i++) {
- scoped_refptr<StreamParserBuffer> buffer;
- SourceBufferStream::Status status = stream_->GetNextBuffer(&buffer);
-
- EXPECT_EQ(SourceBufferStream::kSuccess, status);
- if (status != SourceBufferStream::kSuccess)
- break;
-
- std::stringstream ss;
- ss << buffer->GetDecodeTimestamp().InMilliseconds();
- if (buffer->IsKeyframe())
- ss << "K";
- EXPECT_EQ(timestamps[i], ss.str());
- }
- }
-
- void CheckNoNextBuffer() {
- scoped_refptr<StreamParserBuffer> buffer;
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kNeedBuffer);
- }
-
- void CheckConfig(const VideoDecoderConfig& config) {
- const VideoDecoderConfig& actual = stream_->GetCurrentVideoDecoderConfig();
- EXPECT_TRUE(actual.Matches(config))
- << "Expected: " << config.AsHumanReadableString()
- << "\nActual: " << actual.AsHumanReadableString();
- }
-
- base::TimeDelta frame_duration() const { return frame_duration_; }
-
- scoped_ptr<SourceBufferStream> stream_;
- VideoDecoderConfig config_;
-
- private:
- base::TimeDelta ConvertToFrameDuration(int frames_per_second) {
- return base::TimeDelta::FromMicroseconds(
- base::Time::kMicrosecondsPerSecond / frames_per_second);
- }
-
- void AppendBuffers(int starting_position,
- int number_of_buffers,
- bool begin_media_segment,
- base::TimeDelta first_buffer_offset,
- bool expect_success,
- const uint8* data,
- int size) {
- if (begin_media_segment)
- stream_->OnNewMediaSegment(starting_position * frame_duration_);
-
- int keyframe_interval = frames_per_second_ / keyframes_per_second_;
-
- SourceBufferStream::BufferQueue queue;
- for (int i = 0; i < number_of_buffers; i++) {
- int position = starting_position + i;
- bool is_keyframe = position % keyframe_interval == 0;
- scoped_refptr<StreamParserBuffer> buffer =
- StreamParserBuffer::CopyFrom(data, size, is_keyframe);
- base::TimeDelta timestamp = frame_duration_ * position;
-
- if (i == 0)
- timestamp += first_buffer_offset;
- buffer->SetDecodeTimestamp(timestamp);
-
- // Simulate an IBB...BBP pattern in which all B-frames reference both
- // the I- and P-frames. For a GOP with playback order 12345, this would
- // result in a decode timestamp order of 15234.
- base::TimeDelta presentation_timestamp;
- if (is_keyframe) {
- presentation_timestamp = timestamp;
- } else if ((position - 1) % keyframe_interval == 0) {
- // This is the P-frame (first frame following the I-frame)
- presentation_timestamp =
- (timestamp + frame_duration_ * (keyframe_interval - 2));
- } else {
- presentation_timestamp = timestamp - frame_duration_;
- }
- buffer->SetTimestamp(presentation_timestamp);
-
- queue.push_back(buffer);
- }
- if (!queue.empty())
- EXPECT_EQ(expect_success, stream_->Append(queue));
- }
-
- void AppendBuffers(const std::string& buffers_to_append,
- bool start_new_segment, bool one_by_one) {
- std::vector<std::string> timestamps;
- base::SplitString(buffers_to_append, ' ', ×tamps);
-
- CHECK_GT(timestamps.size(), 0u);
-
- SourceBufferStream::BufferQueue buffers;
- for (size_t i = 0; i < timestamps.size(); i++) {
- bool is_keyframe = false;
- if (EndsWith(timestamps[i], "K", true)) {
- is_keyframe = true;
- // Remove the "K" off of the token.
- timestamps[i] = timestamps[i].substr(0, timestamps[i].length() - 1);
- }
- int time_in_ms;
- CHECK(base::StringToInt(timestamps[i], &time_in_ms));
-
- // Create buffer.
- scoped_refptr<StreamParserBuffer> buffer =
- StreamParserBuffer::CopyFrom(&kDataA, kDataSize, is_keyframe);
- base::TimeDelta timestamp =
- base::TimeDelta::FromMilliseconds(time_in_ms);
- buffer->SetDecodeTimestamp(timestamp);
-
- if (i == 0u && start_new_segment)
- stream_->OnNewMediaSegment(timestamp);
-
- buffers.push_back(buffer);
- }
-
- if (!one_by_one) {
- EXPECT_TRUE(stream_->Append(buffers));
- return;
- }
-
- // Append each buffer one by one.
- for (size_t i = 0; i < buffers.size(); i++) {
- SourceBufferStream::BufferQueue wrapper;
- wrapper.push_back(buffers[i]);
- EXPECT_TRUE(stream_->Append(wrapper));
- }
- }
-
- int frames_per_second_;
- int keyframes_per_second_;
- base::TimeDelta frame_duration_;
- DISALLOW_COPY_AND_ASSIGN(SourceBufferStreamTest);
-};
-
-TEST_F(SourceBufferStreamTest, Append_SingleRange) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 14);
-}
-
-TEST_F(SourceBufferStreamTest, Append_SingleRange_OneBufferAtATime) {
- // Append 15 buffers starting at position 0, one buffer at a time.
- NewSegmentAppend(0, 1);
- for (int i = 1; i < 15; i++)
- AppendBuffers(i, 1);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 14);
-}
-
-TEST_F(SourceBufferStreamTest, Append_DisjointRanges) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 5);
-
- // Append 10 buffers at positions 15 through 24.
- NewSegmentAppend(15, 10);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,4) [15,24) }");
- // Check buffers in ranges.
- Seek(0);
- CheckExpectedBuffers(0, 4);
- Seek(15);
- CheckExpectedBuffers(15, 24);
-}
-
-TEST_F(SourceBufferStreamTest, Append_AdjacentRanges) {
- // Append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10);
-
- // Append 11 buffers at positions 15 through 25.
- NewSegmentAppend(15, 11);
-
- // Append 5 buffers at positions 10 through 14 to bridge the gap.
- NewSegmentAppend(10, 5);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,25) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 25);
-}
-
-TEST_F(SourceBufferStreamTest, Append_DoesNotBeginWithKeyframe) {
- // Append fails because the range doesn't begin with a keyframe.
- NewSegmentAppend_ExpectFailure(3, 2);
-
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,14) }");
- // Check buffers in range.
- Seek(5);
- CheckExpectedBuffers(5, 14);
-
- // Append fails because the range doesn't begin with a keyframe.
- NewSegmentAppend_ExpectFailure(17, 3);
-
- CheckExpectedRanges("{ [5,14) }");
- Seek(5);
- CheckExpectedBuffers(5, 14);
-}
-
-TEST_F(SourceBufferStreamTest, Append_DoesNotBeginWithKeyframe_Adjacent) {
- // Append 8 buffers at positions 0 through 7.
- NewSegmentAppend(0, 8);
-
- // Now start a new media segment at position 8. Append should fail because
- // the media segment does not begin with a keyframe.
- NewSegmentAppend_ExpectFailure(8, 2);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,7) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 7);
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap) {
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5);
-
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 14);
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_EdgeCase) {
- // Make each frame a keyframe so that it's okay to overlap frames at any point
- // (instead of needing to respect keyframe boundaries).
- SetStreamInfo(30, 30);
-
- // Append 6 buffers at positions 6 through 11.
- NewSegmentAppend(6, 6);
-
- // Append 8 buffers at positions 5 through 12.
- NewSegmentAppend(5, 8);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,12) }");
- // Check buffers in range.
- Seek(5);
- CheckExpectedBuffers(5, 12);
-}
-
-TEST_F(SourceBufferStreamTest, Start_Overlap) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 5);
-
- // Append 6 buffers at positions 10 through 15.
- NewSegmentAppend(10, 6);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,15) }");
- // Check buffers in range.
- Seek(5);
- CheckExpectedBuffers(5, 15);
-}
-
-TEST_F(SourceBufferStreamTest, End_Overlap) {
- // Append 10 buffers at positions 10 through 19.
- NewSegmentAppend(10, 10);
-
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,19) }");
- // Check buffers in range.
- Seek(5);
- CheckExpectedBuffers(5, 19);
-}
-
-TEST_F(SourceBufferStreamTest, End_Overlap_Several) {
- // Append 10 buffers at positions 10 through 19.
- NewSegmentAppend(10, 10);
-
- // Append 8 buffers at positions 5 through 12.
- NewSegmentAppend(5, 8);
-
- // Check expected ranges: stream should not have kept buffers 13 and 14
- // because the keyframe on which they depended was overwritten.
- CheckExpectedRanges("{ [5,12) [15,19) }");
-
- // Check buffers in range.
- Seek(5);
- CheckExpectedBuffers(5, 12);
- CheckNoNextBuffer();
-
- Seek(19);
- CheckExpectedBuffers(15, 19);
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Several) {
- // Append 2 buffers at positions 5 through 6.
- NewSegmentAppend(5, 2);
-
- // Append 2 buffers at positions 10 through 11.
- NewSegmentAppend(10, 2);
-
- // Append 2 buffers at positions 15 through 16.
- NewSegmentAppend(15, 2);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [5,6) [10,11) [15,16) }");
-
- // Append buffers at positions 0 through 19.
- NewSegmentAppend(0, 20);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,19) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 19);
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Several_Then_Merge) {
- // Append 2 buffers at positions 5 through 6.
- NewSegmentAppend(5, 2);
-
- // Append 2 buffers at positions 10 through 11.
- NewSegmentAppend(10, 2);
-
- // Append 2 buffers at positions 15 through 16.
- NewSegmentAppend(15, 2);
-
- // Append 2 buffers at positions 20 through 21.
- NewSegmentAppend(20, 2);
-
- // Append buffers at positions 0 through 19.
- NewSegmentAppend(0, 20);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,21) }");
- // Check buffers in range.
- Seek(0);
- CheckExpectedBuffers(0, 21);
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Selected) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to buffer at position 5.
- Seek(5);
-
- // Replace old data with new data.
- NewSegmentAppend(5, 10, &kDataB);
-
- // Check ranges are correct.
- CheckExpectedRanges("{ [5,14) }");
-
- // Check that data has been replaced with new data.
- CheckExpectedBuffers(5, 14, &kDataB);
-}
-
-// This test is testing that a client can append data to SourceBufferStream that
-// overlaps the range from which the client is currently grabbing buffers. We
-// would expect that the SourceBufferStream would return old data until it hits
-// the keyframe of the new data, after which it will return the new data.
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Selected_TrackBuffer) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to buffer at position 5 and get next buffer.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Do a complete overlap by appending 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataB);
-
- // Check range is correct.
- CheckExpectedRanges("{ [0,19) }");
-
- // Expect old data up until next keyframe in new data.
- CheckExpectedBuffers(6, 9, &kDataA);
- CheckExpectedBuffers(10, 10, &kDataB, true);
-
- // Expect rest of data to be new.
- CheckExpectedBuffers(11, 19, &kDataB);
-
- // Seek back to beginning; all data should be new.
- Seek(0);
- CheckExpectedBuffers(0, 19, &kDataB);
-
- // Check range continues to be correct.
- CheckExpectedRanges("{ [0,19) }");
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Selected_EdgeCase) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to buffer at position 5 and get next buffer.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Replace existing data with new data.
- NewSegmentAppend(5, 10, &kDataB);
-
- // Check ranges are correct.
- CheckExpectedRanges("{ [5,14) }");
-
- // Expect old data up until next keyframe in new data.
- CheckExpectedBuffers(6, 9, &kDataA);
- CheckExpectedBuffers(10, 10, &kDataB, true);
-
- // Expect rest of data to be new.
- CheckExpectedBuffers(11, 14, &kDataB);
-
- // Seek back to beginning; all data should be new.
- Seek(5);
- CheckExpectedBuffers(5, 14, &kDataB);
-
- // Check range continues to be correct.
- CheckExpectedRanges("{ [5,14) }");
-}
-
-TEST_F(SourceBufferStreamTest, Complete_Overlap_Selected_Multiple) {
- static const uint8 kDataC = 0x55;
- static const uint8 kDataD = 0x77;
-
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataA);
-
- // Seek to buffer at position 5 and get next buffer.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Replace existing data with new data.
- NewSegmentAppend(5, 5, &kDataB);
-
- // Then replace it again with different data.
- NewSegmentAppend(5, 5, &kDataC);
-
- // Now append 5 new buffers at positions 10 through 14.
- NewSegmentAppend(10, 5, &kDataC);
-
- // Now replace all the data entirely.
- NewSegmentAppend(5, 10, &kDataD);
-
- // Expect buffers 6 through 9 to be DataA, and the remaining
- // buffers to be kDataD.
- CheckExpectedBuffers(6, 9, &kDataA);
- CheckExpectedBuffers(10, 14, &kDataD);
-
- // At this point we cannot fulfill request.
- CheckNoNextBuffer();
-
- // Seek back to beginning; all data should be new.
- Seek(5);
- CheckExpectedBuffers(5, 14, &kDataD);
-}
-
-TEST_F(SourceBufferStreamTest, Start_Overlap_Selected) {
- // Append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataA);
-
- // Seek to position 5, then add buffers to overlap data at that position.
- Seek(5);
- NewSegmentAppend(5, 10, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Because we seeked to a keyframe, the next buffers should all be new data.
- CheckExpectedBuffers(5, 14, &kDataB);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 4, &kDataA);
- CheckExpectedBuffers(5, 14, &kDataB);
-}
-
-TEST_F(SourceBufferStreamTest, Start_Overlap_Selected_TrackBuffer) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15, &kDataA);
-
- // Seek to 10 and get buffer.
- Seek(10);
- CheckExpectedBuffers(10, 10, &kDataA);
-
- // Now append 10 buffers of new data at positions 10 through 19.
- NewSegmentAppend(10, 10, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,19) }");
-
- // The next 4 buffers should be a from the old buffer, followed by a keyframe
- // from the new data.
- CheckExpectedBuffers(11, 14, &kDataA);
- CheckExpectedBuffers(15, 15, &kDataB, true);
-
- // The rest of the buffers should be new data.
- CheckExpectedBuffers(16, 19, &kDataB);
-
- // Now seek to the beginning; positions 0 through 9 should be the original
- // data, positions 10 through 19 should be the new data.
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataA);
- CheckExpectedBuffers(10, 19, &kDataB);
-
- // Make sure range is still correct.
- CheckExpectedRanges("{ [0,19) }");
-}
-
-TEST_F(SourceBufferStreamTest, Start_Overlap_Selected_EdgeCase) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- Seek(10);
- CheckExpectedBuffers(10, 10, &kDataA);
-
- // Now replace the last 5 buffers with new data.
- NewSegmentAppend(10, 5, &kDataB);
-
- // The next 4 buffers should be the origial data, held in the track buffer.
- CheckExpectedBuffers(11, 14, &kDataA);
-
- // The next buffer is at position 15, so we should fail to fulfill the
- // request.
- CheckNoNextBuffer();
-
- // Now append data at 15 through 19 and check to make sure it's correct.
- NewSegmentAppend(15, 5, &kDataB);
- CheckExpectedBuffers(15, 19, &kDataB);
-
- // Seek to beginning of buffered range and check buffers.
- Seek(5);
- CheckExpectedBuffers(5, 9, &kDataA);
- CheckExpectedBuffers(10, 19, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,19) }");
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer is a keyframe that's being overlapped by new
-// buffers.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : *A*a a a a A a a a a
-// new : B b b b b B b b b b
-// after: B b b b b*B*b b b b A a a a a
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 5.
- Seek(5);
-
- // Now append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Because we seeked to a keyframe, the next buffers should be new.
- CheckExpectedBuffers(5, 9, &kDataB);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is after the newly appended buffers.
-// In this particular case, the end overlap does not require a split.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A a a a a A a a*a*a|
-// new : B b b b b B b b b b
-// after: |B b b b b B b b b b A a a*a*a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_AfterEndOfNew_1) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 10, then move to position 13.
- Seek(10);
- CheckExpectedBuffers(10, 12, &kDataA);
-
- // Now append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Make sure rest of data is as expected.
- CheckExpectedBuffers(13, 14, &kDataA);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is after the newly appended buffers.
-// In this particular case, the end overlap requires a split, and the next
-// buffer is in the split range.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A a a a a A a a*a*a|
-// new : B b b b b B b b
-// after: |B b b b b B b b| |A a a*a*a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_AfterEndOfNew_2) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 10, then move to position 13.
- Seek(10);
- CheckExpectedBuffers(10, 12, &kDataA);
-
- // Now append 8 buffers at positions 0 through 7.
- NewSegmentAppend(0, 8, &kDataB);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,7) [10,14) }");
-
- // Make sure rest of data is as expected.
- CheckExpectedBuffers(13, 14, &kDataA);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 7, &kDataB);
- CheckNoNextBuffer();
-
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is after the newly appended buffers.
-// In this particular case, the end overlap requires a split, and the next
-// buffer was in between the end of the new data and the split range.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A a a*a*a A a a a a|
-// new : B b b b b B b b
-// after: |B b b b b B b b| |A a a a a|
-// track: |a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_AfterEndOfNew_3) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 5, then move to position 8.
- Seek(5);
- CheckExpectedBuffers(5, 7, &kDataA);
-
- // Now append 8 buffers at positions 0 through 7.
- NewSegmentAppend(0, 8, &kDataB);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,7) [10,14) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(8, 9, &kDataA);
- // The buffer immediately after the track buffer should be a keyframe.
- CheckExpectedBuffers(10, 10, &kDataA, true);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 7, &kDataB);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is overlapped by the new buffers.
-// In this particular case, the end overlap does not require a split.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A a a*a*a A a a a a|
-// new : B b b b b B b b b b
-// after: |B b b b b B b b b b A a a a a|
-// track: |a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_OverlappedByNew_1) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 5, then move to position 8.
- Seek(5);
- CheckExpectedBuffers(5, 7, &kDataA);
-
- // Now append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(8, 9, &kDataA);
- // The buffer immediately after the track buffer should be a keyframe.
- CheckExpectedBuffers(10, 10, &kDataA, true);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is overlapped by the new buffers.
-// In this particular case, the end overlap requires a split, and the next
-// keyframe after the track buffer is in the split range.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A*a*a a a A a a a a|
-// new : B b b b b B b
-// after: |B b b b b B b| |A a a a a|
-// track: |a a a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_OverlappedByNew_2) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to position 5, then move to position 6.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Now append 7 buffers at positions 0 through 6.
- NewSegmentAppend(0, 7, &kDataB);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,6) [10,14) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(6, 9, &kDataA);
- // The buffer immediately after the track buffer should be a keyframe.
- CheckExpectedBuffers(10, 10, &kDataA, true);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 6, &kDataB);
- CheckNoNextBuffer();
-
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next buffer in the range is overlapped by the new buffers.
-// In this particular case, the end overlap requires a split, and the next
-// keyframe after the track buffer is in the range with the new buffers.
-//
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A*a*a a a A a a a a A a a a a|
-// new : B b b b b B b b b b B b b
-// after: |B b b b b B b b b b B b b| |A a a a a|
-// track: |a a a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_OverlappedByNew_3) {
- // Append 15 buffers at positions 5 through 19.
- NewSegmentAppend(5, 15, &kDataA);
-
- // Seek to position 5, then move to position 6.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Now append 13 buffers at positions 0 through 12.
- NewSegmentAppend(0, 13, &kDataB);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,12) [15,19) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(6, 9, &kDataA);
- // The buffer immediately after the track buffer should be a keyframe
- // from the new data.
- CheckExpectedBuffers(10, 10, &kDataB, true);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 12, &kDataB);
- CheckNoNextBuffer();
-
- Seek(15);
- CheckExpectedBuffers(15, 19, &kDataA);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and there is no keyframe after the end of the new buffers.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A*a*a a a|
-// new : B b b b b B
-// after: |B b b b b B|
-// track: |a a a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_NoKeyframeAfterNew) {
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataA);
-
- // Seek to position 5, then move to position 6.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Now append 6 buffers at positions 0 through 5.
- NewSegmentAppend(0, 6, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,5) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(6, 9, &kDataA);
-
- // Now there's no data to fulfill the request.
- CheckNoNextBuffer();
-
- // Let's fill in the gap, buffers 6 through 10.
- AppendBuffers(6, 5, &kDataB);
-
- // We should be able to get the next buffer.
- CheckExpectedBuffers(10, 10, &kDataB);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and there is no keyframe after the end of the new buffers, then the
-// range gets split.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A a a a a A*a*|
-// new : B b b b b B b b b b B
-// after: |B b b b b B b b b b B|
-// new : A a a a a A
-// after: |A a a a a A| |B b b b b B|
-// track: |a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_NoKeyframeAfterNew2) {
- // Append 7 buffers at positions 10 through 16.
- NewSegmentAppend(10, 7, &kDataA);
-
- // Seek to position 15, then move to position 16.
- Seek(15);
- CheckExpectedBuffers(15, 15, &kDataA);
-
- // Now append 11 buffers at positions 5 through 15.
- NewSegmentAppend(5, 11, &kDataB);
- CheckExpectedRanges("{ [5,15) }");
-
- // Now do another end-overlap to split the range into two parts, where the
- // 2nd range should have the next buffer position.
- NewSegmentAppend(0, 6, &kDataA);
- CheckExpectedRanges("{ [0,5) [10,15) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(16, 16, &kDataA);
-
- // Now there's no data to fulfill the request.
- CheckNoNextBuffer();
-
- // Add data to the 2nd range, should not be able to fulfill the next read
- // until we've added a keyframe.
- NewSegmentAppend(15, 1, &kDataB);
- CheckNoNextBuffer();
- for (int i = 16; i <= 19; i++) {
- AppendBuffers(i, 1, &kDataB);
- CheckNoNextBuffer();
- }
-
- // Now append a keyframe.
- AppendBuffers(20, 1, &kDataB);
-
- // We should be able to get the next buffer.
- CheckExpectedBuffers(20, 20, &kDataB, true);
-}
-
-// This test covers the case where new buffers end-overlap an existing, selected
-// range, and the next keyframe in a separate range.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : |A*a*a a a| |A a a a a|
-// new : B b b b b B
-// after: |B b b b b B| |A a a a a|
-// track: |a a a a|
-TEST_F(SourceBufferStreamTest, End_Overlap_Selected_NoKeyframeAfterNew3) {
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataA);
-
- // Append 5 buffers at positions 15 through 19.
- NewSegmentAppend(15, 5, &kDataA);
-
- // Check expected range.
- CheckExpectedRanges("{ [5,9) [15,19) }");
-
- // Seek to position 5, then move to position 6.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Now append 6 buffers at positions 0 through 5.
- NewSegmentAppend(0, 6, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,5) [15,19) }");
-
- // Check for data in the track buffer.
- CheckExpectedBuffers(6, 9, &kDataA);
-
- // Now there's no data to fulfill the request.
- CheckNoNextBuffer();
-
- // Let's fill in the gap, buffers 6 through 14.
- AppendBuffers(6, 9, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,19) }");
-
- // We should be able to get the next buffer.
- CheckExpectedBuffers(10, 14, &kDataB);
-
- // We should be able to get the next buffer.
- CheckExpectedBuffers(15, 19, &kDataA);
-}
-
-// This test covers the case when new buffers overlap the middle of a selected
-// range. This tests the case when there is no split and the next buffer is a
-// keyframe.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : A a a a a*A*a a a a A a a a a
-// new : B b b b b
-// after: A a a a a*B*b b b b A a a a a
-TEST_F(SourceBufferStreamTest, Middle_Overlap_Selected_1) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15, &kDataA);
-
- // Seek to position 5.
- Seek(5);
-
- // Now append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Check for next data; should be new data.
- CheckExpectedBuffers(5, 9, &kDataB);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 4, &kDataA);
- CheckExpectedBuffers(5, 9, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case when new buffers overlap the middle of a selected
-// range. This tests the case when there is no split and the next buffer is
-// after the new buffers.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : A a a a a A a a a a A*a*a a a
-// new : B b b b b
-// after: A a a a a B b b b b A*a*a a a
-TEST_F(SourceBufferStreamTest, Middle_Overlap_Selected_2) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15, &kDataA);
-
- // Seek to 10 then move to position 11.
- Seek(10);
- CheckExpectedBuffers(10, 10, &kDataA);
-
- // Now append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,14) }");
-
- // Make sure data is correct.
- CheckExpectedBuffers(11, 14, &kDataA);
- Seek(0);
- CheckExpectedBuffers(0, 4, &kDataA);
- CheckExpectedBuffers(5, 9, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case when new buffers overlap the middle of a selected
-// range. This tests the case when there is a split and the next buffer is
-// before the new buffers.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : A a*a*a a A a a a a A a a a a
-// new : B b b
-// after: A a*a*a a B b b| |A a a a a
-TEST_F(SourceBufferStreamTest, Middle_Overlap_Selected_3) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15, &kDataA);
-
- // Seek to beginning then move to position 2.
- Seek(0);
- CheckExpectedBuffers(0, 1, &kDataA);
-
- // Now append 3 buffers at positions 5 through 7.
- NewSegmentAppend(5, 3, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,7) [10,14) }");
-
- // Make sure data is correct.
- CheckExpectedBuffers(2, 4, &kDataA);
- CheckExpectedBuffers(5, 7, &kDataB);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-// This test covers the case when new buffers overlap the middle of a selected
-// range. This tests the case when there is a split and the next buffer is after
-// the new buffers but before the split range.
-// index: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
-// old : A a a a a A a a*a*a A a a a a
-// new : B b b
-// after: |A a a a a B b b| |A a a a a|
-// track: |a a|
-TEST_F(SourceBufferStreamTest, Middle_Overlap_Selected_4) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15, &kDataA);
-
- // Seek to 5 then move to position 8.
- Seek(5);
- CheckExpectedBuffers(5, 7, &kDataA);
-
- // Now append 3 buffers at positions 5 through 7.
- NewSegmentAppend(5, 3, &kDataB);
-
- // Check expected range.
- CheckExpectedRanges("{ [0,7) [10,14) }");
-
- // Buffers 8 and 9 should be in the track buffer.
- CheckExpectedBuffers(8, 9, &kDataA);
- // The buffer immediately after the track buffer should be a keyframe.
- CheckExpectedBuffers(10, 10, &kDataA, true);
-
- // Make sure all data is correct.
- Seek(0);
- CheckExpectedBuffers(0, 4, &kDataA);
- CheckExpectedBuffers(5, 7, &kDataB);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne) {
- // Append 5 buffers starting at 10ms, 30ms apart.
- NewSegmentAppendOneByOne("10K 40 70 100 130");
-
- // The range ends at 160, accounting for the last buffer's duration.
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Overlap with 10 buffers starting at the beginning, appended one at a
- // time.
- NewSegmentAppend(0, 1, &kDataB);
- for (int i = 1; i < 10; i++)
- AppendBuffers(i, 1, &kDataB);
-
- // All data should be replaced.
- Seek(0);
- CheckExpectedRanges("{ [0,9) }");
- CheckExpectedBuffers(0, 9, &kDataB);
-}
-
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_DeleteGroup) {
- NewSegmentAppendOneByOne("10K 40 70 100 130K");
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Seek to 130ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(130));
-
- // Overlap with a new segment from 0 to 120ms.
- NewSegmentAppendOneByOne("0K 120");
-
- // Next buffer should still be 130ms.
- CheckExpectedBuffers("130K");
-
- // Check the final buffers is correct.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
- CheckExpectedBuffers("0K 120 130K");
-}
-
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_BetweenMediaSegments) {
- // Append 5 buffers starting at 110ms, 30ms apart.
- NewSegmentAppendOneByOne("110K 140 170 200 230");
- CheckExpectedRangesByTimestamp("{ [110,260) }");
-
- // Now append 2 media segments from 0ms to 210ms, 30ms apart. Note that the
- // old keyframe 110ms falls in between these two segments.
- NewSegmentAppendOneByOne("0K 30 60 90");
- NewSegmentAppendOneByOne("120K 150 180 210");
- CheckExpectedRangesByTimestamp("{ [0,240) }");
-
- // Check the final buffers is correct; the keyframe at 110ms should be
- // deleted.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
- CheckExpectedBuffers("0K 30 60 90 120K 150 180 210");
-}
-
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_TrackBuffer) {
- NewSegmentAppendOneByOne("10K 40 70 100K 125 130K");
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Seek to 70ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(10));
- CheckExpectedBuffers("10K 40");
-
- // Overlap with a new segment from 0 to 120ms.
- NewSegmentAppendOneByOne("0K 30 60 90 120K");
- CheckExpectedRangesByTimestamp("{ [0,160) }");
-
- // Should return frames 70ms and 100ms from the track buffer, then switch
- // to the new data at 120K, then switch back to the old data at 130K. The
- // frame at 125ms that depended on keyframe 100ms should have been deleted.
- CheckExpectedBuffers("70 100K 120K 130K");
-
- // Check the final result: should not include data from the track buffer.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
- CheckExpectedBuffers("0K 30 60 90 120K 130K");
-}
-
-// Overlap the next keyframe after the end of the track buffer with a new
-// keyframe.
-// old : 10K 40 *70* 100K 125 130K
-// new : 0K 30 60 90 120K
-// after: 0K 30 60 90 *120K* 130K
-// track: 70 100K
-// new : 110K 130
-// after: 0K 30 60 90 *110K* 130
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_TrackBuffer2) {
- NewSegmentAppendOneByOne("10K 40 70 100K 125 130K");
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Seek to 70ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(70));
- CheckExpectedBuffers("10K 40");
-
- // Overlap with a new segment from 0 to 120ms; 70ms and 100ms go in track
- // buffer.
- NewSegmentAppendOneByOne("0K 30 60 90 120K");
- CheckExpectedRangesByTimestamp("{ [0,160) }");
-
- // Now overlap the keyframe at 120ms.
- NewSegmentAppendOneByOne("110K 130");
-
- // Should expect buffers 70ms and 100ms from the track buffer. Then it should
- // return the keyframe after the track buffer, which is at 110ms.
- CheckExpectedBuffers("70 100K 110K 130");
-}
-
-// Overlap the next keyframe after the end of the track buffer without a
-// new keyframe.
-// old : 10K 40 *70* 100K 125 130K
-// new : 0K 30 60 90 120K
-// after: 0K 30 60 90 *120K* 130K
-// track: 70 100K
-// new : 50K 80 110 140
-// after: 0K 30 50K 80 110 140 * (waiting for keyframe)
-// track: 70 100K 120K 130K
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_TrackBuffer3) {
- NewSegmentAppendOneByOne("10K 40 70 100K 125 130K");
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Seek to 70ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(70));
- CheckExpectedBuffers("10K 40");
-
- // Overlap with a new segment from 0 to 120ms; 70ms and 100ms go in track
- // buffer.
- NewSegmentAppendOneByOne("0K 30 60 90 120K");
- CheckExpectedRangesByTimestamp("{ [0,160) }");
-
- // Now overlap the keyframe at 120ms. There's no keyframe after 70ms, so 120ms
- // and 130ms go into the track buffer.
- NewSegmentAppendOneByOne("50K 80 110 140");
-
- // Should have all the buffers from the track buffer, then stall.
- CheckExpectedBuffers("70 100K 120K 130K");
- CheckNoNextBuffer();
-
- // Appending a keyframe should fulfill the read.
- AppendBuffersOneByOne("150K");
- CheckExpectedBuffers("150K");
- CheckNoNextBuffer();
-}
-
-// Overlap the next keyframe after the end of the track buffer with a keyframe
-// that comes before the end of the track buffer.
-// old : 10K 40 *70* 100K 125 130K
-// new : 0K 30 60 90 120K
-// after: 0K 30 60 90 *120K* 130K
-// track: 70 100K
-// new : 80K 110 140
-// after: 0K 30 60 *80K* 110 140
-// track: 70
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_TrackBuffer4) {
- NewSegmentAppendOneByOne("10K 40 70 100K 125 130K");
- CheckExpectedRangesByTimestamp("{ [10,160) }");
-
- // Seek to 70ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(70));
- CheckExpectedBuffers("10K 40");
-
- // Overlap with a new segment from 0 to 120ms; 70ms and 100ms go in track
- // buffer.
- NewSegmentAppendOneByOne("0K 30 60 90 120K");
- CheckExpectedRangesByTimestamp("{ [0,160) }");
-
- // Now append a keyframe at 80ms.
- NewSegmentAppendOneByOne("80K 110 140");
-
- CheckExpectedBuffers("70 80K 110 140");
- CheckNoNextBuffer();
-}
-
-// Overlap the next keyframe after the end of the track buffer with a keyframe
-// that comes before the end of the track buffer, when the selected stream was
-// waiting for the next keyframe.
-// old : 10K 40 *70* 100K
-// new : 0K 30 60 90 120
-// after: 0K 30 60 90 120 * (waiting for keyframe)
-// track: 70 100K
-// new : 80K 110 140
-// after: 0K 30 60 *80K* 110 140
-// track: 70
-TEST_F(SourceBufferStreamTest, Overlap_OneByOne_TrackBuffer5) {
- NewSegmentAppendOneByOne("10K 40 70 100K");
- CheckExpectedRangesByTimestamp("{ [10,130) }");
-
- // Seek to 70ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(70));
- CheckExpectedBuffers("10K 40");
-
- // Overlap with a new segment from 0 to 120ms; 70ms and 100ms go in track
- // buffer.
- NewSegmentAppendOneByOne("0K 30 60 90 120");
- CheckExpectedRangesByTimestamp("{ [0,150) }");
-
- // Now append a keyframe at 80ms. The buffer at 100ms should be deleted from
- // the track buffer.
- NewSegmentAppendOneByOne("80K 110 140");
-
- CheckExpectedBuffers("70 80K 110 140");
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, Seek_Keyframe) {
- // Append 6 buffers at positions 0 through 5.
- NewSegmentAppend(0, 6);
-
- // Seek to beginning.
- Seek(0);
- CheckExpectedBuffers(0, 5, true);
-}
-
-TEST_F(SourceBufferStreamTest, Seek_NonKeyframe) {
- // Append 15 buffers at positions 0 through 14.
- NewSegmentAppend(0, 15);
-
- // Seek to buffer at position 13.
- Seek(13);
-
- // Expect seeking back to the nearest keyframe.
- CheckExpectedBuffers(10, 14, true);
-
- // Seek to buffer at position 3.
- Seek(3);
-
- // Expect seeking back to the nearest keyframe.
- CheckExpectedBuffers(0, 3, true);
-}
-
-TEST_F(SourceBufferStreamTest, Seek_NotBuffered) {
- // Seek to beginning.
- Seek(0);
-
- // Try to get buffer; nothing's appended.
- CheckNoNextBuffer();
-
- // Append 2 buffers at positions 0.
- NewSegmentAppend(0, 2);
- Seek(0);
- CheckExpectedBuffers(0, 1);
-
- // Try to get buffer out of range.
- Seek(2);
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, Seek_InBetweenTimestamps) {
- // Append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10);
-
- base::TimeDelta bump = frame_duration() / 4;
- CHECK(bump > base::TimeDelta());
-
- // Seek to buffer a little after position 5.
- stream_->Seek(5 * frame_duration() + bump);
- CheckExpectedBuffers(5, 5, true);
-
- // Seek to buffer a little before position 5.
- stream_->Seek(5 * frame_duration() - bump);
- CheckExpectedBuffers(0, 0, true);
-}
-
-// This test will do a complete overlap of an existing range in order to add
-// buffers to the track buffers. Then the test does a seek to another part of
-// the stream. The SourceBufferStream should clear its internal track buffer in
-// response to the Seek().
-TEST_F(SourceBufferStreamTest, Seek_After_TrackBuffer_Filled) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10, &kDataA);
-
- // Seek to buffer at position 5 and get next buffer.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Do a complete overlap by appending 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataB);
-
- // Check range is correct.
- CheckExpectedRanges("{ [0,19) }");
-
- // Seek to beginning; all data should be new.
- Seek(0);
- CheckExpectedBuffers(0, 19, &kDataB);
-
- // Check range continues to be correct.
- CheckExpectedRanges("{ [0,19) }");
-}
-
-TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) {
- base::TimeDelta bump = frame_duration() / 4;
- CHECK(bump > base::TimeDelta());
-
- // Append 5 buffers at position (5 + |bump|) through 9, where the media
- // segment begins at position 5.
- Seek(5);
- NewSegmentAppend_OffsetFirstBuffer(5, 5, bump);
- scoped_refptr<StreamParserBuffer> buffer;
-
- // GetNextBuffer() should return the next buffer at position (5 + |bump|).
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
- EXPECT_EQ(buffer->GetDecodeTimestamp(), 5 * frame_duration() + bump);
-
- // Check rest of buffers.
- CheckExpectedBuffers(6, 9);
-
- // Seek to position 15.
- Seek(15);
-
- // Append 5 buffers at positions (15 + |bump|) through 19, where the media
- // segment begins at 15.
- NewSegmentAppend_OffsetFirstBuffer(15, 5, bump);
-
- // GetNextBuffer() should return the next buffer at position (15 + |bump|).
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
- EXPECT_EQ(buffer->GetDecodeTimestamp(), 15 * frame_duration() + bump);
-
- // Check rest of buffers.
- CheckExpectedBuffers(16, 19);
-}
-
-TEST_F(SourceBufferStreamTest, Seek_BeforeStartOfSegment) {
- // Append 10 buffers at positions 5 through 14.
- NewSegmentAppend(5, 10);
-
- // Seek to a time before the first buffer in the range.
- Seek(0);
-
- // Should return buffers from the beginning of the range.
- CheckExpectedBuffers(5, 14);
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 4);
-
- // Append 5 buffers at positions 10 through 14, and seek to the beginning of
- // this range.
- NewSegmentAppend(10, 5);
- Seek(10);
-
- // Now seek to the beginning of the first range.
- Seek(0);
-
- // Completely overlap the old seek point.
- NewSegmentAppend(5, 15);
-
- // The GetNextBuffer() call should respect the 2nd seek point.
- CheckExpectedBuffers(0, 0);
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap_Pending) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 5 buffers at positions 15 through 19 and seek to beginning of the
- // range.
- NewSegmentAppend(15, 5);
- Seek(15);
-
- // Now seek position 5.
- Seek(5);
-
- // Completely overlap the old seek point.
- NewSegmentAppend(10, 15);
-
- // The seek at position 5 should still be pending.
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 15 buffers at positions 5 through 19 and seek to position 15.
- NewSegmentAppend(5, 15);
- Seek(15);
-
- // Now seek to the beginning of the stream.
- Seek(0);
-
- // Overlap the middle of the range such that there are now three ranges.
- NewSegmentAppend(10, 3);
- CheckExpectedRanges("{ [0,1) [5,12) [15,19) }");
-
- // The GetNextBuffer() call should respect the 2nd seek point.
- CheckExpectedBuffers(0, 0);
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap_Pending) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 15 buffers at positions 10 through 24 and seek to position 20.
- NewSegmentAppend(10, 15);
- Seek(20);
-
- // Now seek to position 5.
- Seek(5);
-
- // Overlap the middle of the range such that it is now split into two ranges.
- NewSegmentAppend(15, 3);
- CheckExpectedRanges("{ [0,1) [10,17) [20,24) }");
-
- // The seek at position 5 should still be pending.
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 15 buffers at positions 5 through 19 and seek to position 15.
- NewSegmentAppend(5, 15);
- Seek(15);
-
- // Now seek to the beginning of the stream.
- Seek(0);
-
- // Start overlap the old seek point.
- NewSegmentAppend(10, 10);
-
- // The GetNextBuffer() call should respect the 2nd seek point.
- CheckExpectedBuffers(0, 0);
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap_Pending) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 15 buffers at positions 10 through 24 and seek to position 20.
- NewSegmentAppend(10, 15);
- Seek(20);
-
- // Now seek to position 5.
- Seek(5);
-
- // Start overlap the old seek point.
- NewSegmentAppend(15, 10);
-
- // The seek at time 0 should still be pending.
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 4);
-
- // Append 15 buffers at positions 10 through 24 and seek to start of range.
- NewSegmentAppend(10, 15);
- Seek(10);
-
- // Now seek to the beginning of the stream.
- Seek(0);
-
- // End overlap the old seek point.
- NewSegmentAppend(5, 10);
-
- // The GetNextBuffer() call should respect the 2nd seek point.
- CheckExpectedBuffers(0, 0);
-}
-
-TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap_Pending) {
- // Append 2 buffers at positions 0 through 1.
- NewSegmentAppend(0, 2);
-
- // Append 15 buffers at positions 15 through 29 and seek to start of range.
- NewSegmentAppend(15, 15);
- Seek(15);
-
- // Now seek to position 5
- Seek(5);
-
- // End overlap the old seek point.
- NewSegmentAppend(10, 10);
-
- // The seek at time 0 should still be pending.
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, GetNextBuffer_AfterMerges) {
- // Append 5 buffers at positions 10 through 14.
- NewSegmentAppend(10, 5);
-
- // Seek to buffer at position 12.
- Seek(12);
-
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5);
-
- // Make sure ranges are merged.
- CheckExpectedRanges("{ [5,14) }");
-
- // Make sure the next buffer is correct.
- CheckExpectedBuffers(10, 10);
-
- // Append 5 buffers at positions 15 through 19.
- NewSegmentAppend(15, 5);
- CheckExpectedRanges("{ [5,19) }");
-
- // Make sure the remaining next buffers are correct.
- CheckExpectedBuffers(11, 14);
-}
-
-TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenAppend) {
- // Append 4 buffers at positions 0 through 3.
- NewSegmentAppend(0, 4);
-
- // Seek to buffer at position 0 and get all buffers.
- Seek(0);
- CheckExpectedBuffers(0, 3);
-
- // Next buffer is at position 4, so should not be able to fulfill request.
- CheckNoNextBuffer();
-
- // Append 2 buffers at positions 4 through 5.
- AppendBuffers(4, 2);
- CheckExpectedBuffers(4, 5);
-}
-
-// This test covers the case where new buffers start-overlap a range whose next
-// buffer is not buffered.
-TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenStartOverlap) {
- // Append 10 buffers at positions 0 through 9 and exhaust the buffers.
- NewSegmentAppend(0, 10, &kDataA);
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataA);
-
- // Next buffer is at position 10, so should not be able to fulfill request.
- CheckNoNextBuffer();
-
- // Append 6 buffers at positons 5 through 10. This is to test that doing a
- // start-overlap successfully fulfills the read at position 10, even though
- // position 10 was unbuffered.
- NewSegmentAppend(5, 6, &kDataB);
- CheckExpectedBuffers(10, 10, &kDataB);
-
- // Then add 5 buffers from positions 11 though 15.
- AppendBuffers(11, 5, &kDataB);
-
- // Check the next 4 buffers are correct, which also effectively seeks to
- // position 15.
- CheckExpectedBuffers(11, 14, &kDataB);
-
- // Replace the next buffer at position 15 with another start overlap.
- AppendBuffers(15, 2, &kDataA);
- CheckExpectedBuffers(15, 16, &kDataA);
-}
-
-// This test covers the case where new buffers completely overlap a range
-// whose next buffer is not buffered.
-TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenCompleteOverlap) {
- // Append 5 buffers at positions 10 through 14 and exhaust the buffers.
- NewSegmentAppend(10, 5, &kDataA);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
-
- // Next buffer is at position 15, so should not be able to fulfill request.
- CheckNoNextBuffer();
-
- // Do a complete overlap and test that this successfully fulfills the read
- // at position 15.
- NewSegmentAppend(5, 11, &kDataB);
- CheckExpectedBuffers(15, 15, &kDataB);
-
- // Then add 5 buffers from positions 16 though 20.
- AppendBuffers(16, 5, &kDataB);
-
- // Check the next 4 buffers are correct, which also effectively seeks to
- // position 20.
- CheckExpectedBuffers(16, 19, &kDataB);
-
- // Do a complete overlap and replace the buffer at position 20.
- NewSegmentAppend(0, 21, &kDataA);
- CheckExpectedBuffers(20, 20, &kDataA);
-}
-
-// This test covers the case where a range is stalled waiting for its next
-// buffer, then an end-overlap causes the end of the range to be deleted.
-TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenEndOverlap) {
- // Append 5 buffers at positions 10 through 14 and exhaust the buffers.
- NewSegmentAppend(10, 5, &kDataA);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
- CheckExpectedRanges("{ [10,14) }");
-
- // Next buffer is at position 15, so should not be able to fulfill request.
- CheckNoNextBuffer();
-
- // Do an end overlap that causes the latter half of the range to be deleted.
- NewSegmentAppend(5, 6, &kDataB);
- CheckNoNextBuffer();
- CheckExpectedRanges("{ [5,10) }");
-
- // Fill in the gap. Getting the next buffer should still stall at position 15.
- for (int i = 11; i <= 14; i++) {
- AppendBuffers(i, 1, &kDataB);
- CheckNoNextBuffer();
- }
-
- // Append the buffer at position 15 and check to make sure all is correct.
- AppendBuffers(15, 1);
- CheckExpectedBuffers(15, 15);
- CheckExpectedRanges("{ [5,15) }");
-}
-
-// This test is testing the "next buffer" logic after a complete overlap. In
-// this scenario, when the track buffer is exhausted, there is no buffered data
-// to fulfill the request. The SourceBufferStream should be able to fulfill the
-// request when the data is later appended, and should not lose track of the
-// "next buffer" position.
-TEST_F(SourceBufferStreamTest, GetNextBuffer_Overlap_Selected_Complete) {
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataA);
-
- // Seek to buffer at position 5 and get next buffer.
- Seek(5);
- CheckExpectedBuffers(5, 5, &kDataA);
-
- // Replace existing data with new data.
- NewSegmentAppend(5, 5, &kDataB);
-
- // Expect old data up until next keyframe in new data.
- CheckExpectedBuffers(6, 9, &kDataA);
-
- // Next buffer is at position 10, so should not be able to fulfill the
- // request.
- CheckNoNextBuffer();
-
- // Now add 5 new buffers at positions 10 through 14.
- AppendBuffers(10, 5, &kDataB);
- CheckExpectedBuffers(10, 14, &kDataB);
-}
-
-TEST_F(SourceBufferStreamTest, PresentationTimestampIndependence) {
- // Append 20 buffers at position 0.
- NewSegmentAppend(0, 20);
- Seek(0);
-
- int last_keyframe_idx = -1;
- base::TimeDelta last_keyframe_presentation_timestamp;
- base::TimeDelta last_p_frame_presentation_timestamp;
-
- // Check for IBB...BBP pattern.
- for (int i = 0; i < 20; i++) {
- scoped_refptr<StreamParserBuffer> buffer;
- ASSERT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
-
- if (buffer->IsKeyframe()) {
- EXPECT_EQ(buffer->GetTimestamp(), buffer->GetDecodeTimestamp());
- last_keyframe_idx = i;
- last_keyframe_presentation_timestamp = buffer->GetTimestamp();
- } else if (i == last_keyframe_idx + 1) {
- ASSERT_NE(last_keyframe_idx, -1);
- last_p_frame_presentation_timestamp = buffer->GetTimestamp();
- EXPECT_LT(last_keyframe_presentation_timestamp,
- last_p_frame_presentation_timestamp);
- } else {
- EXPECT_GT(buffer->GetTimestamp(), last_keyframe_presentation_timestamp);
- EXPECT_LT(buffer->GetTimestamp(), last_p_frame_presentation_timestamp);
- EXPECT_LT(buffer->GetTimestamp(), buffer->GetDecodeTimestamp());
- }
- }
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteFront) {
- // Set memory limit to 20 buffers.
- SetMemoryLimit(20);
-
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 1, &kDataA);
- for (int i = 1; i < 20; i++)
- AppendBuffers(i, 1, &kDataA);
-
- // None of the buffers should trigger garbage collection, so all data should
- // be there as expected.
- CheckExpectedRanges("{ [0,19) }");
- Seek(0);
- CheckExpectedBuffers(0, 19, &kDataA);
-
- // Seek to the middle of the stream.
- Seek(10);
-
- // Append 5 buffers to the end of the stream.
- AppendBuffers(20, 5, &kDataA);
-
- // GC should have deleted the first 5 buffers.
- CheckExpectedRanges("{ [5,24) }");
- CheckExpectedBuffers(10, 24, &kDataA);
- Seek(5);
- CheckExpectedBuffers(5, 9, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteFrontGOPsAtATime) {
- // Set memory limit to 20 buffers.
- SetMemoryLimit(20);
-
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataA);
-
- // Seek to position 10.
- Seek(10);
-
- // Add one buffer to put the memory over the cap.
- AppendBuffers(20, 1, &kDataA);
-
- // GC should have deleted the first 5 buffers so that the range still begins
- // with a keyframe.
- CheckExpectedRanges("{ [5,20) }");
- CheckExpectedBuffers(10, 20, &kDataA);
- Seek(5);
- CheckExpectedBuffers(5, 9, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteBack) {
- // Set memory limit to 5 buffers.
- SetMemoryLimit(5);
-
- // Seek to position 0.
- Seek(0);
-
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataA);
-
- // Should leave the first 5 buffers from 0 to 4 and the last GOP appended.
- CheckExpectedRanges("{ [0,4) [15,19) }");
- CheckExpectedBuffers(0, 4, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteFrontAndBack) {
- // Set memory limit to 3 buffers.
- SetMemoryLimit(3);
-
- // Seek to position 15.
- Seek(15);
-
- // Append 40 buffers at positions 0 through 39.
- NewSegmentAppend(0, 40, &kDataA);
-
- // Should leave the GOP containing the seek position and the last GOP
- // appended.
- CheckExpectedRanges("{ [15,19) [35,39) }");
- CheckExpectedBuffers(15, 19, &kDataA);
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteSeveralRanges) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 5);
-
- // Append 5 buffers at positions 10 through 14.
- NewSegmentAppend(10, 5);
-
- // Append 5 buffers at positions 20 through 24.
- NewSegmentAppend(20, 5);
-
- // Append 5 buffers at positions 30 through 34.
- NewSegmentAppend(30, 5);
-
- CheckExpectedRanges("{ [0,4) [10,14) [20,24) [30,34) }");
-
- // Seek to position 21.
- Seek(20);
- CheckExpectedBuffers(20, 20);
-
- // Set memory limit to 1 buffer.
- SetMemoryLimit(1);
-
- // Append 5 buffers at positions 40 through 44. This will trigger GC.
- NewSegmentAppend(40, 5);
-
- // Should delete everything except the GOP containing the current buffer and
- // the last GOP appended.
- CheckExpectedRanges("{ [20,24) [40,44) }");
- CheckExpectedBuffers(21, 24);
- CheckNoNextBuffer();
-
- // Continue appending into the last range to make sure it didn't break.
- AppendBuffers(45, 10);
- // Should only save last GOP appended.
- CheckExpectedRanges("{ [20,24) [50,54) }");
-
- // Make sure appending before and after the ranges didn't somehow break.
- SetMemoryLimit(100);
- NewSegmentAppend(0, 10);
- CheckExpectedRanges("{ [0,9) [20,24) [50,54) }");
- Seek(0);
- CheckExpectedBuffers(0, 9);
-
- NewSegmentAppend(90, 10);
- CheckExpectedRanges("{ [0,9) [20,24) [50,54) [90,99) }");
- Seek(50);
- CheckExpectedBuffers(50, 54);
- CheckNoNextBuffer();
- Seek(90);
- CheckExpectedBuffers(90, 99);
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_NoSeek) {
- // Set memory limit to 20 buffers.
- SetMemoryLimit(20);
-
- // Append 25 buffers at positions 0 through 24.
- NewSegmentAppend(0, 25, &kDataA);
-
- // GC deletes the first 5 buffers to keep the memory limit within cap.
- CheckExpectedRanges("{ [5,24) }");
- CheckNoNextBuffer();
- Seek(5);
- CheckExpectedBuffers(5, 24, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_PendingSeek) {
- // Append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataA);
-
- // Append 5 buffers at positions 25 through 29.
- NewSegmentAppend(25, 5, &kDataA);
-
- // Seek to position 15.
- Seek(15);
- CheckNoNextBuffer();
-
- CheckExpectedRanges("{ [0,9) [25,29) }");
-
- // Set memory limit to 5 buffers.
- SetMemoryLimit(5);
-
- // Append 5 buffers as positions 30 to 34 to trigger GC.
- AppendBuffers(30, 5, &kDataA);
-
- // The current algorithm will delete from the beginning until the memory is
- // under cap.
- CheckExpectedRanges("{ [30,34) }");
-
- // Expand memory limit again so that GC won't be triggered.
- SetMemoryLimit(100);
-
- // Append data to fulfill seek.
- NewSegmentAppend(15, 5, &kDataA);
-
- // Check to make sure all is well.
- CheckExpectedRanges("{ [15,19) [30,34) }");
- CheckExpectedBuffers(15, 19, &kDataA);
- Seek(30);
- CheckExpectedBuffers(30, 34, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_NeedsMoreData) {
- // Set memory limit to 15 buffers.
- SetMemoryLimit(15);
-
- // Append 10 buffers at positions 0 through 9.
- NewSegmentAppend(0, 10, &kDataA);
-
- // Advance next buffer position to 10.
- Seek(0);
- CheckExpectedBuffers(0, 9, &kDataA);
- CheckNoNextBuffer();
-
- // Append 20 buffers at positions 15 through 34.
- NewSegmentAppend(15, 20, &kDataA);
-
- // GC should have saved the keyframe before the current seek position and the
- // data closest to the current seek position. It will also save the last GOP
- // appended.
- CheckExpectedRanges("{ [5,9) [15,19) [30,34) }");
-
- // Now fulfill the seek at position 10. This will make GC delete the data
- // before position 10 to keep it within cap.
- NewSegmentAppend(10, 5, &kDataA);
- CheckExpectedRanges("{ [10,19) [30,34) }");
- CheckExpectedBuffers(10, 19, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_TrackBuffer) {
- // Set memory limit to 3 buffers.
- SetMemoryLimit(3);
-
- // Seek to position 15.
- Seek(15);
-
- // Append 18 buffers at positions 0 through 17.
- NewSegmentAppend(0, 18, &kDataA);
-
- // Should leave GOP containing seek position.
- CheckExpectedRanges("{ [15,17) }");
-
- // Seek ahead to position 16.
- CheckExpectedBuffers(15, 15, &kDataA);
-
- // Completely overlap the existing buffers.
- NewSegmentAppend(0, 20, &kDataB);
-
- // Because buffers 16 and 17 are not keyframes, they are moved to the track
- // buffer upon overlap. The source buffer (i.e. not the track buffer) is now
- // waiting for the next keyframe.
- CheckExpectedRanges("{ [15,19) }");
- CheckExpectedBuffers(16, 17, &kDataA);
- CheckNoNextBuffer();
-
- // Now add a keyframe at position 20.
- AppendBuffers(20, 5, &kDataB);
-
- // Should garbage collect such that there are 5 frames remaining, starting at
- // the keyframe.
- CheckExpectedRanges("{ [20,24) }");
- CheckExpectedBuffers(20, 24, &kDataB);
- CheckNoNextBuffer();
-}
-
-// Test saving the last GOP appended when this GOP is the only GOP in its range.
-TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP) {
- // Set memory limit to 3 and make sure the 4-byte GOP is not garbage
- // collected.
- SetMemoryLimit(3);
- NewSegmentAppend("0K 30 60 90");
- CheckExpectedRangesByTimestamp("{ [0,120) }");
-
- // Make sure you can continue appending data to this GOP; again, GC should not
- // wipe out anything.
- AppendBuffers("120");
- CheckExpectedRangesByTimestamp("{ [0,150) }");
-
- // Set memory limit to 100 and append a 2nd range after this without
- // triggering GC.
- SetMemoryLimit(100);
- NewSegmentAppend("200K 230 260 290K 320 350");
- CheckExpectedRangesByTimestamp("{ [0,150) [200,380) }");
-
- // Seek to 290ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(290));
-
- // Now set memory limit to 3 and append a GOP in a separate range after the
- // selected range. Because it is after 290ms, this tests that the GOP is saved
- // when deleting from the back.
- SetMemoryLimit(3);
- NewSegmentAppend("500K 530 560 590");
-
- // Should save GOP with 290ms and last GOP appended.
- CheckExpectedRangesByTimestamp("{ [290,380) [500,620) }");
-
- // Continue appending to this GOP after GC.
- AppendBuffers("620");
- CheckExpectedRangesByTimestamp("{ [290,380) [500,650) }");
-}
-
-// Test saving the last GOP appended when this GOP is in the middle of a
-// non-selected range.
-TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Middle) {
- // Append 3 GOPs starting at 0ms, 30ms apart.
- NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
- CheckExpectedRangesByTimestamp("{ [0,270) }");
-
- // Now set the memory limit to 1 and overlap the middle of the range with a
- // new GOP.
- SetMemoryLimit(1);
- NewSegmentAppend("80K 110 140");
-
- // This whole GOP should be saved, and should be able to continue appending
- // data to it.
- CheckExpectedRangesByTimestamp("{ [80,170) }");
- AppendBuffers("170");
- CheckExpectedRangesByTimestamp("{ [80,200) }");
-
- // Set memory limit to 100 and append a 2nd range after this without
- // triggering GC.
- SetMemoryLimit(100);
- NewSegmentAppend("400K 430 460 490K 520 550 580K 610 640");
- CheckExpectedRangesByTimestamp("{ [80,200) [400,670) }");
-
- // Seek to 80ms to make the first range the selected range.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(80));
-
- // Now set memory limit to 3 and append a GOP in the middle of the second
- // range. Because it is after the selected range, this tests that the GOP is
- // saved when deleting from the back.
- SetMemoryLimit(3);
- NewSegmentAppend("500K 530 560 590");
-
- // Should save the GOP containing the seek point and GOP that was last
- // appended.
- CheckExpectedRangesByTimestamp("{ [80,200) [500,620) }");
-
- // Continue appending to this GOP after GC.
- AppendBuffers("620");
- CheckExpectedRangesByTimestamp("{ [80,200) [500,650) }");
-}
-
-// Test saving the last GOP appended when the GOP containing the next buffer is
-// adjacent to the last GOP appended.
-TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected1) {
- // Append 3 GOPs at 0ms, 90ms, and 180ms.
- NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
- CheckExpectedRangesByTimestamp("{ [0,270) }");
-
- // Seek to the GOP at 90ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(90));
-
- // Set the memory limit to 1, then overlap the GOP at 0.
- SetMemoryLimit(1);
- NewSegmentAppend("0K 30 60");
-
- // Should save the GOP at 0ms and 90ms.
- CheckExpectedRangesByTimestamp("{ [0,180) }");
-
- // Seek to 0 and check all buffers.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
- CheckExpectedBuffers("0K 30 60 90K 120 150");
- CheckNoNextBuffer();
-
- // Now seek back to 90ms and append a GOP at 180ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(90));
- NewSegmentAppend("180K 210 240");
-
- // Should save the GOP at 90ms and the GOP at 180ms.
- CheckExpectedRangesByTimestamp("{ [90,270) }");
- CheckExpectedBuffers("90K 120 150 180K 210 240");
- CheckNoNextBuffer();
-}
-
-// Test saving the last GOP appended when it is at the beginning or end of the
-// selected range. This tests when the last GOP appended is before or after the
-// GOP containing the next buffer, but not directly adjacent to this GOP.
-TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected2) {
- // Append 4 GOPs starting at positions 0ms, 90ms, 180ms, 270ms.
- NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240 270K 300 330");
- CheckExpectedRangesByTimestamp("{ [0,360) }");
-
- // Seek to the last GOP at 270ms.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(270));
-
- // Set the memory limit to 1, then overlap the GOP at 90ms.
- SetMemoryLimit(1);
- NewSegmentAppend("90K 120 150");
-
- // Should save the GOP at 90ms and the GOP at 270ms.
- CheckExpectedRangesByTimestamp("{ [90,180) [270,360) }");
-
- // Set memory limit to 100 and add 3 GOPs to the end of the selected range
- // at 360ms, 450ms, and 540ms.
- SetMemoryLimit(100);
- NewSegmentAppend("360K 390 420 450K 480 510 540K 570 600");
- CheckExpectedRangesByTimestamp("{ [90,180) [270,630) }");
-
- // Constrain the memory limit again and overlap the GOP at 450ms to test
- // deleting from the back.
- SetMemoryLimit(1);
- NewSegmentAppend("450K 480 510");
-
- // Should save GOP at 270ms and the GOP at 450ms.
- CheckExpectedRangesByTimestamp("{ [270,360) [450,540) }");
-}
-
-// Test saving the last GOP appended when it is the same as the GOP containing
-// the next buffer.
-TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected3) {
- // Seek to start of stream.
- SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
-
- // Append 3 GOPs starting at 0ms, 90ms, 180ms.
- NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
- CheckExpectedRangesByTimestamp("{ [0,270) }");
-
- // Set the memory limit to 1 then begin appending the start of a GOP starting
- // at 0ms.
- SetMemoryLimit(1);
- NewSegmentAppend("0K 30");
-
- // Should save the newly appended GOP, which is also the next GOP that will be
- // returned from the seek request.
- CheckExpectedRangesByTimestamp("{ [0,60) }");
-
- // Check the buffers in the range.
- CheckExpectedBuffers("0K 30");
- CheckNoNextBuffer();
-
- // Continue appending to this buffer.
- AppendBuffers("60 90");
-
- // Should still save the rest of this GOP and should be able to fulfill the
- // read.
- CheckExpectedRangesByTimestamp("{ [0,120) }");
- CheckExpectedBuffers("60 90");
- CheckNoNextBuffer();
-}
-
-// Currently disabled because of bug: crbug.com/140875.
-TEST_F(SourceBufferStreamTest, DISABLED_GarbageCollection_WaitingForKeyframe) {
- // Set memory limit to 10 buffers.
- SetMemoryLimit(10);
-
- // Append 5 buffers at positions 10 through 14 and exhaust the buffers.
- NewSegmentAppend(10, 5, &kDataA);
- Seek(10);
- CheckExpectedBuffers(10, 14, &kDataA);
- CheckExpectedRanges("{ [10,14) }");
-
- // We are now stalled at position 15.
- CheckNoNextBuffer();
-
- // Do an end overlap that causes the latter half of the range to be deleted.
- NewSegmentAppend(5, 6, &kDataA);
- CheckNoNextBuffer();
- CheckExpectedRanges("{ [5,10) }");
-
- // Append buffers from position 20 to 29. This should trigger GC.
- NewSegmentAppend(20, 10, &kDataA);
-
- // GC should keep the keyframe before the seek position 15, and the next 9
- // buffers closest to the seek position.
- CheckNoNextBuffer();
- CheckExpectedRanges("{ [10,10) [20,28) }");
-
- // Fulfill the seek by appending one buffer at 15.
- NewSegmentAppend(15, 1, &kDataA);
- CheckExpectedBuffers(15, 15, &kDataA);
- CheckExpectedRanges("{ [15,15) [20,28) }");
-}
-
-// Test the performance of garbage collection.
-TEST_F(SourceBufferStreamTest, GarbageCollection_Performance) {
- // Force |keyframes_per_second_| to be equal to kDefaultFramesPerSecond.
- SetStreamInfo(kDefaultFramesPerSecond, kDefaultFramesPerSecond);
-
- const int kBuffersToKeep = 1000;
- SetMemoryLimit(kBuffersToKeep);
-
- int buffers_appended = 0;
-
- NewSegmentAppend(0, kBuffersToKeep);
- buffers_appended += kBuffersToKeep;
-
- const int kBuffersToAppend = 1000;
- const int kGarbageCollections = 3;
- for (int i = 0; i < kGarbageCollections; ++i) {
- AppendBuffers(buffers_appended, kBuffersToAppend);
- buffers_appended += kBuffersToAppend;
- }
-}
-
-TEST_F(SourceBufferStreamTest, ConfigChange_Basic) {
- gfx::Size kNewCodedSize(kCodedSize.width() * 2, kCodedSize.height() * 2);
- VideoDecoderConfig new_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::YV12, kNewCodedSize,
- gfx::Rect(kNewCodedSize), kNewCodedSize, NULL, 0, false);
- ASSERT_FALSE(new_config.Matches(config_));
-
- Seek(0);
- CheckConfig(config_);
-
- // Append 5 buffers at positions 0 through 4
- NewSegmentAppend(0, 5, &kDataA);
-
- CheckConfig(config_);
-
- // Signal a config change.
- stream_->UpdateVideoConfig(new_config);
-
- // Make sure updating the config doesn't change anything since new_config
- // should not be associated with the buffer GetNextBuffer() will return.
- CheckConfig(config_);
-
- // Append 5 buffers at positions 5 through 9.
- NewSegmentAppend(5, 5, &kDataB);
-
- // Consume the buffers associated with the initial config.
- scoped_refptr<StreamParserBuffer> buffer;
- for (int i = 0; i < 5; i++) {
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
- CheckConfig(config_);
- }
-
- // Verify the next attempt to get a buffer will signal that a config change
- // has happened.
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
-
- // Verify that the new config is now returned.
- CheckConfig(new_config);
-
- // Consume the remaining buffers associated with the new config.
- for (int i = 0; i < 5; i++) {
- CheckConfig(new_config);
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
- }
-}
-
-TEST_F(SourceBufferStreamTest, ConfigChange_Seek) {
- scoped_refptr<StreamParserBuffer> buffer;
- gfx::Size kNewCodedSize(kCodedSize.width() * 2, kCodedSize.height() * 2);
- VideoDecoderConfig new_config(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::YV12, kNewCodedSize,
- gfx::Rect(kNewCodedSize), kNewCodedSize, NULL, 0, false);
-
- Seek(0);
- NewSegmentAppend(0, 5, &kDataA);
- stream_->UpdateVideoConfig(new_config);
- NewSegmentAppend(5, 5, &kDataB);
-
- // Seek to the start of the buffers with the new config and make sure a
- // config change is signalled.
- CheckConfig(config_);
- Seek(5);
- CheckConfig(config_);
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
- CheckConfig(new_config);
- CheckExpectedBuffers(5, 9, &kDataB);
-
-
- // Seek to the start which has a different config. Don't fetch any buffers and
- // seek back to buffers with the current config. Make sure a config change
- // isn't signalled in this case.
- CheckConfig(new_config);
- Seek(0);
- Seek(7);
- CheckExpectedBuffers(5, 9, &kDataB);
-
-
- // Seek to the start and make sure a config change is signalled.
- CheckConfig(new_config);
- Seek(0);
- CheckConfig(new_config);
- EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
- CheckConfig(config_);
- CheckExpectedBuffers(0, 4, &kDataA);
-}
-
-TEST_F(SourceBufferStreamTest, SetExplicitDuration) {
- // Append 2 buffers at positions 5 through 6.
- NewSegmentAppend(5, 2);
-
- // Append 2 buffers at positions 10 through 11.
- NewSegmentAppend(10, 2);
-
- // Append 2 buffers at positions 15 through 16.
- NewSegmentAppend(15, 2);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [5,6) [10,11) [15,16) }");
-
- // Set duration to be between buffers 6 and 10.
- stream_->OnSetDuration(frame_duration() * 8);
-
- // Should truncate the data after 6.
- CheckExpectedRanges("{ [5,6) }");
-
- // Adding data past the previous duration should still work.
- NewSegmentAppend(0, 20);
- CheckExpectedRanges("{ [0,19) }");
-}
-
-TEST_F(SourceBufferStreamTest, SetExplicitDuration_EdgeCase) {
- // Append 10 buffers at positions 10 through 19.
- NewSegmentAppend(10, 10);
-
- // Append 5 buffers at positions 25 through 29.
- NewSegmentAppend(25, 5);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [10,19) [25,29) }");
-
- // Set duration to be right before buffer 25.
- stream_->OnSetDuration(frame_duration() * 25);
-
- // Should truncate the last range.
- CheckExpectedRanges("{ [10,19) }");
-}
-
-TEST_F(SourceBufferStreamTest, SetExplicitDuration_DeletePartialRange) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 5);
-
- // Append 10 buffers at positions 10 through 19.
- NewSegmentAppend(10, 10);
-
- // Append 5 buffers at positions 25 through 29.
- NewSegmentAppend(25, 5);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,4) [10,19) [25,29) }");
-
- // Set duration to be between buffers 13 and 14.
- stream_->OnSetDuration(frame_duration() * 14);
-
- // Should truncate the data after 13.
- CheckExpectedRanges("{ [0,4) [10,13) }");
-}
-
-TEST_F(SourceBufferStreamTest, SetExplicitDuration_DeleteSelectedRange) {
- // Append 2 buffers at positions 5 through 6.
- NewSegmentAppend(5, 2);
-
- // Append 2 buffers at positions 10 through 11.
- NewSegmentAppend(10, 2);
-
- // Append 2 buffers at positions 15 through 16.
- NewSegmentAppend(15, 2);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [5,6) [10,11) [15,16) }");
-
- // Seek to 10.
- Seek(10);
-
- // Set duration to be after position 3.
- stream_->OnSetDuration(frame_duration() * 4);
-
- // Expect everything to be deleted, and should not have next buffer anymore.
- CheckNoNextBuffer();
- CheckExpectedRanges("{ }");
-
- // Appending data at position 10 should not fulfill the seek.
- // (If the duration is set to be something smaller than the current seek
- // point, then the seek point is reset and the SourceBufferStream waits
- // for a new seek request. Therefore even if the data is re-appended, it
- // should not fulfill the old seek.)
- NewSegmentAppend(0, 15);
- CheckNoNextBuffer();
- CheckExpectedRanges("{ [0,14) }");
-}
-
-TEST_F(SourceBufferStreamTest, SetExplicitDuration_DeletePartialSelectedRange) {
- // Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 5);
-
- // Append 20 buffers at positions 10 through 29.
- NewSegmentAppend(10, 20);
-
- // Check expected ranges.
- CheckExpectedRanges("{ [0,4) [10,29) }");
-
- // Seek to position 10.
- Seek(10);
-
- // Set duration to be between buffers 24 and 25.
- stream_->OnSetDuration(frame_duration() * 25);
-
- // Should truncate the data after 24.
- CheckExpectedRanges("{ [0,4) [10,24) }");
-
- // The seek position should not be lost.
- CheckExpectedBuffers(10, 10);
-
- // Now set the duration immediately after buffer 10.
- stream_->OnSetDuration(frame_duration() * 11);
-
- // Seek position should be reset.
- CheckNoNextBuffer();
- CheckExpectedRanges("{ [0,4) [10,10) }");
-}
-
-// TODO(vrk): Add unit tests where keyframes are unaligned between streams.
-// (crbug.com/133557)
-
-// TODO(vrk): Add unit tests with end of stream being called at interesting
-// times.
-
-} // namespace media
diff --git a/src/media/filters/video_decoder_selector.cc b/src/media/filters/video_decoder_selector.cc
deleted file mode 100644
index 46a0731..0000000
--- a/src/media/filters/video_decoder_selector.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/video_decoder_selector.h"
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_decoder_config.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-#include "media/filters/decrypting_video_decoder.h"
-
-namespace media {
-
-VideoDecoderSelector::VideoDecoderSelector(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const VideoDecoderList& decoders,
- const SetDecryptorReadyCB& set_decryptor_ready_cb)
- : message_loop_(message_loop),
- decoders_(decoders),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
-}
-
-VideoDecoderSelector::~VideoDecoderSelector() {}
-
-void VideoDecoderSelector::SelectVideoDecoder(
- const scoped_refptr<DemuxerStream>& stream,
- const StatisticsCB& statistics_cb,
- const SelectDecoderCB& select_decoder_cb) {
- DVLOG(2) << "SelectVideoDecoder()";
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(stream);
-
- // Make sure |select_decoder_cb| runs on a different execution stack.
- select_decoder_cb_ = BindToCurrentLoop(select_decoder_cb);
-
- const VideoDecoderConfig& config = stream->video_decoder_config();
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid video stream config.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- input_stream_ = stream;
- statistics_cb_ = statistics_cb;
-
- if (!config.is_encrypted()) {
- if (decoders_.empty()) {
- DLOG(ERROR) << "No video decoder can be used to decode the input stream.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- InitializeNextDecoder();
- return;
- }
-
- // This could happen if Encrypted Media Extension (EME) is not enabled.
- if (set_decryptor_ready_cb_.is_null()) {
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
-#if defined(COBALT) || defined(__LB_SHELL__)
- DecryptingVideoDecoderInitDone(DECODER_ERROR_NOT_SUPPORTED);
-#else // defined(COBALT) || defined(__LB_SHELL__)
- video_decoder_ = new DecryptingVideoDecoder(message_loop_,
- set_decryptor_ready_cb_);
- video_decoder_->Initialize(
- input_stream_,
- BindToCurrentLoop(base::Bind(
- &VideoDecoderSelector::DecryptingVideoDecoderInitDone,
- weak_ptr_factory_.GetWeakPtr())),
- statistics_cb_);
-#endif // defined(COBALT) || defined(__LB_SHELL__)
-}
-
-void VideoDecoderSelector::DecryptingVideoDecoderInitDone(
- PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status == PIPELINE_OK) {
- decoders_.clear();
- base::ResetAndReturn(&select_decoder_cb_).Run(video_decoder_, NULL);
- return;
- }
-
- video_decoder_ = NULL;
-
- if (decoders_.empty()) {
- DLOG(ERROR) << "No video decoder can be used to decode the input stream.";
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- decrypted_stream_ = new DecryptingDemuxerStream(
- message_loop_, set_decryptor_ready_cb_);
-
- decrypted_stream_->Initialize(
- input_stream_,
- BindToCurrentLoop(base::Bind(
- &VideoDecoderSelector::DecryptingDemuxerStreamInitDone,
- weak_ptr_factory_.GetWeakPtr())));
-}
-
-void VideoDecoderSelector::DecryptingDemuxerStreamInitDone(
- PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status != PIPELINE_OK) {
- decrypted_stream_ = NULL;
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- DCHECK(!decrypted_stream_->video_decoder_config().is_encrypted());
- input_stream_ = decrypted_stream_;
- InitializeNextDecoder();
-}
-
-void VideoDecoderSelector::InitializeNextDecoder() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- DCHECK(!decoders_.empty());
-
- video_decoder_ = decoders_.front();
- decoders_.pop_front();
- DCHECK(video_decoder_);
- video_decoder_->Initialize(input_stream_,
- BindToCurrentLoop(base::Bind(
- &VideoDecoderSelector::DecoderInitDone,
- weak_ptr_factory_.GetWeakPtr())),
- statistics_cb_);
-}
-
-void VideoDecoderSelector::DecoderInitDone(PipelineStatus status) {
- DCHECK(message_loop_->BelongsToCurrentThread());
-
- if (status != PIPELINE_OK) {
- if (!decoders_.empty())
- InitializeNextDecoder();
- else
- base::ResetAndReturn(&select_decoder_cb_).Run(NULL, NULL);
- return;
- }
-
- decoders_.clear();
- base::ResetAndReturn(&select_decoder_cb_).Run(video_decoder_,
- decrypted_stream_);
-}
-
-} // namespace media
diff --git a/src/media/filters/video_decoder_selector.h b/src/media/filters/video_decoder_selector.h
deleted file mode 100644
index 826bbf8..0000000
--- a/src/media/filters/video_decoder_selector.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_VIDEO_DECODER_SELECTOR_H_
-#define MEDIA_FILTERS_VIDEO_DECODER_SELECTOR_H_
-
-#include <list>
-
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/video_decoder.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecoderBuffer;
-class DecryptingDemuxerStream;
-class Decryptor;
-
-// VideoDecoderSelector (creates if necessary and) initializes the proper
-// VideoDecoder for a given DemuxerStream. If the given DemuxerStream is
-// encrypted, a DecryptingDemuxerStream may also be created.
-class MEDIA_EXPORT VideoDecoderSelector {
- public:
- typedef std::list<scoped_refptr<VideoDecoder> > VideoDecoderList;
-
- // Indicates completion of VideoDecoder selection.
- // - First parameter: The initialized VideoDecoder. If it's set to NULL, then
- // VideoDecoder initialization failed.
- // - Second parameter: The initialized DecryptingDemuxerStream. If it's not
- // NULL, then a DecryptingDemuxerStream is created and initialized to do
- // decryption for the initialized VideoDecoder.
- // Note: The caller owns selected VideoDecoder and DecryptingDemuxerStream.
- // The caller should call DecryptingDemuxerStream::Reset() before
- // calling VideoDecoder::Reset() to release any pending decryption or read.
- typedef base::Callback<
- void(const scoped_refptr<VideoDecoder>&,
- const scoped_refptr<DecryptingDemuxerStream>&)> SelectDecoderCB;
-
- // |set_decryptor_ready_cb| is optional. If |set_decryptor_ready_cb| is null,
- // no decryptor will be available to perform decryption.
- VideoDecoderSelector(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const VideoDecoderList& decoders,
- const SetDecryptorReadyCB& set_decryptor_ready_cb);
- ~VideoDecoderSelector();
-
- // Initializes and selects an VideoDecoder that can decode the |stream|.
- // Selected VideoDecoder (and DecryptingDemuxerStream) is returned via
- // the |select_decoder_cb|.
- void SelectVideoDecoder(const scoped_refptr<DemuxerStream>& stream,
- const StatisticsCB& statistics_cb,
- const SelectDecoderCB& select_decoder_cb);
-
- private:
- void DecryptingVideoDecoderInitDone(PipelineStatus status);
- void DecryptingDemuxerStreamInitDone(PipelineStatus status);
- void InitializeNextDecoder();
- void DecoderInitDone(PipelineStatus status);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
- VideoDecoderList decoders_;
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- scoped_refptr<DemuxerStream> input_stream_;
- StatisticsCB statistics_cb_;
- SelectDecoderCB select_decoder_cb_;
-
- scoped_refptr<VideoDecoder> video_decoder_;
- scoped_refptr<DecryptingDemuxerStream> decrypted_stream_;
-
- base::WeakPtrFactory<VideoDecoderSelector> weak_ptr_factory_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoDecoderSelector);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_VIDEO_DECODER_SELECTOR_H_
diff --git a/src/media/filters/video_decoder_selector_unittest.cc b/src/media/filters/video_decoder_selector_unittest.cc
deleted file mode 100644
index dcf390d..0000000
--- a/src/media/filters/video_decoder_selector_unittest.cc
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <vector>
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/filters/video_decoder_selector.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::IsNull;
-using ::testing::NiceMock;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::SaveArg;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(320, 240);
-static const gfx::Rect kVisibleRect(320, 240);
-static const gfx::Size kNaturalSize(320, 240);
-
-class VideoDecoderSelectorTest : public ::testing::Test {
- public:
- enum DecryptorCapability {
- kNoDecryptor,
- kDecryptOnly,
- kDecryptAndDecode
- };
-
- VideoDecoderSelectorTest()
- : clear_video_config_(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false),
- encrypted_video_config_(
- kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, true),
- demuxer_stream_(new StrictMock<MockDemuxerStream>()),
- decryptor_(new NiceMock<MockDecryptor>()),
- decoder_1_(new StrictMock<MockVideoDecoder>()),
- decoder_2_(new StrictMock<MockVideoDecoder>()) {
- all_decoders_.push_back(decoder_1_);
- all_decoders_.push_back(decoder_2_);
-
- EXPECT_CALL(*demuxer_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::VIDEO));
- }
-
- ~VideoDecoderSelectorTest() {
- EXPECT_CALL(*decoder_1_, Stop(_))
- .WillRepeatedly(RunClosure<0>());
- EXPECT_CALL(*decoder_2_, Stop(_))
- .WillRepeatedly(RunClosure<0>());
-
- if (selected_decoder_)
- selected_decoder_->Stop(NewExpectedClosure());
-
- message_loop_.RunUntilIdle();
- }
-
- MOCK_METHOD1(OnStatistics, void(const PipelineStatistics&));
- MOCK_METHOD1(SetDecryptorReadyCallback, void(const media::DecryptorReadyCB&));
- MOCK_METHOD2(OnDecoderSelected,
- void(const scoped_refptr<VideoDecoder>&,
- const scoped_refptr<DecryptingDemuxerStream>&));
-
- void UseClearStream() {
- EXPECT_CALL(*demuxer_stream_, video_decoder_config())
- .WillRepeatedly(ReturnRef(clear_video_config_));
- }
-
- void UseEncryptedStream() {
- EXPECT_CALL(*demuxer_stream_, video_decoder_config())
- .WillRepeatedly(ReturnRef(encrypted_video_config_));
- }
-
- void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
- int num_decoders) {
- SetDecryptorReadyCB set_decryptor_ready_cb;
- if (decryptor_capability == kDecryptOnly ||
- decryptor_capability == kDecryptAndDecode) {
- set_decryptor_ready_cb = base::Bind(
- &VideoDecoderSelectorTest::SetDecryptorReadyCallback,
- base::Unretained(this));
-
- EXPECT_CALL(*this, SetDecryptorReadyCallback(_))
- .WillRepeatedly(RunCallback<0>(decryptor_.get()));
-
- if (decryptor_capability == kDecryptOnly) {
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillRepeatedly(RunCallback<1>(false));
- } else {
- EXPECT_CALL(*decryptor_, InitializeVideoDecoderMock(_, _))
- .WillRepeatedly(RunCallback<1>(true));
- }
- }
-
- DCHECK_GE(all_decoders_.size(), static_cast<size_t>(num_decoders));
- VideoDecoderSelector::VideoDecoderList decoders(
- all_decoders_.begin(), all_decoders_.begin() + num_decoders);
-
- decoder_selector_.reset(new VideoDecoderSelector(
- message_loop_.message_loop_proxy(),
- decoders,
- set_decryptor_ready_cb));
- }
-
- void SelectDecoder() {
- decoder_selector_->SelectVideoDecoder(
- demuxer_stream_,
- base::Bind(&VideoDecoderSelectorTest::OnStatistics,
- base::Unretained(this)),
- base::Bind(&VideoDecoderSelectorTest::OnDecoderSelected,
- base::Unretained(this)));
- message_loop_.RunUntilIdle();
- }
-
- // Fixture members.
- scoped_ptr<VideoDecoderSelector> decoder_selector_;
- VideoDecoderConfig clear_video_config_;
- VideoDecoderConfig encrypted_video_config_;
- scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_stream_;
- // Use NiceMock since we don't care about most of calls on the decryptor, e.g.
- // RegisterKeyAddedCB().
- scoped_ptr<NiceMock<MockDecryptor> > decryptor_;
- scoped_refptr<StrictMock<MockVideoDecoder> > decoder_1_;
- scoped_refptr<StrictMock<MockVideoDecoder> > decoder_2_;
- std::vector<scoped_refptr<VideoDecoder> > all_decoders_;
-
- scoped_refptr<VideoDecoder> selected_decoder_;
-
- MessageLoop message_loop_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VideoDecoderSelectorTest);
-};
-
-// The stream is not encrypted but we have no clear decoder. No decoder can be
-// selected.
-TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_NoClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 0);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// The stream is not encrypted and we have one clear decoder. The decoder
-// will be selected.
-TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_OneClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<VideoDecoder>(decoder_1_),
- IsNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-// The stream is not encrypted and we have multiple clear decoders. The first
-// decoder that can decode the input stream will be selected.
-TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_MultipleClearDecoder) {
- UseClearStream();
- InitializeDecoderSelector(kNoDecryptor, 2);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- EXPECT_CALL(*decoder_2_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<VideoDecoder>(decoder_2_),
- IsNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-// There is a decryptor but the stream is not encrypted. The decoder will be
-// selected.
-TEST_F(VideoDecoderSelectorTest, ClearStream_HasDecryptor) {
- UseClearStream();
- InitializeDecoderSelector(kDecryptOnly, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<VideoDecoder>(decoder_1_),
- IsNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-// The stream is encrypted and there's no decryptor. No decoder can be selected.
-TEST_F(VideoDecoderSelectorTest, EncryptedStream_NoDecryptor) {
- UseEncryptedStream();
- InitializeDecoderSelector(kNoDecryptor, 1);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can only do decryption and there's no decoder available. No decoder
-// can be selected.
-TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptOnly_NoClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 0);
-
- EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
-
- SelectDecoder();
-}
-
-// Decryptor can do decryption-only and there's a decoder available. The decoder
-// will be selected and a DecryptingDemuxerStream will be created.
-TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptOnly_OneClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 1);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<VideoDecoder>(decoder_1_),
- NotNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-// Decryptor can only do decryption and there are multiple decoders available.
-// The first decoder that can decode the input stream will be selected and
-// a DecryptingDemuxerStream will be created.
-TEST_F(VideoDecoderSelectorTest,
- EncryptedStream_DecryptOnly_MultipleClearDecoder) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptOnly, 2);
-
- EXPECT_CALL(*decoder_1_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- EXPECT_CALL(*decoder_2_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- EXPECT_CALL(*this, OnDecoderSelected(scoped_refptr<VideoDecoder>(decoder_2_),
- NotNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-// Decryptor can do decryption and decoding. A DecryptingVideoDecoder will be
-// created and selected. The clear decoders should not be touched at all.
-// No DecryptingDemuxerStream should to be created.
-TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
- UseEncryptedStream();
- InitializeDecoderSelector(kDecryptAndDecode, 1);
-
- EXPECT_CALL(*this, OnDecoderSelected(NotNull(), IsNull()))
- .WillOnce(SaveArg<0>(&selected_decoder_));
-
- SelectDecoder();
-}
-
-} // namespace media
diff --git a/src/media/filters/video_frame_generator.cc b/src/media/filters/video_frame_generator.cc
deleted file mode 100644
index c72fe13..0000000
--- a/src/media/filters/video_frame_generator.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/video_frame_generator.h"
-
-#include "base/bind.h"
-#include "base/message_loop.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/video_frame.h"
-
-namespace media {
-
-VideoFrameGenerator::VideoFrameGenerator(
- const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
- const gfx::Size& size,
- const base::TimeDelta& frame_duration)
- : message_loop_proxy_(message_loop_proxy),
- size_(size),
- stopped_(true),
- frame_duration_(frame_duration) {
-}
-
-void VideoFrameGenerator::Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- message_loop_proxy_->PostTask(
- FROM_HERE,
- base::Bind(&VideoFrameGenerator::InitializeOnDecoderThread,
- this, stream, status_cb, statistics_cb));
-}
-
-void VideoFrameGenerator::Read(const ReadCB& read_cb) {
- message_loop_proxy_->PostTask(
- FROM_HERE,
- base::Bind(&VideoFrameGenerator::ReadOnDecoderThread, this, read_cb));
-}
-
-void VideoFrameGenerator::Reset(const base::Closure& closure) {
- message_loop_proxy_->PostTask(
- FROM_HERE,
- base::Bind(&VideoFrameGenerator::ResetOnDecoderThread, this, closure));
-}
-
-void VideoFrameGenerator::Stop(const base::Closure& closure) {
- message_loop_proxy_->PostTask(
- FROM_HERE,
- base::Bind(&VideoFrameGenerator::StopOnDecoderThread, this, closure));
-}
-
-VideoFrameGenerator::~VideoFrameGenerator() {}
-
-void VideoFrameGenerator::InitializeOnDecoderThread(
- const scoped_refptr<DemuxerStream>& /* stream */,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) {
- DVLOG(1) << "InitializeOnDecoderThread";
- DCHECK(message_loop_proxy_->BelongsToCurrentThread());
-
- status_cb.Run(PIPELINE_OK);
- stopped_ = false;
-}
-
-void VideoFrameGenerator::ReadOnDecoderThread(const ReadCB& read_cb) {
- DCHECK(message_loop_proxy_->BelongsToCurrentThread());
- CHECK(!read_cb.is_null());
- if (stopped_)
- return;
-
- // Always allocate a new frame.
- //
- // TODO(scherkus): migrate this to proper buffer recycling.
- scoped_refptr<VideoFrame> video_frame =
- VideoFrame::CreateFrame(VideoFrame::YV12, size_, gfx::Rect(size_), size_,
- current_time_);
- current_time_ += frame_duration_;
-
- // TODO(wjia): set pixel data to pre-defined patterns if it's desired to
- // verify frame content.
-
- read_cb.Run(kOk, video_frame);
-}
-
-void VideoFrameGenerator::ResetOnDecoderThread(const base::Closure& closure) {
- DVLOG(1) << "ResetOnDecoderThread";
- DCHECK(message_loop_proxy_->BelongsToCurrentThread());
- current_time_ = base::TimeDelta();
- closure.Run();
-}
-
-void VideoFrameGenerator::StopOnDecoderThread(const base::Closure& closure) {
- DVLOG(1) << "StopOnDecoderThread";
- DCHECK(message_loop_proxy_->BelongsToCurrentThread());
- stopped_ = true;
- current_time_ = base::TimeDelta();
- closure.Run();
-}
-
-} // namespace media
diff --git a/src/media/filters/video_frame_generator.h b/src/media/filters/video_frame_generator.h
deleted file mode 100644
index 2a4667f..0000000
--- a/src/media/filters/video_frame_generator.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_VIDEO_FRAME_GENERATOR_H_
-#define MEDIA_FILTERS_VIDEO_FRAME_GENERATOR_H_
-
-#include "base/time.h"
-#include "media/base/video_decoder.h"
-#include "media/base/pipeline_status.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DemuxerStream;
-class VideoFrame;
-
-// A filter generates raw frames and passes them to media engine as a video
-// decoder filter.
-class MEDIA_EXPORT VideoFrameGenerator : public VideoDecoder {
- public:
- VideoFrameGenerator(
- const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
- const gfx::Size& size,
- const base::TimeDelta& frame_duration);
-
- // VideoDecoder implementation.
- virtual void Initialize(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb) OVERRIDE;
- virtual void Read(const ReadCB& read_cb) OVERRIDE;
- virtual void Reset(const base::Closure& closure) OVERRIDE;
- virtual void Stop(const base::Closure& closure) OVERRIDE;
-
- protected:
- virtual ~VideoFrameGenerator();
-
- private:
- void InitializeOnDecoderThread(
- const scoped_refptr<DemuxerStream>& stream,
- const PipelineStatusCB& status_cb,
- const StatisticsCB& statistics_cb);
- void ReadOnDecoderThread(const ReadCB& read_cb);
- void ResetOnDecoderThread(const base::Closure& closure);
- void StopOnDecoderThread(const base::Closure& closure);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
- gfx::Size size_;
- bool stopped_;
-
- base::TimeDelta current_time_;
- base::TimeDelta frame_duration_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoFrameGenerator);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_VIDEO_FRAME_GENERATOR_H_
diff --git a/src/media/filters/video_renderer_base.cc b/src/media/filters/video_renderer_base.cc
deleted file mode 100644
index de91f9f..0000000
--- a/src/media/filters/video_renderer_base.cc
+++ /dev/null
@@ -1,905 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/filters/video_renderer_base.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/message_loop.h"
-#include "base/threading/platform_thread.h"
-#include "media/base/buffers.h"
-#include "media/base/limits.h"
-#include "media/base/pipeline.h"
-#include "media/base/video_frame.h"
-#include "media/filters/decrypting_demuxer_stream.h"
-#include "media/filters/video_decoder_selector.h"
-
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "base/debug/trace_event.h"
-#include "base/stringprintf.h"
-#include "media/base/shell_media_platform.h"
-#include "media/base/shell_media_statistics.h"
-#endif
-
-namespace media {
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
-int VideoRendererBase::videos_played_;
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-
-base::TimeDelta VideoRendererBase::kMaxLastFrameDuration() {
- return base::TimeDelta::FromMilliseconds(250);
-}
-
-VideoRendererBase::VideoRendererBase(
- const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const base::Closure& paint_cb,
- const SetOpaqueCB& set_opaque_cb,
- bool drop_frames)
- : message_loop_(message_loop),
- set_decryptor_ready_cb_(set_decryptor_ready_cb),
- frame_available_(&lock_),
- state_(kUninitialized),
- thread_(base::kNullThreadHandle),
- pending_read_(false),
- pending_paint_(false),
- pending_paint_with_last_available_(false),
- drop_frames_(drop_frames),
- playback_rate_(0),
- paint_cb_(paint_cb),
- set_opaque_cb_(set_opaque_cb),
- maximum_frames_cached_(0) {
- DCHECK(!paint_cb_.is_null());
-#if defined(__LB_SHELL__) || defined(COBALT)
- frame_provider_ = ShellMediaPlatform::Instance()->GetVideoFrameProvider();
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- late_frames_ = 0;
- DLOG(INFO) << "Start playing back video " << videos_played_;
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-}
-
-VideoRendererBase::~VideoRendererBase() {
- base::AutoLock auto_lock(lock_);
- DCHECK(state_ == kStopped || state_ == kUninitialized) << state_;
- DCHECK_EQ(thread_, base::kNullThreadHandle);
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- ++videos_played_;
- DLOG_IF(INFO, late_frames_ != 0) << "Finished playing back with "
- << late_frames_ << " late frames.";
- DLOG_IF(INFO, late_frames_ == 0)
- << "Finished playing back with no late frame.";
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-}
-
-void VideoRendererBase::Play(const base::Closure& callback) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::Play()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK_EQ(kPrerolled, state_);
- state_ = kPlaying;
- callback.Run();
-}
-
-void VideoRendererBase::Pause(const base::Closure& callback) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::Pause()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK(state_ != kUninitialized || state_ == kError);
- state_ = kPaused;
- callback.Run();
-}
-
-void VideoRendererBase::Flush(const base::Closure& callback) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::Flush()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK_EQ(state_, kPaused);
- flush_cb_ = callback;
- state_ = kFlushingDecoder;
-
- if (decrypting_demuxer_stream_) {
- decrypting_demuxer_stream_->Reset(base::Bind(
- &VideoRendererBase::ResetDecoder, this));
- return;
- }
-
- decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
-}
-
-void VideoRendererBase::ResetDecoder() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- decoder_->Reset(base::Bind(&VideoRendererBase::OnDecoderResetDone, this));
-}
-
-void VideoRendererBase::Stop(const base::Closure& callback) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::Stop()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- if (state_ == kUninitialized || state_ == kStopped) {
- callback.Run();
- return;
- }
-
- base::PlatformThreadHandle thread_to_join = base::kNullThreadHandle;
- {
- base::AutoLock auto_lock(lock_);
- state_ = kStopped;
-
- statistics_cb_.Reset();
- max_time_cb_.Reset();
- if (!pending_paint_ && !pending_paint_with_last_available_)
- DoStopOrError_Locked();
-
- // Clean up our thread if present.
- if (thread_ != base::kNullThreadHandle) {
- // Signal the thread since it's possible to get stopped with the video
- // thread waiting for a read to complete.
- frame_available_.Signal();
- thread_to_join = thread_;
- thread_ = base::kNullThreadHandle;
- }
- }
- if (thread_to_join != base::kNullThreadHandle)
- base::PlatformThread::Join(thread_to_join);
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- if (frame_provider_) { frame_provider_->Stop(); }
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- if (decrypting_demuxer_stream_) {
- decrypting_demuxer_stream_->Reset(base::Bind(
- &VideoRendererBase::StopDecoder, this, callback));
- return;
- }
-
- if (decoder_) {
- decoder_->Stop(callback);
- return;
- }
-
- callback.Run();
-}
-
-void VideoRendererBase::StopDecoder(const base::Closure& callback) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- decoder_->Stop(callback);
-}
-
-void VideoRendererBase::SetPlaybackRate(float playback_rate) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- playback_rate_ = playback_rate;
-}
-
-void VideoRendererBase::Preroll(base::TimeDelta time,
- const PipelineStatusCB& cb) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT1("media_stack", "VideoRendererBase::Preroll()",
- "timestamp", time.InMicroseconds());
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK_EQ(state_, kFlushed) << "Must flush prior to prerolling.";
- DCHECK(!cb.is_null());
- DCHECK(preroll_cb_.is_null());
-
- state_ = kPrerolling;
- preroll_cb_ = cb;
- preroll_timestamp_ = time;
- prerolling_delayed_frame_ = NULL;
- AttemptRead_Locked();
-}
-
-void VideoRendererBase::Initialize(const scoped_refptr<DemuxerStream>& stream,
- const VideoDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const TimeCB& max_time_cb,
- const NaturalSizeChangedCB& size_changed_cb,
- const base::Closure& ended_cb,
- const PipelineStatusCB& error_cb,
- const TimeDeltaCB& get_time_cb,
- const TimeDeltaCB& get_duration_cb) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::Initialize()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- DCHECK(stream);
- DCHECK(!decoders.empty());
- DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
- DCHECK(!init_cb.is_null());
- DCHECK(!statistics_cb.is_null());
- DCHECK(!max_time_cb.is_null());
- DCHECK(!size_changed_cb.is_null());
- DCHECK(!ended_cb.is_null());
- DCHECK(!get_time_cb.is_null());
- DCHECK(!get_duration_cb.is_null());
- DCHECK_EQ(kUninitialized, state_);
-
- init_cb_ = init_cb;
- statistics_cb_ = statistics_cb;
- max_time_cb_ = max_time_cb;
- size_changed_cb_ = size_changed_cb;
- ended_cb_ = ended_cb;
- error_cb_ = error_cb;
- get_time_cb_ = get_time_cb;
- get_duration_cb_ = get_duration_cb;
- state_ = kInitializing;
-
- scoped_ptr<VideoDecoderSelector> decoder_selector(
- new VideoDecoderSelector(base::MessageLoopProxy::current(),
- decoders,
- set_decryptor_ready_cb_));
-
- // To avoid calling |decoder_selector| methods and passing ownership of
- // |decoder_selector| in the same line.
- VideoDecoderSelector* decoder_selector_ptr = decoder_selector.get();
-
- decoder_selector_ptr->SelectVideoDecoder(
- stream,
- statistics_cb,
- base::Bind(&VideoRendererBase::OnDecoderSelected, this,
- base::Passed(&decoder_selector)));
-}
-
-void VideoRendererBase::OnDecoderSelected(
- scoped_ptr<VideoDecoderSelector> decoder_selector,
- const scoped_refptr<VideoDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream) {
- DCHECK(message_loop_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
-
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(state_, kInitializing);
-
- if (!selected_decoder) {
- state_ = kUninitialized;
- base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
- return;
- }
-
- decoder_ = selected_decoder;
- decrypting_demuxer_stream_ = decrypting_demuxer_stream;
-
- // We're all good! Consider ourselves flushed. (ThreadMain() should never
- // see us in the kUninitialized state).
- // Since we had an initial Preroll(), we consider ourself flushed, because we
- // have not populated any buffers yet.
- state_ = kFlushed;
-
- set_opaque_cb_.Run(!decoder_->HasAlpha());
- set_opaque_cb_.Reset();
-
- // Create our video thread.
- if (!base::PlatformThread::Create(0, this, &thread_)) {
- NOTREACHED() << "Video thread creation failed";
- state_ = kError;
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED);
- return;
- }
-
-#if defined(OS_WIN)
- // Bump up our priority so our sleeping is more accurate.
- // TODO(scherkus): find out if this is necessary, but it seems to help.
- ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
-#endif // defined(OS_WIN)
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
-}
-
-// PlatformThread::Delegate implementation.
-void VideoRendererBase::ThreadMain() {
- base::PlatformThread::SetName("CrVideoRenderer");
-
- // The number of milliseconds to idle when we do not have anything to do.
- // Nothing special about the value, other than we're being more OS-friendly
- // than sleeping for 1 millisecond.
- //
- // TOOD(scherkus): switch to pure event-driven frame timing instead of this
- // kIdleTimeDelta business http://crbug.com/106874
- const base::TimeDelta kIdleTimeDelta =
- base::TimeDelta::FromMilliseconds(10);
-
- uint32 frames_dropped = 0;
-
- for (;;) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- frames_dropped =
- static_cast<uint32>(frame_provider_->ResetAndReturnDroppedFrames());
- if (frames_dropped > 0) {
- SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_DROP);
- }
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- if (frames_dropped > 0) {
- if (!statistics_cb_.is_null()) {
- PipelineStatistics statistics;
- statistics.video_frames_dropped = frames_dropped;
- statistics_cb_.Run(statistics);
- }
- frames_dropped = 0;
- }
-
- base::AutoLock auto_lock(lock_);
-
- // Thread exit condition.
- if (state_ == kStopped)
- return;
-
- // Remain idle as long as we're not playing.
- if (state_ != kPlaying || playback_rate_ == 0) {
- frame_available_.TimedWait(kIdleTimeDelta);
- continue;
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- if (frame_provider_ && frame_provider_->QueryAndResetHasConsumedFrames()) {
- // The consumer of the frame_provider_ has consumed frames. Post another
- // AttemptRead task to ensure that new frames are being read in to keep
- // the frame_provider_'s queue full.
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoRendererBase::AttemptRead, this));
- }
-#endif
-
- // Remain idle until we have the next frame ready for rendering.
- if (ready_frames_.empty()) {
- frame_available_.TimedWait(kIdleTimeDelta);
- continue;
- }
-
- // Remain idle until we've initialized |current_frame_| via prerolling.
- if (!current_frame_) {
- // This can happen if our preroll only contains end of stream frames.
- if (ready_frames_.front()->IsEndOfStream()) {
- state_ = kEnded;
- ended_cb_.Run();
- ready_frames_.clear();
-
- // No need to sleep here as we idle when |state_ != kPlaying|.
- continue;
- }
-
- frame_available_.TimedWait(kIdleTimeDelta);
- continue;
- }
-
- // Calculate how long until we should advance the frame, which is
- // typically negative but for playback rates < 1.0f may be long enough
- // that it makes more sense to idle and check again.
- base::TimeDelta remaining_time =
- CalculateSleepDuration(ready_frames_.front(), playback_rate_);
-
- // Sleep up to a maximum of our idle time until we're within the time to
- // render the next frame.
- if (remaining_time.InMicroseconds() > 0) {
- remaining_time = std::min(remaining_time, kIdleTimeDelta);
- frame_available_.TimedWait(remaining_time);
- continue;
- }
-
-
- // We're almost there!
- //
- // At this point we've rendered |current_frame_| for the proper amount
- // of time and also have the next frame that ready for rendering.
-
-
- // If the next frame is end of stream then we are truly at the end of the
- // video stream.
- //
- // TODO(scherkus): deduplicate this end of stream check after we get rid of
- // |current_frame_|.
- if (ready_frames_.front()->IsEndOfStream()) {
- state_ = kEnded;
- ended_cb_.Run();
- ready_frames_.clear();
-
- // No need to sleep here as we idle when |state_ != kPlaying|.
- continue;
- }
-
- // We cannot update |current_frame_| until we've completed the pending
- // paint. Furthermore, the pending paint might be really slow: check to
- // see if we have any ready frames that we can drop if they've already
- // expired.
- if (pending_paint_) {
- while (!ready_frames_.empty()) {
- // Can't drop anything if we're at the end.
- if (ready_frames_.front()->IsEndOfStream())
- break;
-
- base::TimeDelta remaining_time =
- ready_frames_.front()->GetTimestamp() - get_time_cb_.Run();
-
- // Still a chance we can render the frame!
- if (remaining_time.InMicroseconds() > 0)
- break;
-
- if (!drop_frames_)
- break;
-
- // Frame dropped: read again.
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT1("media_stack", "VideoRendererBase drop frame",
- "timestamp",
- ready_frames_.front()->GetTimestamp().InMicroseconds());
-#endif
- ++frames_dropped;
- ready_frames_.pop_front();
-#if defined(__LB_SHELL__) || defined(COBALT)
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_RENDERER_BACKLOG,
- ready_frames_.size());
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoRendererBase::AttemptRead, this));
- }
- // Continue waiting for the current paint to finish.
- frame_available_.TimedWait(kIdleTimeDelta);
- continue;
- }
-
-
- // Congratulations! You've made it past the video frame timing gauntlet.
- //
- // We can now safely update the current frame, request another frame, and
- // signal to the client that a new frame is available.
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase proceed to next frame");
-#endif
- DCHECK(!pending_paint_);
- DCHECK(!ready_frames_.empty());
- SetCurrentFrameToNextReadyFrame();
- message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoRendererBase::AttemptRead, this));
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase paint_cb_");
-#endif
- base::AutoUnlock auto_unlock(lock_);
- paint_cb_.Run();
- }
-}
-
-void VideoRendererBase::SetCurrentFrameToNextReadyFrame() {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT1("media_stack",
- "VideoRendererBase::SetCurrentFrameToNextReadyFrame()",
- "timestamp",
- ready_frames_.front()->GetTimestamp().InMicroseconds());
-#endif
- current_frame_ = ready_frames_.front();
- ready_frames_.pop_front();
-#if defined(__LB_SHELL__) || defined(COBALT)
- UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_RENDERER_BACKLOG,
- ready_frames_.size());
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // Notify the pipeline of natural_size() changes.
- const gfx::Size& natural_size = current_frame_->natural_size();
- if (natural_size != last_natural_size_) {
- size_changed_cb_.Run(natural_size);
- last_natural_size_ = natural_size;
- }
-}
-
-void VideoRendererBase::GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out) {
- base::AutoLock auto_lock(lock_);
- DCHECK(!pending_paint_ && !pending_paint_with_last_available_);
-
- if ((!current_frame_ || current_frame_->IsEndOfStream()) &&
- (!last_available_frame_ || last_available_frame_->IsEndOfStream())) {
- *frame_out = NULL;
- return;
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::GetCurrentFrame()");
-#endif
-
- // We should have initialized and have the current frame.
- DCHECK_NE(state_, kUninitialized);
- DCHECK_NE(state_, kStopped);
- DCHECK_NE(state_, kError);
-
- if (current_frame_) {
- *frame_out = current_frame_;
- last_available_frame_ = current_frame_;
- pending_paint_ = true;
- } else {
- DCHECK(last_available_frame_);
- *frame_out = last_available_frame_;
- pending_paint_with_last_available_ = true;
- }
-}
-
-void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::PutCurrentFrame()");
-#endif
-
- base::AutoLock auto_lock(lock_);
-
- // Note that we do not claim |pending_paint_| when we return NULL frame, in
- // that case, |current_frame_| could be changed before PutCurrentFrame.
- if (pending_paint_) {
- DCHECK_EQ(current_frame_, frame);
- DCHECK(!pending_paint_with_last_available_);
- pending_paint_ = false;
- } else if (pending_paint_with_last_available_) {
- DCHECK_EQ(last_available_frame_, frame);
- DCHECK(!pending_paint_);
- pending_paint_with_last_available_ = false;
- } else {
- DCHECK(!frame);
- }
-
- // We had cleared the |pending_paint_| flag, there are chances that current
- // frame is timed-out. We will wake up our main thread to advance the current
- // frame when this is true.
- frame_available_.Signal();
- if (state_ == kFlushingDecoder)
- return;
-
- if (state_ == kFlushing) {
- AttemptFlush_Locked();
- return;
- }
-
- if (state_ == kError || state_ == kStopped) {
- DoStopOrError_Locked();
- }
-}
-
-void VideoRendererBase::FrameReady(
- VideoDecoder::Status status,
- const scoped_refptr<VideoFrame>& incoming_frame) {
- // Make a copy as we may assign newly created punch out frame to `frame` when
- // frame provider is available.
- scoped_refptr<VideoFrame> frame(incoming_frame);
-
- base::AutoLock auto_lock(lock_);
- DCHECK_NE(state_, kUninitialized);
-
- CHECK(pending_read_);
- pending_read_ = false;
-
- if (status != VideoDecoder::kOk) {
- DCHECK(!frame);
- PipelineStatus error = PIPELINE_ERROR_DECODE;
- if (status == VideoDecoder::kDecryptError)
- error = PIPELINE_ERROR_DECRYPT;
-
- if (!preroll_cb_.is_null()) {
- base::ResetAndReturn(&preroll_cb_).Run(error);
- return;
- }
-
- error_cb_.Run(error);
- return;
- }
-
- // Already-queued Decoder ReadCB's can fire after various state transitions
- // have happened; in that case just drop those frames immediately.
- if (state_ == kStopped || state_ == kError || state_ == kFlushed ||
- state_ == kFlushingDecoder)
- return;
-
- if (state_ == kFlushing) {
- AttemptFlush_Locked();
- return;
- }
-
- if (!frame) {
- if (state_ != kPrerolling)
- return;
-
- // Abort preroll early for a NULL frame because we won't get more frames.
- // A new preroll will be requested after this one completes so there is no
- // point trying to collect more frames.
- state_ = kPrerolled;
- base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
- return;
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- scoped_refptr<VideoFrame> original_frame;
- if (frame_provider_ && !frame->IsEndOfStream()) {
- // Save the frame to original_frame so it can be added into frame_provider
- // later.
- original_frame = frame;
- frame = VideoFrame::CreatePunchOutFrame(
- original_frame->visible_rect().size());
- frame->SetTimestamp(original_frame->GetTimestamp());
- }
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // Discard frames until we reach our desired preroll timestamp.
- if (state_ == kPrerolling && !frame->IsEndOfStream() &&
- frame->GetTimestamp() <= preroll_timestamp_) {
- prerolling_delayed_frame_ = frame;
- AttemptRead_Locked();
- return;
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- if (frame_provider_ && original_frame) {
- frame_provider_->AddFrame(original_frame);
- }
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- if (prerolling_delayed_frame_) {
- DCHECK_EQ(state_, kPrerolling);
- AddReadyFrame(prerolling_delayed_frame_);
- prerolling_delayed_frame_ = NULL;
- }
-
- AddReadyFrame(frame);
- PipelineStatistics statistics;
- statistics.video_frames_decoded = 1;
- statistics_cb_.Run(statistics);
-
- // Always request more decoded video if we have capacity. This serves two
- // purposes:
- // 1) Prerolling while paused
- // 2) Keeps decoding going if video rendering thread starts falling behind
-#if defined(__LB_SHELL__) || defined(COBALT)
- bool is_prerolling = state_ == kPrerolling;
- int max_video_frames =
- is_prerolling ? ShellMediaPlatform::Instance()->GetMaxVideoPrerollFrames()
- : ShellMediaPlatform::Instance()->GetMaxVideoFrames();
- if (NumFrames_Locked() < max_video_frames && !frame->IsEndOfStream()) {
-#else // defined(__LB_SHELL__) || defined(COBALT)
- if (NumFrames_Locked() < limits::kMaxVideoFrames && !frame->IsEndOfStream()) {
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- AttemptRead_Locked();
- return;
- }
-
- // If we're at capacity or end of stream while prerolling we need to
- // transition to prerolled.
- if (state_ == kPrerolling) {
- DCHECK(!current_frame_);
- state_ = kPrerolled;
-
- // Because we might remain in the prerolled state for an undetermined amount
- // of time (i.e., we were not playing before we started prerolling), we'll
- // manually update the current frame and notify the subclass below.
- if (!ready_frames_.front()->IsEndOfStream())
- SetCurrentFrameToNextReadyFrame();
-
- // ...and we're done prerolling!
- DCHECK(!preroll_cb_.is_null());
- base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK);
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase paint_cb_");
-#endif
- base::AutoUnlock ul(lock_);
- paint_cb_.Run();
- }
-}
-
-void VideoRendererBase::AddReadyFrame(const scoped_refptr<VideoFrame>& frame) {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT1("media_stack", "VideoRendererBase::AddReadyFrame()",
- "timestamp", frame->GetTimestamp().InMicroseconds());
-#endif
- // Adjust the incoming frame if its rendering stop time is past the duration
- // of the video itself. This is typically the last frame of the video and
- // occurs if the container specifies a duration that isn't a multiple of the
- // frame rate. Another way for this to happen is for the container to state a
- // smaller duration than the largest packet timestamp.
- base::TimeDelta duration = get_duration_cb_.Run();
- if (frame->IsEndOfStream()) {
- base::TimeDelta end_timestamp = kNoTimestamp();
- if (!ready_frames_.empty()) {
- end_timestamp = std::min(
- duration,
- ready_frames_.back()->GetTimestamp() + kMaxLastFrameDuration());
- } else if (current_frame_) {
- end_timestamp =
- std::min(duration,
- current_frame_->GetTimestamp() + kMaxLastFrameDuration());
- }
- frame->SetTimestamp(end_timestamp);
- } else if (frame->GetTimestamp() > duration) {
- frame->SetTimestamp(duration);
- }
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- if (frame->GetTimestamp() < get_time_cb_.Run()) {
- SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_LATE);
- ++late_frames_;
- }
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-
- ready_frames_.push_back(frame);
-#if defined(__LB_SHELL__) || defined(COBALT)
- DCHECK_LE(NumFrames_Locked(),
- ShellMediaPlatform::Instance()->GetMaxVideoFrames());
-#else // defined(__LB_SHELL__) || defined(COBALT)
- DCHECK_LE(NumFrames_Locked(), limits::kMaxVideoFrames);
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- base::TimeDelta max_clock_time =
- frame->IsEndOfStream() ? duration : frame->GetTimestamp();
- DCHECK(max_clock_time != kNoTimestamp());
- max_time_cb_.Run(max_clock_time);
-
- frame_available_.Signal();
-}
-
-void VideoRendererBase::AttemptRead() {
- base::AutoLock auto_lock(lock_);
- AttemptRead_Locked();
-}
-
-void VideoRendererBase::AttemptRead_Locked() {
-#if defined(__LB_SHELL__) || defined(COBALT)
- TRACE_EVENT0("media_stack", "VideoRendererBase::AttemptRead_Locked()");
-#endif
- DCHECK(message_loop_->BelongsToCurrentThread());
- lock_.AssertAcquired();
-
- if (pending_read_ ||
-#if defined(__LB_SHELL__) || defined(COBALT)
- NumFrames_Locked() ==
- ShellMediaPlatform::Instance()->GetMaxVideoFrames() ||
-#else // defined(__LB_SHELL__) || defined(COBALT)
- NumFrames_Locked() == limits::kMaxVideoFrames ||
-#endif // defined(__LB_SHELL__) || defined(COBALT)
- (!ready_frames_.empty() && ready_frames_.back()->IsEndOfStream())) {
- return;
- }
-
- UpdateUnderflowStatusToDecoder_Locked();
-
- switch (state_) {
- case kPaused:
- case kFlushing:
- case kPrerolling:
- case kPlaying:
- pending_read_ = true;
- decoder_->Read(base::Bind(&VideoRendererBase::FrameReady, this));
- return;
-
- case kUninitialized:
- case kInitializing:
- case kPrerolled:
- case kFlushingDecoder:
- case kFlushed:
- case kEnded:
- case kStopped:
- case kError:
- return;
- }
-}
-
-void VideoRendererBase::UpdateUnderflowStatusToDecoder_Locked() {
- DCHECK(message_loop_->BelongsToCurrentThread());
- lock_.AssertAcquired();
-
- if (!decoder_) {
- return;
- }
- if (state_ != kPlaying) {
- // If we are not playing, inform the decoder that we have enough frames so
- // it will decode in high quality.
- decoder_->HaveEnoughFrames();
- return;
- }
-
- // There are two stages during playback: prerolling and playing. In the
- // following example, we assume the preroll frame count is 4, the underflow
- // frame count is 16 and the maximum frame count is 32.
- // 1. Prerolling:
- // When the video starts to play back, there will be 4 frames buffered. Then
- // it will slowly grow up to 32 frames if the decoding speed is fast enough.
- // When the cached frame count start to decrease consistently, there is an
- // overflow. Note that we cannot simply compare the current cached frame
- // against GetVideoUnderflowFrames() because as long as we are accumulating
- // more frames, we don't want to trigger underflow during start up. So we
- // record the maximum frame ever reached and when the cached frame count
- // drops below `maximum frame count` - kUnderflowAdjustment, we trigger
- // underflow.
- // 2. Playing.
- // In this case we already have maximum frames in cache so we have more room
- // for slow decoding. We only trigger underflow when the cached frame count
- // drops below GetVideoUnderflowFrames().
- const int kUnderflowAdjustment = 2;
- maximum_frames_cached_ = std::max(maximum_frames_cached_, NumFrames_Locked());
- int underflow_threshold = std::min(
- ShellMediaPlatform::Instance()->GetVideoUnderflowFrames(),
- std::max(maximum_frames_cached_ - kUnderflowAdjustment, 0));
-
- if (NumFrames_Locked() < underflow_threshold) {
- decoder_->NearlyUnderflow();
- } else {
- decoder_->HaveEnoughFrames();
- }
-}
-
-void VideoRendererBase::OnDecoderResetDone() {
- base::AutoLock auto_lock(lock_);
- if (state_ == kStopped)
- return;
-
- DCHECK_EQ(kFlushingDecoder, state_);
- DCHECK(!pending_read_);
-
- state_ = kFlushing;
- AttemptFlush_Locked();
-}
-
-void VideoRendererBase::AttemptFlush_Locked() {
- lock_.AssertAcquired();
- DCHECK_EQ(kFlushing, state_);
-
- prerolling_delayed_frame_ = NULL;
- ready_frames_.clear();
- maximum_frames_cached_ = 0;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- if (frame_provider_) { frame_provider_->Flush(); }
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- if (!pending_paint_ && !pending_read_) {
- state_ = kFlushed;
- current_frame_ = NULL;
- base::ResetAndReturn(&flush_cb_).Run();
- }
-}
-
-base::TimeDelta VideoRendererBase::CalculateSleepDuration(
- const scoped_refptr<VideoFrame>& next_frame,
- float playback_rate) {
- // Determine the current and next presentation timestamps.
- base::TimeDelta now = get_time_cb_.Run();
- base::TimeDelta next_pts = next_frame->GetTimestamp();
-
- // Scale our sleep based on the playback rate.
- base::TimeDelta sleep = next_pts - now;
- return base::TimeDelta::FromMicroseconds(
- static_cast<int64>(sleep.InMicroseconds() / playback_rate));
-}
-
-void VideoRendererBase::DoStopOrError_Locked() {
- DCHECK(!pending_paint_);
- DCHECK(!pending_paint_with_last_available_);
- lock_.AssertAcquired();
- current_frame_ = NULL;
- last_available_frame_ = NULL;
- ready_frames_.clear();
-}
-
-int VideoRendererBase::NumFrames_Locked() const {
- lock_.AssertAcquired();
- if (frame_provider_) {
- return frame_provider_->GetNumOfFramesCached();
- }
-
- int outstanding_frames =
- (current_frame_ ? 1 : 0) + (last_available_frame_ ? 1 : 0) +
- (current_frame_ && (current_frame_ == last_available_frame_) ? -1 : 0);
- return ready_frames_.size() + outstanding_frames;
-}
-
-} // namespace media
diff --git a/src/media/filters/video_renderer_base.h b/src/media/filters/video_renderer_base.h
deleted file mode 100644
index f842f18..0000000
--- a/src/media/filters/video_renderer_base.h
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
-#define MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
-
-#include <deque>
-
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/platform_thread.h"
-#include "media/base/decryptor.h"
-#include "media/base/demuxer_stream.h"
-#include "media/base/pipeline_status.h"
-#if defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/shell_video_frame_provider.h"
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-#include "media/base/video_decoder.h"
-#include "media/base/video_frame.h"
-#include "media/base/video_renderer.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecryptingDemuxerStream;
-class VideoDecoderSelector;
-
-// VideoRendererBase creates its own thread for the sole purpose of timing frame
-// presentation. It handles reading from the decoder and stores the results in
-// a queue of decoded frames and executing a callback when a frame is ready for
-// rendering.
-class MEDIA_EXPORT VideoRendererBase
- : public VideoRenderer,
- public base::PlatformThread::Delegate {
- public:
- typedef base::Callback<void(bool)> SetOpaqueCB;
-
- // Maximum duration of the last frame.
- static base::TimeDelta kMaxLastFrameDuration();
-
- // |paint_cb| is executed on the video frame timing thread whenever a new
- // frame is available for painting via GetCurrentFrame().
- //
- // |set_opaque_cb| is executed when the renderer is initialized to inform
- // the player whether the decoder's output will be opaque or not.
- //
- // Implementors should avoid doing any sort of heavy work in this method and
- // instead post a task to a common/worker thread to handle rendering. Slowing
- // down the video thread may result in losing synchronization with audio.
- //
- // Setting |drop_frames_| to true causes the renderer to drop expired frames.
- //
- // TODO(scherkus): pass the VideoFrame* to this callback and remove
- // Get/PutCurrentFrame() http://crbug.com/108435
- VideoRendererBase(const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const SetDecryptorReadyCB& set_decryptor_ready_cb,
- const base::Closure& paint_cb,
- const SetOpaqueCB& set_opaque_cb,
- bool drop_frames);
-
- // VideoRenderer implementation.
- virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
- const VideoDecoderList& decoders,
- const PipelineStatusCB& init_cb,
- const StatisticsCB& statistics_cb,
- const TimeCB& max_time_cb,
- const NaturalSizeChangedCB& size_changed_cb,
- const base::Closure& ended_cb,
- const PipelineStatusCB& error_cb,
- const TimeDeltaCB& get_time_cb,
- const TimeDeltaCB& get_duration_cb) OVERRIDE;
- virtual void Play(const base::Closure& callback) OVERRIDE;
- virtual void Pause(const base::Closure& callback) OVERRIDE;
- virtual void Flush(const base::Closure& callback) OVERRIDE;
- virtual void Preroll(base::TimeDelta time,
- const PipelineStatusCB& cb) OVERRIDE;
- virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void SetPlaybackRate(float playback_rate) OVERRIDE;
-
- // PlatformThread::Delegate implementation.
- virtual void ThreadMain() OVERRIDE;
-
- // Clients of this class (painter/compositor) should use GetCurrentFrame()
- // obtain ownership of VideoFrame, it should always relinquish the ownership
- // by use PutCurrentFrame(). Current frame is not guaranteed to be non-NULL.
- // It expects clients to use color-fill the background if current frame
- // is NULL. This could happen before pipeline is pre-rolled or during
- // pause/flush/preroll.
- void GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out);
- void PutCurrentFrame(scoped_refptr<VideoFrame> frame);
-
- protected:
- virtual ~VideoRendererBase();
-
- private:
- // Called when |decoder_selector_| selected the |selected_decoder|.
- // |decrypting_demuxer_stream| was also populated if a DecryptingDemuxerStream
- // created to help decrypt the encrypted stream.
- // Note: |decoder_selector| is passed here to keep the VideoDecoderSelector
- // alive until OnDecoderSelected() finishes.
- void OnDecoderSelected(
- scoped_ptr<VideoDecoderSelector> decoder_selector,
- const scoped_refptr<VideoDecoder>& selected_decoder,
- const scoped_refptr<DecryptingDemuxerStream>& decrypting_demuxer_stream);
-
- // Callback from the video decoder delivering decoded video frames and
- // reporting video decoder status.
- void FrameReady(VideoDecoder::Status status,
- const scoped_refptr<VideoFrame>& incoming_frame);
-
- // Helper method for adding a frame to |ready_frames_|
- void AddReadyFrame(const scoped_refptr<VideoFrame>& frame);
-
- // Helper method that schedules an asynchronous read from the decoder as long
- // as there isn't a pending read and we have capacity.
- void AttemptRead();
- void AttemptRead_Locked();
-
- void UpdateUnderflowStatusToDecoder_Locked();
-
- // Called when VideoDecoder::Reset() completes.
- void OnDecoderResetDone();
-
- // Attempts to complete flushing and transition into the flushed state.
- void AttemptFlush_Locked();
-
- // Calculates the duration to sleep for based on |current_frame_|'s timestamp,
- // the next frame timestamp (may be NULL), and the provided playback rate.
- //
- // We don't use |playback_rate_| to avoid locking.
- base::TimeDelta CalculateSleepDuration(
- const scoped_refptr<VideoFrame>& next_frame,
- float playback_rate);
-
- // Helper function that flushes the buffers when a Stop() or error occurs.
- void DoStopOrError_Locked();
-
- // Return the number of frames currently held by this class.
- int NumFrames_Locked() const;
-
- // Updates |current_frame_| to the next frame on |ready_frames_| and calls
- // |size_changed_cb_| if the natural size changes.
- void SetCurrentFrameToNextReadyFrame();
-
- void ResetDecoder();
- void StopDecoder(const base::Closure& callback);
-
- // Pops the front of |decoders|, assigns it to |decoder_| and then
- // calls initialize on the new decoder.
- void InitializeNextDecoder(const scoped_refptr<DemuxerStream>& demuxer_stream,
- scoped_ptr<VideoDecoderList> decoders);
-
- // Called when |decoder_| initialization completes.
- // |demuxer_stream| & |decoders| are used if initialization failed and
- // InitializeNextDecoder() needs to be called again.
- void OnDecoderInitDone(const scoped_refptr<DemuxerStream>& demuxer_stream,
- scoped_ptr<VideoDecoderList> decoders,
- PipelineStatus status);
-
- scoped_refptr<base::MessageLoopProxy> message_loop_;
-
- // Used for accessing data members.
- base::Lock lock_;
-
- SetDecryptorReadyCB set_decryptor_ready_cb_;
-
- // These two will be set by VideoDecoderSelector::SelectVideoDecoder().
- scoped_refptr<VideoDecoder> decoder_;
- scoped_refptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
-
- // Queue of incoming frames as well as the current frame since the last time
- // OnFrameAvailable() was called.
- typedef std::deque<scoped_refptr<VideoFrame> > VideoFrameQueue;
- VideoFrameQueue ready_frames_;
-
- // The current frame available to subclasses for rendering via
- // GetCurrentFrame(). |current_frame_| can only be altered when
- // |pending_paint_| is false.
- scoped_refptr<VideoFrame> current_frame_;
-
- // The previous |current_frame_| and is returned via GetCurrentFrame() in the
- // situation where all frames were deallocated (i.e., during a flush).
- //
- // TODO(scherkus): remove this after getting rid of Get/PutCurrentFrame() in
- // favour of passing ownership of the current frame to the renderer via
- // callback.
- scoped_refptr<VideoFrame> last_available_frame_;
-
- // Used to signal |thread_| as frames are added to |frames_|. Rule of thumb:
- // always check |state_| to see if it was set to STOPPED after waking up!
- base::ConditionVariable frame_available_;
-
- // State transition Diagram of this class:
- // [kUninitialized] -------> [kError]
- // |
- // | Initialize()
- // [kInitializing]
- // |
- // V All frames returned
- // +------[kFlushed]<-----[kFlushing]<--- OnDecoderResetDone()
- // | | Preroll() or upon ^
- // | V got first frame [kFlushingDecoder]
- // | [kPrerolling] ^
- // | | | Flush()
- // | V Got enough frames |
- // | [kPrerolled]---------------------->[kPaused]
- // | | Pause() ^
- // | V Play() |
- // | [kPlaying]---------------------------|
- // | | Pause() ^
- // | V Receive EOF frame. | Pause()
- // | [kEnded]-----------------------------+
- // | ^
- // | |
- // +-----> [kStopped] [Any state other than]
- // [kUninitialized/kError]
-
- // Simple state tracking variable.
- enum State {
- kUninitialized,
- kInitializing,
- kPrerolled,
- kPaused,
- kFlushingDecoder,
- kFlushing,
- kFlushed,
- kPrerolling,
- kPlaying,
- kEnded,
- kStopped,
- kError,
- };
- State state_;
-
- // Video thread handle.
- base::PlatformThreadHandle thread_;
-
- // Keep track of various pending operations:
- // - |pending_read_| is true when there's an active video decoding request.
- // - |pending_paint_| is true when |current_frame_| is currently being
- // accessed by the subclass.
- // - |pending_paint_with_last_available_| is true when
- // |last_available_frame_| is currently being accessed by the subclass.
- //
- // Flushing cannot complete until both |pending_read_| and |pending_paint_|
- // are false.
- bool pending_read_;
- bool pending_paint_;
- bool pending_paint_with_last_available_;
-
- bool drop_frames_;
-
- float playback_rate_;
-
- // Playback operation callbacks.
- base::Closure flush_cb_;
- PipelineStatusCB preroll_cb_;
-
- // Event callbacks.
- PipelineStatusCB init_cb_;
- StatisticsCB statistics_cb_;
- TimeCB max_time_cb_;
- NaturalSizeChangedCB size_changed_cb_;
- base::Closure ended_cb_;
- PipelineStatusCB error_cb_;
- TimeDeltaCB get_time_cb_;
- TimeDeltaCB get_duration_cb_;
-
- base::TimeDelta preroll_timestamp_;
-
- // Delayed frame used during kPrerolling to determine whether
- // |preroll_timestamp_| is between this frame and the next one.
- scoped_refptr<VideoFrame> prerolling_delayed_frame_;
-
- // Embedder callback for notifying a new frame is available for painting.
- base::Closure paint_cb_;
-
- // Callback to execute to inform the player if the video decoder's output is
- // opaque.
- SetOpaqueCB set_opaque_cb_;
-
- // The last natural size |size_changed_cb_| was called with.
- gfx::Size last_natural_size_;
-
- // TODO: Investigate if we could move underflow logic into
- // ShellVideoFrameProvider.
- int maximum_frames_cached_;
-#if defined(__LB_SHELL__) || defined(COBALT)
- ShellVideoFrameProvider* frame_provider_;
-
-#if !defined(__LB_SHELL__FOR_RELEASE__)
- static int videos_played_;
- int late_frames_;
-#endif // !defined(__LB_SHELL__FOR_RELEASE__)
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- DISALLOW_COPY_AND_ASSIGN(VideoRendererBase);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_VIDEO_RENDERER_BASE_H_
diff --git a/src/media/filters/video_renderer_base_unittest.cc b/src/media/filters/video_renderer_base_unittest.cc
deleted file mode 100644
index f252a2e..0000000
--- a/src/media/filters/video_renderer_base_unittest.cc
+++ /dev/null
@@ -1,661 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/debug/stack_trace.h"
-#include "base/message_loop.h"
-#include "base/stl_util.h"
-#include "base/stringprintf.h"
-#include "base/synchronization/lock.h"
-#include "base/timer.h"
-#include "media/base/data_buffer.h"
-#include "media/base/gmock_callback_support.h"
-#include "media/base/limits.h"
-#include "media/base/mock_filters.h"
-#include "media/base/test_helpers.h"
-#include "media/base/video_frame.h"
-#include "media/filters/video_renderer_base.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::InSequence;
-using ::testing::Invoke;
-using ::testing::NotNull;
-using ::testing::Return;
-using ::testing::ReturnRef;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const int kFrameDurationInMs = 10;
-static const int kVideoDurationInMs = kFrameDurationInMs * 100;
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(16u, 16u);
-static const gfx::Rect kVisibleRect(16u, 16u);
-static const gfx::Size kNaturalSize(16u, 16u);
-
-class VideoRendererBaseTest : public ::testing::Test {
- public:
- VideoRendererBaseTest()
- : decoder_(new MockVideoDecoder()),
- demuxer_stream_(new MockDemuxerStream()),
- video_config_(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat,
- kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false) {
- renderer_ = new VideoRendererBase(
- message_loop_.message_loop_proxy(),
- media::SetDecryptorReadyCB(),
- base::Bind(&VideoRendererBaseTest::OnPaint, base::Unretained(this)),
- base::Bind(&VideoRendererBaseTest::OnSetOpaque, base::Unretained(this)),
- true);
-#if defined(__LB_SHELL__) || defined(COBALT)
- EXPECT_CALL(*demuxer_stream_, StreamWasEncrypted())
- .WillRepeatedly(Return(false));
-#endif
-
- EXPECT_CALL(*demuxer_stream_, type())
- .WillRepeatedly(Return(DemuxerStream::VIDEO));
- EXPECT_CALL(*demuxer_stream_, video_decoder_config())
- .WillRepeatedly(ReturnRef(video_config_));
-
- // We expect these to be called but we don't care how/when.
- EXPECT_CALL(*decoder_, Stop(_))
- .WillRepeatedly(RunClosure<0>());
- EXPECT_CALL(statistics_cb_object_, OnStatistics(_))
- .Times(AnyNumber());
- EXPECT_CALL(*this, OnTimeUpdate(_))
- .Times(AnyNumber());
- EXPECT_CALL(*this, OnPaint())
- .Times(AnyNumber());
- EXPECT_CALL(*this, OnSetOpaque(_))
- .Times(AnyNumber());
- }
-
- virtual ~VideoRendererBaseTest() {}
-
- // Callbacks passed into VideoRendererBase().
- MOCK_CONST_METHOD0(OnPaint, void());
- MOCK_CONST_METHOD1(OnSetOpaque, void(bool));
-
- // Callbacks passed into Initialize().
- MOCK_METHOD1(OnTimeUpdate, void(base::TimeDelta));
- MOCK_METHOD1(OnNaturalSizeChanged, void(const gfx::Size&));
-
- void Initialize() {
- InitializeWithDuration(kVideoDurationInMs);
- }
-
- void InitializeWithDuration(int duration_ms) {
- duration_ = base::TimeDelta::FromMilliseconds(duration_ms);
-
- // Monitor reads from the decoder.
- EXPECT_CALL(*decoder_, Read(_))
- .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FrameRequested));
-
- EXPECT_CALL(*decoder_, Reset(_))
- .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FlushRequested));
-
- InSequence s;
-
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
-
- // Set playback rate before anything else happens.
- renderer_->SetPlaybackRate(1.0f);
-
- // Initialize, we shouldn't have any reads.
- InitializeRenderer(PIPELINE_OK);
-
- // We expect the video size to be set.
- EXPECT_CALL(*this, OnNaturalSizeChanged(kNaturalSize));
-
- // Start prerolling.
- QueuePrerollFrames(0);
- Preroll(0, PIPELINE_OK);
- }
-
- void InitializeRenderer(PipelineStatus expected) {
- SCOPED_TRACE(base::StringPrintf("InitializeRenderer(%d)", expected));
- WaitableMessageLoopEvent event;
- CallInitialize(event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(expected);
- }
-
- void CallInitialize(const PipelineStatusCB& status_cb) {
- VideoRendererBase::VideoDecoderList decoders;
- decoders.push_back(decoder_);
- renderer_->Initialize(
- demuxer_stream_, decoders, status_cb,
- base::Bind(&MockStatisticsCB::OnStatistics,
- base::Unretained(&statistics_cb_object_)),
- base::Bind(&VideoRendererBaseTest::OnTimeUpdate,
- base::Unretained(this)),
- base::Bind(&VideoRendererBaseTest::OnNaturalSizeChanged,
- base::Unretained(this)),
- ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(),
- base::Bind(&VideoRendererBaseTest::GetTime, base::Unretained(this)),
- base::Bind(&VideoRendererBaseTest::GetDuration,
- base::Unretained(this)));
- }
-
- void Play() {
- SCOPED_TRACE("Play()");
- WaitableMessageLoopEvent event;
- renderer_->Play(event.GetClosure());
- event.RunAndWait();
- }
-
- void Preroll(int timestamp_ms, PipelineStatus expected) {
- SCOPED_TRACE(base::StringPrintf("Preroll(%d, %d)", timestamp_ms, expected));
- WaitableMessageLoopEvent event;
- renderer_->Preroll(
- base::TimeDelta::FromMilliseconds(timestamp_ms),
- event.GetPipelineStatusCB());
- event.RunAndWaitForStatus(expected);
- }
-
- void Pause() {
- SCOPED_TRACE("Pause()");
- WaitableMessageLoopEvent event;
- renderer_->Pause(event.GetClosure());
- event.RunAndWait();
- }
-
- void Flush() {
- SCOPED_TRACE("Flush()");
- WaitableMessageLoopEvent event;
- renderer_->Flush(event.GetClosure());
- event.RunAndWait();
- }
-
- void Stop() {
- SCOPED_TRACE("Stop()");
- WaitableMessageLoopEvent event;
- renderer_->Stop(event.GetClosure());
- event.RunAndWait();
- }
-
- void Shutdown() {
- Pause();
- Flush();
- Stop();
- }
-
- // Queues a VideoFrame with |next_frame_timestamp_|.
- void QueueNextFrame() {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- DCHECK_LT(next_frame_timestamp_.InMicroseconds(),
- duration_.InMicroseconds());
-
- scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
- VideoFrame::RGB32, kNaturalSize, gfx::Rect(kNaturalSize), kNaturalSize,
- next_frame_timestamp_);
- decode_results_.push_back(std::make_pair(
- VideoDecoder::kOk, frame));
- next_frame_timestamp_ +=
- base::TimeDelta::FromMilliseconds(kFrameDurationInMs);
- }
-
- void QueueEndOfStream() {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- decode_results_.push_back(std::make_pair(
- VideoDecoder::kOk, VideoFrame::CreateEmptyFrame()));
- }
-
- void QueueDecodeError() {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- scoped_refptr<VideoFrame> null_frame;
- decode_results_.push_back(std::make_pair(
- VideoDecoder::kDecodeError, null_frame));
- }
-
- void QueueAbortedRead() {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- scoped_refptr<VideoFrame> null_frame;
- decode_results_.push_back(std::make_pair(
- VideoDecoder::kOk, null_frame));
- }
-
- void QueuePrerollFrames(int timestamp_ms) {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- next_frame_timestamp_ = base::TimeDelta();
- base::TimeDelta timestamp = base::TimeDelta::FromMilliseconds(timestamp_ms);
- while (next_frame_timestamp_ < timestamp) {
- QueueNextFrame();
- }
-
- // Queue the frame at |timestamp| plus additional ones for prerolling.
- for (int i = 0; i < limits::kMaxVideoFrames; ++i) {
- QueueNextFrame();
- }
- }
-
- scoped_refptr<VideoFrame> GetCurrentFrame() {
- scoped_refptr<VideoFrame> frame;
- renderer_->GetCurrentFrame(&frame);
- renderer_->PutCurrentFrame(frame);
- return frame;
- }
-
- int GetCurrentTimestampInMs() {
- scoped_refptr<VideoFrame> frame = GetCurrentFrame();
- if (!frame)
- return -1;
- return frame->GetTimestamp().InMilliseconds();
- }
-
- void WaitForError(PipelineStatus expected) {
- SCOPED_TRACE(base::StringPrintf("WaitForError(%d)", expected));
- error_event_.RunAndWaitForStatus(expected);
- }
-
- void WaitForEnded() {
- SCOPED_TRACE("WaitForEnded()");
- ended_event_.RunAndWait();
- }
-
- void WaitForPendingRead() {
- SCOPED_TRACE("WaitForPendingRead()");
- if (!read_cb_.is_null())
- return;
-
- DCHECK(pending_read_cb_.is_null());
-
- WaitableMessageLoopEvent event;
- pending_read_cb_ = event.GetClosure();
- event.RunAndWait();
-
- DCHECK(!read_cb_.is_null());
- DCHECK(pending_read_cb_.is_null());
- }
-
- void SatisfyPendingRead() {
- CHECK(!read_cb_.is_null());
- CHECK(!decode_results_.empty());
-
- base::Closure closure = base::Bind(
- read_cb_, decode_results_.front().first,
- decode_results_.front().second);
-
- read_cb_.Reset();
- decode_results_.pop_front();
-
- message_loop_.PostTask(FROM_HERE, closure);
- }
-
- void AdvanceTimeInMs(int time_ms) {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- base::AutoLock l(lock_);
- time_ += base::TimeDelta::FromMilliseconds(time_ms);
- DCHECK_LE(time_.InMicroseconds(), duration_.InMicroseconds());
- }
-
- protected:
- // Fixture members.
- scoped_refptr<VideoRendererBase> renderer_;
- scoped_refptr<MockVideoDecoder> decoder_;
- scoped_refptr<MockDemuxerStream> demuxer_stream_;
- MockStatisticsCB statistics_cb_object_;
-
- private:
- base::TimeDelta GetTime() {
- base::AutoLock l(lock_);
- return time_;
- }
-
- base::TimeDelta GetDuration() {
- return duration_;
- }
-
- void FrameRequested(const VideoDecoder::ReadCB& read_cb) {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- CHECK(read_cb_.is_null());
- read_cb_ = read_cb;
-
- // Wake up WaitForPendingRead() if needed.
- if (!pending_read_cb_.is_null())
- base::ResetAndReturn(&pending_read_cb_).Run();
-
- if (decode_results_.empty())
- return;
-
- SatisfyPendingRead();
- }
-
- void FlushRequested(const base::Closure& callback) {
- DCHECK_EQ(&message_loop_, MessageLoop::current());
- decode_results_.clear();
- if (!read_cb_.is_null()) {
- QueueAbortedRead();
- SatisfyPendingRead();
- }
-
- message_loop_.PostTask(FROM_HERE, callback);
- }
-
- MessageLoop message_loop_;
-
- VideoDecoderConfig video_config_;
-
- // Used to protect |time_|.
- base::Lock lock_;
- base::TimeDelta time_;
-
- // Used for satisfying reads.
- VideoDecoder::ReadCB read_cb_;
- base::TimeDelta next_frame_timestamp_;
- base::TimeDelta duration_;
-
- WaitableMessageLoopEvent error_event_;
- WaitableMessageLoopEvent ended_event_;
- base::Closure pending_read_cb_;
-
- std::deque<std::pair<
- VideoDecoder::Status, scoped_refptr<VideoFrame> > > decode_results_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest);
-};
-
-TEST_F(VideoRendererBaseTest, DoNothing) {
- // Test that creation and deletion doesn't depend on calls to Initialize()
- // and/or Stop().
-}
-
-TEST_F(VideoRendererBaseTest, StopWithoutInitialize) {
- Stop();
-}
-
-TEST_F(VideoRendererBaseTest, Initialize) {
- Initialize();
- EXPECT_EQ(0, GetCurrentTimestampInMs());
- Shutdown();
-}
-
-static void ExpectNotCalled(PipelineStatus) {
- base::debug::StackTrace stack;
- ADD_FAILURE() << "Expected callback not to be called\n" << stack.ToString();
-}
-
-TEST_F(VideoRendererBaseTest, StopWhileInitializing) {
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(PIPELINE_OK));
- CallInitialize(base::Bind(&ExpectNotCalled));
- Stop();
-
- // ~VideoRendererBase() will CHECK() if we left anything initialized.
-}
-
-TEST_F(VideoRendererBaseTest, StopWhileFlushing) {
- Initialize();
- Pause();
- renderer_->Flush(base::Bind(&ExpectNotCalled, PIPELINE_OK));
- Stop();
-
- // ~VideoRendererBase() will CHECK() if we left anything initialized.
-}
-
-TEST_F(VideoRendererBaseTest, Play) {
- Initialize();
- Play();
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, EndOfStream_DefaultFrameDuration) {
- Initialize();
- Play();
-
- // Verify that the ended callback fires when the default last frame duration
- // has elapsed.
- int end_timestamp = kFrameDurationInMs * limits::kMaxVideoFrames +
- VideoRendererBase::kMaxLastFrameDuration().InMilliseconds();
- EXPECT_LT(end_timestamp, kVideoDurationInMs);
-
- QueueEndOfStream();
- AdvanceTimeInMs(end_timestamp);
- WaitForEnded();
-
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, EndOfStream_ClipDuration) {
- int duration = kVideoDurationInMs + kFrameDurationInMs / 2;
- InitializeWithDuration(duration);
- Play();
-
- // Render all frames except for the last |limits::kMaxVideoFrames| frames
- // and deliver all the frames between the start and |duration|. The preroll
- // inside Initialize() makes this a little confusing, but |timestamp| is
- // the current render time and QueueNextFrame() delivers a frame with a
- // timestamp that is |timestamp| + limits::kMaxVideoFrames *
- // kFrameDurationInMs.
- int timestamp = kFrameDurationInMs;
- int end_timestamp = duration - limits::kMaxVideoFrames * kFrameDurationInMs;
- for (; timestamp < end_timestamp; timestamp += kFrameDurationInMs) {
- QueueNextFrame();
- }
-
- // Queue the end of stream frame and wait for the last frame to be rendered.
- QueueEndOfStream();
- AdvanceTimeInMs(duration);
- WaitForEnded();
-
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, DecodeError_Playing) {
- Initialize();
- Play();
-
- QueueDecodeError();
- AdvanceTimeInMs(kVideoDurationInMs);
- WaitForError(PIPELINE_ERROR_DECODE);
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, DecodeError_DuringPreroll) {
- Initialize();
- Pause();
- Flush();
-
- QueueDecodeError();
- Preroll(kFrameDurationInMs * 6, PIPELINE_ERROR_DECODE);
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, Preroll_Exact) {
- Initialize();
- Pause();
- Flush();
- QueuePrerollFrames(kFrameDurationInMs * 6);
-
- Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
- EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, Preroll_RightBefore) {
- Initialize();
- Pause();
- Flush();
- QueuePrerollFrames(kFrameDurationInMs * 6);
-
- Preroll(kFrameDurationInMs * 6 - 1, PIPELINE_OK);
- EXPECT_EQ(kFrameDurationInMs * 5, GetCurrentTimestampInMs());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, Preroll_RightAfter) {
- Initialize();
- Pause();
- Flush();
- QueuePrerollFrames(kFrameDurationInMs * 6);
-
- Preroll(kFrameDurationInMs * 6 + 1, PIPELINE_OK);
- EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Initialized) {
- Initialize();
- EXPECT_TRUE(GetCurrentFrame()); // Due to prerolling.
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Playing) {
- Initialize();
- Play();
- EXPECT_TRUE(GetCurrentFrame());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Paused) {
- Initialize();
- Play();
- Pause();
- EXPECT_TRUE(GetCurrentFrame());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Flushed) {
- Initialize();
- Play();
- Pause();
- Flush();
- EXPECT_FALSE(GetCurrentFrame());
- Shutdown();
-}
-
-#if defined(OS_MACOSX) || defined(ADDRESS_SANITIZER)
-// http://crbug.com/109405
-#define MAYBE_GetCurrentFrame_EndOfStream DISABLED_GetCurrentFrame_EndOfStream
-#else
-#define MAYBE_GetCurrentFrame_EndOfStream GetCurrentFrame_EndOfStream
-#endif
-TEST_F(VideoRendererBaseTest, MAYBE_GetCurrentFrame_EndOfStream) {
- Initialize();
- Play();
- Pause();
- Flush();
-
- // Preroll only end of stream frames.
- QueueEndOfStream();
- Preroll(0, PIPELINE_OK);
- EXPECT_FALSE(GetCurrentFrame());
-
- // Start playing, we should immediately get notified of end of stream.
- Play();
- WaitForEnded();
-
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Shutdown) {
- Initialize();
- Shutdown();
- EXPECT_FALSE(GetCurrentFrame());
-}
-
-// Stop() is called immediately during an error.
-TEST_F(VideoRendererBaseTest, GetCurrentFrame_Error) {
- Initialize();
- Stop();
- EXPECT_FALSE(GetCurrentFrame());
-}
-
-// Verify that shutdown can only proceed after we return the current frame.
-TEST_F(VideoRendererBaseTest, Shutdown_DuringPaint) {
- Initialize();
- Play();
-
- // Grab the frame.
- scoped_refptr<VideoFrame> frame;
- renderer_->GetCurrentFrame(&frame);
- EXPECT_TRUE(frame);
-
- Pause();
-
- // Start flushing -- it won't complete until we return the frame.
- WaitableMessageLoopEvent event;
- renderer_->Flush(event.GetClosure());
-
- // Return the frame and wait.
- renderer_->PutCurrentFrame(frame);
- event.RunAndWait();
-
- Stop();
-}
-
-// Verify that a late decoder response doesn't break invariants in the renderer.
-TEST_F(VideoRendererBaseTest, StopDuringOutstandingRead) {
- Initialize();
- Play();
-
- // Advance time a bit to trigger a Read().
- AdvanceTimeInMs(kFrameDurationInMs);
- WaitForPendingRead();
-
- WaitableMessageLoopEvent event;
- renderer_->Stop(event.GetClosure());
-
- QueueEndOfStream();
- SatisfyPendingRead();
-
- event.RunAndWait();
-}
-
-TEST_F(VideoRendererBaseTest, AbortPendingRead_Playing) {
- Initialize();
- Play();
-
- // Advance time a bit to trigger a Read().
- AdvanceTimeInMs(kFrameDurationInMs);
- WaitForPendingRead();
-
- QueueAbortedRead();
- SatisfyPendingRead();
-
- Pause();
- Flush();
- QueuePrerollFrames(kFrameDurationInMs * 6);
- Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
- EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs());
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, AbortPendingRead_Flush) {
- Initialize();
- Play();
-
- // Advance time a bit to trigger a Read().
- AdvanceTimeInMs(kFrameDurationInMs);
- WaitForPendingRead();
-
- Pause();
- Flush();
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, AbortPendingRead_Preroll) {
- Initialize();
- Pause();
- Flush();
-
- QueueAbortedRead();
- Preroll(kFrameDurationInMs * 6, PIPELINE_OK);
- Shutdown();
-}
-
-TEST_F(VideoRendererBaseTest, VideoDecoder_InitFailure) {
- InSequence s;
-
- EXPECT_CALL(*decoder_, Initialize(_, _, _))
- .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED));
- InitializeRenderer(DECODER_ERROR_NOT_SUPPORTED);
-
- Stop();
-}
-
-} // namespace media
diff --git a/src/media/media.gyp b/src/media/media.gyp
deleted file mode 100644
index 7aff606..0000000
--- a/src/media/media.gyp
+++ /dev/null
@@ -1,1571 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-{
- 'variables': {
- 'chromium_code': 1,
- # Override to dynamically link the PulseAudio library.
- 'use_pulseaudio%': 0,
- # Override to dynamically link the cras (ChromeOS audio) library.
- 'use_cras%': 0,
- 'conditions': [
- ['OS == "android" or OS == "ios" or OS == "lb_shell" or OS == "starboard"', {
- # Android and iOS don't use ffmpeg.
- 'use_ffmpeg%': 0,
- }, { # 'OS != "android" and OS != "ios"' and OS != "lb_shell" and OS != "starboard"
- 'use_ffmpeg%': 1,
- }],
- ],
- },
- 'targets': [
- {
- 'target_name': 'media',
- 'type': '<(component)',
- 'dependencies': [
- '../base/base.gyp:base',
- '../ui/ui.gyp:ui',
- ],
- 'defines': [
- 'MEDIA_IMPLEMENTATION',
- ],
- 'include_dirs': [
- '..',
- ],
- 'sources': [
- 'audio/android/audio_manager_android.cc',
- 'audio/android/audio_manager_android.h',
- 'audio/android/opensles_input.cc',
- 'audio/android/opensles_input.h',
- 'audio/android/opensles_output.cc',
- 'audio/android/opensles_output.h',
- 'audio/async_socket_io_handler.h',
- 'audio/async_socket_io_handler_posix.cc',
- 'audio/async_socket_io_handler_win.cc',
- 'audio/audio_buffers_state.cc',
- 'audio/audio_buffers_state.h',
- 'audio/audio_device_name.cc',
- 'audio/audio_device_name.h',
- 'audio/audio_device_thread.cc',
- 'audio/audio_device_thread.h',
- 'audio/audio_input_controller.cc',
- 'audio/audio_input_controller.h',
- 'audio/audio_input_device.cc',
- 'audio/audio_input_device.h',
- 'audio/audio_input_ipc.cc',
- 'audio/audio_input_ipc.h',
- # TODO(dalecurtis): Temporarily disabled while switching pipeline to use
- # float, http://crbug.com/114700
- # 'audio/audio_output_mixer.cc',
- # 'audio/audio_output_mixer.h',
- 'audio/audio_input_stream_impl.cc',
- 'audio/audio_input_stream_impl.h',
- 'audio/audio_io.h',
- 'audio/audio_manager.cc',
- 'audio/audio_manager.h',
- 'audio/audio_manager_base.cc',
- 'audio/audio_manager_base.h',
- 'audio/audio_output_controller.cc',
- 'audio/audio_output_controller.h',
- 'audio/audio_output_device.cc',
- 'audio/audio_output_device.h',
- 'audio/audio_output_dispatcher.cc',
- 'audio/audio_output_dispatcher.h',
- 'audio/audio_output_dispatcher_impl.cc',
- 'audio/audio_output_dispatcher_impl.h',
- 'audio/audio_output_ipc.cc',
- 'audio/audio_output_ipc.h',
- 'audio/audio_output_proxy.cc',
- 'audio/audio_output_proxy.h',
- 'audio/audio_output_resampler.cc',
- 'audio/audio_output_resampler.h',
- 'audio/audio_util.cc',
- 'audio/audio_util.h',
- 'audio/cross_process_notification.cc',
- 'audio/cross_process_notification.h',
- 'audio/cross_process_notification_posix.cc',
- 'audio/cross_process_notification_win.cc',
- 'audio/fake_audio_input_stream.cc',
- 'audio/fake_audio_input_stream.h',
- 'audio/fake_audio_output_stream.cc',
- 'audio/fake_audio_output_stream.h',
- 'audio/ios/audio_manager_ios.h',
- 'audio/ios/audio_manager_ios.mm',
- 'audio/linux/alsa_input.cc',
- 'audio/linux/alsa_input.h',
- 'audio/linux/alsa_output.cc',
- 'audio/linux/alsa_output.h',
- 'audio/linux/alsa_util.cc',
- 'audio/linux/alsa_util.h',
- 'audio/linux/alsa_wrapper.cc',
- 'audio/linux/alsa_wrapper.h',
- 'audio/linux/audio_manager_linux.cc',
- 'audio/linux/audio_manager_linux.h',
- 'audio/linux/cras_input.cc',
- 'audio/linux/cras_input.h',
- 'audio/linux/cras_output.cc',
- 'audio/linux/cras_output.h',
- 'audio/mac/audio_input_mac.cc',
- 'audio/mac/audio_input_mac.h',
- 'audio/mac/audio_low_latency_input_mac.cc',
- 'audio/mac/audio_low_latency_input_mac.h',
- 'audio/mac/audio_low_latency_output_mac.cc',
- 'audio/mac/audio_low_latency_output_mac.h',
- 'audio/mac/audio_manager_mac.cc',
- 'audio/mac/audio_manager_mac.h',
- 'audio/mac/audio_output_mac.cc',
- 'audio/mac/audio_output_mac.h',
- 'audio/mac/audio_synchronized_mac.cc',
- 'audio/mac/audio_synchronized_mac.h',
- 'audio/mac/audio_unified_mac.cc',
- 'audio/mac/audio_unified_mac.h',
- 'audio/null_audio_sink.cc',
- 'audio/null_audio_sink.h',
- 'audio/null_audio_streamer.cc',
- 'audio/null_audio_streamer.h',
- 'audio/openbsd/audio_manager_openbsd.cc',
- 'audio/openbsd/audio_manager_openbsd.h',
- 'audio/pulse/pulse_output.cc',
- 'audio/pulse/pulse_output.h',
- 'audio/sample_rates.cc',
- 'audio/sample_rates.h',
- 'audio/scoped_loop_observer.cc',
- 'audio/scoped_loop_observer.h',
- 'audio/shell_wav_test_probe.cc',
- 'audio/shell_wav_test_probe.h',
- 'audio/shell_audio_sink.cc',
- 'audio/shell_audio_sink.h',
- 'audio/simple_sources.cc',
- 'audio/simple_sources.h',
- 'audio/virtual_audio_input_stream.cc',
- 'audio/virtual_audio_input_stream.h',
- 'audio/virtual_audio_output_stream.cc',
- 'audio/virtual_audio_output_stream.h',
- 'audio/win/audio_device_listener_win.cc',
- 'audio/win/audio_device_listener_win.h',
- 'audio/win/audio_low_latency_input_win.cc',
- 'audio/win/audio_low_latency_input_win.h',
- 'audio/win/audio_low_latency_output_win.cc',
- 'audio/win/audio_low_latency_output_win.h',
- 'audio/win/audio_manager_win.cc',
- 'audio/win/audio_manager_win.h',
- 'audio/win/audio_unified_win.cc',
- 'audio/win/audio_unified_win.h',
- 'audio/win/avrt_wrapper_win.cc',
- 'audio/win/avrt_wrapper_win.h',
- 'audio/win/device_enumeration_win.cc',
- 'audio/win/device_enumeration_win.h',
- 'audio/win/core_audio_util_win.cc',
- 'audio/win/core_audio_util_win.h',
- 'audio/win/wavein_input_win.cc',
- 'audio/win/wavein_input_win.h',
- 'audio/win/waveout_output_win.cc',
- 'audio/win/waveout_output_win.h',
- 'base/android/cookie_getter.cc',
- 'base/android/cookie_getter.h',
- 'base/android/media_player_bridge_manager.cc',
- 'base/android/media_player_bridge_manager.h',
- 'base/audio_capturer_source.h',
- 'base/audio_converter.cc',
- 'base/audio_converter.h',
- 'base/audio_decoder.cc',
- 'base/audio_decoder.h',
- 'base/audio_decoder_config.cc',
- 'base/audio_decoder_config.h',
- 'base/audio_fifo.cc',
- 'base/audio_fifo.h',
- 'base/audio_pull_fifo.cc',
- 'base/audio_pull_fifo.h',
- 'base/audio_renderer.cc',
- 'base/audio_renderer.h',
- 'base/audio_renderer_sink.h',
- 'base/audio_renderer_mixer.cc',
- 'base/audio_renderer_mixer.h',
- 'base/audio_renderer_mixer_input.cc',
- 'base/audio_renderer_mixer_input.h',
- 'base/audio_splicer.cc',
- 'base/audio_splicer.h',
- 'base/audio_timestamp_helper.cc',
- 'base/audio_timestamp_helper.h',
- 'base/bind_to_loop.h',
- 'base/bitstream_buffer.h',
- 'base/bit_reader.cc',
- 'base/bit_reader.h',
- 'base/buffers.cc',
- 'base/buffers.h',
- 'base/byte_queue.cc',
- 'base/byte_queue.h',
- 'base/channel_mixer.cc',
- 'base/channel_mixer.h',
- 'base/clock.cc',
- 'base/clock.h',
- 'base/color_space.cc',
- 'base/color_space.h',
- 'base/data_buffer.cc',
- 'base/data_buffer.h',
- 'base/data_source.cc',
- 'base/data_source.h',
- 'base/decoder_buffer.cc',
- 'base/decoder_buffer.h',
- 'base/decoder_buffer_pool.cc',
- 'base/decoder_buffer_pool.h',
- 'base/decryptor.cc',
- 'base/decryptor.h',
- 'base/decryptor_client.h',
- 'base/decrypt_config.cc',
- 'base/decrypt_config.h',
- 'base/demuxer.cc',
- 'base/demuxer.h',
- 'base/demuxer_stream.cc',
- 'base/demuxer_stream.h',
- 'base/djb2.cc',
- 'base/djb2.h',
- 'base/filter_collection.cc',
- 'base/filter_collection.h',
- 'base/gfx_export.h',
- 'base/hdr_metadata.cc',
- 'base/hdr_metadata.h',
- 'base/interleaved_sinc_resampler.cc',
- 'base/interleaved_sinc_resampler.h',
- 'base/media.h',
- 'base/media_log.cc',
- 'base/media_log.h',
- 'base/media_log_event.h',
- 'base/media_posix.cc',
- 'base/media_switches.cc',
- 'base/media_switches.h',
- 'base/media_win.cc',
- 'base/message_loop_factory.cc',
- 'base/message_loop_factory.h',
- 'base/multi_channel_resampler.cc',
- 'base/multi_channel_resampler.h',
- 'base/pipeline.h',
- 'base/pipeline_impl.cc',
- 'base/pipeline_impl.h',
- 'base/pipeline_status.cc',
- 'base/pipeline_status.h',
- 'base/ranges.cc',
- 'base/ranges.h',
- 'base/sample_format.cc',
- 'base/sample_format.h',
- 'base/seekable_buffer.cc',
- 'base/seekable_buffer.h',
- 'base/serial_runner.cc',
- 'base/serial_runner.h',
- 'base/shell_audio_bus.cc',
- 'base/shell_audio_bus.h',
- 'base/shell_buffer_factory.cc',
- 'base/shell_buffer_factory.h',
- 'base/shell_data_source_reader.cc',
- 'base/shell_data_source_reader.h',
- 'base/shell_media_platform.cc',
- 'base/shell_media_platform.h',
- 'base/shell_media_statistics.cc',
- 'base/shell_media_statistics.h',
- 'base/shell_video_data_allocator.cc',
- 'base/shell_video_data_allocator.h',
- 'base/shell_video_frame_provider.cc',
- 'base/shell_video_frame_provider.h',
- 'base/sinc_resampler.cc',
- 'base/sinc_resampler.h',
- 'base/stream_parser.cc',
- 'base/stream_parser.h',
- 'base/stream_parser_buffer.cc',
- 'base/stream_parser_buffer.h',
- 'base/vector_math.cc',
- 'base/vector_math.h',
- 'base/video_decoder.cc',
- 'base/video_decoder.h',
- 'base/video_decoder_config.cc',
- 'base/video_decoder_config.h',
- 'base/video_frame.cc',
- 'base/video_frame.h',
- 'base/video_renderer.cc',
- 'base/video_renderer.h',
- 'base/video_util.cc',
- 'base/video_util.h',
- 'crypto/aes_decryptor.cc',
- 'crypto/aes_decryptor.h',
- 'crypto/shell_decryptor_factory.cc',
- 'crypto/shell_decryptor_factory.h',
- 'ffmpeg/ffmpeg_common.cc',
- 'ffmpeg/ffmpeg_common.h',
- 'filters/audio_decoder_selector.cc',
- 'filters/audio_decoder_selector.h',
- 'filters/audio_file_reader.cc',
- 'filters/audio_file_reader.h',
- 'filters/audio_renderer_algorithm.cc',
- 'filters/audio_renderer_algorithm.h',
- 'filters/audio_renderer_impl.cc',
- 'filters/audio_renderer_impl.h',
- 'filters/blocking_url_protocol.cc',
- 'filters/blocking_url_protocol.h',
- 'filters/chunk_demuxer.cc',
- 'filters/chunk_demuxer.h',
- 'filters/decrypting_audio_decoder.cc',
- 'filters/decrypting_audio_decoder.h',
- 'filters/decrypting_demuxer_stream.cc',
- 'filters/decrypting_demuxer_stream.h',
- 'filters/decrypting_video_decoder.cc',
- 'filters/decrypting_video_decoder.h',
- 'filters/dummy_demuxer.cc',
- 'filters/dummy_demuxer.h',
- 'filters/ffmpeg_audio_decoder.cc',
- 'filters/ffmpeg_audio_decoder.h',
- 'filters/ffmpeg_demuxer.cc',
- 'filters/ffmpeg_demuxer.h',
- 'filters/ffmpeg_glue.cc',
- 'filters/ffmpeg_glue.h',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter.cc',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter.h',
- 'filters/ffmpeg_video_decoder.cc',
- 'filters/ffmpeg_video_decoder.h',
- 'filters/file_data_source.cc',
- 'filters/file_data_source.h',
- 'filters/gpu_video_decoder.cc',
- 'filters/gpu_video_decoder.h',
- 'filters/h264_to_annex_b_bitstream_converter.cc',
- 'filters/h264_to_annex_b_bitstream_converter.h',
- 'filters/in_memory_url_protocol.cc',
- 'filters/in_memory_url_protocol.h',
- 'filters/opus_audio_decoder.cc',
- 'filters/opus_audio_decoder.h',
- 'filters/skcanvas_video_renderer.cc',
- 'filters/skcanvas_video_renderer.h',
- 'filters/shell_au.cc',
- 'filters/shell_au.h',
- 'filters/shell_audio_decoder_impl.cc',
- 'filters/shell_audio_decoder_impl.h',
- 'filters/shell_audio_renderer.h',
- 'filters/shell_audio_renderer_impl.cc',
- 'filters/shell_audio_renderer_impl.h',
- 'filters/shell_avc_parser.cc',
- 'filters/shell_avc_parser.h',
- 'filters/shell_demuxer.cc',
- 'filters/shell_demuxer.h',
- 'filters/shell_flv_parser.cc',
- 'filters/shell_flv_parser.h',
- 'filters/shell_mp4_map.cc',
- 'filters/shell_mp4_map.h',
- 'filters/shell_mp4_parser.cc',
- 'filters/shell_mp4_parser.h',
- 'filters/shell_parser.cc',
- 'filters/shell_parser.h',
- 'filters/shell_raw_audio_decoder_stub.cc',
- 'filters/shell_raw_audio_decoder_stub.h',
- 'filters/shell_raw_video_decoder_stub.cc',
- 'filters/shell_raw_video_decoder_stub.h',
- 'filters/shell_rbsp_stream.cc',
- 'filters/shell_rbsp_stream.h',
- 'filters/shell_video_decoder_impl.cc',
- 'filters/shell_video_decoder_impl.h',
- 'filters/source_buffer_stream.cc',
- 'filters/source_buffer_stream.h',
- 'filters/video_decoder_selector.cc',
- 'filters/video_decoder_selector.h',
- 'filters/video_frame_generator.cc',
- 'filters/video_frame_generator.h',
- 'filters/video_renderer_base.cc',
- 'filters/video_renderer_base.h',
- 'mp4/aac.cc',
- 'mp4/aac.h',
- 'mp4/avc.cc',
- 'mp4/avc.h',
- 'mp4/box_definitions.cc',
- 'mp4/box_definitions.h',
- 'mp4/box_reader.cc',
- 'mp4/box_reader.h',
- 'mp4/cenc.cc',
- 'mp4/cenc.h',
- 'mp4/es_descriptor.cc',
- 'mp4/es_descriptor.h',
- 'mp4/mp4_stream_parser.cc',
- 'mp4/mp4_stream_parser.h',
- 'mp4/offset_byte_queue.cc',
- 'mp4/offset_byte_queue.h',
- 'mp4/track_run_iterator.cc',
- 'mp4/track_run_iterator.h',
- 'video/capture/fake_video_capture_device.cc',
- 'video/capture/fake_video_capture_device.h',
- 'video/capture/linux/video_capture_device_linux.cc',
- 'video/capture/linux/video_capture_device_linux.h',
- 'video/capture/mac/video_capture_device_mac.h',
- 'video/capture/mac/video_capture_device_mac.mm',
- 'video/capture/mac/video_capture_device_qtkit_mac.h',
- 'video/capture/mac/video_capture_device_qtkit_mac.mm',
- 'video/capture/video_capture.h',
- 'video/capture/video_capture_device.h',
- 'video/capture/video_capture_device_dummy.cc',
- 'video/capture/video_capture_device_dummy.h',
- 'video/capture/video_capture_proxy.cc',
- 'video/capture/video_capture_proxy.h',
- 'video/capture/video_capture_types.h',
- 'video/capture/win/capability_list_win.cc',
- 'video/capture/win/capability_list_win.h',
- 'video/capture/win/filter_base_win.cc',
- 'video/capture/win/filter_base_win.h',
- 'video/capture/win/pin_base_win.cc',
- 'video/capture/win/pin_base_win.h',
- 'video/capture/win/sink_filter_observer_win.h',
- 'video/capture/win/sink_filter_win.cc',
- 'video/capture/win/sink_filter_win.h',
- 'video/capture/win/sink_input_pin_win.cc',
- 'video/capture/win/sink_input_pin_win.h',
- 'video/capture/win/video_capture_device_mf_win.cc',
- 'video/capture/win/video_capture_device_mf_win.h',
- 'video/capture/win/video_capture_device_win.cc',
- 'video/capture/win/video_capture_device_win.h',
- 'video/picture.cc',
- 'video/picture.h',
- 'video/video_decode_accelerator.cc',
- 'video/video_decode_accelerator.h',
- 'webm/webm_audio_client.cc',
- 'webm/webm_audio_client.h',
- 'webm/webm_cluster_parser.cc',
- 'webm/webm_cluster_parser.h',
- 'webm/webm_colour_parser.cc',
- 'webm/webm_colour_parser.h',
- 'webm/webm_constants.h',
- 'webm/webm_content_encodings.cc',
- 'webm/webm_content_encodings.h',
- 'webm/webm_content_encodings_client.cc',
- 'webm/webm_content_encodings_client.h',
- 'webm/webm_info_parser.cc',
- 'webm/webm_info_parser.h',
- 'webm/webm_parser.cc',
- 'webm/webm_parser.h',
- 'webm/webm_stream_parser.cc',
- 'webm/webm_stream_parser.h',
- 'webm/webm_tracks_parser.cc',
- 'webm/webm_tracks_parser.h',
- 'webm/webm_video_client.cc',
- 'webm/webm_video_client.h',
- ],
- 'direct_dependent_settings': {
- 'include_dirs': [
- '..',
- ],
- },
- 'conditions': [
- ['arm_neon == 1', {
- 'defines': [
- 'USE_NEON'
- ],
- }],
- ['OS != "ios"', {
- 'dependencies': [
- '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
- 'shared_memory_support',
- ],
- }],
- ['OS != "ios" and cobalt != 1', {
- 'dependencies': [
- # This is only used in [x11|gl|skcanvas]_video_renderer.cc, but not
- # when compiling Cobalt. When linking as a shared library, the
- # unused optimized YUV functions are left as undefined symbols
- # after they get dead-stripped.
- 'yuv_convert',
- ],
- }],
- ['use_ffmpeg == 1', {
- 'dependencies': [
- '../third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
- ],
- }, { # use_ffmpeg == 0
- # Exclude the sources that depend on ffmpeg.
- 'sources!': [
- 'base/media_posix.cc',
- 'ffmpeg/ffmpeg_common.cc',
- 'ffmpeg/ffmpeg_common.h',
- 'filters/audio_file_reader.cc',
- 'filters/audio_file_reader.h',
- 'filters/chunk_demuxer.cc',
- 'filters/chunk_demuxer.h',
- 'filters/blocking_url_protocol.cc',
- 'filters/blocking_url_protocol.h',
- 'filters/ffmpeg_audio_decoder.cc',
- 'filters/ffmpeg_audio_decoder.h',
- 'filters/ffmpeg_demuxer.cc',
- 'filters/ffmpeg_demuxer.h',
- 'filters/ffmpeg_glue.cc',
- 'filters/ffmpeg_glue.h',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter.cc',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter.h',
- 'filters/ffmpeg_video_decoder.cc',
- 'filters/ffmpeg_video_decoder.h',
- ],
- }],
- ['OS == "lb_shell" or OS == "starboard"', {
- 'include_dirs': [
- '<(DEPTH)/../..', # for our explicit external/ inclusion of headers
- ],
- 'sources/': [
- ['exclude', 'android'],
- # media is not excluding windows sources automatically.
- # forcibly exclude them.
- ['exclude', 'win'],
- ['exclude', 'audio/audio_device_thread'],
- ['exclude', 'audio/audio_input_stream_impl'],
- ['exclude', 'audio/audio_io.h'],
- ['exclude', 'audio/audio_input_controller'],
- ['exclude', 'audio/audio_input_device'],
- ['exclude', 'audio/audio_manager'],
- ['exclude', 'audio/audio_manager_base'],
- ['exclude', 'audio/audio_output_controller'],
- ['exclude', 'audio/audio_output_device'],
- ['exclude', 'audio/audio_output_dispatcher'],
- ['exclude', 'audio/audio_output_dispatcher_impl'],
- ['exclude', 'audio/audio_output_ipc'],
- ['exclude', 'audio/audio_output_proxy'],
- ['exclude', 'audio/audio_output_resampler'],
- ['exclude', 'audio/cross_process_notification'],
- ['exclude', 'audio/cross_process_notification_posix'],
- ['exclude', 'audio/cross_process_notification_win'],
- ['exclude', 'audio/fake_audio_input_stream'],
- ['exclude', 'audio/fake_audio_output_stream'],
- ['exclude', 'audio/ios/audio_manager_ios'],
- ['exclude', 'audio/linux/alsa_input'],
- ['exclude', 'audio/linux/alsa_output'],
- ['exclude', 'audio/linux/alsa_util'],
- ['exclude', 'audio/linux/alsa_wrapper'],
- ['exclude', 'audio/linux/audio_manager_linux'],
- ['exclude', 'audio/linux/cras_input'],
- ['exclude', 'audio/linux/cras_output'],
- ['exclude', 'audio/mac/audio_input_mac'],
- ['exclude', 'audio/mac/audio_low_latency_input_mac'],
- ['exclude', 'audio/mac/audio_low_latency_output_mac'],
- ['exclude', 'audio/mac/audio_manager_mac'],
- ['exclude', 'audio/mac/audio_output_mac'],
- ['exclude', 'audio/mac/audio_synchronized_mac'],
- ['exclude', 'audio/mac/audio_unified_mac'],
- ['exclude', 'audio/openbsd/audio_manager_openbsd'],
- ['exclude', 'audio/pulse/pulse_output'],
- ['exclude', 'audio/scoped_loop_observer'],
- ['exclude', 'audio/simple_sources'],
- ['exclude', 'audio/virtual_audio_input_stream'],
- ['exclude', 'audio/virtual_audio_output_stream'],
- ['exclude', 'audio/win/audio_device_listener_win'],
- ['exclude', 'audio/win/audio_low_latency_input_win'],
- ['exclude', 'audio/win/audio_low_latency_output_win'],
- ['exclude', 'audio/win/audio_manager_win'],
- ['exclude', 'audio/win/audio_unified_win'],
- ['exclude', 'audio/win/avrt_wrapper_win'],
- ['exclude', 'audio/win/device_enumeration_win'],
- ['exclude', 'audio/win/core_audio_util_win'],
- ['exclude', 'audio/win/wavein_input_win'],
- ['exclude', 'audio/win/waveout_output_win'],
- # we use hardware mixers
- ['exclude', 'base/audio_converter'],
- ['exclude', 'base/audio_renderer_mixer'],
- ['exclude', 'base/channel_mixer'],
- # we use our own encryption
- ['exclude', 'crypto/aes'],
- # we use our own AudioRendererAlgorithm and Impl
- ['exclude', 'filters/audio_renderer'],
- # but re-use various other parts of the stack
- ['include', 'filters/chunk_demuxer'],
- ['exclude', 'filters/decrypting_audio_decoder'],
- ['exclude', 'filters/decrypting_video_decoder'],
- # we stream from network only
- ['exclude', 'filters/file_data_source'],
- # gpu-based decoding is interesting, perhaps explore further
- ['exclude', 'filters/gpu'],
- # avc/aac only (for now)
- ['exclude', 'filters/opus'],
- ['exclude', 'video/capture/fake_video_capture_device'],
- ],
- 'conditions': [
- ['target_arch=="linux"', {
- 'sources': [
- 'audio/shell_audio_streamer_linux.cc',
- 'audio/shell_audio_streamer_linux.h',
- 'audio/shell_pulse_audio.cc',
- 'audio/shell_pulse_audio.h',
- 'filters/shell_ffmpeg.cc',
- 'filters/shell_ffmpeg.h',
- 'filters/shell_raw_audio_decoder_linux.cc',
- 'filters/shell_raw_audio_decoder_linux.h',
- 'filters/shell_raw_video_decoder_linux.cc',
- 'filters/shell_raw_video_decoder_linux.h',
- ],
- }],
- ['target_arch=="ps3"', {
- 'sources': [
- 'base/shell_cached_decoder_buffer.cc',
- 'base/shell_cached_decoder_buffer.h',
- 'audio/shell_audio_streamer_ps3.cc',
- 'audio/shell_audio_streamer_ps3.h',
- 'audio/shell_audio_streamer_v2_ps3.cc',
- 'audio/shell_audio_streamer_v2_ps3.h',
- 'audio/shell_spurs_ps3.cc',
- 'audio/shell_spurs_ps3.h',
- 'filters/shell_audio_decoder_ps3.cc',
- 'filters/shell_audio_decoder_ps3.h',
- 'filters/shell_audio_renderer_ps3.cc',
- 'filters/shell_audio_renderer_ps3.h',
- 'filters/shell_audio_resampler_ps3.cc',
- 'filters/shell_audio_resampler_ps3.h',
- 'filters/shell_raw_audio_decoder_ps3.cc',
- 'filters/shell_raw_audio_decoder_ps3.h',
- 'filters/shell_raw_video_decoder_ps3.cc',
- 'filters/shell_raw_video_decoder_ps3.h',
- 'filters/shell_vdec_helper_ps3.cc',
- 'filters/shell_vdec_helper_ps3.h',
- ],
- 'sources/': [
- # PS3 has its own implementations.
- ['exclude', 'filters/shell_audio_renderer_impl'],
- ],
- 'dependencies' : [
- 'spurs_tasks',
- ],
- }],
- ['target_arch=="ps4"', {
- 'sources': [
- 'audio/shell_audio_streamer_ps4.cc',
- 'filters/shell_raw_audio_decoder_ps4.cc',
- 'filters/shell_raw_audio_decoder_ps4.h',
- 'filters/shell_raw_avc_decoder_ps4.cc',
- 'filters/shell_raw_avc_decoder_ps4.h',
- 'filters/shell_raw_video_decoder_ps4.cc',
- 'filters/shell_raw_video_decoder_ps4.h',
- 'filters/shell_raw_vp9_decoder_ps4.cc',
- 'filters/shell_raw_vp9_decoder_ps4.h',
- 'filters/videodec2_working_memory_ps4.cc',
- 'filters/videodec2_working_memory_ps4.h',
- ],
- 'dependencies' : [
- '<(DEPTH)/nb/nb.gyp:nb',
- '<(DEPTH)/third_party/libvpx_gpu/libvpx_gpu.gyp:libvpx_gpu',
- ],
- }],
- ['target_arch=="xb1" or target_arch=="xb360"', {
- 'sources/': [
- # These platforms have their own implementations.
- ['exclude', 'filters/shell_audio_decoder_impl'],
- ['exclude', 'filters/shell_audio_renderer_impl'],
- ['exclude', 'filters/shell_video_decoder_impl'],
- ]
- }],
- ['use_widevine==1', {
- 'sources': [
- 'crypto/shell_widevine_decryptor.cc',
- 'crypto/shell_widevine_decryptor.h',
- ],
- 'conditions': [
- ['cobalt==1', {
- 'include_dirs': [
- '../third_party/cdm',
- ],
- 'dependencies': [
- 'crypto/widevine_cobalt.gyp:wvcdm_static',
- ],
- }, { #cobalt!=1
- 'include_dirs': [
- '../../cdm',
- ],
- 'dependencies': [
- 'crypto/widevine_steel.gyp:wvcdm_static',
- ],
- }],
- ],
- }],
- ['cobalt==1', {
- 'dependencies': [
- '../googleurl/googleurl.gyp:googleurl',
- ],
- 'sources': [
- 'player/buffered_data_source.h',
- 'player/can_play_type.cc',
- 'player/can_play_type.h',
- 'player/crypto/key_systems.h',
- 'player/crypto/key_systems.cc',
- 'player/crypto/proxy_decryptor.cc',
- 'player/crypto/proxy_decryptor.h',
- 'player/mime_util.cc',
- 'player/mime_util.h',
- 'player/mime_util_certificate_type_list.h',
- 'player/web_media_player.h',
- 'player/web_media_player_delegate.h',
- 'player/web_media_player_impl.cc',
- 'player/web_media_player_impl.h',
- 'player/web_media_player_proxy.cc',
- 'player/web_media_player_proxy.h',
- ],
- 'sources!': [
- 'filters/decrypting_audio_decoder.cc',
- 'filters/decrypting_audio_decoder.h',
- 'filters/decrypting_video_decoder.cc',
- 'filters/decrypting_video_decoder.h',
- ],
- }],
- ],
- }, { # OS != "lb_shell" and OS != "starboard"
- 'dependencies': [
- 'yuv_convert',
- '../third_party/openmax/openmax.gyp:il',
- '../third_party/opus/opus.gyp:opus',
- ],
- 'sources/': [
- ['exclude', 'shell_'],
- ],
- }], # OS != "lb_shell" and OS != "starboard"
- ['OS == "starboard"', {
- 'dependencies': [
- '<(DEPTH)/starboard/starboard.gyp:starboard',
- ],
- 'sources': [
- 'base/shell_cached_decoder_buffer.cc',
- 'base/shell_cached_decoder_buffer.h',
- ],
- }], # OS == "starboard"
- ['OS == "ios"', {
- 'includes': [
- # For shared_memory_support_sources variable.
- 'shared_memory_support.gypi',
- ],
- 'sources': [
- 'base/media_stub.cc',
- # These sources are normally built via a dependency on the
- # shared_memory_support target, but that target is not built on iOS.
- # Instead, directly build only the files that are needed for iOS.
- '<@(shared_memory_support_sources)',
- ],
- 'sources/': [
- # iOS support is limited to audio input only.
- ['exclude', '.*'],
- ['include', '^audio/audio_buffers_state\\.'],
- ['include', '^audio/audio_input_controller\\.'],
- ['include', '^audio/audio_io\\.h$'],
- ['include', '^audio/audio_manager\\.'],
- ['include', '^audio/audio_manager_base\\.'],
- ['include', '^audio/audio_parameters\\.'],
- ['include', '^audio/fake_audio_input_stream\\.'],
- ['include', '^audio/fake_audio_output_stream\\.'],
- ['include', '^audio/ios/audio_manager_ios\\.'],
- ['include', '^base/audio_bus\\.'],
- ['include', '^base/channel_layout\\.'],
- ['include', '^base/media\\.h$'],
- ['include', '^base/media_stub\\.cc$'],
- ],
- 'link_settings': {
- 'libraries': [
- '$(SDKROOT)/System/Library/Frameworks/AudioToolbox.framework',
- '$(SDKROOT)/System/Library/Frameworks/AVFoundation.framework',
- '$(SDKROOT)/System/Library/Frameworks/CoreAudio.framework',
- ],
- },
- }],
- ['OS == "android"', {
- 'sources': [
- 'base/media_stub.cc',
- ],
- 'link_settings': {
- 'libraries': [
- '-lOpenSLES',
- ],
- },
- }],
- ['OS=="linux" or OS=="freebsd" or OS=="solaris"', {
- 'link_settings': {
- 'libraries': [
- '-lasound',
- ],
- },
- }],
- ['OS=="openbsd"', {
- 'sources/': [ ['exclude', '/alsa_' ],
- ['exclude', '/audio_manager_linux' ] ],
- 'link_settings': {
- 'libraries': [
- ],
- },
- }],
- ['OS!="openbsd"', {
- 'sources!': [
- 'audio/openbsd/audio_manager_openbsd.cc',
- 'audio/openbsd/audio_manager_openbsd.h',
- ],
- }],
- ['OS=="linux"', {
- 'variables': {
- 'conditions': [
- ['sysroot!=""', {
- 'pkg-config': '../build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)"',
- }, {
- 'pkg-config': 'pkg-config'
- }],
- ],
- },
- 'conditions': [
- ['use_cras == 1', {
- 'cflags': [
- '<!@(<(pkg-config) --cflags libcras)',
- ],
- 'link_settings': {
- 'libraries': [
- '<!@(<(pkg-config) --libs libcras)',
- ],
- },
- 'defines': [
- 'USE_CRAS',
- ],
- }, { # else: use_cras == 0
- 'sources!': [
- 'audio/linux/cras_input.cc',
- 'audio/linux/cras_input.h',
- 'audio/linux/cras_output.cc',
- 'audio/linux/cras_output.h',
- ],
- }],
- ],
- }],
- ['os_posix == 1', {
- 'conditions': [
- ['use_pulseaudio == 1', {
- 'cflags': [
- '<!@(pkg-config --cflags libpulse)',
- ],
- 'link_settings': {
- 'libraries': [
- '<!@(pkg-config --libs-only-l libpulse)',
- ],
- },
- 'defines': [
- 'USE_PULSEAUDIO',
- ],
- }, { # else: use_pulseaudio == 0
- 'sources!': [
- 'audio/pulse/pulse_output.cc',
- 'audio/pulse/pulse_output.h',
- ],
- }],
- ],
- }],
- ['os_posix == 1 and OS != "android"', {
- # Video capture isn't supported in Android yet.
- 'sources!': [
- 'video/capture/video_capture_device_dummy.cc',
- 'video/capture/video_capture_device_dummy.h',
- ],
- }],
- ['OS=="mac"', {
- 'link_settings': {
- 'libraries': [
- '$(SDKROOT)/System/Library/Frameworks/AudioUnit.framework',
- '$(SDKROOT)/System/Library/Frameworks/AudioToolbox.framework',
- '$(SDKROOT)/System/Library/Frameworks/CoreAudio.framework',
- '$(SDKROOT)/System/Library/Frameworks/CoreVideo.framework',
- '$(SDKROOT)/System/Library/Frameworks/QTKit.framework',
- ],
- },
- }],
- ['OS=="win"', {
- 'sources!': [
- 'audio/pulse/pulse_output.cc',
- 'audio/pulse/pulse_output.h',
- 'video/capture/video_capture_device_dummy.cc',
- 'video/capture/video_capture_device_dummy.h',
- ],
- 'link_settings': {
- 'libraries': [
- '-lmf.lib',
- '-lmfplat.lib',
- '-lmfreadwrite.lib',
- '-lmfuuid.lib',
- ],
- },
- # Specify delayload for media.dll.
- 'msvs_settings': {
- 'VCLinkerTool': {
- 'DelayLoadDLLs': [
- 'mf.dll',
- 'mfplat.dll',
- 'mfreadwrite.dll',
- ],
- },
- },
- # Specify delayload for components that link with media.lib.
- 'all_dependent_settings': {
- 'msvs_settings': {
- 'VCLinkerTool': {
- 'DelayLoadDLLs': [
- 'mf.dll',
- 'mfplat.dll',
- 'mfreadwrite.dll',
- ],
- },
- },
- },
- }],
- ],
- 'target_conditions': [
- ['OS == "ios"', {
- 'sources/': [
- # Pull in specific Mac files for iOS (which have been filtered out
- # by file name rules).
- ['include', '^audio/mac/audio_input_mac\\.'],
- ],
- }],
- ],
- },
- {
- 'target_name': 'media_unittests',
- 'type': '<(gtest_target_type)',
- 'dependencies': [
- 'media',
- 'media_test_support',
- '../base/base.gyp:base',
- '../base/base.gyp:base_i18n',
- '../base/base.gyp:test_support_base',
- '../testing/gmock.gyp:gmock',
- '../testing/gtest.gyp:gtest',
- '../ui/ui.gyp:ui',
- ],
- 'sources': [
- 'audio/async_socket_io_handler_unittest.cc',
- 'audio/audio_input_controller_unittest.cc',
- 'audio/audio_input_device_unittest.cc',
- 'audio/audio_input_unittest.cc',
- 'audio/audio_input_volume_unittest.cc',
- 'audio/audio_low_latency_input_output_unittest.cc',
- 'audio/audio_output_controller_unittest.cc',
- 'audio/audio_output_device_unittest.cc',
- 'audio/audio_output_proxy_unittest.cc',
- 'audio/audio_parameters_unittest.cc',
- 'audio/audio_util_unittest.cc',
- 'audio/cross_process_notification_unittest.cc',
- 'audio/fake_audio_output_stream_unittest.cc',
- 'audio/ios/audio_manager_ios_unittest.cc',
- 'audio/linux/alsa_output_unittest.cc',
- 'audio/mac/audio_low_latency_input_mac_unittest.cc',
- 'audio/mac/audio_output_mac_unittest.cc',
- 'audio/simple_sources_unittest.cc',
- 'audio/virtual_audio_input_stream_unittest.cc',
- 'audio/virtual_audio_output_stream_unittest.cc',
- 'audio/win/audio_device_listener_win_unittest.cc',
- 'audio/win/audio_low_latency_input_win_unittest.cc',
- 'audio/win/audio_low_latency_output_win_unittest.cc',
- 'audio/win/audio_output_win_unittest.cc',
- 'audio/win/audio_unified_win_unittest.cc',
- 'audio/win/core_audio_util_win_unittest.cc',
- 'base/audio_bus_unittest.cc',
- 'base/audio_converter_unittest.cc',
- 'base/audio_fifo_unittest.cc',
- 'base/audio_pull_fifo_unittest.cc',
- 'base/audio_renderer_mixer_input_unittest.cc',
- 'base/audio_renderer_mixer_unittest.cc',
- 'base/audio_splicer_unittest.cc',
- 'base/audio_timestamp_helper_unittest.cc',
- 'base/bit_reader_unittest.cc',
- 'base/bind_to_loop_unittest.cc',
- 'base/buffers_unittest.cc',
- 'base/channel_mixer_unittest.cc',
- 'base/clock_unittest.cc',
- 'base/data_buffer_unittest.cc',
- 'base/decoder_buffer_unittest.cc',
- 'base/djb2_unittest.cc',
- 'base/filter_collection_unittest.cc',
- 'base/gmock_callback_support_unittest.cc',
- 'base/interleaved_sinc_resampler_unittest.cc',
- 'base/multi_channel_resampler_unittest.cc',
- 'base/pipeline_impl_unittest.cc',
- 'base/ranges_unittest.cc',
- 'base/run_all_unittests.cc',
- 'base/seekable_buffer_unittest.cc',
- 'base/shell_audio_bus_test.cc',
- 'base/sinc_resampler_unittest.cc',
- 'base/test_data_util.cc',
- 'base/test_data_util.h',
- 'base/vector_math_testing.h',
- 'base/vector_math_unittest.cc',
- 'base/video_frame_unittest.cc',
- 'base/video_util_unittest.cc',
- 'base/yuv_convert_unittest.cc',
- 'crypto/aes_decryptor_unittest.cc',
- 'ffmpeg/ffmpeg_common_unittest.cc',
- 'filters/audio_decoder_selector_unittest.cc',
- 'filters/audio_file_reader_unittest.cc',
- 'filters/audio_renderer_algorithm_unittest.cc',
- 'filters/audio_renderer_impl_unittest.cc',
- 'filters/blocking_url_protocol_unittest.cc',
- 'filters/chunk_demuxer_unittest.cc',
- 'filters/decrypting_audio_decoder_unittest.cc',
- 'filters/decrypting_demuxer_stream_unittest.cc',
- 'filters/decrypting_video_decoder_unittest.cc',
- 'filters/ffmpeg_audio_decoder_unittest.cc',
- 'filters/ffmpeg_demuxer_unittest.cc',
- 'filters/ffmpeg_glue_unittest.cc',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc',
- 'filters/ffmpeg_video_decoder_unittest.cc',
- 'filters/file_data_source_unittest.cc',
- 'filters/h264_to_annex_b_bitstream_converter_unittest.cc',
- 'filters/pipeline_integration_test.cc',
- 'filters/pipeline_integration_test_base.cc',
- 'filters/skcanvas_video_renderer_unittest.cc',
- 'filters/source_buffer_stream_unittest.cc',
- 'filters/video_decoder_selector_unittest.cc',
- 'filters/video_renderer_base_unittest.cc',
- 'mp4/aac_unittest.cc',
- 'mp4/avc_unittest.cc',
- 'mp4/box_reader_unittest.cc',
- 'mp4/es_descriptor_unittest.cc',
- 'mp4/mp4_stream_parser_unittest.cc',
- 'mp4/offset_byte_queue_unittest.cc',
- 'mp4/track_run_iterator_unittest.cc',
- 'video/capture/video_capture_device_unittest.cc',
- 'webm/cluster_builder.cc',
- 'webm/cluster_builder.h',
- 'webm/webm_cluster_parser_unittest.cc',
- 'webm/webm_content_encodings_client_unittest.cc',
- 'webm/webm_parser_unittest.cc',
- ],
- 'conditions': [
- ['arm_neon == 1', {
- 'defines': [
- 'USE_NEON'
- ],
- }],
- ['OS != "ios"', {
- 'dependencies': [
- 'shared_memory_support',
- 'yuv_convert',
- ],
- }],
- ['use_ffmpeg == 1', {
- 'dependencies': [
- '../third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
- ],
- }],
- ['OS == "ios"', {
- 'sources/': [
- ['exclude', '.*'],
- ['include', '^audio/audio_input_controller_unittest\\.cc$'],
- ['include', '^audio/audio_input_unittest\\.cc$'],
- ['include', '^audio/audio_parameters_unittest\\.cc$'],
- ['include', '^audio/ios/audio_manager_ios_unittest\\.cc$'],
- ['include', '^base/mock_reader\\.h$'],
- ['include', '^base/run_all_unittests\\.cc$'],
- ],
- }],
- ['OS=="android" or OS=="lb_shell" or OS=="starboard"', {
- 'sources!': [
- 'audio/audio_input_volume_unittest.cc',
- 'base/test_data_util.cc',
- 'base/test_data_util.h',
- 'ffmpeg/ffmpeg_common_unittest.cc',
- 'filters/audio_file_reader_unittest.cc',
- 'filters/blocking_url_protocol_unittest.cc',
- 'filters/chunk_demuxer_unittest.cc',
- 'filters/ffmpeg_audio_decoder_unittest.cc',
- 'filters/ffmpeg_demuxer_unittest.cc',
- 'filters/ffmpeg_glue_unittest.cc',
- 'filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc',
- 'filters/ffmpeg_video_decoder_unittest.cc',
- 'filters/pipeline_integration_test.cc',
- 'filters/pipeline_integration_test_base.cc',
- 'mp4/mp4_stream_parser_unittest.cc',
- 'webm/webm_cluster_parser_unittest.cc',
- ],
- }],
- ['OS == "linux"', {
- 'conditions': [
- ['use_cras == 1', {
- 'sources': [
- 'audio/linux/cras_input_unittest.cc',
- 'audio/linux/cras_output_unittest.cc',
- ],
- 'defines': [
- 'USE_CRAS',
- ],
- }],
- ],
- }],
- [ 'target_arch=="ia32" or target_arch=="x64"', {
- 'sources': [
- 'base/simd/convert_rgb_to_yuv_unittest.cc',
- ],
- }],
- ['OS == "lb_shell" or OS=="starboard"', {
- 'sources': [
- 'audio/mock_shell_audio_streamer.h',
- 'audio/shell_audio_sink_unittest.cc',
- 'base/mock_shell_data_source_reader.h',
- 'base/shell_buffer_factory_unittest.cc',
- 'filters/shell_audio_renderer_unittest.cc',
- 'filters/shell_mp4_map_unittest.cc',
- 'filters/shell_rbsp_stream_unittest.cc',
- ],
- }],
- ],
- },
- {
- 'target_name': 'media_test_support',
- 'type': 'static_library',
- 'dependencies': [
- 'media',
- '../base/base.gyp:base',
- '../testing/gmock.gyp:gmock',
- '../testing/gtest.gyp:gtest',
- ],
- 'sources': [
- 'audio/mock_audio_manager.cc',
- 'audio/mock_audio_manager.h',
- 'audio/test_audio_input_controller_factory.cc',
- 'audio/test_audio_input_controller_factory.h',
- 'base/fake_audio_render_callback.cc',
- 'base/fake_audio_render_callback.h',
- 'base/gmock_callback_support.h',
- 'base/mock_audio_renderer_sink.cc',
- 'base/mock_audio_renderer_sink.h',
- 'base/mock_data_source_host.cc',
- 'base/mock_data_source_host.h',
- 'base/mock_demuxer_host.cc',
- 'base/mock_demuxer_host.h',
- 'base/mock_filters.cc',
- 'base/mock_filters.h',
- 'base/test_helpers.cc',
- 'base/test_helpers.h',
- ],
- },
- ],
- 'conditions': [
- ['OS != "ios"', {
- 'targets': [
- {
- # Minimal target for NaCl and other renderer side media clients which
- # only need to send audio data across the shared memory to the browser
- # process.
- 'target_name': 'shared_memory_support',
- 'type': '<(component)',
- 'dependencies': [
- '../base/base.gyp:base',
- ],
- 'defines': [
- 'MEDIA_IMPLEMENTATION',
- ],
- 'include_dirs': [
- '..',
- ],
- 'includes': [
- 'shared_memory_support.gypi',
- ],
- 'sources': [
- '<@(shared_memory_support_sources)',
- ],
- 'conditions': [
- ['OS == "lb_shell"', {
- 'type': 'static_library',
- }],
- ],
- },
- {
- 'target_name': 'yuv_convert',
- 'type': 'static_library',
- 'include_dirs': [
- '..',
- ],
- 'conditions': [
- [ 'target_arch == "ia32" or target_arch == "x64"', {
- 'dependencies': [
- 'yuv_convert_simd_x86',
- ],
- }],
- [ 'target_arch == "arm" or target_arch == "mipsel"', {
- 'dependencies': [
- 'yuv_convert_simd_c',
- ],
- }],
- ],
- 'sources': [
- 'base/yuv_convert.cc',
- 'base/yuv_convert.h',
- ],
- },
- {
- 'target_name': 'yuv_convert_simd_x86',
- 'type': 'static_library',
- 'include_dirs': [
- '..',
- ],
- 'sources': [
- 'base/simd/convert_rgb_to_yuv_c.cc',
- 'base/simd/convert_rgb_to_yuv_sse2.cc',
- 'base/simd/convert_rgb_to_yuv_ssse3.asm',
- 'base/simd/convert_rgb_to_yuv_ssse3.cc',
- 'base/simd/convert_rgb_to_yuv_ssse3.inc',
- 'base/simd/convert_yuv_to_rgb_c.cc',
- 'base/simd/convert_yuv_to_rgb_mmx.asm',
- 'base/simd/convert_yuv_to_rgb_mmx.inc',
- 'base/simd/convert_yuv_to_rgb_sse.asm',
- 'base/simd/convert_yuv_to_rgb_x86.cc',
- 'base/simd/filter_yuv.h',
- 'base/simd/filter_yuv_c.cc',
- 'base/simd/filter_yuv_mmx.cc',
- 'base/simd/filter_yuv_sse2.cc',
- 'base/simd/linear_scale_yuv_to_rgb_mmx.asm',
- 'base/simd/linear_scale_yuv_to_rgb_mmx.inc',
- 'base/simd/linear_scale_yuv_to_rgb_sse.asm',
- 'base/simd/scale_yuv_to_rgb_mmx.asm',
- 'base/simd/scale_yuv_to_rgb_mmx.inc',
- 'base/simd/scale_yuv_to_rgb_sse.asm',
- 'base/simd/yuv_to_rgb_table.cc',
- 'base/simd/yuv_to_rgb_table.h',
- ],
- 'conditions': [
- [ 'target_arch == "x64"', {
- # Source files optimized for X64 systems.
- 'sources': [
- 'base/simd/linear_scale_yuv_to_rgb_mmx_x64.asm',
- 'base/simd/scale_yuv_to_rgb_sse2_x64.asm',
- ],
- }],
- [ 'os_posix == 1 and OS != "mac" and OS != "android"', {
- 'cflags': [
- '-msse2',
- ],
- }],
- [ 'OS == "mac"', {
- 'configurations': {
- 'Debug': {
- 'xcode_settings': {
- # gcc on the mac builds horribly unoptimized sse code in
- # debug mode. Since this is rarely going to be debugged,
- # run with full optimizations in Debug as well as Release.
- 'GCC_OPTIMIZATION_LEVEL': '3', # -O3
- },
- },
- },
- }],
- [ 'OS=="win"', {
- 'variables': {
- 'yasm_flags': [
- '-DWIN32',
- '-DMSVC',
- '-DCHROMIUM',
- '-Isimd',
- ],
- },
- }],
- [ 'OS=="mac"', {
- 'variables': {
- 'conditions': [
- [ 'target_arch=="ia32"', {
- 'yasm_flags': [
- '-DPREFIX',
- '-DMACHO',
- '-DCHROMIUM',
- '-Isimd',
- ],
- }, {
- 'yasm_flags': [
- '-DPREFIX',
- '-DARCH_X86_64',
- '-DMACHO',
- '-DCHROMIUM',
- '-Isimd',
- ],
- }],
- ],
- },
- }],
- [ 'os_posix==1 and OS!="mac"', {
- 'variables': {
- 'conditions': [
- [ 'target_arch=="ia32"', {
- 'yasm_flags': [
- '-DX86_32',
- '-DELF',
- '-DCHROMIUM',
- '-Isimd',
- ],
- }, {
- 'yasm_flags': [
- '-DARCH_X86_64',
- '-DELF',
- '-DPIC',
- '-DCHROMIUM',
- '-Isimd',
- ],
- }],
- ],
- },
- }],
- ],
- 'variables': {
- 'yasm_output_path': '<(SHARED_INTERMEDIATE_DIR)/media',
- },
- 'msvs_2010_disable_uldi_when_referenced': 1,
- # Comment this out so we no longer depends on yasm_compile.gypi.
- # Choose to comment instead of removing so we wouldn't get lost if we
- # ever want to get it back.
- # 'includes': [
- # '../third_party/yasm/yasm_compile.gypi',
- # ],
- },
- {
- 'target_name': 'yuv_convert_simd_c',
- 'type': 'static_library',
- 'include_dirs': [
- '..',
- ],
- 'sources': [
- 'base/simd/convert_rgb_to_yuv.h',
- 'base/simd/convert_rgb_to_yuv_c.cc',
- 'base/simd/convert_yuv_to_rgb.h',
- 'base/simd/convert_yuv_to_rgb_c.cc',
- 'base/simd/filter_yuv.h',
- 'base/simd/filter_yuv_c.cc',
- 'base/simd/yuv_to_rgb_table.cc',
- 'base/simd/yuv_to_rgb_table.h',
- ],
- },
- {
- 'target_name': 'qt_faststart',
- 'type': 'executable',
- 'sources': [
- 'tools/qt_faststart/qt_faststart.c'
- ],
- },
- {
- 'target_name': 'seek_tester',
- 'type': 'executable',
- 'dependencies': [
- 'media',
- '../base/base.gyp:base',
- ],
- 'sources': [
- 'tools/seek_tester/seek_tester.cc',
- ],
- },
- {
- 'target_name': 'demuxer_bench',
- 'type': 'executable',
- 'dependencies': [
- 'media',
- '../base/base.gyp:base',
- ],
- 'sources': [
- 'tools/demuxer_bench/demuxer_bench.cc',
- ],
- },
- ],
- }],
- ['OS == "linux" and target_arch != "arm" and target_arch != "mipsel"', {
- 'targets': [
- {
- 'target_name': 'tile_render_bench',
- 'type': 'executable',
- 'dependencies': [
- '../base/base.gyp:base',
- '../ui/gl/gl.gyp:gl',
- ],
- 'libraries': [
- '-lGL',
- '-ldl',
- ],
- 'sources': [
- 'tools/tile_render_bench/tile_render_bench.cc',
- ],
- },
- ],
- }],
- ['(OS == "lb_shell" or OS == "starboard") and target_arch == "ps3"', {
- 'targets': [
- {
- # this target builds SPU task objects into a PPU-linkable static library
- # that is then linked with the main executable.
- #
- # HORRIBLENESS WARNING:
- # the python script in the rule action contains compiler and linker
- # flags and determines build configuration based on the presence of
- # the string 'Debug' in the output directory. I'm very sorry.
- # TODO: refactor script to take more defines from the command line..
- 'target_name': 'spurs_tasks',
- 'type': 'static_library',
- 'sources': [
- '<(INTERMEDIATE_DIR)/sinc_resampler.o',
- ],
- 'actions': [
- {
- 'action_name' : 'spurs_tasks_builder',
- 'variables': {
- 'spu_source': [
- 'audio/spurs/tasks/sinc_resampler.c',
- ],
- 'spu_includes': [
- 'audio/spurs/tasks/spu_log.h',
- ],
- },
- 'inputs' : [
- '<@(spu_source)',
- '<@(spu_includes)',
- ],
- 'outputs' : [
- '<(INTERMEDIATE_DIR)/sinc_resampler.o',
- ],
- 'action': [
- 'python',
- 'audio/spurs/scripts/spu_task_to_ppu_obj.py',
- '<(INTERMEDIATE_DIR)',
- '<(SHARED_INTERMEDIATE_DIR)/spu',
- '<@(spu_source)',
- ],
- 'msvs_cygwin_shell' : 1,
- },
- ],
- 'dependencies': [
- 'sinc_resampler_table'
- ],
- },
- {
- 'target_name': 'sinc_resampler_table',
- 'type': 'none',
- 'hard_dependency': 1,
- 'actions': [
- {
- 'action_name': 'generate_sinc_resampler_table',
- 'inputs': [
- 'audio/spurs/scripts/build_sinc_table.py',
- ],
- 'outputs': [
- # please keep this list to just this one file
- '<(SHARED_INTERMEDIATE_DIR)/spu/sinc_table.c',
- ],
- 'action': ['python', 'audio/spurs/scripts/build_sinc_table.py', '<@(_outputs)'],
- 'message': 'generating sinc resampler table in <@(_outputs)',
- 'msvs_cygwin_shell' : 1,
- },
- ],
- },
- ],
- }],
- ['os_posix == 1 and OS != "mac" and OS != "android" and OS != "lb_shell"', {
- 'targets': [
- {
- 'target_name': 'player_x11',
- 'type': 'executable',
- 'dependencies': [
- 'media',
- 'yuv_convert',
- '../base/base.gyp:base',
- '../ui/gl/gl.gyp:gl',
- '../ui/ui.gyp:ui',
- ],
- 'link_settings': {
- 'libraries': [
- '-ldl',
- '-lX11',
- '-lXrender',
- '-lXext',
- ],
- },
- 'sources': [
- 'tools/player_x11/data_source_logger.cc',
- 'tools/player_x11/data_source_logger.h',
- 'tools/player_x11/gl_video_renderer.cc',
- 'tools/player_x11/gl_video_renderer.h',
- 'tools/player_x11/player_x11.cc',
- 'tools/player_x11/x11_video_renderer.cc',
- 'tools/player_x11/x11_video_renderer.h',
- ],
- },
- ],
- }],
- # Special target to wrap a gtest_target_type==shared_library
- # media_unittests into an android apk for execution.
- ['OS == "android" and gtest_target_type == "shared_library"', {
- 'targets': [
- {
- 'target_name': 'media_unittests_apk',
- 'type': 'none',
- 'dependencies': [
- 'media_java',
- 'media_unittests',
- ],
- 'variables': {
- 'test_suite_name': 'media_unittests',
- 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)media_unittests<(SHARED_LIB_SUFFIX)',
- },
- 'includes': [ '../build/apk_test.gypi' ],
- },
- ],
- }],
- ['OS == "android"', {
- 'targets': [
- {
- 'target_name': 'media_player_jni_headers',
- 'type': 'none',
- 'variables': {
- 'jni_gen_dir': 'media',
- 'input_java_class': 'android/media/MediaPlayer.class',
- 'input_jar_file': '<(android_sdk)/android.jar',
- },
- 'includes': [ '../build/jar_file_jni_generator.gypi' ],
- },
- {
- 'target_name': 'player_android_jni_headers',
- 'type': 'none',
- 'dependencies': [
- 'media_player_jni_headers',
- ],
- 'sources': [
- 'base/android/java/src/org/chromium/media/AudioFocusBridge.java',
- 'base/android/java/src/org/chromium/media/AudioTrackBridge.java',
- 'base/android/java/src/org/chromium/media/MediaCodecBridge.java',
- 'base/android/java/src/org/chromium/media/MediaDrmBridge.java',
- 'base/android/java/src/org/chromium/media/MediaPlayerBridge.java',
- 'base/android/java/src/org/chromium/media/MediaPlayerListener.java',
- ],
- 'variables': {
- 'jni_gen_dir': 'media',
- },
- 'includes': [ '../build/jni_generator.gypi' ],
- },
- {
- 'target_name': 'player_android',
- 'type': 'static_library',
- 'sources': [
- 'base/android/audio_focus_bridge.cc',
- 'base/android/audio_focus_bridge.h',
- 'base/android/audio_track_bridge.cc',
- 'base/android/audio_track_bridge.h',
- 'base/android/media_codec_bridge.cc',
- 'base/android/media_codec_bridge.h',
- 'base/android/media_drm_bridge.cc',
- 'base/android/media_drm_bridge.h',
- 'base/android/media_jni_registrar.cc',
- 'base/android/media_jni_registrar.h',
- 'base/android/media_player_bridge.cc',
- 'base/android/media_player_bridge.h',
- 'base/android/media_player_listener.cc',
- 'base/android/media_player_listener.h',
- ],
- 'dependencies': [
- '../base/base.gyp:base',
- 'player_android_jni_headers',
- ],
- 'include_dirs': [
- '<(SHARED_INTERMEDIATE_DIR)/media',
- ],
- },
- {
- 'target_name': 'media_java',
- 'type': 'none',
- 'dependencies': [
- '../base/base.gyp:base',
- ],
- 'export_dependent_settings': [
- '../base/base.gyp:base',
- ],
- 'variables': {
- 'package_name': 'media',
- 'java_in_dir': 'base/android/java',
- },
- 'includes': [ '../build/java.gypi' ],
- },
-
- ],
- }],
- ],
-}
diff --git a/src/media/media_untrusted.gyp b/src/media/media_untrusted.gyp
deleted file mode 100644
index b29f7d1..0000000
--- a/src/media/media_untrusted.gyp
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-{
- 'variables': {
- 'chromium_code': 1,
- },
- 'includes': [
- '../native_client/build/untrusted.gypi',
- ],
- 'conditions': [
- ['disable_nacl==0', {
- 'targets': [
- {
- 'target_name': 'shared_memory_support_untrusted',
- 'type': 'none',
- 'variables': {
- 'nacl_untrusted_build': 1,
- 'nlib_target': 'libshared_memory_support_untrusted.a',
- 'build_glibc': 0,
- 'build_newlib': 1,
- },
- 'dependencies': [
- '../native_client/tools.gyp:prep_toolchain',
- '../base/base_untrusted.gyp:base_untrusted',
- ],
- 'defines': [
- 'MEDIA_IMPLEMENTATION',
- ],
- 'include_dirs': [
- '..',
- ],
- 'includes': [
- 'shared_memory_support.gypi',
- ],
- 'sources': [
- '<@(shared_memory_support_sources)',
- ],
- },
- ],
- }],
- ],
-}
diff --git a/src/media/mp4/aac.cc b/src/media/mp4/aac.cc
deleted file mode 100644
index 6079428..0000000
--- a/src/media/mp4/aac.cc
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/aac.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "media/base/bit_reader.h"
-#include "media/mp4/rcheck.h"
-
-// The following conversion table is extracted from ISO 14496 Part 3 -
-// Table 1.16 - Sampling Frequency Index.
-static const int kFrequencyMap[] = {
- 96000, 88200, 64000, 48000, 44100, 32000, 24000,
- 22050, 16000, 12000, 11025, 8000, 7350
-};
-
-namespace media {
-
-static ChannelLayout GetChannelLayout(uint8 channel_config) {
- switch (channel_config) {
- case 1:
- return CHANNEL_LAYOUT_MONO;
- case 2:
- return CHANNEL_LAYOUT_STEREO;
- case 3:
- return CHANNEL_LAYOUT_SURROUND;
- case 4:
- return CHANNEL_LAYOUT_4_0;
- case 5:
- return CHANNEL_LAYOUT_5_0;
- case 6:
- return CHANNEL_LAYOUT_5_1;
- case 8:
- return CHANNEL_LAYOUT_7_1;
- default:
- break;
- }
-
- return CHANNEL_LAYOUT_UNSUPPORTED;
-}
-
-namespace mp4 {
-
-AAC::AAC()
- : profile_(0), frequency_index_(0), channel_config_(0), frequency_(0),
- extension_frequency_(0), channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED) {
-}
-
-AAC::~AAC() {
-}
-
-bool AAC::Parse(const std::vector<uint8>& data) {
- if (data.empty())
- return false;
-
- raw_data_ = data;
-
- BitReader reader(&data[0], data.size());
- uint8 extension_type = 0;
- bool ps_present = false;
- uint8 extension_frequency_index = 0xff;
-
- frequency_ = 0;
- extension_frequency_ = 0;
-
- // The following code is written according to ISO 14496 Part 3 Table 1.13 -
- // Syntax of AudioSpecificConfig.
-
- // Read base configuration
- RCHECK(reader.ReadBits(5, &profile_));
- RCHECK(reader.ReadBits(4, &frequency_index_));
- if (frequency_index_ == 0xf)
- RCHECK(reader.ReadBits(24, &frequency_));
- RCHECK(reader.ReadBits(4, &channel_config_));
-
- // Read extension configuration.
- if (profile_ == 5 || profile_ == 29) {
- ps_present = (profile_ == 29);
- extension_type = 5;
- RCHECK(reader.ReadBits(4, &extension_frequency_index));
- if (extension_frequency_index == 0xf)
- RCHECK(reader.ReadBits(24, &extension_frequency_));
- RCHECK(reader.ReadBits(5, &profile_));
- }
-
- RCHECK(SkipDecoderGASpecificConfig(&reader));
- RCHECK(SkipErrorSpecificConfig());
-
- // Read extension configuration again
- if (extension_type != 5) {
- uint16 sync_extension_type;
- uint8 sbr_present_flag;
- uint8 ps_present_flag;
-
- if (reader.ReadBits(11, &sync_extension_type) &&
- sync_extension_type == 0x2b7) {
- if (reader.ReadBits(5, &extension_type) && extension_type == 5) {
- RCHECK(reader.ReadBits(1, &sbr_present_flag));
-
- if (sbr_present_flag) {
- RCHECK(reader.ReadBits(4, &extension_frequency_index));
-
- if (extension_frequency_index == 0xf)
- RCHECK(reader.ReadBits(24, &extension_frequency_));
-
- RCHECK(reader.ReadBits(11, &sync_extension_type));
-
- if (sync_extension_type == 0x548) {
- RCHECK(reader.ReadBits(1, &ps_present_flag));
- ps_present = ps_present_flag != 0;
- }
- }
- }
- }
- }
-
- if (frequency_ == 0) {
- RCHECK(frequency_index_ < arraysize(kFrequencyMap));
- frequency_ = kFrequencyMap[frequency_index_];
- }
-
- if (extension_frequency_ == 0 && extension_frequency_index != 0xff) {
- RCHECK(extension_frequency_index < arraysize(kFrequencyMap));
- extension_frequency_ = kFrequencyMap[extension_frequency_index];
- }
-
- // When Parametric Stereo is on, mono will be played as stereo.
- if (ps_present && channel_config_ == 1)
- channel_layout_ = GetChannelLayout(2);
- else
- channel_layout_ = GetChannelLayout(channel_config_);
-
- return frequency_ != 0 && channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED &&
- profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf &&
- channel_config_ <= 7;
-}
-
-int AAC::GetOutputSamplesPerSecond(bool sbr_in_mimetype) const {
- if (extension_frequency_ > 0)
- return extension_frequency_;
-
- if (!sbr_in_mimetype)
- return frequency_;
-
- // The following code is written according to ISO 14496 Part 3 Table 1.11 and
- // Table 1.22. (Table 1.11 refers to the capping to 48000, Table 1.22 refers
- // to SBR doubling the AAC sample rate.)
- // TODO(acolwell) : Extend sample rate cap to 96kHz for Level 5 content.
- DCHECK_GT(frequency_, 0);
- return std::min(2 * frequency_, 48000);
-}
-
-ChannelLayout AAC::channel_layout() const {
- return channel_layout_;
-}
-
-bool AAC::ConvertEsdsToADTS(std::vector<uint8>* buffer) const {
- size_t size = buffer->size() + kADTSHeaderSize;
-
- DCHECK(profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf &&
- channel_config_ <= 7);
-
- // ADTS header uses 13 bits for packet size.
- if (size >= (1 << 13))
- return false;
-
- std::vector<uint8>& adts = *buffer;
-
- adts.insert(buffer->begin(), kADTSHeaderSize, 0);
- adts[0] = 0xff;
- adts[1] = 0xf1;
- adts[2] = ((profile_ - 1) << 6) + (frequency_index_ << 2) +
- (channel_config_ >> 2);
- adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11);
- adts[4] = (size & 0x7ff) >> 3;
- adts[5] = ((size & 7) << 5) + 0x1f;
- adts[6] = 0xfc;
-
- return true;
-}
-
-// Currently this function only support GASpecificConfig defined in
-// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig()
-bool AAC::SkipDecoderGASpecificConfig(BitReader* bit_reader) const {
- switch (profile_) {
- case 1:
- case 2:
- case 3:
- case 4:
- case 6:
- case 7:
- case 17:
- case 19:
- case 20:
- case 21:
- case 22:
- case 23:
- return SkipGASpecificConfig(bit_reader);
- default:
- break;
- }
-
- return false;
-}
-
-bool AAC::SkipErrorSpecificConfig() const {
- switch (profile_) {
- case 17:
- case 19:
- case 20:
- case 21:
- case 22:
- case 23:
- case 24:
- case 25:
- case 26:
- case 27:
- return false;
- default:
- break;
- }
-
- return true;
-}
-
-// The following code is written according to ISO 14496 part 3 Table 4.1 -
-// GASpecificConfig.
-bool AAC::SkipGASpecificConfig(BitReader* bit_reader) const {
- uint8 extension_flag = 0;
- uint8 depends_on_core_coder;
- uint16 dummy;
-
- RCHECK(bit_reader->ReadBits(1, &dummy)); // frameLengthFlag
- RCHECK(bit_reader->ReadBits(1, &depends_on_core_coder));
- if (depends_on_core_coder == 1)
- RCHECK(bit_reader->ReadBits(14, &dummy)); // coreCoderDelay
-
- RCHECK(bit_reader->ReadBits(1, &extension_flag));
- RCHECK(channel_config_ != 0);
-
- if (profile_ == 6 || profile_ == 20)
- RCHECK(bit_reader->ReadBits(3, &dummy)); // layerNr
-
- if (extension_flag) {
- if (profile_ == 22) {
- RCHECK(bit_reader->ReadBits(5, &dummy)); // numOfSubFrame
- RCHECK(bit_reader->ReadBits(11, &dummy)); // layer_length
- }
-
- if (profile_ == 17 || profile_ == 19 || profile_ == 20 || profile_ == 23) {
- RCHECK(bit_reader->ReadBits(3, &dummy)); // resilience flags
- }
-
- RCHECK(bit_reader->ReadBits(1, &dummy)); // extensionFlag3
- }
-
- return true;
-}
-
-} // namespace mp4
-
-} // namespace media
diff --git a/src/media/mp4/aac.h b/src/media/mp4/aac.h
deleted file mode 100644
index 0a61e38..0000000
--- a/src/media/mp4/aac.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_AAC_H_
-#define MEDIA_MP4_AAC_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class BitReader;
-
-namespace mp4 {
-
-// This class parses the AAC information from decoder specific information
-// embedded in the esds box in an ISO BMFF file.
-// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig
-// for more details.
-class MEDIA_EXPORT AAC {
- public:
- // Size in bytes of the ADTS header added by ConvertEsdsToADTS().
- static const size_t kADTSHeaderSize = 7;
- static const size_t kFramesPerAccessUnit = 1024;
-
- AAC();
- ~AAC();
-
- // Parse the AAC config from the raw binary data embedded in esds box.
- // The function will parse the data and get the ElementaryStreamDescriptor,
- // then it will parse the ElementaryStreamDescriptor to get audio stream
- // configurations.
- bool Parse(const std::vector<uint8>& data);
-
- // Gets the output sample rate for the AAC stream.
- // |sbr_in_mimetype| should be set to true if the SBR mode is
- // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter).
- // Returns the samples_per_second value that should used in an
- // AudioDecoderConfig.
- int GetOutputSamplesPerSecond(bool sbr_in_mimetype) const;
- ChannelLayout channel_layout() const;
-
- // This function converts a raw AAC frame into an AAC frame with an ADTS
- // header. On success, the function returns true and stores the converted data
- // in the buffer. The function returns false on failure and leaves the buffer
- // unchanged.
- bool ConvertEsdsToADTS(std::vector<uint8>* buffer) const;
-
- const std::vector<uint8>& raw_data() const { return raw_data_; }
-
- private:
- bool SkipDecoderGASpecificConfig(BitReader* bit_reader) const;
- bool SkipErrorSpecificConfig() const;
- bool SkipGASpecificConfig(BitReader* bit_reader) const;
-
- // The following variables store the AAC specific configuration information
- // that are used to generate the ADTS header.
- uint8 profile_;
- uint8 frequency_index_;
- uint8 channel_config_;
-
- // The following variables store audio configuration information that
- // can be used by Chromium. They are based on the AAC specific
- // configuration but can be overridden by extensions in elementary
- // stream descriptor.
- int frequency_;
- int extension_frequency_;
- ChannelLayout channel_layout_;
-
- std::vector<uint8> raw_data_;
-};
-
-} // namespace mp4
-
-} // namespace media
-
-#endif // MEDIA_MP4_AAC_H_
diff --git a/src/media/mp4/aac_unittest.cc b/src/media/mp4/aac_unittest.cc
deleted file mode 100644
index c9f888c..0000000
--- a/src/media/mp4/aac_unittest.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/aac.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-namespace mp4 {
-
-TEST(AACTest, BasicProfileTest) {
- AAC aac;
- uint8 buffer[] = {0x12, 0x10};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(aac.Parse(data));
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 44100);
- EXPECT_EQ(aac.channel_layout(), CHANNEL_LAYOUT_STEREO);
-}
-
-TEST(AACTest, ExtensionTest) {
- AAC aac;
- uint8 buffer[] = {0x13, 0x08, 0x56, 0xe5, 0x9d, 0x48, 0x80};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(aac.Parse(data));
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 48000);
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(true), 48000);
- EXPECT_EQ(aac.channel_layout(), CHANNEL_LAYOUT_STEREO);
-}
-
-TEST(AACTest, TestImplicitSBR) {
- AAC aac;
- uint8 buffer[] = {0x13, 0x10};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(aac.Parse(data));
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 24000);
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(true), 48000);
- EXPECT_EQ(aac.channel_layout(), CHANNEL_LAYOUT_STEREO);
-}
-
-TEST(AACTest, SixChannelTest) {
- AAC aac;
- uint8 buffer[] = {0x11, 0xb0};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(aac.Parse(data));
- EXPECT_EQ(aac.GetOutputSamplesPerSecond(false), 48000);
- EXPECT_EQ(aac.channel_layout(), CHANNEL_LAYOUT_5_1);
-}
-
-TEST(AACTest, DataTooShortTest) {
- AAC aac;
- std::vector<uint8> data;
-
- EXPECT_FALSE(aac.Parse(data));
-
- data.push_back(0x12);
- EXPECT_FALSE(aac.Parse(data));
-}
-
-TEST(AACTest, IncorrectProfileTest) {
- AAC aac;
- uint8 buffer[] = {0x0, 0x08};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_FALSE(aac.Parse(data));
-
- data[0] = 0x08;
- EXPECT_TRUE(aac.Parse(data));
-
- data[0] = 0x28;
- EXPECT_FALSE(aac.Parse(data));
-}
-
-TEST(AACTest, IncorrectFrequencyTest) {
- AAC aac;
- uint8 buffer[] = {0x0f, 0x88};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_FALSE(aac.Parse(data));
-
- data[0] = 0x0e;
- data[1] = 0x08;
- EXPECT_TRUE(aac.Parse(data));
-}
-
-TEST(AACTest, IncorrectChannelTest) {
- AAC aac;
- uint8 buffer[] = {0x0e, 0x00};
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_FALSE(aac.Parse(data));
-
- data[1] = 0x08;
- EXPECT_TRUE(aac.Parse(data));
-}
-
-} // namespace mp4
-
-} // namespace media
diff --git a/src/media/mp4/avc.cc b/src/media/mp4/avc.cc
deleted file mode 100644
index ae28ffd..0000000
--- a/src/media/mp4/avc.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/avc.h"
-
-#include <algorithm>
-#include <vector>
-
-#include "media/mp4/box_definitions.h"
-#include "media/mp4/box_reader.h"
-
-namespace media {
-namespace mp4 {
-
-static const uint8 kAnnexBStartCode[] = {0, 0, 0, 1};
-static const int kAnnexBStartCodeSize = 4;
-
-static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector<uint8>* buf) {
- const int kLengthSize = 4;
- size_t pos = 0;
- while (pos + kLengthSize < buf->size()) {
- int nal_size = (*buf)[pos];
- nal_size = (nal_size << 8) + (*buf)[pos+1];
- nal_size = (nal_size << 8) + (*buf)[pos+2];
- nal_size = (nal_size << 8) + (*buf)[pos+3];
- std::copy(kAnnexBStartCode, kAnnexBStartCode + kAnnexBStartCodeSize,
- buf->begin() + pos);
- pos += kLengthSize + nal_size;
- }
- return pos == buf->size();
-}
-
-// static
-bool AVC::ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer) {
- RCHECK(length_size == 1 || length_size == 2 || length_size == 4);
-
- if (length_size == 4)
- return ConvertAVCToAnnexBInPlaceForLengthSize4(buffer);
-
- std::vector<uint8> temp;
- temp.swap(*buffer);
- buffer->reserve(temp.size() + 32);
-
- size_t pos = 0;
- while (pos + length_size < temp.size()) {
- int nal_size = temp[pos];
- if (length_size == 2) nal_size = (nal_size << 8) + temp[pos+1];
- pos += length_size;
-
- RCHECK(pos + nal_size <= temp.size());
- buffer->insert(buffer->end(), kAnnexBStartCode,
- kAnnexBStartCode + kAnnexBStartCodeSize);
- buffer->insert(buffer->end(), temp.begin() + pos,
- temp.begin() + pos + nal_size);
- pos += nal_size;
- }
- return pos == temp.size();
-}
-
-// static
-bool AVC::ConvertConfigToAnnexB(
- const AVCDecoderConfigurationRecord& avc_config,
- std::vector<uint8>* buffer) {
- DCHECK(buffer->empty());
- buffer->clear();
- int total_size = 0;
- for (size_t i = 0; i < avc_config.sps_list.size(); i++)
- total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize;
- for (size_t i = 0; i < avc_config.pps_list.size(); i++)
- total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize;
- buffer->reserve(total_size);
-
- for (size_t i = 0; i < avc_config.sps_list.size(); i++) {
- buffer->insert(buffer->end(), kAnnexBStartCode,
- kAnnexBStartCode + kAnnexBStartCodeSize);
- buffer->insert(buffer->end(), avc_config.sps_list[i].begin(),
- avc_config.sps_list[i].end());
- }
-
- for (size_t i = 0; i < avc_config.pps_list.size(); i++) {
- buffer->insert(buffer->end(), kAnnexBStartCode,
- kAnnexBStartCode + kAnnexBStartCodeSize);
- buffer->insert(buffer->end(), avc_config.pps_list[i].begin(),
- avc_config.pps_list[i].end());
- }
- return true;
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/avc.h b/src/media/mp4/avc.h
deleted file mode 100644
index 3d815a1..0000000
--- a/src/media/mp4/avc.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_AVC_H_
-#define MEDIA_MP4_AVC_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-namespace media {
-namespace mp4 {
-
-struct AVCDecoderConfigurationRecord;
-
-class MEDIA_EXPORT AVC {
- public:
- static bool ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer);
-
- static bool ConvertConfigToAnnexB(
- const AVCDecoderConfigurationRecord& avc_config,
- std::vector<uint8>* buffer);
-};
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_AVC_H_
diff --git a/src/media/mp4/avc_unittest.cc b/src/media/mp4/avc_unittest.cc
deleted file mode 100644
index 766a979..0000000
--- a/src/media/mp4/avc_unittest.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string.h>
-
-#include "base/basictypes.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/mp4/avc.h"
-#include "media/mp4/box_definitions.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/gtest/include/gtest/gtest-param-test.h"
-
-namespace media {
-namespace mp4 {
-
-static const uint8 kNALU1[] = { 0x01, 0x02, 0x03 };
-static const uint8 kNALU2[] = { 0x04, 0x05, 0x06, 0x07 };
-static const uint8 kExpected[] = {
- 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03,
- 0x00, 0x00, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07 };
-
-static const uint8 kExpectedParamSets[] = {
- 0x00, 0x00, 0x00, 0x01, 0x67, 0x12,
- 0x00, 0x00, 0x00, 0x01, 0x67, 0x34,
- 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78};
-
-class AVCConversionTest : public testing::TestWithParam<int> {
- protected:
- void MakeInputForLength(int length_size, std::vector<uint8>* buf) {
- buf->clear();
- for (int i = 1; i < length_size; i++)
- buf->push_back(0);
- buf->push_back(sizeof(kNALU1));
- buf->insert(buf->end(), kNALU1, kNALU1 + sizeof(kNALU1));
-
- for (int i = 1; i < length_size; i++)
- buf->push_back(0);
- buf->push_back(sizeof(kNALU2));
- buf->insert(buf->end(), kNALU2, kNALU2 + sizeof(kNALU2));
- }
-};
-
-TEST_P(AVCConversionTest, ParseCorrectly) {
- std::vector<uint8> buf;
- MakeInputForLength(GetParam(), &buf);
- EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
- EXPECT_EQ(buf.size(), sizeof(kExpected));
- EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected)));
-}
-
-TEST_P(AVCConversionTest, ParsePartial) {
- std::vector<uint8> buf;
- MakeInputForLength(GetParam(), &buf);
- buf.pop_back();
- EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
- // This tests a buffer ending in the middle of a NAL length. For length size
- // of one, this can't happen, so we skip that case.
- if (GetParam() != 1) {
- MakeInputForLength(GetParam(), &buf);
- buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end());
- EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
- }
-}
-
-TEST_P(AVCConversionTest, ParseEmpty) {
- std::vector<uint8> buf;
- EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
- EXPECT_EQ(0u, buf.size());
-}
-
-INSTANTIATE_TEST_CASE_P(AVCConversionTestValues,
- AVCConversionTest,
- ::testing::Values(1, 2, 4));
-
-TEST_F(AVCConversionTest, ConvertConfigToAnnexB) {
- AVCDecoderConfigurationRecord avc_config;
- avc_config.sps_list.resize(2);
- avc_config.sps_list[0].push_back(0x67);
- avc_config.sps_list[0].push_back(0x12);
- avc_config.sps_list[1].push_back(0x67);
- avc_config.sps_list[1].push_back(0x34);
- avc_config.pps_list.resize(1);
- avc_config.pps_list[0].push_back(0x68);
- avc_config.pps_list[0].push_back(0x56);
- avc_config.pps_list[0].push_back(0x78);
-
- std::vector<uint8> buf;
- EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf));
- EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0],
- sizeof(kExpectedParamSets)));
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/box_definitions.cc b/src/media/mp4/box_definitions.cc
deleted file mode 100644
index 6c645be..0000000
--- a/src/media/mp4/box_definitions.cc
+++ /dev/null
@@ -1,756 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/box_definitions.h"
-
-#include "base/logging.h"
-#include "media/mp4/es_descriptor.h"
-#include "media/mp4/rcheck.h"
-
-namespace media {
-namespace mp4 {
-
-FileType::FileType() {}
-FileType::~FileType() {}
-FourCC FileType::BoxType() const { return FOURCC_FTYP; }
-
-bool FileType::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFourCC(&major_brand) && reader->Read4(&minor_version));
- size_t num_brands = (reader->size() - reader->pos()) / sizeof(FourCC);
- return reader->SkipBytes(sizeof(FourCC) * num_brands); // compatible_brands
-}
-
-ProtectionSystemSpecificHeader::ProtectionSystemSpecificHeader() {}
-ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {}
-FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; }
-
-bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) {
- // Validate the box's contents and hang on to the system ID.
- uint32 size;
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->ReadVec(&system_id, 16) &&
- reader->Read4(&size) &&
- reader->HasBytes(size));
-
- // Copy the entire box, including the header, for passing to EME as initData.
- DCHECK(raw_box.empty());
- raw_box.assign(reader->data(), reader->data() + reader->size());
- return true;
-}
-
-SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {}
-SampleAuxiliaryInformationOffset::~SampleAuxiliaryInformationOffset() {}
-FourCC SampleAuxiliaryInformationOffset::BoxType() const { return FOURCC_SAIO; }
-
-bool SampleAuxiliaryInformationOffset::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
- if (reader->flags() & 1)
- RCHECK(reader->SkipBytes(8));
-
- uint32 count;
- RCHECK(reader->Read4(&count) &&
- reader->HasBytes(count * (reader->version() == 1 ? 8 : 4)));
- offsets.resize(count);
-
- for (uint32 i = 0; i < count; i++) {
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&offsets[i]));
- } else {
- RCHECK(reader->Read4Into8(&offsets[i]));
- }
- }
- return true;
-}
-
-SampleAuxiliaryInformationSize::SampleAuxiliaryInformationSize()
- : default_sample_info_size(0), sample_count(0) {
-}
-SampleAuxiliaryInformationSize::~SampleAuxiliaryInformationSize() {}
-FourCC SampleAuxiliaryInformationSize::BoxType() const { return FOURCC_SAIZ; }
-
-bool SampleAuxiliaryInformationSize::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
- if (reader->flags() & 1)
- RCHECK(reader->SkipBytes(8));
-
- RCHECK(reader->Read1(&default_sample_info_size) &&
- reader->Read4(&sample_count));
- if (default_sample_info_size == 0)
- return reader->ReadVec(&sample_info_sizes, sample_count);
- return true;
-}
-
-OriginalFormat::OriginalFormat() : format(FOURCC_NULL) {}
-OriginalFormat::~OriginalFormat() {}
-FourCC OriginalFormat::BoxType() const { return FOURCC_FRMA; }
-
-bool OriginalFormat::Parse(BoxReader* reader) {
- return reader->ReadFourCC(&format);
-}
-
-SchemeType::SchemeType() : type(FOURCC_NULL), version(0) {}
-SchemeType::~SchemeType() {}
-FourCC SchemeType::BoxType() const { return FOURCC_SCHM; }
-
-bool SchemeType::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->ReadFourCC(&type) &&
- reader->Read4(&version));
- return true;
-}
-
-TrackEncryption::TrackEncryption()
- : is_encrypted(false), default_iv_size(0) {
-}
-TrackEncryption::~TrackEncryption() {}
-FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; }
-
-bool TrackEncryption::Parse(BoxReader* reader) {
- uint8 flag;
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->SkipBytes(2) &&
- reader->Read1(&flag) &&
- reader->Read1(&default_iv_size) &&
- reader->ReadVec(&default_kid, 16));
- is_encrypted = (flag != 0);
- if (is_encrypted) {
- RCHECK(default_iv_size == 8 || default_iv_size == 16);
- } else {
- RCHECK(default_iv_size == 0);
- }
- return true;
-}
-
-SchemeInfo::SchemeInfo() {}
-SchemeInfo::~SchemeInfo() {}
-FourCC SchemeInfo::BoxType() const { return FOURCC_SCHI; }
-
-bool SchemeInfo::Parse(BoxReader* reader) {
- return reader->ScanChildren() && reader->ReadChild(&track_encryption);
-}
-
-ProtectionSchemeInfo::ProtectionSchemeInfo() {}
-ProtectionSchemeInfo::~ProtectionSchemeInfo() {}
-FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; }
-
-bool ProtectionSchemeInfo::Parse(BoxReader* reader) {
- RCHECK(reader->ScanChildren() &&
- reader->ReadChild(&format) &&
- reader->ReadChild(&type));
- if (type.type == FOURCC_CENC)
- RCHECK(reader->ReadChild(&info));
- // Other protection schemes are silently ignored. Since the protection scheme
- // type can't be determined until this box is opened, we return 'true' for
- // non-CENC protection scheme types. It is the parent box's responsibility to
- // ensure that this scheme type is a supported one.
- return true;
-}
-
-MovieHeader::MovieHeader()
- : creation_time(0),
- modification_time(0),
- timescale(0),
- duration(0),
- rate(-1),
- volume(-1),
- next_track_id(0) {}
-MovieHeader::~MovieHeader() {}
-FourCC MovieHeader::BoxType() const { return FOURCC_MVHD; }
-
-bool MovieHeader::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
-
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&creation_time) &&
- reader->Read8(&modification_time) &&
- reader->Read4(×cale) &&
- reader->Read8(&duration));
- } else {
- RCHECK(reader->Read4Into8(&creation_time) &&
- reader->Read4Into8(&modification_time) &&
- reader->Read4(×cale) &&
- reader->Read4Into8(&duration));
- }
-
- RCHECK(reader->Read4s(&rate) &&
- reader->Read2s(&volume) &&
- reader->SkipBytes(10) && // reserved
- reader->SkipBytes(36) && // matrix
- reader->SkipBytes(24) && // predefined zero
- reader->Read4(&next_track_id));
- return true;
-}
-
-TrackHeader::TrackHeader()
- : creation_time(0),
- modification_time(0),
- track_id(0),
- duration(0),
- layer(-1),
- alternate_group(-1),
- volume(-1),
- width(0),
- height(0) {}
-TrackHeader::~TrackHeader() {}
-FourCC TrackHeader::BoxType() const { return FOURCC_TKHD; }
-
-bool TrackHeader::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&creation_time) &&
- reader->Read8(&modification_time) &&
- reader->Read4(&track_id) &&
- reader->SkipBytes(4) && // reserved
- reader->Read8(&duration));
- } else {
- RCHECK(reader->Read4Into8(&creation_time) &&
- reader->Read4Into8(&modification_time) &&
- reader->Read4(&track_id) &&
- reader->SkipBytes(4) && // reserved
- reader->Read4Into8(&duration));
- }
-
- RCHECK(reader->SkipBytes(8) && // reserved
- reader->Read2s(&layer) &&
- reader->Read2s(&alternate_group) &&
- reader->Read2s(&volume) &&
- reader->SkipBytes(2) && // reserved
- reader->SkipBytes(36) && // matrix
- reader->Read4(&width) &&
- reader->Read4(&height));
- width >>= 16;
- height >>= 16;
- return true;
-}
-
-SampleDescription::SampleDescription() : type(kInvalid) {}
-SampleDescription::~SampleDescription() {}
-FourCC SampleDescription::BoxType() const { return FOURCC_STSD; }
-
-bool SampleDescription::Parse(BoxReader* reader) {
- uint32 count;
- RCHECK(reader->SkipBytes(4) &&
- reader->Read4(&count));
- video_entries.clear();
- audio_entries.clear();
-
- // Note: this value is preset before scanning begins. See comments in the
- // Parse(Media*) function.
- if (type == kVideo) {
- RCHECK(reader->ReadAllChildren(&video_entries));
- } else if (type == kAudio) {
- RCHECK(reader->ReadAllChildren(&audio_entries));
- }
- return true;
-}
-
-SampleTable::SampleTable() {}
-SampleTable::~SampleTable() {}
-FourCC SampleTable::BoxType() const { return FOURCC_STBL; }
-
-bool SampleTable::Parse(BoxReader* reader) {
- return reader->ScanChildren() &&
- reader->ReadChild(&description);
-}
-
-EditList::EditList() {}
-EditList::~EditList() {}
-FourCC EditList::BoxType() const { return FOURCC_ELST; }
-
-bool EditList::Parse(BoxReader* reader) {
- uint32 count;
- RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&count));
-
- if (reader->version() == 1) {
- RCHECK(reader->HasBytes(count * 20));
- } else {
- RCHECK(reader->HasBytes(count * 12));
- }
- edits.resize(count);
-
- for (std::vector<EditListEntry>::iterator edit = edits.begin();
- edit != edits.end(); ++edit) {
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&edit->segment_duration) &&
- reader->Read8s(&edit->media_time));
- } else {
- RCHECK(reader->Read4Into8(&edit->segment_duration) &&
- reader->Read4sInto8s(&edit->media_time));
- }
- RCHECK(reader->Read2s(&edit->media_rate_integer) &&
- reader->Read2s(&edit->media_rate_fraction));
- }
- return true;
-}
-
-Edit::Edit() {}
-Edit::~Edit() {}
-FourCC Edit::BoxType() const { return FOURCC_EDTS; }
-
-bool Edit::Parse(BoxReader* reader) {
- return reader->ScanChildren() && reader->ReadChild(&list);
-}
-
-HandlerReference::HandlerReference() : type(kInvalid) {}
-HandlerReference::~HandlerReference() {}
-FourCC HandlerReference::BoxType() const { return FOURCC_HDLR; }
-
-bool HandlerReference::Parse(BoxReader* reader) {
- FourCC hdlr_type;
- RCHECK(reader->SkipBytes(8) && reader->ReadFourCC(&hdlr_type));
- // Note: remaining fields in box ignored
- if (hdlr_type == FOURCC_VIDE) {
- type = kVideo;
- } else if (hdlr_type == FOURCC_SOUN) {
- type = kAudio;
- } else {
- type = kInvalid;
- }
- return true;
-}
-
-AVCDecoderConfigurationRecord::AVCDecoderConfigurationRecord()
- : version(0),
- profile_indication(0),
- profile_compatibility(0),
- avc_level(0),
- length_size(0) {}
-
-AVCDecoderConfigurationRecord::~AVCDecoderConfigurationRecord() {}
-FourCC AVCDecoderConfigurationRecord::BoxType() const { return FOURCC_AVCC; }
-
-bool AVCDecoderConfigurationRecord::Parse(BoxReader* reader) {
- RCHECK(reader->Read1(&version) && version == 1 &&
- reader->Read1(&profile_indication) &&
- reader->Read1(&profile_compatibility) &&
- reader->Read1(&avc_level));
-
- uint8 length_size_minus_one;
- RCHECK(reader->Read1(&length_size_minus_one) &&
- (length_size_minus_one & 0xfc) == 0xfc);
- length_size = (length_size_minus_one & 0x3) + 1;
-
- uint8 num_sps;
- RCHECK(reader->Read1(&num_sps) && (num_sps & 0xe0) == 0xe0);
- num_sps &= 0x1f;
-
- sps_list.resize(num_sps);
- for (int i = 0; i < num_sps; i++) {
- uint16 sps_length;
- RCHECK(reader->Read2(&sps_length) &&
- reader->ReadVec(&sps_list[i], sps_length));
- }
-
- uint8 num_pps;
- RCHECK(reader->Read1(&num_pps));
-
- pps_list.resize(num_pps);
- for (int i = 0; i < num_pps; i++) {
- uint16 pps_length;
- RCHECK(reader->Read2(&pps_length) &&
- reader->ReadVec(&pps_list[i], pps_length));
- }
-
- return true;
-}
-
-PixelAspectRatioBox::PixelAspectRatioBox() : h_spacing(1), v_spacing(1) {}
-PixelAspectRatioBox::~PixelAspectRatioBox() {}
-FourCC PixelAspectRatioBox::BoxType() const { return FOURCC_PASP; }
-
-bool PixelAspectRatioBox::Parse(BoxReader* reader) {
- RCHECK(reader->Read4(&h_spacing) &&
- reader->Read4(&v_spacing));
- return true;
-}
-
-VideoSampleEntry::VideoSampleEntry()
- : format(FOURCC_NULL),
- data_reference_index(0),
- width(0),
- height(0) {}
-
-VideoSampleEntry::~VideoSampleEntry() {}
-FourCC VideoSampleEntry::BoxType() const {
- DCHECK(false) << "VideoSampleEntry should be parsed according to the "
- << "handler type recovered in its Media ancestor.";
- return FOURCC_NULL;
-}
-
-bool VideoSampleEntry::Parse(BoxReader* reader) {
- format = reader->type();
- RCHECK(reader->SkipBytes(6) &&
- reader->Read2(&data_reference_index) &&
- reader->SkipBytes(16) &&
- reader->Read2(&width) &&
- reader->Read2(&height) &&
- reader->SkipBytes(50));
-
- RCHECK(reader->ScanChildren() &&
- reader->MaybeReadChild(&pixel_aspect));
-
- if (format == FOURCC_ENCV) {
- // Continue scanning until a recognized protection scheme is found, or until
- // we run out of protection schemes.
- while (sinf.type.type != FOURCC_CENC) {
- if (!reader->ReadChild(&sinf))
- return false;
- }
- }
-
- if (format == FOURCC_AVC1 ||
- (format == FOURCC_ENCV && sinf.format.format == FOURCC_AVC1)) {
- RCHECK(reader->ReadChild(&avcc));
- }
- return true;
-}
-
-ElementaryStreamDescriptor::ElementaryStreamDescriptor()
- : object_type(kForbidden) {}
-
-ElementaryStreamDescriptor::~ElementaryStreamDescriptor() {}
-
-FourCC ElementaryStreamDescriptor::BoxType() const {
- return FOURCC_ESDS;
-}
-
-bool ElementaryStreamDescriptor::Parse(BoxReader* reader) {
- std::vector<uint8> data;
- ESDescriptor es_desc;
-
- RCHECK(reader->ReadFullBoxHeader());
- RCHECK(reader->ReadVec(&data, reader->size() - reader->pos()));
- RCHECK(es_desc.Parse(data));
-
- object_type = es_desc.object_type();
-
- RCHECK(aac.Parse(es_desc.decoder_specific_info()));
-
- return true;
-}
-
-AudioSampleEntry::AudioSampleEntry()
- : format(FOURCC_NULL),
- data_reference_index(0),
- channelcount(0),
- samplesize(0),
- samplerate(0) {}
-
-AudioSampleEntry::~AudioSampleEntry() {}
-
-FourCC AudioSampleEntry::BoxType() const {
- DCHECK(false) << "AudioSampleEntry should be parsed according to the "
- << "handler type recovered in its Media ancestor.";
- return FOURCC_NULL;
-}
-
-bool AudioSampleEntry::Parse(BoxReader* reader) {
- format = reader->type();
- RCHECK(reader->SkipBytes(6) &&
- reader->Read2(&data_reference_index) &&
- reader->SkipBytes(8) &&
- reader->Read2(&channelcount) &&
- reader->Read2(&samplesize) &&
- reader->SkipBytes(4) &&
- reader->Read4(&samplerate));
- // Convert from 16.16 fixed point to integer
- samplerate >>= 16;
-
- RCHECK(reader->ScanChildren());
- if (format == FOURCC_ENCA) {
- // Continue scanning until a recognized protection scheme is found, or until
- // we run out of protection schemes.
- while (sinf.type.type != FOURCC_CENC) {
- if (!reader->ReadChild(&sinf))
- return false;
- }
- }
-
- RCHECK(reader->ReadChild(&esds));
- return true;
-}
-
-MediaHeader::MediaHeader()
- : creation_time(0),
- modification_time(0),
- timescale(0),
- duration(0) {}
-MediaHeader::~MediaHeader() {}
-FourCC MediaHeader::BoxType() const { return FOURCC_MDHD; }
-
-bool MediaHeader::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
-
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&creation_time) &&
- reader->Read8(&modification_time) &&
- reader->Read4(×cale) &&
- reader->Read8(&duration));
- } else {
- RCHECK(reader->Read4Into8(&creation_time) &&
- reader->Read4Into8(&modification_time) &&
- reader->Read4(×cale) &&
- reader->Read4Into8(&duration));
- }
- // Skip language information
- return reader->SkipBytes(4);
-}
-
-MediaInformation::MediaInformation() {}
-MediaInformation::~MediaInformation() {}
-FourCC MediaInformation::BoxType() const { return FOURCC_MINF; }
-
-bool MediaInformation::Parse(BoxReader* reader) {
- return reader->ScanChildren() &&
- reader->ReadChild(&sample_table);
-}
-
-Media::Media() {}
-Media::~Media() {}
-FourCC Media::BoxType() const { return FOURCC_MDIA; }
-
-bool Media::Parse(BoxReader* reader) {
- RCHECK(reader->ScanChildren() &&
- reader->ReadChild(&header) &&
- reader->ReadChild(&handler));
-
- // Maddeningly, the HandlerReference box specifies how to parse the
- // SampleDescription box, making the latter the only box (of those that we
- // support) which cannot be parsed correctly on its own (or even with
- // information from its strict ancestor tree). We thus copy the handler type
- // to the sample description box *before* parsing it to provide this
- // information while parsing.
- information.sample_table.description.type = handler.type;
- RCHECK(reader->ReadChild(&information));
- return true;
-}
-
-Track::Track() {}
-Track::~Track() {}
-FourCC Track::BoxType() const { return FOURCC_TRAK; }
-
-bool Track::Parse(BoxReader* reader) {
- RCHECK(reader->ScanChildren() &&
- reader->ReadChild(&header) &&
- reader->ReadChild(&media) &&
- reader->MaybeReadChild(&edit));
- return true;
-}
-
-MovieExtendsHeader::MovieExtendsHeader() : fragment_duration(0) {}
-MovieExtendsHeader::~MovieExtendsHeader() {}
-FourCC MovieExtendsHeader::BoxType() const { return FOURCC_MEHD; }
-
-bool MovieExtendsHeader::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
- if (reader->version() == 1) {
- RCHECK(reader->Read8(&fragment_duration));
- } else {
- RCHECK(reader->Read4Into8(&fragment_duration));
- }
- return true;
-}
-
-TrackExtends::TrackExtends()
- : track_id(0),
- default_sample_description_index(0),
- default_sample_duration(0),
- default_sample_size(0),
- default_sample_flags(0) {}
-TrackExtends::~TrackExtends() {}
-FourCC TrackExtends::BoxType() const { return FOURCC_TREX; }
-
-bool TrackExtends::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->Read4(&track_id) &&
- reader->Read4(&default_sample_description_index) &&
- reader->Read4(&default_sample_duration) &&
- reader->Read4(&default_sample_size) &&
- reader->Read4(&default_sample_flags));
- return true;
-}
-
-MovieExtends::MovieExtends() {}
-MovieExtends::~MovieExtends() {}
-FourCC MovieExtends::BoxType() const { return FOURCC_MVEX; }
-
-bool MovieExtends::Parse(BoxReader* reader) {
- header.fragment_duration = 0;
- return reader->ScanChildren() &&
- reader->MaybeReadChild(&header) &&
- reader->ReadChildren(&tracks);
-}
-
-Movie::Movie() : fragmented(false) {}
-Movie::~Movie() {}
-FourCC Movie::BoxType() const { return FOURCC_MOOV; }
-
-bool Movie::Parse(BoxReader* reader) {
- return reader->ScanChildren() &&
- reader->ReadChild(&header) &&
- reader->ReadChildren(&tracks) &&
- // Media Source specific: 'mvex' required
- reader->ReadChild(&extends) &&
- reader->MaybeReadChildren(&pssh);
-}
-
-TrackFragmentDecodeTime::TrackFragmentDecodeTime() : decode_time(0) {}
-TrackFragmentDecodeTime::~TrackFragmentDecodeTime() {}
-FourCC TrackFragmentDecodeTime::BoxType() const { return FOURCC_TFDT; }
-
-bool TrackFragmentDecodeTime::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader());
- if (reader->version() == 1)
- return reader->Read8(&decode_time);
- else
- return reader->Read4Into8(&decode_time);
-}
-
-MovieFragmentHeader::MovieFragmentHeader() : sequence_number(0) {}
-MovieFragmentHeader::~MovieFragmentHeader() {}
-FourCC MovieFragmentHeader::BoxType() const { return FOURCC_MFHD; }
-
-bool MovieFragmentHeader::Parse(BoxReader* reader) {
- return reader->SkipBytes(4) && reader->Read4(&sequence_number);
-}
-
-TrackFragmentHeader::TrackFragmentHeader()
- : track_id(0),
- sample_description_index(0),
- default_sample_duration(0),
- default_sample_size(0),
- default_sample_flags(0),
- has_default_sample_flags(false) {}
-
-TrackFragmentHeader::~TrackFragmentHeader() {}
-FourCC TrackFragmentHeader::BoxType() const { return FOURCC_TFHD; }
-
-bool TrackFragmentHeader::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&track_id));
-
- // Media Source specific: reject tracks that set 'base-data-offset-present'.
- // Although the Media Source requires that 'default-base-is-moof' (14496-12
- // Amendment 2) be set, we omit this check as many otherwise-valid files in
- // the wild don't set it.
- //
- // RCHECK((flags & 0x020000) && !(flags & 0x1));
- RCHECK(!(reader->flags() & 0x1));
-
- if (reader->flags() & 0x2) {
- RCHECK(reader->Read4(&sample_description_index));
- } else {
- sample_description_index = 0;
- }
-
- if (reader->flags() & 0x8) {
- RCHECK(reader->Read4(&default_sample_duration));
- } else {
- default_sample_duration = 0;
- }
-
- if (reader->flags() & 0x10) {
- RCHECK(reader->Read4(&default_sample_size));
- } else {
- default_sample_size = 0;
- }
-
- if (reader->flags() & 0x20) {
- RCHECK(reader->Read4(&default_sample_flags));
- has_default_sample_flags = true;
- } else {
- has_default_sample_flags = false;
- }
-
- return true;
-}
-
-TrackFragmentRun::TrackFragmentRun()
- : sample_count(0), data_offset(0) {}
-TrackFragmentRun::~TrackFragmentRun() {}
-FourCC TrackFragmentRun::BoxType() const { return FOURCC_TRUN; }
-
-bool TrackFragmentRun::Parse(BoxReader* reader) {
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->Read4(&sample_count));
- const uint32 flags = reader->flags();
-
- bool data_offset_present = (flags & 0x1) != 0;
- bool first_sample_flags_present = (flags & 0x4) != 0;
- bool sample_duration_present = (flags & 0x100) != 0;
- bool sample_size_present = (flags & 0x200) != 0;
- bool sample_flags_present = (flags & 0x400) != 0;
- bool sample_composition_time_offsets_present = (flags & 0x800) != 0;
-
- if (data_offset_present) {
- RCHECK(reader->Read4(&data_offset));
- } else {
- data_offset = 0;
- }
-
- uint32 first_sample_flags;
- if (first_sample_flags_present)
- RCHECK(reader->Read4(&first_sample_flags));
-
- int fields = sample_duration_present + sample_size_present +
- sample_flags_present + sample_composition_time_offsets_present;
- RCHECK(reader->HasBytes(fields * sample_count));
-
- if (sample_duration_present)
- sample_durations.resize(sample_count);
- if (sample_size_present)
- sample_sizes.resize(sample_count);
- if (sample_flags_present)
- sample_flags.resize(sample_count);
- if (sample_composition_time_offsets_present)
- sample_composition_time_offsets.resize(sample_count);
-
- for (uint32 i = 0; i < sample_count; ++i) {
- if (sample_duration_present)
- RCHECK(reader->Read4(&sample_durations[i]));
- if (sample_size_present)
- RCHECK(reader->Read4(&sample_sizes[i]));
- if (sample_flags_present)
- RCHECK(reader->Read4(&sample_flags[i]));
- if (sample_composition_time_offsets_present)
- RCHECK(reader->Read4s(&sample_composition_time_offsets[i]));
- }
-
- if (first_sample_flags_present) {
- if (sample_flags.size() == 0) {
- sample_flags.push_back(first_sample_flags);
- } else {
- sample_flags[0] = first_sample_flags;
- }
- }
- return true;
-}
-
-TrackFragment::TrackFragment() {}
-TrackFragment::~TrackFragment() {}
-FourCC TrackFragment::BoxType() const { return FOURCC_TRAF; }
-
-bool TrackFragment::Parse(BoxReader* reader) {
- return reader->ScanChildren() &&
- reader->ReadChild(&header) &&
- // Media Source specific: 'tfdt' required
- reader->ReadChild(&decode_time) &&
- reader->MaybeReadChildren(&runs) &&
- reader->MaybeReadChild(&auxiliary_offset) &&
- reader->MaybeReadChild(&auxiliary_size);
-}
-
-MovieFragment::MovieFragment() {}
-MovieFragment::~MovieFragment() {}
-FourCC MovieFragment::BoxType() const { return FOURCC_MOOF; }
-
-bool MovieFragment::Parse(BoxReader* reader) {
- RCHECK(reader->ScanChildren() &&
- reader->ReadChild(&header) &&
- reader->ReadChildren(&tracks) &&
- reader->MaybeReadChildren(&pssh));
- return true;
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/box_definitions.h b/src/media/mp4/box_definitions.h
deleted file mode 100644
index eab8c4f..0000000
--- a/src/media/mp4/box_definitions.h
+++ /dev/null
@@ -1,351 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_BOX_DEFINITIONS_H_
-#define MEDIA_MP4_BOX_DEFINITIONS_H_
-
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "media/base/media_export.h"
-#include "media/mp4/aac.h"
-#include "media/mp4/avc.h"
-#include "media/mp4/box_reader.h"
-#include "media/mp4/fourccs.h"
-
-namespace media {
-namespace mp4 {
-
-enum TrackType {
- kInvalid = 0,
- kVideo,
- kAudio,
- kHint
-};
-
-#define DECLARE_BOX_METHODS(T) \
- T(); \
- virtual ~T(); \
- virtual bool Parse(BoxReader* reader) OVERRIDE; \
- virtual FourCC BoxType() const OVERRIDE; \
-
-struct MEDIA_EXPORT FileType : Box {
- DECLARE_BOX_METHODS(FileType);
-
- FourCC major_brand;
- uint32 minor_version;
-};
-
-struct MEDIA_EXPORT ProtectionSystemSpecificHeader : Box {
- DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader);
-
- std::vector<uint8> system_id;
- std::vector<uint8> raw_box;
-};
-
-struct MEDIA_EXPORT SampleAuxiliaryInformationOffset : Box {
- DECLARE_BOX_METHODS(SampleAuxiliaryInformationOffset);
-
- std::vector<uint64> offsets;
-};
-
-struct MEDIA_EXPORT SampleAuxiliaryInformationSize : Box {
- DECLARE_BOX_METHODS(SampleAuxiliaryInformationSize);
-
- uint8 default_sample_info_size;
- uint32 sample_count;
- std::vector<uint8> sample_info_sizes;
-};
-
-struct MEDIA_EXPORT OriginalFormat : Box {
- DECLARE_BOX_METHODS(OriginalFormat);
-
- FourCC format;
-};
-
-struct MEDIA_EXPORT SchemeType : Box {
- DECLARE_BOX_METHODS(SchemeType);
-
- FourCC type;
- uint32 version;
-};
-
-struct MEDIA_EXPORT TrackEncryption : Box {
- DECLARE_BOX_METHODS(TrackEncryption);
-
- // Note: this definition is specific to the CENC protection type.
- bool is_encrypted;
- uint8 default_iv_size;
- std::vector<uint8> default_kid;
-};
-
-struct MEDIA_EXPORT SchemeInfo : Box {
- DECLARE_BOX_METHODS(SchemeInfo);
-
- TrackEncryption track_encryption;
-};
-
-struct MEDIA_EXPORT ProtectionSchemeInfo : Box {
- DECLARE_BOX_METHODS(ProtectionSchemeInfo);
-
- OriginalFormat format;
- SchemeType type;
- SchemeInfo info;
-};
-
-struct MEDIA_EXPORT MovieHeader : Box {
- DECLARE_BOX_METHODS(MovieHeader);
-
- uint64 creation_time;
- uint64 modification_time;
- uint32 timescale;
- uint64 duration;
- int32 rate;
- int16 volume;
- uint32 next_track_id;
-};
-
-struct MEDIA_EXPORT TrackHeader : Box {
- DECLARE_BOX_METHODS(TrackHeader);
-
- uint64 creation_time;
- uint64 modification_time;
- uint32 track_id;
- uint64 duration;
- int16 layer;
- int16 alternate_group;
- int16 volume;
- uint32 width;
- uint32 height;
-};
-
-struct MEDIA_EXPORT EditListEntry {
- uint64 segment_duration;
- int64 media_time;
- int16 media_rate_integer;
- int16 media_rate_fraction;
-};
-
-struct MEDIA_EXPORT EditList : Box {
- DECLARE_BOX_METHODS(EditList);
-
- std::vector<EditListEntry> edits;
-};
-
-struct MEDIA_EXPORT Edit : Box {
- DECLARE_BOX_METHODS(Edit);
-
- EditList list;
-};
-
-struct MEDIA_EXPORT HandlerReference : Box {
- DECLARE_BOX_METHODS(HandlerReference);
-
- TrackType type;
-};
-
-struct MEDIA_EXPORT AVCDecoderConfigurationRecord : Box {
- DECLARE_BOX_METHODS(AVCDecoderConfigurationRecord);
-
- uint8 version;
- uint8 profile_indication;
- uint8 profile_compatibility;
- uint8 avc_level;
- uint8 length_size;
-
- typedef std::vector<uint8> SPS;
- typedef std::vector<uint8> PPS;
-
- std::vector<SPS> sps_list;
- std::vector<PPS> pps_list;
-};
-
-struct MEDIA_EXPORT PixelAspectRatioBox : Box {
- DECLARE_BOX_METHODS(PixelAspectRatioBox);
-
- uint32 h_spacing;
- uint32 v_spacing;
-};
-
-struct MEDIA_EXPORT VideoSampleEntry : Box {
- DECLARE_BOX_METHODS(VideoSampleEntry);
-
- FourCC format;
- uint16 data_reference_index;
- uint16 width;
- uint16 height;
-
- PixelAspectRatioBox pixel_aspect;
- ProtectionSchemeInfo sinf;
-
- // Currently expected to be present regardless of format.
- AVCDecoderConfigurationRecord avcc;
-};
-
-struct MEDIA_EXPORT ElementaryStreamDescriptor : Box {
- DECLARE_BOX_METHODS(ElementaryStreamDescriptor);
-
- uint8 object_type;
- AAC aac;
-};
-
-struct MEDIA_EXPORT AudioSampleEntry : Box {
- DECLARE_BOX_METHODS(AudioSampleEntry);
-
- FourCC format;
- uint16 data_reference_index;
- uint16 channelcount;
- uint16 samplesize;
- uint32 samplerate;
-
- ProtectionSchemeInfo sinf;
- ElementaryStreamDescriptor esds;
-};
-
-struct MEDIA_EXPORT SampleDescription : Box {
- DECLARE_BOX_METHODS(SampleDescription);
-
- TrackType type;
- std::vector<VideoSampleEntry> video_entries;
- std::vector<AudioSampleEntry> audio_entries;
-};
-
-struct MEDIA_EXPORT SampleTable : Box {
- DECLARE_BOX_METHODS(SampleTable);
-
- // Media Source specific: we ignore many of the sub-boxes in this box,
- // including some that are required to be present in the BMFF spec. This
- // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no
- // samples in order to be compliant files.
- SampleDescription description;
-};
-
-struct MEDIA_EXPORT MediaHeader : Box {
- DECLARE_BOX_METHODS(MediaHeader);
-
- uint64 creation_time;
- uint64 modification_time;
- uint32 timescale;
- uint64 duration;
-};
-
-struct MEDIA_EXPORT MediaInformation : Box {
- DECLARE_BOX_METHODS(MediaInformation);
-
- SampleTable sample_table;
-};
-
-struct MEDIA_EXPORT Media : Box {
- DECLARE_BOX_METHODS(Media);
-
- MediaHeader header;
- HandlerReference handler;
- MediaInformation information;
-};
-
-struct MEDIA_EXPORT Track : Box {
- DECLARE_BOX_METHODS(Track);
-
- TrackHeader header;
- Media media;
- Edit edit;
-};
-
-struct MEDIA_EXPORT MovieExtendsHeader : Box {
- DECLARE_BOX_METHODS(MovieExtendsHeader);
-
- uint64 fragment_duration;
-};
-
-struct MEDIA_EXPORT TrackExtends : Box {
- DECLARE_BOX_METHODS(TrackExtends);
-
- uint32 track_id;
- uint32 default_sample_description_index;
- uint32 default_sample_duration;
- uint32 default_sample_size;
- uint32 default_sample_flags;
-};
-
-struct MEDIA_EXPORT MovieExtends : Box {
- DECLARE_BOX_METHODS(MovieExtends);
-
- MovieExtendsHeader header;
- std::vector<TrackExtends> tracks;
-};
-
-struct MEDIA_EXPORT Movie : Box {
- DECLARE_BOX_METHODS(Movie);
-
- bool fragmented;
- MovieHeader header;
- MovieExtends extends;
- std::vector<Track> tracks;
- std::vector<ProtectionSystemSpecificHeader> pssh;
-};
-
-struct MEDIA_EXPORT TrackFragmentDecodeTime : Box {
- DECLARE_BOX_METHODS(TrackFragmentDecodeTime);
-
- uint64 decode_time;
-};
-
-struct MEDIA_EXPORT MovieFragmentHeader : Box {
- DECLARE_BOX_METHODS(MovieFragmentHeader);
-
- uint32 sequence_number;
-};
-
-struct MEDIA_EXPORT TrackFragmentHeader : Box {
- DECLARE_BOX_METHODS(TrackFragmentHeader);
-
- uint32 track_id;
-
- uint32 sample_description_index;
- uint32 default_sample_duration;
- uint32 default_sample_size;
- uint32 default_sample_flags;
-
- // As 'flags' might be all zero, we cannot use zeroness alone to identify
- // when default_sample_flags wasn't specified, unlike the other values.
- bool has_default_sample_flags;
-};
-
-struct MEDIA_EXPORT TrackFragmentRun : Box {
- DECLARE_BOX_METHODS(TrackFragmentRun);
-
- uint32 sample_count;
- uint32 data_offset;
- std::vector<uint32> sample_flags;
- std::vector<uint32> sample_sizes;
- std::vector<uint32> sample_durations;
- std::vector<int32> sample_composition_time_offsets;
-};
-
-struct MEDIA_EXPORT TrackFragment : Box {
- DECLARE_BOX_METHODS(TrackFragment);
-
- TrackFragmentHeader header;
- std::vector<TrackFragmentRun> runs;
- TrackFragmentDecodeTime decode_time;
- SampleAuxiliaryInformationOffset auxiliary_offset;
- SampleAuxiliaryInformationSize auxiliary_size;
-};
-
-struct MEDIA_EXPORT MovieFragment : Box {
- DECLARE_BOX_METHODS(MovieFragment);
-
- MovieFragmentHeader header;
- std::vector<TrackFragment> tracks;
- std::vector<ProtectionSystemSpecificHeader> pssh;
-};
-
-#undef DECLARE_BOX
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_BOX_DEFINITIONS_H_
diff --git a/src/media/mp4/box_reader.cc b/src/media/mp4/box_reader.cc
deleted file mode 100644
index 71d93a0..0000000
--- a/src/media/mp4/box_reader.cc
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/box_reader.h"
-
-#include <string.h>
-#include <algorithm>
-#include <map>
-#include <set>
-
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/mp4/box_definitions.h"
-#include "media/mp4/rcheck.h"
-
-namespace media {
-namespace mp4 {
-
-Box::~Box() {}
-
-bool BufferReader::Read1(uint8* v) {
- RCHECK(HasBytes(1));
- *v = buf_[pos_++];
- return true;
-}
-
-// Internal implementation of multi-byte reads
-template<typename T> bool BufferReader::Read(T* v) {
- RCHECK(HasBytes(sizeof(T)));
-
- T tmp = 0;
- for (size_t i = 0; i < sizeof(T); i++) {
- tmp <<= 8;
- tmp += buf_[pos_++];
- }
- *v = tmp;
- return true;
-}
-
-bool BufferReader::Read2(uint16* v) { return Read(v); }
-bool BufferReader::Read2s(int16* v) { return Read(v); }
-bool BufferReader::Read4(uint32* v) { return Read(v); }
-bool BufferReader::Read4s(int32* v) { return Read(v); }
-bool BufferReader::Read8(uint64* v) { return Read(v); }
-bool BufferReader::Read8s(int64* v) { return Read(v); }
-
-bool BufferReader::ReadFourCC(FourCC* v) {
- return Read4(reinterpret_cast<uint32*>(v));
-}
-
-bool BufferReader::ReadVec(std::vector<uint8>* vec, int count) {
- RCHECK(HasBytes(count));
- vec->clear();
- vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count);
- pos_ += count;
- return true;
-}
-
-bool BufferReader::SkipBytes(int bytes) {
- RCHECK(HasBytes(bytes));
- pos_ += bytes;
- return true;
-}
-
-bool BufferReader::Read4Into8(uint64* v) {
- uint32 tmp;
- RCHECK(Read4(&tmp));
- *v = tmp;
- return true;
-}
-
-bool BufferReader::Read4sInto8s(int64* v) {
- // Beware of the need for sign extension.
- int32 tmp;
- RCHECK(Read4s(&tmp));
- *v = tmp;
- return true;
-}
-
-
-BoxReader::BoxReader(const uint8* buf, const int size,
- const LogCB& log_cb)
- : BufferReader(buf, size),
- log_cb_(log_cb),
- type_(FOURCC_NULL),
- version_(0),
- flags_(0),
- scanned_(false) {
-}
-
-BoxReader::~BoxReader() {
- if (scanned_ && !children_.empty()) {
- for (ChildMap::iterator itr = children_.begin();
- itr != children_.end(); ++itr) {
- DVLOG(1) << "Skipping unknown box: " << FourCCToString(itr->first);
- }
- }
-}
-
-// static
-BoxReader* BoxReader::ReadTopLevelBox(const uint8* buf,
- const int buf_size,
- const LogCB& log_cb,
- bool* err) {
- scoped_ptr<BoxReader> reader(new BoxReader(buf, buf_size, log_cb));
- if (!reader->ReadHeader(err))
- return NULL;
-
- if (!IsValidTopLevelBox(reader->type(), log_cb)) {
- *err = true;
- return NULL;
- }
-
- if (reader->size() <= buf_size)
- return reader.release();
-
- return NULL;
-}
-
-// static
-bool BoxReader::StartTopLevelBox(const uint8* buf,
- const int buf_size,
- const LogCB& log_cb,
- FourCC* type,
- int* box_size,
- bool* err) {
- BoxReader reader(buf, buf_size, log_cb);
- if (!reader.ReadHeader(err)) return false;
- if (!IsValidTopLevelBox(reader.type(), log_cb)) {
- *err = true;
- return false;
- }
- *type = reader.type();
- *box_size = reader.size();
- return true;
-}
-
-// static
-bool BoxReader::IsValidTopLevelBox(const FourCC& type,
- const LogCB& log_cb) {
- switch (type) {
- case FOURCC_FTYP:
- case FOURCC_PDIN:
- case FOURCC_MOOV:
- case FOURCC_MOOF:
- case FOURCC_MFRA:
- case FOURCC_MDAT:
- case FOURCC_FREE:
- case FOURCC_SKIP:
- case FOURCC_META:
- case FOURCC_MECO:
- case FOURCC_STYP:
- case FOURCC_SIDX:
- case FOURCC_SSIX:
- case FOURCC_PRFT:
- return true;
- default:
- // Hex is used to show nonprintable characters and aid in debugging
- MEDIA_LOG(log_cb) << "Unrecognized top-level box type 0x"
- << std::hex << type;
- return false;
- }
-}
-
-bool BoxReader::ScanChildren() {
- DCHECK(!scanned_);
- scanned_ = true;
-
- bool err = false;
- while (pos() < size()) {
- BoxReader child(&buf_[pos_], size_ - pos_, log_cb_);
- if (!child.ReadHeader(&err)) break;
-
- children_.insert(std::pair<FourCC, BoxReader>(child.type(), child));
- pos_ += child.size();
- }
-
- DCHECK(!err);
- return !err && pos() == size();
-}
-
-bool BoxReader::ReadChild(Box* child) {
- DCHECK(scanned_);
- FourCC child_type = child->BoxType();
-
- ChildMap::iterator itr = children_.find(child_type);
- RCHECK(itr != children_.end());
- DVLOG(2) << "Found a " << FourCCToString(child_type) << " box.";
- RCHECK(child->Parse(&itr->second));
- children_.erase(itr);
- return true;
-}
-
-bool BoxReader::MaybeReadChild(Box* child) {
- if (!children_.count(child->BoxType())) return true;
- return ReadChild(child);
-}
-
-bool BoxReader::ReadFullBoxHeader() {
- uint32 vflags;
- RCHECK(Read4(&vflags));
- version_ = vflags >> 24;
- flags_ = vflags & 0xffffff;
- return true;
-}
-
-bool BoxReader::ReadHeader(bool* err) {
- uint64 size = 0;
- *err = false;
-
- if (!HasBytes(8)) return false;
- CHECK(Read4Into8(&size) && ReadFourCC(&type_));
-
- if (size == 0) {
- // Media Source specific: we do not support boxes that run to EOS.
- *err = true;
- return false;
- } else if (size == 1) {
- if (!HasBytes(8)) return false;
- CHECK(Read8(&size));
- }
-
- // Implementation-specific: support for boxes larger than 2^31 has been
- // removed.
- if (size < static_cast<uint64>(pos_) ||
- size > static_cast<uint64>(kint32max)) {
- *err = true;
- return false;
- }
-
- // Note that the pos_ head has advanced to the byte immediately after the
- // header, which is where we want it.
- size_ = size;
- return true;
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/box_reader.h b/src/media/mp4/box_reader.h
deleted file mode 100644
index 43f11d5..0000000
--- a/src/media/mp4/box_reader.h
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_BOX_READER_H_
-#define MEDIA_MP4_BOX_READER_H_
-
-#include <map>
-#include <vector>
-
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/mp4/fourccs.h"
-#include "media/mp4/rcheck.h"
-
-namespace media {
-namespace mp4 {
-
-class BoxReader;
-
-struct MEDIA_EXPORT Box {
- virtual ~Box();
- virtual bool Parse(BoxReader* reader) = 0;
- virtual FourCC BoxType() const = 0;
-};
-
-class MEDIA_EXPORT BufferReader {
- public:
- BufferReader(const uint8* buf, const int size)
- : buf_(buf), size_(size), pos_(0) {}
-
- bool HasBytes(int count) { return (pos() + count <= size()); }
-
- // Read a value from the stream, perfoming endian correction, and advance the
- // stream pointer.
- bool Read1(uint8* v) WARN_UNUSED_RESULT;
- bool Read2(uint16* v) WARN_UNUSED_RESULT;
- bool Read2s(int16* v) WARN_UNUSED_RESULT;
- bool Read4(uint32* v) WARN_UNUSED_RESULT;
- bool Read4s(int32* v) WARN_UNUSED_RESULT;
- bool Read8(uint64* v) WARN_UNUSED_RESULT;
- bool Read8s(int64* v) WARN_UNUSED_RESULT;
-
- bool ReadFourCC(FourCC* v) WARN_UNUSED_RESULT;
-
- bool ReadVec(std::vector<uint8>* t, int count) WARN_UNUSED_RESULT;
-
- // These variants read a 4-byte integer of the corresponding signedness and
- // store it in the 8-byte return type.
- bool Read4Into8(uint64* v) WARN_UNUSED_RESULT;
- bool Read4sInto8s(int64* v) WARN_UNUSED_RESULT;
-
- // Advance the stream by this many bytes.
- bool SkipBytes(int nbytes) WARN_UNUSED_RESULT;
-
- const uint8* data() const { return buf_; }
- int size() const { return size_; }
- int pos() const { return pos_; }
-
- protected:
- const uint8* buf_;
- int size_;
- int pos_;
-
- template<typename T> bool Read(T* t) WARN_UNUSED_RESULT;
-};
-
-class MEDIA_EXPORT BoxReader : public BufferReader {
- public:
- ~BoxReader();
-
- // Create a BoxReader from a buffer. Note that this function may return NULL
- // if an intact, complete box was not available in the buffer. If |*err| is
- // set, there was a stream-level error when creating the box; otherwise, NULL
- // values are only expected when insufficient data is available.
- //
- // |buf| is retained but not owned, and must outlive the BoxReader instance.
- static BoxReader* ReadTopLevelBox(const uint8* buf,
- const int buf_size,
- const LogCB& log_cb,
- bool* err);
-
- // Read the box header from the current buffer. This function returns true if
- // there is enough data to read the header and the header is sane; that is, it
- // does not check to ensure the entire box is in the buffer before returning
- // true. The semantics of |*err| are the same as above.
- //
- // |buf| is not retained.
- static bool StartTopLevelBox(const uint8* buf,
- const int buf_size,
- const LogCB& log_cb,
- FourCC* type,
- int* box_size,
- bool* err) WARN_UNUSED_RESULT;
-
- // Returns true if |type| is recognized to be a top-level box, false
- // otherwise. This returns true for some boxes which we do not parse.
- // Helpful in debugging misaligned appends.
- static bool IsValidTopLevelBox(const FourCC& type,
- const LogCB& log_cb);
-
- // Scan through all boxes within the current box, starting at the current
- // buffer position. Must be called before any of the *Child functions work.
- bool ScanChildren() WARN_UNUSED_RESULT;
-
- // Read exactly one child box from the set of children. The type of the child
- // will be determined by the BoxType() method of |child|.
- bool ReadChild(Box* child) WARN_UNUSED_RESULT;
-
- // Read one child if available. Returns false on error, true on successful
- // read or on child absent.
- bool MaybeReadChild(Box* child) WARN_UNUSED_RESULT;
-
- // Read at least one child. False means error or no such child present.
- template<typename T> bool ReadChildren(
- std::vector<T>* children) WARN_UNUSED_RESULT;
-
- // Read any number of children. False means error.
- template<typename T> bool MaybeReadChildren(
- std::vector<T>* children) WARN_UNUSED_RESULT;
-
- // Read all children, regardless of FourCC. This is used from exactly one box,
- // corresponding to a rather significant inconsistency in the BMFF spec.
- // Note that this method is mutually exclusive with ScanChildren().
- template<typename T> bool ReadAllChildren(
- std::vector<T>* children) WARN_UNUSED_RESULT;
-
- // Populate the values of 'version()' and 'flags()' from a full box header.
- // Many boxes, but not all, use these values. This call should happen after
- // the box has been initialized, and does not re-read the main box header.
- bool ReadFullBoxHeader() WARN_UNUSED_RESULT;
-
- FourCC type() const { return type_; }
- uint8 version() const { return version_; }
- uint32 flags() const { return flags_; }
-
- private:
- BoxReader(const uint8* buf, const int size, const LogCB& log_cb);
-
- // Must be called immediately after init. If the return is false, this
- // indicates that the box header and its contents were not available in the
- // stream or were nonsensical, and that the box must not be used further. In
- // this case, if |*err| is false, the problem was simply a lack of data, and
- // should only be an error condition if some higher-level component knows that
- // no more data is coming (i.e. EOS or end of containing box). If |*err| is
- // true, the error is unrecoverable and the stream should be aborted.
- bool ReadHeader(bool* err);
-
- LogCB log_cb_;
- FourCC type_;
- uint8 version_;
- uint32 flags_;
-
- typedef std::multimap<FourCC, BoxReader> ChildMap;
-
- // The set of child box FourCCs and their corresponding buffer readers. Only
- // valid if scanned_ is true.
- ChildMap children_;
- bool scanned_;
-};
-
-// Template definitions
-template<typename T> bool BoxReader::ReadChildren(std::vector<T>* children) {
- RCHECK(MaybeReadChildren(children) && !children->empty());
- return true;
-}
-
-template<typename T>
-bool BoxReader::MaybeReadChildren(std::vector<T>* children) {
- DCHECK(scanned_);
- DCHECK(children->empty());
-
- children->resize(1);
- FourCC child_type = (*children)[0].BoxType();
-
- ChildMap::iterator start_itr = children_.lower_bound(child_type);
- ChildMap::iterator end_itr = children_.upper_bound(child_type);
- children->resize(std::distance(start_itr, end_itr));
- typename std::vector<T>::iterator child_itr = children->begin();
- for (ChildMap::iterator itr = start_itr; itr != end_itr; ++itr) {
- RCHECK(child_itr->Parse(&itr->second));
- ++child_itr;
- }
- children_.erase(start_itr, end_itr);
-
- DVLOG(2) << "Found " << children->size() << " "
- << FourCCToString(child_type) << " boxes.";
- return true;
-}
-
-template<typename T>
-bool BoxReader::ReadAllChildren(std::vector<T>* children) {
- DCHECK(!scanned_);
- scanned_ = true;
-
- bool err = false;
- while (pos() < size()) {
- BoxReader child_reader(&buf_[pos_], size_ - pos_, log_cb_);
- if (!child_reader.ReadHeader(&err)) break;
- T child;
- RCHECK(child.Parse(&child_reader));
- children->push_back(child);
- pos_ += child_reader.size();
- }
-
- return !err;
-}
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_BOX_READER_H_
diff --git a/src/media/mp4/box_reader_unittest.cc b/src/media/mp4/box_reader_unittest.cc
deleted file mode 100644
index 419b5af..0000000
--- a/src/media/mp4/box_reader_unittest.cc
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string.h>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/mp4/box_reader.h"
-#include "media/mp4/rcheck.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-namespace mp4 {
-
-static const uint8 kSkipBox[] = {
- // Top-level test box containing three children
- 0x00, 0x00, 0x00, 0x40, 's', 'k', 'i', 'p',
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
- 0xf9, 0x0a, 0x0b, 0x0c, 0xfd, 0x0e, 0x0f, 0x10,
- // Ordinary (8-byte header) child box
- 0x00, 0x00, 0x00, 0x0c, 'p', 's', 's', 'h', 0xde, 0xad, 0xbe, 0xef,
- // Extended-size header child box
- 0x00, 0x00, 0x00, 0x01, 'p', 's', 's', 'h',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0xfa, 0xce, 0xca, 0xfe,
- // Empty free box
- 0x00, 0x00, 0x00, 0x08, 'f', 'r', 'e', 'e',
- // Trailing garbage
- 0x00 };
-
-struct FreeBox : Box {
- virtual bool Parse(BoxReader* reader) OVERRIDE {
- return true;
- }
- virtual FourCC BoxType() const OVERRIDE { return FOURCC_FREE; }
-};
-
-struct PsshBox : Box {
- uint32 val;
-
- virtual bool Parse(BoxReader* reader) OVERRIDE {
- return reader->Read4(&val);
- }
- virtual FourCC BoxType() const OVERRIDE { return FOURCC_PSSH; }
-};
-
-struct SkipBox : Box {
- uint8 a, b;
- uint16 c;
- int32 d;
- int64 e;
-
- std::vector<PsshBox> kids;
- FreeBox mpty;
-
- virtual bool Parse(BoxReader* reader) OVERRIDE {
- RCHECK(reader->ReadFullBoxHeader() &&
- reader->Read1(&a) &&
- reader->Read1(&b) &&
- reader->Read2(&c) &&
- reader->Read4s(&d) &&
- reader->Read4sInto8s(&e));
- return reader->ScanChildren() &&
- reader->ReadChildren(&kids) &&
- reader->MaybeReadChild(&mpty);
- }
- virtual FourCC BoxType() const OVERRIDE { return FOURCC_SKIP; }
-
- SkipBox();
- ~SkipBox();
-};
-
-SkipBox::SkipBox() {}
-SkipBox::~SkipBox() {}
-
-class BoxReaderTest : public testing::Test {
- protected:
- std::vector<uint8> GetBuf() {
- return std::vector<uint8>(kSkipBox, kSkipBox + sizeof(kSkipBox));
- }
-};
-
-TEST_F(BoxReaderTest, ExpectedOperationTest) {
- std::vector<uint8> buf = GetBuf();
- bool err;
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err));
- EXPECT_FALSE(err);
- EXPECT_TRUE(reader.get());
-
- SkipBox box;
- EXPECT_TRUE(box.Parse(reader.get()));
- EXPECT_EQ(0x01, reader->version());
- EXPECT_EQ(0x020304u, reader->flags());
- EXPECT_EQ(0x05, box.a);
- EXPECT_EQ(0x06, box.b);
- EXPECT_EQ(0x0708, box.c);
- EXPECT_EQ(static_cast<int32>(0xf90a0b0c), box.d);
- EXPECT_EQ(static_cast<int32>(0xfd0e0f10), box.e);
-
- EXPECT_EQ(2u, box.kids.size());
- EXPECT_EQ(0xdeadbeef, box.kids[0].val);
- EXPECT_EQ(0xfacecafe, box.kids[1].val);
-
- // Accounting for the extra byte outside of the box above
- EXPECT_EQ(buf.size(), static_cast<uint64>(reader->size() + 1));
-}
-
-TEST_F(BoxReaderTest, OuterTooShortTest) {
- std::vector<uint8> buf = GetBuf();
- bool err;
-
- // Create a soft failure by truncating the outer box.
- scoped_ptr<BoxReader> r(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size() - 2, LogCB(), &err));
-
- EXPECT_FALSE(err);
- EXPECT_FALSE(r.get());
-}
-
-TEST_F(BoxReaderTest, InnerTooLongTest) {
- std::vector<uint8> buf = GetBuf();
- bool err;
-
- // Make an inner box too big for its outer box.
- buf[25] = 1;
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err));
-
- SkipBox box;
- EXPECT_FALSE(box.Parse(reader.get()));
-}
-
-TEST_F(BoxReaderTest, WrongFourCCTest) {
- std::vector<uint8> buf = GetBuf();
- bool err;
-
- // Set an unrecognized top-level FourCC.
- buf[5] = 1;
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err));
- EXPECT_FALSE(reader.get());
- EXPECT_TRUE(err);
-}
-
-TEST_F(BoxReaderTest, ScanChildrenTest) {
- std::vector<uint8> buf = GetBuf();
- bool err;
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err));
-
- EXPECT_TRUE(reader->SkipBytes(16) && reader->ScanChildren());
-
- FreeBox free;
- EXPECT_TRUE(reader->ReadChild(&free));
- EXPECT_FALSE(reader->ReadChild(&free));
- EXPECT_TRUE(reader->MaybeReadChild(&free));
-
- std::vector<PsshBox> kids;
-
- EXPECT_TRUE(reader->ReadChildren(&kids));
- EXPECT_EQ(2u, kids.size());
- kids.clear();
- EXPECT_FALSE(reader->ReadChildren(&kids));
- EXPECT_TRUE(reader->MaybeReadChildren(&kids));
-}
-
-TEST_F(BoxReaderTest, ReadAllChildrenTest) {
- std::vector<uint8> buf = GetBuf();
- // Modify buffer to exclude its last 'free' box
- buf[3] = 0x38;
- bool err;
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(&buf[0], buf.size(), LogCB(), &err));
-
- std::vector<PsshBox> kids;
- EXPECT_TRUE(reader->SkipBytes(16) && reader->ReadAllChildren(&kids));
- EXPECT_EQ(2u, kids.size());
- EXPECT_EQ(kids[0].val, 0xdeadbeef); // Ensure order is preserved
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/cenc.cc b/src/media/mp4/cenc.cc
deleted file mode 100644
index 10f3a2a..0000000
--- a/src/media/mp4/cenc.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/cenc.h"
-
-#include <cstring>
-
-#include "media/mp4/box_reader.h"
-#include "media/mp4/rcheck.h"
-
-namespace media {
-namespace mp4 {
-
-FrameCENCInfo::FrameCENCInfo() {}
-FrameCENCInfo::~FrameCENCInfo() {}
-
-bool FrameCENCInfo::Parse(int iv_size, BufferReader* reader) {
- const int kEntrySize = 6;
- // Mandated by CENC spec
- RCHECK(iv_size == 8 || iv_size == 16);
-
- memset(iv, 0, sizeof(iv));
- for (int i = 0; i < iv_size; i++)
- RCHECK(reader->Read1(&iv[i]));
-
- if (!reader->HasBytes(1)) return true;
-
- uint16 subsample_count;
- RCHECK(reader->Read2(&subsample_count) &&
- reader->HasBytes(subsample_count * kEntrySize));
-
- subsamples.resize(subsample_count);
- for (int i = 0; i < subsample_count; i++) {
- uint16 clear_bytes;
- uint32 cypher_bytes;
- RCHECK(reader->Read2(&clear_bytes) &&
- reader->Read4(&cypher_bytes));
- subsamples[i].clear_bytes = clear_bytes;
- subsamples[i].cypher_bytes = cypher_bytes;
- }
- return true;
-}
-
-bool FrameCENCInfo::GetTotalSizeOfSubsamples(size_t* total_size) const {
- size_t size = 0;
- for (size_t i = 0; i < subsamples.size(); i++) {
- size += subsamples[i].clear_bytes;
- RCHECK(size >= subsamples[i].clear_bytes); // overflow
- size += subsamples[i].cypher_bytes;
- RCHECK(size >= subsamples[i].cypher_bytes); // overflow
- }
- *total_size = size;
- return true;
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/cenc.h b/src/media/mp4/cenc.h
deleted file mode 100644
index e427091..0000000
--- a/src/media/mp4/cenc.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_CENC_H_
-#define MEDIA_MP4_CENC_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/decrypt_config.h"
-
-namespace media {
-namespace mp4 {
-
-class BufferReader;
-
-struct FrameCENCInfo {
- uint8 iv[16];
- std::vector<SubsampleEntry> subsamples;
-
- FrameCENCInfo();
- ~FrameCENCInfo();
- bool Parse(int iv_size, BufferReader* r) WARN_UNUSED_RESULT;
- bool GetTotalSizeOfSubsamples(size_t* total_size) const WARN_UNUSED_RESULT;
-};
-
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_CENC_H_
diff --git a/src/media/mp4/es_descriptor.cc b/src/media/mp4/es_descriptor.cc
deleted file mode 100644
index f9480dd..0000000
--- a/src/media/mp4/es_descriptor.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/es_descriptor.h"
-
-#include "media/base/bit_reader.h"
-#include "media/mp4/rcheck.h"
-
-// The elementary stream size is specific by up to 4 bytes.
-// The MSB of a byte indicates if there are more bytes for the size.
-static bool ReadESSize(media::BitReader* reader, uint32* size) {
- uint8 msb;
- uint8 byte;
-
- *size = 0;
-
- for (size_t i = 0; i < 4; ++i) {
- RCHECK(reader->ReadBits(1, &msb));
- RCHECK(reader->ReadBits(7, &byte));
- *size = (*size << 7) + byte;
-
- if (msb == 0)
- break;
- }
-
- return true;
-}
-
-namespace media {
-
-namespace mp4 {
-
-ESDescriptor::ESDescriptor()
- : object_type_(kForbidden) {
-}
-
-ESDescriptor::~ESDescriptor() {}
-
-bool ESDescriptor::Parse(const std::vector<uint8>& data) {
- BitReader reader(&data[0], data.size());
- uint8 tag;
- uint32 size;
- uint8 stream_dependency_flag;
- uint8 url_flag;
- uint8 ocr_stream_flag;
- uint16 dummy;
-
- RCHECK(reader.ReadBits(8, &tag));
- RCHECK(tag == kESDescrTag);
- RCHECK(ReadESSize(&reader, &size));
-
- RCHECK(reader.ReadBits(16, &dummy)); // ES_ID
- RCHECK(reader.ReadBits(1, &stream_dependency_flag));
- RCHECK(reader.ReadBits(1, &url_flag));
- RCHECK(!url_flag); // We don't support url flag
- RCHECK(reader.ReadBits(1, &ocr_stream_flag));
- RCHECK(reader.ReadBits(5, &dummy)); // streamPriority
-
- if (stream_dependency_flag)
- RCHECK(reader.ReadBits(16, &dummy)); // dependsOn_ES_ID
- if (ocr_stream_flag)
- RCHECK(reader.ReadBits(16, &dummy)); // OCR_ES_Id
-
- RCHECK(ParseDecoderConfigDescriptor(&reader));
-
- return true;
-}
-
-uint8 ESDescriptor::object_type() const {
- return object_type_;
-}
-
-const std::vector<uint8>& ESDescriptor::decoder_specific_info() const {
- return decoder_specific_info_;
-}
-
-bool ESDescriptor::ParseDecoderConfigDescriptor(BitReader* reader) {
- uint8 tag;
- uint32 size;
- uint64 dummy;
-
- RCHECK(reader->ReadBits(8, &tag));
- RCHECK(tag == kDecoderConfigDescrTag);
- RCHECK(ReadESSize(reader, &size));
-
- RCHECK(reader->ReadBits(8, &object_type_));
- RCHECK(reader->ReadBits(64, &dummy));
- RCHECK(reader->ReadBits(32, &dummy));
- RCHECK(ParseDecoderSpecificInfo(reader));
-
- return true;
-}
-
-bool ESDescriptor::ParseDecoderSpecificInfo(BitReader* reader) {
- uint8 tag;
- uint32 size;
-
- RCHECK(reader->ReadBits(8, &tag));
- RCHECK(tag == kDecoderSpecificInfoTag);
- RCHECK(ReadESSize(reader, &size));
-
- decoder_specific_info_.resize(size);
- for (uint32 i = 0; i < size; ++i)
- RCHECK(reader->ReadBits(8, &decoder_specific_info_[i]));
-
- return true;
-}
-
-} // namespace mp4
-
-} // namespace media
diff --git a/src/media/mp4/es_descriptor.h b/src/media/mp4/es_descriptor.h
deleted file mode 100644
index daddbc0..0000000
--- a/src/media/mp4/es_descriptor.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_ES_DESCRIPTOR_H_
-#define MEDIA_MP4_ES_DESCRIPTOR_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class BitReader;
-
-namespace mp4 {
-
-// The following values are extracted from ISO 14496 Part 1 Table 5 -
-// objectTypeIndication Values. Only values currently in use are included.
-enum ObjectType {
- kForbidden = 0,
- kISO_14496_3 = 0x40 // MPEG4 AAC
-};
-
-// This class parse object type and decoder specific information from an
-// elementary stream descriptor, which is usually contained in an esds box.
-// Please refer to ISO 14496 Part 1 7.2.6.5 for more details.
-class MEDIA_EXPORT ESDescriptor {
- public:
- ESDescriptor();
- ~ESDescriptor();
-
- bool Parse(const std::vector<uint8>& data);
-
- uint8 object_type() const;
- const std::vector<uint8>& decoder_specific_info() const;
-
- private:
- enum Tag {
- kESDescrTag = 0x03,
- kDecoderConfigDescrTag = 0x04,
- kDecoderSpecificInfoTag = 0x05
- };
-
- bool ParseDecoderConfigDescriptor(BitReader* reader);
- bool ParseDecoderSpecificInfo(BitReader* reader);
-
- uint8 object_type_;
- std::vector<uint8> decoder_specific_info_;
-};
-
-} // namespace mp4
-
-} // namespace media
-
-#endif // MEDIA_MP4_ES_DESCRIPTOR_H_
diff --git a/src/media/mp4/es_descriptor_unittest.cc b/src/media/mp4/es_descriptor_unittest.cc
deleted file mode 100644
index c3a39fb..0000000
--- a/src/media/mp4/es_descriptor_unittest.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/es_descriptor.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-namespace mp4 {
-
-TEST(ESDescriptorTest, SingleByteLengthTest) {
- ESDescriptor es_desc;
- uint8 buffer[] = {
- 0x03, 0x19, 0x00, 0x01, 0x00, 0x04, 0x11, 0x40,
- 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x12, 0x10,
- 0x06, 0x01, 0x02
- };
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_EQ(es_desc.object_type(), kForbidden);
- EXPECT_TRUE(es_desc.Parse(data));
- EXPECT_EQ(es_desc.object_type(), kISO_14496_3);
- EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u);
- EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12);
- EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10);
-}
-
-TEST(ESDescriptorTest, NonAACTest) {
- ESDescriptor es_desc;
- uint8 buffer[] = {
- 0x03, 0x19, 0x00, 0x01, 0x00, 0x04, 0x11, 0x66,
- 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x12, 0x10,
- 0x06, 0x01, 0x02
- };
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(es_desc.Parse(data));
- EXPECT_NE(es_desc.object_type(), kISO_14496_3);
- EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u);
- EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12);
- EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10);
-}
-
-TEST(ESDescriptorTest, MultiByteLengthTest) {
- ESDescriptor es_desc;
- uint8 buffer[] = {
- 0x03, 0x80, 0x19, 0x00, 0x01, 0x00, 0x04, 0x80,
- 0x80, 0x11, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x80, 0x80, 0x80, 0x02, 0x12, 0x10, 0x06, 0x01,
- 0x02
- };
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(es_desc.Parse(data));
- EXPECT_EQ(es_desc.object_type(), kISO_14496_3);
- EXPECT_EQ(es_desc.decoder_specific_info().size(), 2u);
- EXPECT_EQ(es_desc.decoder_specific_info()[0], 0x12);
- EXPECT_EQ(es_desc.decoder_specific_info()[1], 0x10);
-}
-
-TEST(ESDescriptorTest, FiveByteLengthTest) {
- ESDescriptor es_desc;
- uint8 buffer[] = {
- 0x03, 0x80, 0x19, 0x00, 0x01, 0x00, 0x04, 0x80,
- 0x80, 0x11, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x80, 0x80, 0x80, 0x80, 0x02, 0x12, 0x10, 0x06,
- 0x01, 0x02
- };
- std::vector<uint8> data;
-
- data.assign(buffer, buffer + sizeof(buffer));
-
- EXPECT_TRUE(es_desc.Parse(data));
- EXPECT_EQ(es_desc.object_type(), kISO_14496_3);
- EXPECT_EQ(es_desc.decoder_specific_info().size(), 0u);
-}
-
-} // namespace mp4
-
-} // namespace media
diff --git a/src/media/mp4/fourccs.h b/src/media/mp4/fourccs.h
deleted file mode 100644
index a986f34..0000000
--- a/src/media/mp4/fourccs.h
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_FOURCCS_H_
-#define MEDIA_MP4_FOURCCS_H_
-
-#include <string>
-
-namespace media {
-namespace mp4 {
-
-enum FourCC {
- FOURCC_NULL = 0,
- FOURCC_AVC1 = 0x61766331,
- FOURCC_AVCC = 0x61766343,
- FOURCC_CENC = 0x63656e63,
- FOURCC_CO64 = 0x636f3634,
- FOURCC_CTTS = 0x63747473,
- FOURCC_DINF = 0x64696e66,
- FOURCC_EDTS = 0x65647473,
- FOURCC_ELST = 0x656c7374,
- FOURCC_ENCA = 0x656e6361,
- FOURCC_ENCV = 0x656e6376,
- FOURCC_ESDS = 0x65736473,
- FOURCC_FREE = 0x66726565,
- FOURCC_FRMA = 0x66726d61,
- FOURCC_FTYP = 0x66747970,
- FOURCC_HDLR = 0x68646c72,
- FOURCC_HINT = 0x68696e74,
- FOURCC_IODS = 0x696f6473,
- FOURCC_MDAT = 0x6d646174,
- FOURCC_MDHD = 0x6d646864,
- FOURCC_MDIA = 0x6d646961,
- FOURCC_MECO = 0x6d65636f,
- FOURCC_MEHD = 0x6d656864,
- FOURCC_META = 0x6d657461,
- FOURCC_MFHD = 0x6d666864,
- FOURCC_MFRA = 0x6d667261,
- FOURCC_MINF = 0x6d696e66,
- FOURCC_MOOF = 0x6d6f6f66,
- FOURCC_MOOV = 0x6d6f6f76,
- FOURCC_MP4A = 0x6d703461,
- FOURCC_MP4V = 0x6d703476,
- FOURCC_MVEX = 0x6d766578,
- FOURCC_MVHD = 0x6d766864,
- FOURCC_PASP = 0x70617370,
- FOURCC_PDIN = 0x7064696e,
- FOURCC_PRFT = 0x70726674,
- FOURCC_PSSH = 0x70737368,
- FOURCC_SAIO = 0x7361696f,
- FOURCC_SAIZ = 0x7361697a,
- FOURCC_SCHI = 0x73636869,
- FOURCC_SCHM = 0x7363686d,
- FOURCC_SDTP = 0x73647470,
- FOURCC_SIDX = 0x73696478,
- FOURCC_SINF = 0x73696e66,
- FOURCC_SKIP = 0x736b6970,
- FOURCC_SMHD = 0x736d6864,
- FOURCC_SOUN = 0x736f756e,
- FOURCC_SSIX = 0x73736978,
- FOURCC_STBL = 0x7374626c,
- FOURCC_STCO = 0x7374636f,
- FOURCC_STSC = 0x73747363,
- FOURCC_STSD = 0x73747364,
- FOURCC_STSS = 0x73747373,
- FOURCC_STSZ = 0x7374737a,
- FOURCC_STTS = 0x73747473,
- FOURCC_STYP = 0x73747970,
- FOURCC_TENC = 0x74656e63,
- FOURCC_TFDT = 0x74666474,
- FOURCC_TFHD = 0x74666864,
- FOURCC_TKHD = 0x746b6864,
- FOURCC_TRAF = 0x74726166,
- FOURCC_TRAK = 0x7472616b,
- FOURCC_TREX = 0x74726578,
- FOURCC_TRUN = 0x7472756e,
- FOURCC_UDTA = 0x75647461,
- FOURCC_UUID = 0x75756964,
- FOURCC_VIDE = 0x76696465,
- FOURCC_VMHD = 0x766d6864,
- FOURCC_WIDE = 0x77696465,
-};
-
-const inline std::string FourCCToString(FourCC fourcc) {
- char buf[5];
- buf[0] = (fourcc >> 24) & 0xff;
- buf[1] = (fourcc >> 16) & 0xff;
- buf[2] = (fourcc >> 8) & 0xff;
- buf[3] = (fourcc) & 0xff;
- buf[4] = 0;
- return std::string(buf);
-}
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_FOURCCS_H_
diff --git a/src/media/mp4/mp4_stream_parser.cc b/src/media/mp4/mp4_stream_parser.cc
deleted file mode 100644
index 55339ff..0000000
--- a/src/media/mp4/mp4_stream_parser.cc
+++ /dev/null
@@ -1,572 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/mp4_stream_parser.h"
-
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/logging.h"
-#include "base/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_types.h"
-#include "media/base/video_util.h"
-#include "media/mp4/box_definitions.h"
-#include "media/mp4/box_reader.h"
-#include "media/mp4/es_descriptor.h"
-#include "media/mp4/rcheck.h"
-
-namespace media {
-namespace mp4 {
-
-// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
-static const char kMp4InitDataType[] = "video/mp4";
-
-MP4StreamParser::MP4StreamParser(bool has_sbr)
- : state_(kWaitingForInit),
- moof_head_(0),
- mdat_tail_(0),
- has_audio_(false),
- has_video_(false),
- audio_track_id_(0),
- video_track_id_(0),
- has_sbr_(has_sbr),
- is_audio_track_encrypted_(false),
- is_video_track_encrypted_(false) {
-}
-
-MP4StreamParser::~MP4StreamParser() {}
-
-void MP4StreamParser::Init(const InitCB& init_cb,
- const NewConfigCB& config_cb,
- const NewBuffersCB& audio_cb,
- const NewBuffersCB& video_cb,
- const NeedKeyCB& need_key_cb,
- const NewMediaSegmentCB& new_segment_cb,
- const base::Closure& end_of_segment_cb,
- const LogCB& log_cb) {
- DCHECK_EQ(state_, kWaitingForInit);
- DCHECK(init_cb_.is_null());
- DCHECK(!init_cb.is_null());
- DCHECK(!config_cb.is_null());
- DCHECK(!audio_cb.is_null() || !video_cb.is_null());
- DCHECK(!need_key_cb.is_null());
- DCHECK(!end_of_segment_cb.is_null());
-
- ChangeState(kParsingBoxes);
- init_cb_ = init_cb;
- config_cb_ = config_cb;
- audio_cb_ = audio_cb;
- video_cb_ = video_cb;
- need_key_cb_ = need_key_cb;
- new_segment_cb_ = new_segment_cb;
- end_of_segment_cb_ = end_of_segment_cb;
- log_cb_ = log_cb;
-}
-
-void MP4StreamParser::Reset() {
- queue_.Reset();
- moov_.reset();
- runs_.reset();
- moof_head_ = 0;
- mdat_tail_ = 0;
-}
-
-void MP4StreamParser::Flush() {
- DCHECK_NE(state_, kWaitingForInit);
- Reset();
- ChangeState(kParsingBoxes);
-}
-
-bool MP4StreamParser::Parse(const uint8* buf, int size) {
- DCHECK_NE(state_, kWaitingForInit);
-
- if (state_ == kError)
- return false;
-
- queue_.Push(buf, size);
-
- BufferQueue audio_buffers;
- BufferQueue video_buffers;
-
- bool result, err = false;
-
- do {
- if (state_ == kParsingBoxes) {
- result = ParseBox(&err);
- } else {
- DCHECK_EQ(kEmittingSamples, state_);
- result = EnqueueSample(&audio_buffers, &video_buffers, &err);
- if (result) {
- int64 max_clear = runs_->GetMaxClearOffset() + moof_head_;
- err = !ReadAndDiscardMDATsUntil(max_clear);
- }
- }
- } while (result && !err);
-
- if (!err)
- err = !SendAndFlushSamples(&audio_buffers, &video_buffers);
-
- if (err) {
- DLOG(ERROR) << "Error while parsing MP4";
- Reset();
- ChangeState(kError);
- return false;
- }
-
- return true;
-}
-
-bool MP4StreamParser::ParseBox(bool* err) {
- const uint8* buf;
- int size;
- queue_.Peek(&buf, &size);
- if (!size) return false;
-
- scoped_ptr<BoxReader> reader(
- BoxReader::ReadTopLevelBox(buf, size, log_cb_, err));
- if (reader.get() == NULL) return false;
-
- if (reader->type() == FOURCC_MOOV) {
- *err = !ParseMoov(reader.get());
- } else if (reader->type() == FOURCC_MOOF) {
- moof_head_ = queue_.head();
- *err = !ParseMoof(reader.get());
-
- // Set up first mdat offset for ReadMDATsUntil().
- mdat_tail_ = queue_.head() + reader->size();
-
- // Return early to avoid evicting 'moof' data from queue. Auxiliary info may
- // be located anywhere in the file, including inside the 'moof' itself.
- // (Since 'default-base-is-moof' is mandated, no data references can come
- // before the head of the 'moof', so keeping this box around is sufficient.)
- return !(*err);
- } else {
- MEDIA_LOG(log_cb_) << "Skipping unrecognized top-level box: "
- << FourCCToString(reader->type());
- }
-
- queue_.Pop(reader->size());
- return !(*err);
-}
-
-
-bool MP4StreamParser::ParseMoov(BoxReader* reader) {
- moov_.reset(new Movie);
- RCHECK(moov_->Parse(reader));
- runs_.reset(new TrackRunIterator(moov_.get(), log_cb_));
-
- has_audio_ = false;
- has_video_ = false;
-
- AudioDecoderConfig audio_config;
- VideoDecoderConfig video_config;
-
- for (std::vector<Track>::const_iterator track = moov_->tracks.begin();
- track != moov_->tracks.end(); ++track) {
- // TODO(strobe): Only the first audio and video track present in a file are
- // used. (Track selection is better accomplished via Source IDs, though, so
- // adding support for track selection within a stream is low-priority.)
- const SampleDescription& samp_descr =
- track->media.information.sample_table.description;
-
- // TODO(strobe): When codec reconfigurations are supported, detect and send
- // a codec reconfiguration for fragments using a sample description index
- // different from the previous one
- size_t desc_idx = 0;
- for (size_t t = 0; t < moov_->extends.tracks.size(); t++) {
- const TrackExtends& trex = moov_->extends.tracks[t];
- if (trex.track_id == track->header.track_id) {
- desc_idx = trex.default_sample_description_index;
- break;
- }
- }
- RCHECK(desc_idx > 0);
- desc_idx -= 1; // BMFF descriptor index is one-based
-
- if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) {
- RCHECK(!samp_descr.audio_entries.empty());
-
- // It is not uncommon to find otherwise-valid files with incorrect sample
- // description indices, so we fail gracefully in that case.
- if (desc_idx >= samp_descr.audio_entries.size())
- desc_idx = 0;
- const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx];
- const AAC& aac = entry.esds.aac;
-
- if (!(entry.format == FOURCC_MP4A ||
- (entry.format == FOURCC_ENCA &&
- entry.sinf.format.format == FOURCC_MP4A))) {
- LOG(ERROR) << "Unsupported audio format.";
- return false;
- }
- // Check if it is MPEG4 AAC defined in ISO 14496 Part 3.
- if (entry.esds.object_type != kISO_14496_3) {
- LOG(ERROR) << "Unsupported audio object type.";
- return false;
- }
-
- is_audio_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
- DVLOG(1) << "is_audio_track_encrypted_: " << is_audio_track_encrypted_;
- audio_config.Initialize(
- kCodecAAC, entry.samplesize, aac.channel_layout(),
- aac.GetOutputSamplesPerSecond(has_sbr_),
- aac.raw_data().empty() ? NULL : &aac.raw_data().front(),
- aac.raw_data().size(), is_audio_track_encrypted_, false);
- has_audio_ = true;
- audio_track_id_ = track->header.track_id;
- }
- if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) {
- RCHECK(!samp_descr.video_entries.empty());
- if (desc_idx >= samp_descr.video_entries.size())
- desc_idx = 0;
- const VideoSampleEntry& entry = samp_descr.video_entries[desc_idx];
-
- if (!(entry.format == FOURCC_AVC1 ||
- (entry.format == FOURCC_ENCV &&
- entry.sinf.format.format == FOURCC_AVC1))) {
- LOG(ERROR) << "Unsupported video format.";
- return false;
- }
-
- // TODO(strobe): Recover correct crop box
- gfx::Size coded_size(entry.width, entry.height);
- gfx::Rect visible_rect(coded_size);
- gfx::Size natural_size = GetNaturalSize(visible_rect.size(),
- entry.pixel_aspect.h_spacing,
- entry.pixel_aspect.v_spacing);
- is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
- DVLOG(1) << "is_video_track_encrypted_: " << is_video_track_encrypted_;
-#if defined(__LB_SHELL__) || defined(COBALT)
- // VideoDecoderConfig::Matches() only compares things like profile and
- // resolution. So it cannot catch the difference for subtle changes on
- // things like interface mode. Now we send full sps/pps information as
- // extra data as this also gets compared inside
- // VideoDecoderConfig::Matches().
- std::vector<uint8> param_sets;
- RCHECK(AVC::ConvertConfigToAnnexB(entry.avcc, ¶m_sets));
- video_config.Initialize(kCodecH264,
- H264PROFILE_MAIN,
- VideoFrame::NATIVE_TEXTURE,
- COLOR_SPACE_HD_REC709,
- coded_size,
- visible_rect,
- natural_size,
- ¶m_sets[0],
- param_sets.size(),
- is_video_track_encrypted_,
- true);
-#else
- video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
- COLOR_SPACE_HD_REC709, coded_size, visible_rect,
- natural_size,
- // No decoder-specific buffer needed for AVC;
- // SPS/PPS are embedded in the video stream
- NULL, 0, is_video_track_encrypted_, true);
-#endif
- has_video_ = true;
- video_track_id_ = track->header.track_id;
- }
- }
-
- RCHECK(config_cb_.Run(audio_config, video_config));
-
- base::TimeDelta duration;
- if (moov_->extends.header.fragment_duration > 0) {
- duration = TimeDeltaFromRational(moov_->extends.header.fragment_duration,
- moov_->header.timescale);
- } else if (moov_->header.duration > 0) {
- duration = TimeDeltaFromRational(moov_->header.duration,
- moov_->header.timescale);
- } else {
- duration = kInfiniteDuration();
- }
-
- if (!init_cb_.is_null())
- base::ResetAndReturn(&init_cb_).Run(true, duration);
-
- RCHECK(EmitNeedKeyIfNecessary(moov_->pssh));
- return true;
-}
-
-bool MP4StreamParser::ParseMoof(BoxReader* reader) {
- RCHECK(moov_.get()); // Must already have initialization segment
- MovieFragment moof;
- RCHECK(moof.Parse(reader));
- RCHECK(runs_->Init(moof));
- RCHECK(EmitNeedKeyIfNecessary(moof.pssh));
- new_segment_cb_.Run(runs_->GetMinDecodeTimestamp());
- ChangeState(kEmittingSamples);
- return true;
-}
-
-bool MP4StreamParser::EmitNeedKeyIfNecessary(
- const std::vector<ProtectionSystemSpecificHeader>& headers) {
- // TODO(strobe): ensure that the value of init_data (all PSSH headers
- // concatenated in arbitrary order) matches the EME spec.
- // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673.
- if (headers.empty())
- return true;
-
- size_t total_size = 0;
- for (size_t i = 0; i < headers.size(); i++)
- total_size += headers[i].raw_box.size();
-
- scoped_array<uint8> init_data(new uint8[total_size]);
- size_t pos = 0;
- for (size_t i = 0; i < headers.size(); i++) {
- memcpy(&init_data.get()[pos], &headers[i].raw_box[0],
- headers[i].raw_box.size());
- pos += headers[i].raw_box.size();
- }
- return need_key_cb_.Run(kMp4InitDataType, init_data.Pass(), total_size);
-}
-
-bool MP4StreamParser::PrepareAVCBuffer(
- const AVCDecoderConfigurationRecord& avc_config,
- std::vector<uint8>* frame_buf,
- std::vector<SubsampleEntry>* subsamples) const {
- // Convert the AVC NALU length fields to Annex B headers, as expected by
- // decoding libraries. Since this may enlarge the size of the buffer, we also
- // update the clear byte count for each subsample if encryption is used to
- // account for the difference in size between the length prefix and Annex B
- // start code.
- RCHECK(AVC::ConvertFrameToAnnexB(avc_config.length_size, frame_buf));
- if (!subsamples->empty()) {
- const int nalu_size_diff = 4 - avc_config.length_size;
- size_t expected_size = runs_->sample_size() +
- subsamples->size() * nalu_size_diff;
- RCHECK(frame_buf->size() == expected_size);
- for (size_t i = 0; i < subsamples->size(); i++)
- (*subsamples)[i].clear_bytes += nalu_size_diff;
- }
- const bool prepend_header = runs_->is_keyframe();
- if (prepend_header) {
- // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of
- // a frame. If subsample info is present, we also update the clear byte
- // count for that first subsample.
- std::vector<uint8> param_sets;
- RCHECK(AVC::ConvertConfigToAnnexB(avc_config, ¶m_sets));
- frame_buf->insert(frame_buf->begin(),
- param_sets.begin(), param_sets.end());
- if (!subsamples->empty())
- (*subsamples)[0].clear_bytes += param_sets.size();
- }
- return true;
-}
-
-bool MP4StreamParser::PrepareAACBuffer(
- const AAC& aac_config, std::vector<uint8>* frame_buf,
- std::vector<SubsampleEntry>* subsamples) const {
-#if !defined(COBALT_WIN)
- // Append an ADTS header to every audio sample.
- RCHECK(aac_config.ConvertEsdsToADTS(frame_buf));
-
- // As above, adjust subsample information to account for the headers. AAC is
- // not required to use subsample encryption, so we may need to add an entry.
- if (subsamples->empty()) {
- SubsampleEntry entry;
- entry.clear_bytes = AAC::kADTSHeaderSize;
- entry.cypher_bytes = frame_buf->size() - AAC::kADTSHeaderSize;
- subsamples->push_back(entry);
- } else {
- (*subsamples)[0].clear_bytes += AAC::kADTSHeaderSize;
- }
-#endif // !defined(COBALT_WIN)
- return true;
-}
-
-bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
- BufferQueue* video_buffers,
- bool* err) {
- if (!runs_->IsRunValid()) {
- // Flush any buffers we've gotten in this chunk so that buffers don't
- // cross NewSegment() calls
- *err = !SendAndFlushSamples(audio_buffers, video_buffers);
- if (*err)
- return false;
-
- // Remain in kEnqueueingSamples state, discarding data, until the end of
- // the current 'mdat' box has been appended to the queue.
- if (!queue_.Trim(mdat_tail_))
- return false;
-
- ChangeState(kParsingBoxes);
- end_of_segment_cb_.Run();
- return true;
- }
-
- if (!runs_->IsSampleValid()) {
- runs_->AdvanceRun();
- return true;
- }
-
- DCHECK(!(*err));
-
- const uint8* buf;
- int buf_size;
- queue_.Peek(&buf, &buf_size);
- if (!buf_size) return false;
-
- bool audio = has_audio_ && audio_track_id_ == runs_->track_id();
- bool video = has_video_ && video_track_id_ == runs_->track_id();
-
- // Skip this entire track if it's not one we're interested in
- if (!audio && !video)
- runs_->AdvanceRun();
-
- // Attempt to cache the auxiliary information first. Aux info is usually
- // placed in a contiguous block before the sample data, rather than being
- // interleaved. If we didn't cache it, this would require that we retain the
- // start of the segment buffer while reading samples. Aux info is typically
- // quite small compared to sample data, so this pattern is useful on
- // memory-constrained devices where the source buffer consumes a substantial
- // portion of the total system memory.
- if (runs_->AuxInfoNeedsToBeCached()) {
- queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size);
- if (buf_size < runs_->aux_info_size()) return false;
- *err = !runs_->CacheAuxInfo(buf, buf_size);
- return !*err;
- }
-
- queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &buf_size);
- if (buf_size < runs_->sample_size()) return false;
-
- scoped_ptr<DecryptConfig> decrypt_config;
- std::vector<SubsampleEntry> subsamples;
- if (runs_->is_encrypted()) {
- decrypt_config = runs_->GetDecryptConfig();
- subsamples = decrypt_config->subsamples();
- }
-
- std::vector<uint8> frame_buf(buf, buf + runs_->sample_size());
- if (video) {
- if (!PrepareAVCBuffer(runs_->video_description().avcc,
- &frame_buf, &subsamples)) {
- MEDIA_LOG(log_cb_) << "Failed to prepare AVC sample for decode";
- *err = true;
- return false;
- }
- }
-
- if (audio) {
- if (!PrepareAACBuffer(runs_->audio_description().esds.aac,
- &frame_buf, &subsamples)) {
- MEDIA_LOG(log_cb_) << "Failed to prepare AAC sample for decode";
- *err = true;
- return false;
- }
- }
-
- if (decrypt_config) {
- if (!subsamples.empty()) {
- // Create a new config with the updated subsamples.
- decrypt_config.reset(new DecryptConfig(
- decrypt_config->key_id(),
- decrypt_config->iv(),
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- decrypt_config->data_offset(),
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
- subsamples));
- }
- // else, use the existing config.
- } else if ((audio && is_audio_track_encrypted_) ||
- (video && is_video_track_encrypted_)) {
- // The media pipeline requires a DecryptConfig with an empty |iv|.
- // TODO(ddorwin): Refactor so we do not need a fake key ID ("1");
-#if defined(__LB_SHELL__) || defined(COBALT)
- decrypt_config.reset(
- new DecryptConfig("1", "", std::vector<SubsampleEntry>()));
-#else // !defined(__LB_SHELL__) || defined(COBALT)
- decrypt_config.reset(
- new DecryptConfig("1", "", 0, std::vector<SubsampleEntry>()));
-#endif // !defined(__LB_SHELL__) || defined(COBALT)
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- scoped_refptr<StreamParserBuffer> stream_buf =
- StreamParserBuffer::CopyFrom(&frame_buf[0],
- frame_buf.size(),
- runs_->is_keyframe());
-
- if (!stream_buf) {
- MEDIA_LOG(log_cb_) << "Failed to allocate StreamParserBuffer";
- *err = true;
- return false;
- }
-#else
- scoped_refptr<StreamParserBuffer> stream_buf =
- StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
- runs_->is_keyframe());
-#endif
-
- if (decrypt_config)
- stream_buf->SetDecryptConfig(decrypt_config.Pass());
-
- stream_buf->SetDuration(runs_->duration());
- stream_buf->SetTimestamp(runs_->cts());
- stream_buf->SetDecodeTimestamp(runs_->dts());
-
- DVLOG(3) << "Pushing frame: aud=" << audio
- << ", key=" << runs_->is_keyframe()
- << ", dur=" << runs_->duration().InMilliseconds()
- << ", dts=" << runs_->dts().InMilliseconds()
- << ", cts=" << runs_->cts().InMilliseconds()
- << ", size=" << runs_->sample_size();
-
- if (audio) {
- audio_buffers->push_back(stream_buf);
- } else {
- video_buffers->push_back(stream_buf);
- }
-
- runs_->AdvanceSample();
- return true;
-}
-
-bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers,
- BufferQueue* video_buffers) {
- bool err = false;
- if (!audio_buffers->empty()) {
- err |= (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers));
- audio_buffers->clear();
- }
- if (!video_buffers->empty()) {
- err |= (video_cb_.is_null() || !video_cb_.Run(*video_buffers));
- video_buffers->clear();
- }
- return !err;
-}
-
-bool MP4StreamParser::ReadAndDiscardMDATsUntil(const int64 offset) {
- bool err = false;
- while (mdat_tail_ < offset) {
- const uint8* buf;
- int size;
- queue_.PeekAt(mdat_tail_, &buf, &size);
-
- FourCC type;
- int box_sz;
- if (!BoxReader::StartTopLevelBox(buf, size, log_cb_,
- &type, &box_sz, &err))
- break;
-
- if (type != FOURCC_MDAT) {
- MEDIA_LOG(log_cb_) << "Unexpected box type while parsing MDATs: "
- << FourCCToString(type);
- }
- mdat_tail_ += box_sz;
- }
- queue_.Trim(std::min(mdat_tail_, offset));
- return !err;
-}
-
-void MP4StreamParser::ChangeState(State new_state) {
- DVLOG(2) << "Changing state: " << new_state;
- state_ = new_state;
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/mp4_stream_parser.h b/src/media/mp4/mp4_stream_parser.h
deleted file mode 100644
index 05206ef..0000000
--- a/src/media/mp4/mp4_stream_parser.h
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_MP4_STREAM_PARSER_H_
-#define MEDIA_MP4_MP4_STREAM_PARSER_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-#include "media/base/stream_parser.h"
-#include "media/mp4/offset_byte_queue.h"
-#include "media/mp4/track_run_iterator.h"
-
-namespace media {
-namespace mp4 {
-
-struct Movie;
-class BoxReader;
-
-class MEDIA_EXPORT MP4StreamParser : public StreamParser {
- public:
- MP4StreamParser(bool has_sbr);
- virtual ~MP4StreamParser();
-
- virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb,
- const NewBuffersCB& audio_cb,
- const NewBuffersCB& video_cb,
- const NeedKeyCB& need_key_cb,
- const NewMediaSegmentCB& new_segment_cb,
- const base::Closure& end_of_segment_cb,
- const LogCB& log_cb) OVERRIDE;
- virtual void Flush() OVERRIDE;
- virtual bool Parse(const uint8* buf, int size) OVERRIDE;
-
- private:
- enum State {
- kWaitingForInit,
- kParsingBoxes,
- kEmittingSamples,
- kError
- };
-
- bool ParseBox(bool* err);
- bool ParseMoov(mp4::BoxReader* reader);
- bool ParseMoof(mp4::BoxReader* reader);
-
- // Returns 'true' if sent or not required, 'false' if there was an error.
- bool EmitNeedKeyIfNecessary(
- const std::vector<ProtectionSystemSpecificHeader>& headers);
-
- // To retain proper framing, each 'mdat' atom must be read; to limit memory
- // usage, the atom's data needs to be discarded incrementally as frames are
- // extracted from the stream. This function discards data from the stream up
- // to |offset|, updating the |mdat_tail_| value so that framing can be
- // retained after all 'mdat' information has been read.
- // Returns 'true' on success, 'false' if there was an error.
- bool ReadAndDiscardMDATsUntil(const int64 offset);
-
- void ChangeState(State new_state);
-
- bool EmitConfigs();
- bool PrepareAVCBuffer(const AVCDecoderConfigurationRecord& avc_config,
- std::vector<uint8>* frame_buf,
- std::vector<SubsampleEntry>* subsamples) const;
- bool PrepareAACBuffer(const AAC& aac_config,
- std::vector<uint8>* frame_buf,
- std::vector<SubsampleEntry>* subsamples) const;
- bool EnqueueSample(BufferQueue* audio_buffers,
- BufferQueue* video_buffers,
- bool* err);
- bool SendAndFlushSamples(BufferQueue* audio_buffers,
- BufferQueue* video_buffers);
-
- void Reset();
-
- State state_;
- InitCB init_cb_;
- NewConfigCB config_cb_;
- NewBuffersCB audio_cb_;
- NewBuffersCB video_cb_;
- NeedKeyCB need_key_cb_;
- NewMediaSegmentCB new_segment_cb_;
- base::Closure end_of_segment_cb_;
- LogCB log_cb_;
-
- OffsetByteQueue queue_;
-
- // These two parameters are only valid in the |kEmittingSegments| state.
- //
- // |moof_head_| is the offset of the start of the most recently parsed moof
- // block. All byte offsets in sample information are relative to this offset,
- // as mandated by the Media Source spec.
- int64 moof_head_;
- // |mdat_tail_| is the stream offset of the end of the current 'mdat' box.
- // Valid iff it is greater than the head of the queue.
- int64 mdat_tail_;
-
- scoped_ptr<mp4::Movie> moov_;
- scoped_ptr<mp4::TrackRunIterator> runs_;
-
- bool has_audio_;
- bool has_video_;
- uint32 audio_track_id_;
- uint32 video_track_id_;
- bool has_sbr_;
- bool is_audio_track_encrypted_;
- bool is_video_track_encrypted_;
-
- DISALLOW_COPY_AND_ASSIGN(MP4StreamParser);
-};
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_
diff --git a/src/media/mp4/mp4_stream_parser_unittest.cc b/src/media/mp4/mp4_stream_parser_unittest.cc
deleted file mode 100644
index 66f3410..0000000
--- a/src/media/mp4/mp4_stream_parser_unittest.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <algorithm>
-#include <string>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/test_data_util.h"
-#include "media/base/video_decoder_config.h"
-#include "media/mp4/mp4_stream_parser.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::TimeDelta;
-
-namespace media {
-namespace mp4 {
-
-// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
-static const char kMp4InitDataType[] = "video/mp4";
-
-class MP4StreamParserTest : public testing::Test {
- public:
- MP4StreamParserTest()
- : parser_(new MP4StreamParser(false)),
- configs_received_(false) {
- }
-
- protected:
- scoped_ptr<MP4StreamParser> parser_;
- base::TimeDelta segment_start_;
- bool configs_received_;
-
- bool AppendData(const uint8* data, size_t length) {
- return parser_->Parse(data, length);
- }
-
- bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
- const uint8* start = data;
- const uint8* end = data + length;
- while (start < end) {
- size_t append_size = std::min(piece_size,
- static_cast<size_t>(end - start));
- if (!AppendData(start, append_size))
- return false;
- start += append_size;
- }
- return true;
- }
-
- void InitF(bool init_ok, base::TimeDelta duration) {
- DVLOG(1) << "InitF: ok=" << init_ok
- << ", dur=" << duration.InMilliseconds();
- }
-
- bool NewConfigF(const AudioDecoderConfig& ac, const VideoDecoderConfig& vc) {
- DVLOG(1) << "NewConfigF: audio=" << ac.IsValidConfig()
- << ", video=" << vc.IsValidConfig();
- configs_received_ = true;
- return true;
- }
-
- bool NewBuffersF(const StreamParser::BufferQueue& bufs) {
- DVLOG(2) << "NewBuffersF: " << bufs.size() << " buffers";
- for (StreamParser::BufferQueue::const_iterator buf = bufs.begin();
- buf != bufs.end(); buf++) {
- DVLOG(3) << " n=" << buf - bufs.begin()
- << ", size=" << (*buf)->GetDataSize()
- << ", dur=" << (*buf)->GetDuration().InMilliseconds();
- EXPECT_GE((*buf)->GetTimestamp(), segment_start_);
- }
- return true;
- }
-
- bool KeyNeededF(const std::string& type,
- scoped_array<uint8> init_data, int init_data_size) {
- DVLOG(1) << "KeyNeededF: " << init_data_size;
- EXPECT_EQ(kMp4InitDataType, type);
- EXPECT_TRUE(init_data.get());
- EXPECT_GT(init_data_size, 0);
- return true;
- }
-
- void NewSegmentF(TimeDelta start_dts) {
- DVLOG(1) << "NewSegmentF: " << start_dts.InMilliseconds();
- segment_start_ = start_dts;
- }
-
- void EndOfSegmentF() {
- DVLOG(1) << "EndOfSegmentF()";
- }
-
- void InitializeParser() {
- parser_->Init(
- base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::NewConfigF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)),
- base::Bind(&MP4StreamParserTest::EndOfSegmentF,
- base::Unretained(this)),
- LogCB());
- }
-
- bool ParseMP4File(const std::string& filename, int append_bytes) {
- InitializeParser();
-
- scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
- EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
- buffer->GetDataSize(),
- append_bytes));
- return true;
- }
-};
-
-TEST_F(MP4StreamParserTest, TestUnalignedAppend) {
- // Test small, non-segment-aligned appends (small enough to exercise
- // incremental append system)
- ParseMP4File("bear.1280x720_dash.mp4", 512);
-}
-
-TEST_F(MP4StreamParserTest, TestBytewiseAppend) {
- // Ensure no incremental errors occur when parsing
- ParseMP4File("bear.1280x720_dash.mp4", 1);
-}
-
-TEST_F(MP4StreamParserTest, TestMultiFragmentAppend) {
- // Large size ensures multiple fragments are appended in one call (size is
- // larger than this particular test file)
- ParseMP4File("bear.1280x720_dash.mp4", 768432);
-}
-
-TEST_F(MP4StreamParserTest, TestFlush) {
- // Flush while reading sample data, then start a new stream.
- InitializeParser();
-
- scoped_refptr<DecoderBuffer> buffer =
- ReadTestDataFile("bear.1280x720_dash.mp4");
- EXPECT_TRUE(AppendDataInPieces(buffer->GetData(), 65536, 512));
- parser_->Flush();
- EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
- buffer->GetDataSize(),
- 512));
-}
-
-TEST_F(MP4StreamParserTest, TestReinitialization) {
- InitializeParser();
-
- scoped_refptr<DecoderBuffer> buffer =
- ReadTestDataFile("bear.1280x720_dash.mp4");
- EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
- buffer->GetDataSize(),
- 512));
- EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
- buffer->GetDataSize(),
- 512));
-}
-
-// TODO(strobe): Create and test media which uses CENC auxiliary info stored
-// inside a private box
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/offset_byte_queue.cc b/src/media/mp4/offset_byte_queue.cc
deleted file mode 100644
index a530150..0000000
--- a/src/media/mp4/offset_byte_queue.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/offset_byte_queue.h"
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-
-namespace media {
-
-OffsetByteQueue::OffsetByteQueue() : buf_(NULL), size_(0), head_(0) {}
-OffsetByteQueue::~OffsetByteQueue() {}
-
-void OffsetByteQueue::Reset() {
- queue_.Reset();
- buf_ = NULL;
- size_ = 0;
- head_ = 0;
-}
-
-void OffsetByteQueue::Push(const uint8* buf, int size) {
- queue_.Push(buf, size);
- Sync();
- DVLOG(4) << "Buffer pushed. head=" << head() << " tail=" << tail();
-}
-
-void OffsetByteQueue::Peek(const uint8** buf, int* size) {
- *buf = size_ > 0 ? buf_ : NULL;
- *size = size_;
-}
-
-void OffsetByteQueue::Pop(int count) {
- queue_.Pop(count);
- head_ += count;
- Sync();
-}
-
-void OffsetByteQueue::PeekAt(int64 offset, const uint8** buf, int* size) {
- DCHECK(offset >= head());
- if (offset < head() || offset >= tail()) {
- *buf = NULL;
- *size = 0;
- return;
- }
- *buf = &buf_[offset - head()];
- *size = tail() - offset;
-}
-
-bool OffsetByteQueue::Trim(int64 max_offset) {
- if (max_offset < head_) return true;
- if (max_offset > tail()) {
- Pop(size_);
- return false;
- }
- Pop(max_offset - head_);
- return true;
-}
-
-void OffsetByteQueue::Sync() {
- queue_.Peek(&buf_, &size_);
-}
-
-} // namespace media
diff --git a/src/media/mp4/offset_byte_queue.h b/src/media/mp4/offset_byte_queue.h
deleted file mode 100644
index 9349b96..0000000
--- a/src/media/mp4/offset_byte_queue.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_OFFSET_BYTE_QUEUE_H_
-#define MEDIA_MP4_OFFSET_BYTE_QUEUE_H_
-
-#include "base/basictypes.h"
-#include "media/base/byte_queue.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// A wrapper around a ByteQueue which maintains a notion of a
-// monotonically-increasing offset. All buffer access is done by passing these
-// offsets into this class, going some way towards preventing the proliferation
-// of many different meanings of "offset", "head", etc.
-class MEDIA_EXPORT OffsetByteQueue {
- public:
- OffsetByteQueue();
- ~OffsetByteQueue();
-
- // These work like their underlying ByteQueue counterparts.
- void Reset();
- void Push(const uint8* buf, int size);
- void Peek(const uint8** buf, int* size);
- void Pop(int count);
-
- // Sets |buf| to point at the first buffered byte corresponding to |offset|,
- // and |size| to the number of bytes available starting from that offset.
- //
- // It is an error if the offset is before the current head. It's not an error
- // if the current offset is beyond tail(), but you will of course get back
- // a null |buf| and a |size| of zero.
- void PeekAt(int64 offset, const uint8** buf, int* size);
-
- // Marks the bytes up to (but not including) |max_offset| as ready for
- // deletion. This is relatively inexpensive, but will not necessarily reduce
- // the resident buffer size right away (or ever).
- //
- // Returns true if the full range of bytes were successfully trimmed,
- // including the case where |max_offset| is less than the current head.
- // Returns false if |max_offset| > tail() (although all bytes currently
- // buffered are still cleared).
- bool Trim(int64 max_offset);
-
- // The head and tail positions, in terms of the file's absolute offsets.
- // tail() is an exclusive bound.
- int64 head() { return head_; }
- int64 tail() { return head_ + size_; }
-
- private:
- // Synchronize |buf_| and |size_| with |queue_|.
- void Sync();
-
- ByteQueue queue_;
- const uint8* buf_;
- int size_;
- int64 head_;
-
- DISALLOW_COPY_AND_ASSIGN(OffsetByteQueue);
-};
-
-} // namespace media
-
-#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_
diff --git a/src/media/mp4/offset_byte_queue_unittest.cc b/src/media/mp4/offset_byte_queue_unittest.cc
deleted file mode 100644
index e2d40dc..0000000
--- a/src/media/mp4/offset_byte_queue_unittest.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <string.h>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/mp4/offset_byte_queue.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class OffsetByteQueueTest : public testing::Test {
- public:
- virtual void SetUp() OVERRIDE {
- uint8 buf[256];
- for (int i = 0; i < 256; i++) {
- buf[i] = i;
- }
- queue_.reset(new OffsetByteQueue);
- queue_->Push(buf, sizeof(buf));
- queue_->Push(buf, sizeof(buf));
- queue_->Pop(384);
-
- // Queue will start with 128 bytes of data and an offset of 384 bytes.
- // These values are used throughout the test.
- }
-
- protected:
- scoped_ptr<OffsetByteQueue> queue_;
-};
-
-TEST_F(OffsetByteQueueTest, TestSetUp) {
- EXPECT_EQ(384, queue_->head());
- EXPECT_EQ(512, queue_->tail());
-
- const uint8* buf;
- int size;
-
- queue_->Peek(&buf, &size);
- EXPECT_EQ(128, size);
- EXPECT_EQ(128, buf[0]);
- EXPECT_EQ(255, buf[size-1]);
-}
-
-TEST_F(OffsetByteQueueTest, TestPeekAt) {
- const uint8* buf;
- int size;
-
- queue_->PeekAt(400, &buf, &size);
- EXPECT_EQ(queue_->tail() - 400, size);
- EXPECT_EQ(400 - 256, buf[0]);
-
- queue_->PeekAt(512, &buf, &size);
- EXPECT_EQ(NULL, buf);
- EXPECT_EQ(0, size);
-}
-
-TEST_F(OffsetByteQueueTest, TestTrim) {
- EXPECT_TRUE(queue_->Trim(128));
- EXPECT_TRUE(queue_->Trim(384));
- EXPECT_EQ(384, queue_->head());
- EXPECT_EQ(512, queue_->tail());
-
- EXPECT_TRUE(queue_->Trim(400));
- EXPECT_EQ(400, queue_->head());
- EXPECT_EQ(512, queue_->tail());
-
- const uint8* buf;
- int size;
- queue_->PeekAt(400, &buf, &size);
- EXPECT_EQ(queue_->tail() - 400, size);
- EXPECT_EQ(400 - 256, buf[0]);
-
- // Trimming to the exact end of the buffer should return 'true'. This
- // accomodates EOS cases.
- EXPECT_TRUE(queue_->Trim(512));
- EXPECT_EQ(512, queue_->head());
- queue_->Peek(&buf, &size);
- EXPECT_EQ(NULL, buf);
-
- // Trimming past the end of the buffer should return 'false'; we haven't seen
- // the preceeding bytes.
- EXPECT_FALSE(queue_->Trim(513));
-
- // However, doing that shouldn't affect the EOS case. Only adding new data
- // should alter this behavior.
- EXPECT_TRUE(queue_->Trim(512));
-}
-
-} // namespace media
diff --git a/src/media/mp4/rcheck.h b/src/media/mp4/rcheck.h
deleted file mode 100644
index 8165056..0000000
--- a/src/media/mp4/rcheck.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_RCHECK_H_
-#define MEDIA_MP4_RCHECK_H_
-
-#include "base/logging.h"
-
-#define RCHECK(x) \
- do { \
- if (!(x)) { \
- DLOG(ERROR) << "Failure while parsing MP4: " << #x; \
- return false; \
- } \
- } while (0)
-
-#endif // MEDIA_MP4_RCHECK_H_
diff --git a/src/media/mp4/track_run_iterator.cc b/src/media/mp4/track_run_iterator.cc
deleted file mode 100644
index 13202b2..0000000
--- a/src/media/mp4/track_run_iterator.cc
+++ /dev/null
@@ -1,452 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/mp4/track_run_iterator.h"
-
-#include <algorithm>
-
-#include "media/base/stream_parser_buffer.h"
-#include "media/mp4/rcheck.h"
-
-namespace {
-static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
-}
-
-namespace media {
-namespace mp4 {
-
-struct SampleInfo {
- int size;
- int duration;
- int cts_offset;
- bool is_keyframe;
-};
-
-struct TrackRunInfo {
- uint32 track_id;
- std::vector<SampleInfo> samples;
- int64 timescale;
- int64 start_dts;
- int64 sample_start_offset;
-
- bool is_audio;
- const AudioSampleEntry* audio_description;
- const VideoSampleEntry* video_description;
-
- int64 aux_info_start_offset; // Only valid if aux_info_total_size > 0.
- int aux_info_default_size;
- std::vector<uint8> aux_info_sizes; // Populated if default_size == 0.
- int aux_info_total_size;
-
- TrackRunInfo();
- ~TrackRunInfo();
-};
-
-TrackRunInfo::TrackRunInfo()
- : track_id(0),
- timescale(-1),
- start_dts(-1),
- sample_start_offset(-1),
- is_audio(false),
- aux_info_start_offset(-1),
- aux_info_default_size(-1),
- aux_info_total_size(-1) {
-}
-TrackRunInfo::~TrackRunInfo() {}
-
-TimeDelta TimeDeltaFromRational(int64 numer, int64 denom) {
- DCHECK_LT((numer > 0 ? numer : -numer),
- kint64max / base::Time::kMicrosecondsPerSecond);
- return TimeDelta::FromMicroseconds(
- base::Time::kMicrosecondsPerSecond * numer / denom);
-}
-
-TrackRunIterator::TrackRunIterator(const Movie* moov,
- const LogCB& log_cb)
- : moov_(moov), log_cb_(log_cb), sample_offset_(0) {
- CHECK(moov);
-}
-
-TrackRunIterator::~TrackRunIterator() {}
-
-static void PopulateSampleInfo(const TrackExtends& trex,
- const TrackFragmentHeader& tfhd,
- const TrackFragmentRun& trun,
- const int64 edit_list_offset,
- const uint32 i,
- SampleInfo* sample_info) {
- if (i < trun.sample_sizes.size()) {
- sample_info->size = trun.sample_sizes[i];
- } else if (tfhd.default_sample_size > 0) {
- sample_info->size = tfhd.default_sample_size;
- } else {
- sample_info->size = trex.default_sample_size;
- }
-
- if (i < trun.sample_durations.size()) {
- sample_info->duration = trun.sample_durations[i];
- } else if (tfhd.default_sample_duration > 0) {
- sample_info->duration = tfhd.default_sample_duration;
- } else {
- sample_info->duration = trex.default_sample_duration;
- }
-
- if (i < trun.sample_composition_time_offsets.size()) {
- sample_info->cts_offset = trun.sample_composition_time_offsets[i];
- } else {
- sample_info->cts_offset = 0;
- }
- sample_info->cts_offset += edit_list_offset;
-
- uint32 flags;
- if (i < trun.sample_flags.size()) {
- flags = trun.sample_flags[i];
- } else if (tfhd.has_default_sample_flags) {
- flags = tfhd.default_sample_flags;
- } else {
- flags = trex.default_sample_flags;
- }
- sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask);
-}
-
-// In well-structured encrypted media, each track run will be immediately
-// preceded by its auxiliary information; this is the only optimal storage
-// pattern in terms of minimum number of bytes from a serial stream needed to
-// begin playback. It also allows us to optimize caching on memory-constrained
-// architectures, because we can cache the relatively small auxiliary
-// information for an entire run and then discard data from the input stream,
-// instead of retaining the entire 'mdat' box.
-//
-// We optimize for this situation (with no loss of generality) by sorting track
-// runs during iteration in order of their first data offset (either sample data
-// or auxiliary data).
-class CompareMinTrackRunDataOffset {
- public:
- bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) {
- int64 a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max;
- int64 b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max;
-
- int64 a_lesser = std::min(a_aux, a.sample_start_offset);
- int64 a_greater = std::max(a_aux, a.sample_start_offset);
- int64 b_lesser = std::min(b_aux, b.sample_start_offset);
- int64 b_greater = std::max(b_aux, b.sample_start_offset);
-
- if (a_lesser == b_lesser) return a_greater < b_greater;
- return a_lesser < b_lesser;
- }
-};
-
-bool TrackRunIterator::Init(const MovieFragment& moof) {
- runs_.clear();
-
- for (size_t i = 0; i < moof.tracks.size(); i++) {
- const TrackFragment& traf = moof.tracks[i];
-
- const Track* trak = NULL;
- for (size_t t = 0; t < moov_->tracks.size(); t++) {
- if (moov_->tracks[t].header.track_id == traf.header.track_id)
- trak = &moov_->tracks[t];
- }
- RCHECK(trak);
-
- const TrackExtends* trex = NULL;
- for (size_t t = 0; t < moov_->extends.tracks.size(); t++) {
- if (moov_->extends.tracks[t].track_id == traf.header.track_id)
- trex = &moov_->extends.tracks[t];
- }
- RCHECK(trex);
-
- const SampleDescription& stsd =
- trak->media.information.sample_table.description;
- if (stsd.type != kAudio && stsd.type != kVideo) {
- DVLOG(1) << "Skipping unhandled track type";
- continue;
- }
- size_t desc_idx = traf.header.sample_description_index;
- if (!desc_idx) desc_idx = trex->default_sample_description_index;
- RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file
- desc_idx -= 1;
-
- // Process edit list to remove CTS offset introduced in the presence of
- // B-frames (those that contain a single edit with a nonnegative media
- // time). Other uses of edit lists are not supported, as they are
- // both uncommon and better served by higher-level protocols.
- int64 edit_list_offset = 0;
- const std::vector<EditListEntry>& edits = trak->edit.list.edits;
- if (!edits.empty()) {
- if (edits.size() > 1)
- DVLOG(1) << "Multi-entry edit box detected; some components ignored.";
-
- if (edits[0].media_time < 0) {
- DVLOG(1) << "Empty edit list entry ignored.";
- } else {
- edit_list_offset = -edits[0].media_time;
- }
- }
-
- int64 run_start_dts = traf.decode_time.decode_time;
- int sample_count_sum = 0;
-
- for (size_t j = 0; j < traf.runs.size(); j++) {
- const TrackFragmentRun& trun = traf.runs[j];
- TrackRunInfo tri;
- tri.track_id = traf.header.track_id;
- tri.timescale = trak->media.header.timescale;
- tri.start_dts = run_start_dts;
- tri.sample_start_offset = trun.data_offset;
-
- tri.is_audio = (stsd.type == kAudio);
- if (tri.is_audio) {
- RCHECK(!stsd.audio_entries.empty());
- if (desc_idx > stsd.audio_entries.size())
- desc_idx = 0;
- tri.audio_description = &stsd.audio_entries[desc_idx];
- } else {
- RCHECK(!stsd.video_entries.empty());
- if (desc_idx > stsd.video_entries.size())
- desc_idx = 0;
- tri.video_description = &stsd.video_entries[desc_idx];
- }
-
- // Collect information from the auxiliary_offset entry with the same index
- // in the 'saiz' container as the current run's index in the 'trun'
- // container, if it is present.
- if (traf.auxiliary_offset.offsets.size() > j) {
- // There should be an auxiliary info entry corresponding to each sample
- // in the auxiliary offset entry's corresponding track run.
- RCHECK(traf.auxiliary_size.sample_count >=
- sample_count_sum + trun.sample_count);
- tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j];
- tri.aux_info_default_size =
- traf.auxiliary_size.default_sample_info_size;
- if (tri.aux_info_default_size == 0) {
- const std::vector<uint8>& sizes =
- traf.auxiliary_size.sample_info_sizes;
- tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(),
- sizes.begin() + sample_count_sum,
- sizes.begin() + sample_count_sum + trun.sample_count);
- }
-
- // If the default info size is positive, find the total size of the aux
- // info block from it, otherwise sum over the individual sizes of each
- // aux info entry in the aux_offset entry.
- if (tri.aux_info_default_size) {
- tri.aux_info_total_size =
- tri.aux_info_default_size * trun.sample_count;
- } else {
- tri.aux_info_total_size = 0;
- for (size_t k = 0; k < trun.sample_count; k++) {
- tri.aux_info_total_size += tri.aux_info_sizes[k];
- }
- }
- } else {
- tri.aux_info_start_offset = -1;
- tri.aux_info_total_size = 0;
- }
-
- tri.samples.resize(trun.sample_count);
- for (size_t k = 0; k < trun.sample_count; k++) {
- PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset,
- k, &tri.samples[k]);
- run_start_dts += tri.samples[k].duration;
- }
- runs_.push_back(tri);
- sample_count_sum += trun.sample_count;
- }
- }
-
- std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset());
- run_itr_ = runs_.begin();
- ResetRun();
- return true;
-}
-
-void TrackRunIterator::AdvanceRun() {
- ++run_itr_;
- ResetRun();
-}
-
-void TrackRunIterator::ResetRun() {
- if (!IsRunValid()) return;
- sample_dts_ = run_itr_->start_dts;
- sample_offset_ = run_itr_->sample_start_offset;
- sample_itr_ = run_itr_->samples.begin();
- cenc_info_.clear();
-}
-
-void TrackRunIterator::AdvanceSample() {
- DCHECK(IsSampleValid());
- sample_dts_ += sample_itr_->duration;
- sample_offset_ += sample_itr_->size;
- ++sample_itr_;
-}
-
-// This implementation only indicates a need for caching if CENC auxiliary
-// info is available in the stream.
-bool TrackRunIterator::AuxInfoNeedsToBeCached() {
- DCHECK(IsRunValid());
- return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0;
-}
-
-// This implementation currently only caches CENC auxiliary info.
-bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) {
- RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size());
-
- cenc_info_.resize(run_itr_->samples.size());
- int64 pos = 0;
- for (size_t i = 0; i < run_itr_->samples.size(); i++) {
- int info_size = run_itr_->aux_info_default_size;
- if (!info_size)
- info_size = run_itr_->aux_info_sizes[i];
-
- BufferReader reader(buf + pos, info_size);
- RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader));
- pos += info_size;
- }
-
- return true;
-}
-
-bool TrackRunIterator::IsRunValid() const {
- return run_itr_ != runs_.end();
-}
-
-bool TrackRunIterator::IsSampleValid() const {
- return IsRunValid() && (sample_itr_ != run_itr_->samples.end());
-}
-
-// Because tracks are in sorted order and auxiliary information is cached when
-// returning samples, it is guaranteed that no data will be required before the
-// lesser of the minimum data offset of this track and the next in sequence.
-// (The stronger condition - that no data is required before the minimum data
-// offset of this track alone - is not guaranteed, because the BMFF spec does
-// not have any inter-run ordering restrictions.)
-int64 TrackRunIterator::GetMaxClearOffset() {
- int64 offset = kint64max;
-
- if (IsSampleValid()) {
- offset = std::min(offset, sample_offset_);
- if (AuxInfoNeedsToBeCached())
- offset = std::min(offset, aux_info_offset());
- }
- if (run_itr_ != runs_.end()) {
- std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1;
- if (next_run != runs_.end()) {
- offset = std::min(offset, next_run->sample_start_offset);
- if (next_run->aux_info_total_size)
- offset = std::min(offset, next_run->aux_info_start_offset);
- }
- }
- if (offset == kint64max) return 0;
- return offset;
-}
-
-TimeDelta TrackRunIterator::GetMinDecodeTimestamp() {
- TimeDelta dts = kInfiniteDuration();
- for (size_t i = 0; i < runs_.size(); i++) {
- dts = std::min(dts, TimeDeltaFromRational(runs_[i].start_dts,
- runs_[i].timescale));
- }
- return dts;
-}
-
-uint32 TrackRunIterator::track_id() const {
- DCHECK(IsRunValid());
- return run_itr_->track_id;
-}
-
-bool TrackRunIterator::is_encrypted() const {
- DCHECK(IsRunValid());
- return track_encryption().is_encrypted;
-}
-
-int64 TrackRunIterator::aux_info_offset() const {
- return run_itr_->aux_info_start_offset;
-}
-
-int TrackRunIterator::aux_info_size() const {
- return run_itr_->aux_info_total_size;
-}
-
-bool TrackRunIterator::is_audio() const {
- DCHECK(IsRunValid());
- return run_itr_->is_audio;
-}
-
-const AudioSampleEntry& TrackRunIterator::audio_description() const {
- DCHECK(is_audio());
- DCHECK(run_itr_->audio_description);
- return *run_itr_->audio_description;
-}
-
-const VideoSampleEntry& TrackRunIterator::video_description() const {
- DCHECK(!is_audio());
- DCHECK(run_itr_->video_description);
- return *run_itr_->video_description;
-}
-
-int64 TrackRunIterator::sample_offset() const {
- DCHECK(IsSampleValid());
- return sample_offset_;
-}
-
-int TrackRunIterator::sample_size() const {
- DCHECK(IsSampleValid());
- return sample_itr_->size;
-}
-
-TimeDelta TrackRunIterator::dts() const {
- DCHECK(IsSampleValid());
- return TimeDeltaFromRational(sample_dts_, run_itr_->timescale);
-}
-
-TimeDelta TrackRunIterator::cts() const {
- DCHECK(IsSampleValid());
- return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset,
- run_itr_->timescale);
-}
-
-TimeDelta TrackRunIterator::duration() const {
- DCHECK(IsSampleValid());
- return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale);
-}
-
-bool TrackRunIterator::is_keyframe() const {
- DCHECK(IsSampleValid());
- return sample_itr_->is_keyframe;
-}
-
-const TrackEncryption& TrackRunIterator::track_encryption() const {
- if (is_audio())
- return audio_description().sinf.info.track_encryption;
- return video_description().sinf.info.track_encryption;
-}
-
-scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
- size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
- DCHECK(sample_idx < cenc_info_.size());
- const FrameCENCInfo& cenc_info = cenc_info_[sample_idx];
- DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
-
- size_t total_size = 0;
- if (!cenc_info.subsamples.empty() &&
- (!cenc_info.GetTotalSizeOfSubsamples(&total_size) ||
- total_size != static_cast<size_t>(sample_size()))) {
- MEDIA_LOG(log_cb_) << "Incorrect CENC subsample size.";
- return scoped_ptr<DecryptConfig>();
- }
-
- const std::vector<uint8>& kid = track_encryption().default_kid;
- return scoped_ptr<DecryptConfig>(new DecryptConfig(
- std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
- std::string(reinterpret_cast<const char*>(cenc_info.iv),
- arraysize(cenc_info.iv)),
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- 0, // No offset to start of media data in MP4 using CENC.
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
- cenc_info.subsamples));
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/mp4/track_run_iterator.h b/src/media/mp4/track_run_iterator.h
deleted file mode 100644
index 1e083ca..0000000
--- a/src/media/mp4/track_run_iterator.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MP4_TRACK_RUN_ITERATOR_H_
-#define MEDIA_MP4_TRACK_RUN_ITERATOR_H_
-
-#include <vector>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/mp4/box_definitions.h"
-#include "media/mp4/cenc.h"
-
-namespace media {
-
-class DecryptConfig;
-
-namespace mp4 {
-
-using base::TimeDelta;
-base::TimeDelta MEDIA_EXPORT TimeDeltaFromRational(int64 numer, int64 denom);
-
-struct SampleInfo;
-struct TrackRunInfo;
-
-class MEDIA_EXPORT TrackRunIterator {
- public:
- // Create a new TrackRunIterator. A reference to |moov| will be retained for
- // the lifetime of this object.
- TrackRunIterator(const Movie* moov, const LogCB& log_cb);
- ~TrackRunIterator();
-
- void Reset();
-
- // Sets up the iterator to handle all the runs from the current fragment.
- bool Init(const MovieFragment& moof);
-
- // Returns true if the properties of the current run or sample are valid.
- bool IsRunValid() const;
- bool IsSampleValid() const;
-
- // Advance the properties to refer to the next run or sample. Requires that
- // the current sample be valid.
- void AdvanceRun();
- void AdvanceSample();
-
- // Returns true if this track run has auxiliary information and has not yet
- // been cached. Only valid if IsRunValid().
- bool AuxInfoNeedsToBeCached();
-
- // Caches the CENC data from the given buffer. |buf| must be a buffer starting
- // at the offset given by cenc_offset(), with a |size| of at least
- // cenc_size(). Returns true on success, false on error.
- bool CacheAuxInfo(const uint8* buf, int size);
-
- // Returns the maximum buffer location at which no data earlier in the stream
- // will be required in order to read the current or any subsequent sample. You
- // may clear all data up to this offset before reading the current sample
- // safely. Result is in the same units as offset() (for Media Source this is
- // in bytes past the the head of the MOOF box).
- int64 GetMaxClearOffset();
-
- // Returns the minimum timestamp (or kInfiniteDuration if no runs present).
- TimeDelta GetMinDecodeTimestamp();
-
- // Property of the current run. Only valid if IsRunValid().
- uint32 track_id() const;
- int64 aux_info_offset() const;
- int aux_info_size() const;
- bool is_encrypted() const;
- bool is_audio() const;
- // Only one is valid, based on the value of is_audio().
- const AudioSampleEntry& audio_description() const;
- const VideoSampleEntry& video_description() const;
-
- // Properties of the current sample. Only valid if IsSampleValid().
- int64 sample_offset() const;
- int sample_size() const;
- TimeDelta dts() const;
- TimeDelta cts() const;
- TimeDelta duration() const;
- bool is_keyframe() const;
-
- // Only call when is_encrypted() is true and AuxInfoNeedsToBeCached() is
- // false. Result is owned by caller.
- scoped_ptr<DecryptConfig> GetDecryptConfig();
-
- private:
- void ResetRun();
- const TrackEncryption& track_encryption() const;
-
- const Movie* moov_;
- LogCB log_cb_;
-
- std::vector<TrackRunInfo> runs_;
- std::vector<TrackRunInfo>::const_iterator run_itr_;
- std::vector<SampleInfo>::const_iterator sample_itr_;
-
- std::vector<FrameCENCInfo> cenc_info_;
-
- int64 sample_dts_;
- int64 sample_offset_;
-
- DISALLOW_COPY_AND_ASSIGN(TrackRunIterator);
-};
-
-} // namespace mp4
-} // namespace media
-
-#endif // MEDIA_MP4_TRACK_RUN_ITERATOR_H_
diff --git a/src/media/mp4/track_run_iterator_unittest.cc b/src/media/mp4/track_run_iterator_unittest.cc
deleted file mode 100644
index c0cbbe6..0000000
--- a/src/media/mp4/track_run_iterator_unittest.cc
+++ /dev/null
@@ -1,447 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/basictypes.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/mp4/box_definitions.h"
-#include "media/mp4/rcheck.h"
-#include "media/mp4/track_run_iterator.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// The sum of the elements in a vector initialized with SumAscending,
-// less the value of the last element.
-static const int kSumAscending1 = 45;
-
-static const int kAudioScale = 48000;
-static const int kVideoScale = 25;
-
-static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
-
-static const uint8 kAuxInfo[] = {
- 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
- 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
- 0x00, 0x02,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
- 0x00, 0x03, 0x00, 0x00, 0x00, 0x04
-};
-
-static const char kIv1[] = {
- 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8 kKeyId[] = {
- 0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
- 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44
-};
-
-namespace media {
-namespace mp4 {
-
-class TrackRunIteratorTest : public testing::Test {
- public:
- TrackRunIteratorTest() {
- CreateMovie();
- }
-
- protected:
- Movie moov_;
- LogCB log_cb_;
- scoped_ptr<TrackRunIterator> iter_;
-
- void CreateMovie() {
- moov_.header.timescale = 1000;
- moov_.tracks.resize(3);
- moov_.extends.tracks.resize(2);
- moov_.tracks[0].header.track_id = 1;
- moov_.tracks[0].media.header.timescale = kAudioScale;
- SampleDescription& desc1 =
- moov_.tracks[0].media.information.sample_table.description;
- AudioSampleEntry aud_desc;
- aud_desc.format = FOURCC_MP4A;
- aud_desc.sinf.info.track_encryption.is_encrypted = false;
- desc1.type = kAudio;
- desc1.audio_entries.push_back(aud_desc);
- moov_.extends.tracks[0].track_id = 1;
- moov_.extends.tracks[0].default_sample_description_index = 1;
-
- moov_.tracks[1].header.track_id = 2;
- moov_.tracks[1].media.header.timescale = kVideoScale;
- SampleDescription& desc2 =
- moov_.tracks[1].media.information.sample_table.description;
- VideoSampleEntry vid_desc;
- vid_desc.format = FOURCC_AVC1;
- vid_desc.sinf.info.track_encryption.is_encrypted = false;
- desc2.type = kVideo;
- desc2.video_entries.push_back(vid_desc);
- moov_.extends.tracks[1].track_id = 2;
- moov_.extends.tracks[1].default_sample_description_index = 1;
-
- moov_.tracks[2].header.track_id = 3;
- moov_.tracks[2].media.information.sample_table.description.type = kHint;
- }
-
- MovieFragment CreateFragment() {
- MovieFragment moof;
- moof.tracks.resize(2);
- moof.tracks[0].decode_time.decode_time = 0;
- moof.tracks[0].header.track_id = 1;
- moof.tracks[0].header.has_default_sample_flags = true;
- moof.tracks[0].header.default_sample_duration = 1024;
- moof.tracks[0].header.default_sample_size = 4;
- moof.tracks[0].runs.resize(2);
- moof.tracks[0].runs[0].sample_count = 10;
- moof.tracks[0].runs[0].data_offset = 100;
- SetAscending(&moof.tracks[0].runs[0].sample_sizes);
-
- moof.tracks[0].runs[1].sample_count = 10;
- moof.tracks[0].runs[1].data_offset = 10000;
-
- moof.tracks[1].header.track_id = 2;
- moof.tracks[1].header.has_default_sample_flags = false;
- moof.tracks[1].decode_time.decode_time = 10;
- moof.tracks[1].runs.resize(1);
- moof.tracks[1].runs[0].sample_count = 10;
- moof.tracks[1].runs[0].data_offset = 200;
- SetAscending(&moof.tracks[1].runs[0].sample_sizes);
- SetAscending(&moof.tracks[1].runs[0].sample_durations);
- moof.tracks[1].runs[0].sample_flags.resize(10);
- for (size_t i = 1; i < moof.tracks[1].runs[0].sample_flags.size(); i++) {
- moof.tracks[1].runs[0].sample_flags[i] =
- kSampleIsDifferenceSampleFlagMask;
- }
-
- return moof;
- }
-
- // Update the first sample description of a Track to indicate encryption
- void AddEncryption(Track* track) {
- SampleDescription* stsd =
- &track->media.information.sample_table.description;
- ProtectionSchemeInfo* sinf;
- if (!stsd->video_entries.empty()) {
- sinf = &stsd->video_entries[0].sinf;
- } else {
- sinf = &stsd->audio_entries[0].sinf;
- }
-
- sinf->type.type = FOURCC_CENC;
- sinf->info.track_encryption.is_encrypted = true;
- sinf->info.track_encryption.default_iv_size = 8;
- sinf->info.track_encryption.default_kid.insert(
- sinf->info.track_encryption.default_kid.begin(),
- kKeyId, kKeyId + arraysize(kKeyId));
- }
-
- // Add aux info covering the first track run to a TrackFragment, and update
- // the run to ensure it matches length and subsample information.
- void AddAuxInfoHeaders(int offset, TrackFragment* frag) {
- frag->auxiliary_offset.offsets.push_back(offset);
- frag->auxiliary_size.sample_count = 2;
- frag->auxiliary_size.sample_info_sizes.push_back(8);
- frag->auxiliary_size.sample_info_sizes.push_back(22);
- frag->runs[0].sample_count = 2;
- frag->runs[0].sample_sizes[1] = 10;
- }
-
- void SetAscending(std::vector<uint32>* vec) {
- vec->resize(10);
- for (size_t i = 0; i < vec->size(); i++)
- (*vec)[i] = i+1;
- }
-};
-
-TEST_F(TrackRunIteratorTest, NoRunsTest) {
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- ASSERT_TRUE(iter_->Init(MovieFragment()));
- EXPECT_FALSE(iter_->IsRunValid());
- EXPECT_FALSE(iter_->IsSampleValid());
-}
-
-TEST_F(TrackRunIteratorTest, BasicOperationTest) {
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- MovieFragment moof = CreateFragment();
-
- // Test that runs are sorted correctly, and that properties of the initial
- // sample of the first run are correct
- ASSERT_TRUE(iter_->Init(moof));
- EXPECT_TRUE(iter_->IsRunValid());
- EXPECT_FALSE(iter_->is_encrypted());
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->sample_offset(), 100);
- EXPECT_EQ(iter_->sample_size(), 1);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kAudioScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kAudioScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale));
- EXPECT_TRUE(iter_->is_keyframe());
-
- // Advance to the last sample in the current run, and test its properties
- for (int i = 0; i < 9; i++) iter_->AdvanceSample();
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1);
- EXPECT_EQ(iter_->sample_size(), 10);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 9, kAudioScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale));
- EXPECT_TRUE(iter_->is_keyframe());
-
- // Test end-of-run
- iter_->AdvanceSample();
- EXPECT_FALSE(iter_->IsSampleValid());
-
- // Test last sample of next run
- iter_->AdvanceRun();
- EXPECT_TRUE(iter_->is_keyframe());
- for (int i = 0; i < 9; i++) iter_->AdvanceSample();
- EXPECT_EQ(iter_->track_id(), 2u);
- EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1);
- EXPECT_EQ(iter_->sample_size(), 10);
- int64 base_dts = kSumAscending1 + moof.tracks[1].decode_time.decode_time;
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(base_dts, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(10, kVideoScale));
- EXPECT_FALSE(iter_->is_keyframe());
-
- // Test final run
- iter_->AdvanceRun();
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 10, kAudioScale));
- iter_->AdvanceSample();
- EXPECT_EQ(moof.tracks[0].runs[1].data_offset +
- moof.tracks[0].header.default_sample_size,
- iter_->sample_offset());
- iter_->AdvanceRun();
- EXPECT_FALSE(iter_->IsRunValid());
-}
-
-TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) {
- moov_.extends.tracks[0].default_sample_duration = 50;
- moov_.extends.tracks[0].default_sample_size = 3;
- moov_.extends.tracks[0].default_sample_flags =
- kSampleIsDifferenceSampleFlagMask;
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- MovieFragment moof = CreateFragment();
- moof.tracks[0].header.has_default_sample_flags = false;
- moof.tracks[0].header.default_sample_size = 0;
- moof.tracks[0].header.default_sample_duration = 0;
- moof.tracks[0].runs[0].sample_sizes.clear();
- ASSERT_TRUE(iter_->Init(moof));
- iter_->AdvanceSample();
- EXPECT_FALSE(iter_->is_keyframe());
- EXPECT_EQ(iter_->sample_size(), 3);
- EXPECT_EQ(iter_->sample_offset(), moof.tracks[0].runs[0].data_offset + 3);
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(50, kAudioScale));
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(50, kAudioScale));
-}
-
-TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
- // Ensure that keyframes are flagged correctly in the face of BMFF boxes which
- // explicitly specify the flags for the first sample in a run and rely on
- // defaults for all subsequent samples
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- MovieFragment moof = CreateFragment();
- moof.tracks[1].header.has_default_sample_flags = true;
- moof.tracks[1].header.default_sample_flags =
- kSampleIsDifferenceSampleFlagMask;
- moof.tracks[1].runs[0].sample_flags.resize(1);
- ASSERT_TRUE(iter_->Init(moof));
- iter_->AdvanceRun();
- EXPECT_TRUE(iter_->is_keyframe());
- iter_->AdvanceSample();
- EXPECT_FALSE(iter_->is_keyframe());
-}
-
-TEST_F(TrackRunIteratorTest, MinDecodeTest) {
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- MovieFragment moof = CreateFragment();
- moof.tracks[0].decode_time.decode_time = kAudioScale;
- ASSERT_TRUE(iter_->Init(moof));
- EXPECT_EQ(TimeDeltaFromRational(moof.tracks[1].decode_time.decode_time,
- kVideoScale),
- iter_->GetMinDecodeTimestamp());
-}
-
-TEST_F(TrackRunIteratorTest, ReorderingTest) {
- // Test frame reordering and edit list support. The frames have the following
- // decode timestamps:
- //
- // 0ms 40ms 120ms 240ms
- // | 0 | 1 - | 2 - - |
- //
- // ...and these composition timestamps, after edit list adjustment:
- //
- // 0ms 40ms 160ms 240ms
- // | 0 | 2 - - | 1 - |
-
- // Create an edit list with one entry, with an initial start time of 80ms
- // (that is, 2 / kVideoTimescale) and a duration of zero (which is treated as
- // infinite according to 14496-12:2012). This will cause the first 80ms of the
- // media timeline - which will be empty, due to CTS biasing - to be discarded.
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- EditListEntry entry;
- entry.segment_duration = 0;
- entry.media_time = 2;
- entry.media_rate_integer = 1;
- entry.media_rate_fraction = 0;
- moov_.tracks[1].edit.list.edits.push_back(entry);
-
- // Add CTS offsets. Without bias, the CTS offsets for the first three frames
- // would simply be [0, 3, -2]. Since CTS offsets should be non-negative for
- // maximum compatibility, these values are biased up to [2, 5, 0], and the
- // extra 80ms is removed via the edit list.
- MovieFragment moof = CreateFragment();
- std::vector<int32>& cts_offsets =
- moof.tracks[1].runs[0].sample_composition_time_offsets;
- cts_offsets.resize(10);
- cts_offsets[0] = 2;
- cts_offsets[1] = 5;
- cts_offsets[2] = 0;
- moof.tracks[1].decode_time.decode_time = 0;
-
- ASSERT_TRUE(iter_->Init(moof));
- iter_->AdvanceRun();
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kVideoScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1, kVideoScale));
- iter_->AdvanceSample();
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1, kVideoScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(4, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(2, kVideoScale));
- iter_->AdvanceSample();
- EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(3, kVideoScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(1, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(3, kVideoScale));
-}
-
-TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) {
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
- MovieFragment moof = CreateFragment();
- moof.tracks[1].auxiliary_offset.offsets.push_back(50);
- moof.tracks[1].auxiliary_size.default_sample_info_size = 2;
- moof.tracks[1].auxiliary_size.sample_count = 2;
- moof.tracks[1].runs[0].sample_count = 2;
- ASSERT_TRUE(iter_->Init(moof));
- iter_->AdvanceRun();
- EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
-}
-
-TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
- AddEncryption(&moov_.tracks[1]);
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
-
- MovieFragment moof = CreateFragment();
- AddAuxInfoHeaders(50, &moof.tracks[1]);
-
- ASSERT_TRUE(iter_->Init(moof));
-
- // The run for track 2 will be first, since its aux info offset is the first
- // element in the file.
- EXPECT_EQ(iter_->track_id(), 2u);
- EXPECT_TRUE(iter_->is_encrypted());
- EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached());
- EXPECT_EQ(static_cast<uint32>(iter_->aux_info_size()), arraysize(kAuxInfo));
- EXPECT_EQ(iter_->aux_info_offset(), 50);
- EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
- EXPECT_FALSE(iter_->CacheAuxInfo(NULL, 0));
- EXPECT_FALSE(iter_->CacheAuxInfo(kAuxInfo, 3));
- EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached());
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
- EXPECT_EQ(iter_->sample_offset(), 200);
- EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset);
- scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
- ASSERT_EQ(arraysize(kKeyId), config->key_id().size());
- EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(),
- config->key_id().size()));
- ASSERT_EQ(arraysize(kIv1), config->iv().size());
- EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
- EXPECT_TRUE(config->subsamples().empty());
- iter_->AdvanceSample();
- config = iter_->GetDecryptConfig();
- EXPECT_EQ(config->subsamples().size(), 2u);
- EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
- EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u);
-}
-
-// It is legal for aux info blocks to be shared among multiple formats.
-TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) {
- AddEncryption(&moov_.tracks[0]);
- AddEncryption(&moov_.tracks[1]);
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
-
- MovieFragment moof = CreateFragment();
- moof.tracks[0].runs.resize(1);
- AddAuxInfoHeaders(50, &moof.tracks[0]);
- AddAuxInfoHeaders(50, &moof.tracks[1]);
- moof.tracks[0].auxiliary_size.default_sample_info_size = 8;
-
- ASSERT_TRUE(iter_->Init(moof));
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->aux_info_offset(), 50);
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
- ASSERT_EQ(arraysize(kIv1), config->iv().size());
- EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
- iter_->AdvanceSample();
- EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
- iter_->AdvanceRun();
- EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
- EXPECT_EQ(iter_->aux_info_offset(), 50);
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- EXPECT_EQ(iter_->GetMaxClearOffset(), 200);
- ASSERT_EQ(arraysize(kIv1), config->iv().size());
- EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
- iter_->AdvanceSample();
- EXPECT_EQ(iter_->GetMaxClearOffset(), 201);
-}
-
-// Sensible files are expected to place auxiliary information for a run
-// immediately before the main data for that run. Alternative schemes are
-// possible, however, including the somewhat reasonable behavior of placing all
-// aux info at the head of the 'mdat' box together, and the completely
-// unreasonable behavior demonstrated here:
-// byte 50: track 2, run 1 aux info
-// byte 100: track 1, run 1 data
-// byte 200: track 2, run 1 data
-// byte 201: track 1, run 2 aux info (*inside* track 2, run 1 data)
-// byte 10000: track 1, run 2 data
-// byte 20000: track 1, run 1 aux info
-TEST_F(TrackRunIteratorTest, UnexpectedOrderingTest) {
- AddEncryption(&moov_.tracks[0]);
- AddEncryption(&moov_.tracks[1]);
- iter_.reset(new TrackRunIterator(&moov_, log_cb_));
-
- MovieFragment moof = CreateFragment();
- AddAuxInfoHeaders(20000, &moof.tracks[0]);
- moof.tracks[0].auxiliary_offset.offsets.push_back(201);
- moof.tracks[0].auxiliary_size.sample_count += 2;
- moof.tracks[0].auxiliary_size.default_sample_info_size = 8;
- moof.tracks[0].runs[1].sample_count = 2;
- AddAuxInfoHeaders(50, &moof.tracks[1]);
- moof.tracks[1].runs[0].sample_sizes[0] = 5;
-
- ASSERT_TRUE(iter_->Init(moof));
- EXPECT_EQ(iter_->track_id(), 2u);
- EXPECT_EQ(iter_->aux_info_offset(), 50);
- EXPECT_EQ(iter_->sample_offset(), 200);
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- EXPECT_EQ(iter_->GetMaxClearOffset(), 100);
- iter_->AdvanceRun();
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->aux_info_offset(), 20000);
- EXPECT_EQ(iter_->sample_offset(), 100);
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- EXPECT_EQ(iter_->GetMaxClearOffset(), 100);
- iter_->AdvanceSample();
- EXPECT_EQ(iter_->GetMaxClearOffset(), 101);
- iter_->AdvanceRun();
- EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->aux_info_offset(), 201);
- EXPECT_EQ(iter_->sample_offset(), 10000);
- EXPECT_EQ(iter_->GetMaxClearOffset(), 201);
- EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
- EXPECT_EQ(iter_->GetMaxClearOffset(), 10000);
-}
-
-} // namespace mp4
-} // namespace media
diff --git a/src/media/player/buffered_data_source.h b/src/media/player/buffered_data_source.h
deleted file mode 100644
index 9c6a31c..0000000
--- a/src/media/player/buffered_data_source.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_PLAYER_BUFFERED_DATA_SOURCE_H_
-#define MEDIA_PLAYER_BUFFERED_DATA_SOURCE_H_
-
-#include <stdio.h>
-
-#include "base/basictypes.h"
-#include "base/message_loop.h"
-#include "googleurl/src/gurl.h"
-#include "media/base/data_source.h"
-
-namespace media {
-
-enum Preload {
- kPreloadNone,
- kPreloadMetaData,
- kPreloadAuto,
-};
-
-// TODO: Investigate if we still need BufferedDataSource.
-class BufferedDataSource : public DataSource {
- public:
- typedef base::Callback<void(bool)> DownloadingStatusCB;
-
- virtual void SetDownloadingStatusCB(
- const DownloadingStatusCB& downloading_status_cb) {
- UNREFERENCED_PARAMETER(downloading_status_cb);
- }
- virtual void SetPreload(Preload preload) { UNREFERENCED_PARAMETER(preload); }
- virtual bool HasSingleOrigin() { return true; }
- virtual bool DidPassCORSAccessCheck() const { return true; }
- virtual void Abort() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_BUFFERED_DATA_SOURCE_H_
diff --git a/src/media/player/can_play_type.cc b/src/media/player/can_play_type.cc
deleted file mode 100644
index d3880ae..0000000
--- a/src/media/player/can_play_type.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "media/player/can_play_type.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "base/string_split.h"
-#include "base/string_util.h"
-#include "media/audio/shell_audio_streamer.h"
-#include "media/player/crypto/key_systems.h"
-#include "media/player/mime_util.h"
-
-namespace media {
-
-namespace {
-
-bool ContainsAAC51(const std::vector<std::string>& codecs) {
- const std::string kAAC51Codec = "aac51";
-
- std::vector<std::string>::const_iterator iter =
- std::find(codecs.begin(), codecs.end(), kAAC51Codec);
- return iter != codecs.end();
-}
-
-bool HasAAC51HardwareSupport() {
-#if defined(COBALT_WIN)
- return false;
-#else // defined(COBALT_WIN)
- ShellAudioStreamer* streamer = ShellAudioStreamer::Instance();
- DCHECK(streamer);
-
- return streamer->GetConfig().max_hardware_channels() >= 6;
-#endif // defined(COBALT_WIN)
-}
-
-// Parse mime and codecs from content type. It will return "video/mp4" and
-// "avc1.42E01E" for "video/mp4; codecs="avc1.42E01E". Note that this function
-// does minimum validation as the media stack will check the mime type and
-// codecs strictly.
-bool ParseContentType(const std::string& content_type,
- std::string* mime,
- std::string* codecs) {
- DCHECK(mime);
- DCHECK(codecs);
- static const char kCodecs[] = "codecs=";
-
- std::vector<std::string> tokens;
- // SplitString will also trim the results.
- ::base::SplitString(content_type, ';', &tokens);
-
- // The first one has to be mime type with delimiter '/' like 'video/mp4'.
- if (tokens.empty() || tokens[0].find('/') == tokens[0].npos) {
- return false;
- }
-
- *mime = tokens[0];
- codecs->clear();
-
- for (size_t i = 1; i < tokens.size(); ++i) {
- if (strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
- continue;
- }
- *codecs = tokens[i].substr(strlen(kCodecs));
- TrimString(*codecs, " \"", codecs);
- break;
- }
-
- return true;
-}
-
-} // namespace
-
-std::string CanPlayType(const std::string& content_type,
- const std::string& key_system) {
- std::string mime_type;
- std::string codecs;
- const char kNotSupported[] = "";
- const char kMaybe[] = "maybe";
- const char kProbably[] = "probably";
-
- // Check if the content type is in valid format.
- if (!ParseContentType(content_type, &mime_type, &codecs)) {
- return kNotSupported;
- }
-
- if (!IsSupportedMediaMimeType(mime_type)) {
- return kNotSupported;
- }
-
- if (!key_system.empty()) {
- if (!IsSupportedKeySystem(key_system)) {
- return kNotSupported;
- }
-
- std::vector<std::string> strict_codecs;
- bool strip_suffix = !IsStrictMediaMimeType(mime_type);
- ParseCodecString(codecs, &strict_codecs, strip_suffix);
-
- if (!IsSupportedKeySystemWithMediaMimeType(mime_type, strict_codecs,
- key_system)) {
- return kNotSupported;
- }
-
- // Even if all above functions don't claim that they don't support of the
- // particular format with the key_system, we still want to double check if
- // the implementation support this format without encryption.
- }
-
- if (IsStrictMediaMimeType(mime_type)) {
- if (codecs.empty()) {
- return kMaybe;
- }
-
- std::vector<std::string> strict_codecs;
- ParseCodecString(codecs, &strict_codecs, false);
-
- if (ContainsAAC51(strict_codecs) && !HasAAC51HardwareSupport()) {
- return kNotSupported;
- }
-
- if (!IsSupportedStrictMediaMimeType(mime_type, strict_codecs)) {
- return kNotSupported;
- }
-
- return kProbably;
- }
-
- std::vector<std::string> parsed_codecs;
- ParseCodecString(codecs, &parsed_codecs, true);
-
- if (ContainsAAC51(parsed_codecs) && !HasAAC51HardwareSupport()) {
- return kNotSupported;
- }
-
- if (!AreSupportedMediaCodecs(parsed_codecs)) {
- return kMaybe;
- }
-
- return kProbably;
-}
-
-} // namespace media
diff --git a/src/media/player/can_play_type.h b/src/media/player/can_play_type.h
deleted file mode 100644
index 3b16d8f..0000000
--- a/src/media/player/can_play_type.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_PLAYER_CAN_PLAY_TYPE_H_
-#define MEDIA_PLAYER_CAN_PLAY_TYPE_H_
-
-#include <string>
-
-namespace media {
-
-// TODO: Find a better home for CanPlayType. I don't want to make
-// it a member function of WebMediaPlayer because of:
-// 1. It is relatively complex.
-// 2. The HTMLMediaElement doesn't always hold a WebMediaPlayer instance while
-// static function does't work here because it cannot be override in
-// inherited classes like WebMediaPlayerImpl.
-
-// This function can return "", "maybe" or "probably" as defined in
-// http://www.w3.org/TR/html5/embedded-content-0.html#dom-navigator-canplaytype
-// to indicate how certain that the particular implementation can play videos in
-// the format specified by content_type and key_system. Note that "" means the
-// implementation cannot play such videos.
-std::string CanPlayType(const std::string& content_type,
- const std::string& key_system);
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_CAN_PLAY_TYPE_H_
diff --git a/src/media/player/crypto/key_systems.cc b/src/media/player/crypto/key_systems.cc
deleted file mode 100644
index b0e84a9..0000000
--- a/src/media/player/crypto/key_systems.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-//
-// Copyright 2013 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include "media/player/crypto/key_systems.h"
-
-#include "base/string_util.h"
-#include "media/crypto/shell_decryptor_factory.h"
-#include "media/player/mime_util.h"
-
-namespace media {
-
-bool IsSupportedKeySystem(const std::string& key_system) {
- return ShellDecryptorFactory::Supports(key_system);
-}
-
-bool IsSupportedKeySystemWithMediaMimeType(
- const std::string& mime_type,
- const std::vector<std::string>& codecs,
- const std::string& key_system) {
- if (ShellDecryptorFactory::Supports(key_system)) {
- if (mime_type == "video/mp4" || mime_type == "video/webm" ||
- mime_type == "audio/mp4") {
- return AreSupportedMediaCodecs(codecs);
- }
- }
- return false;
-}
-
-// Used for logging only.
-std::string KeySystemNameForUMA(const std::string& key_system) {
- return key_system;
-}
-
-bool CanUseAesDecryptor(const std::string& key_system) {
- return false;
-}
-
-std::string GetPluginType(const std::string& key_system) {
- return std::string();
-}
-
-} // namespace media
diff --git a/src/media/player/crypto/key_systems.h b/src/media/player/crypto/key_systems.h
deleted file mode 100644
index 5253d38..0000000
--- a/src/media/player/crypto/key_systems.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_PLAYER_CRYPTO_KEY_SYSTEMS_H_
-#define MEDIA_PLAYER_CRYPTO_KEY_SYSTEMS_H_
-
-#include <string>
-#include <vector>
-
-#include "base/memory/scoped_ptr.h"
-
-namespace media {
-
-// Returns whether |key_sytem| is supported at all.
-// Call IsSupportedKeySystemWithMediaMimeType() to determine whether a
-// |key_system| supports a specific type of media.
-bool IsSupportedKeySystem(const std::string& key_system);
-
-// Returns whether |key_sytem| supports the specified media type and codec(s).
-bool IsSupportedKeySystemWithMediaMimeType(
- const std::string& mime_type,
- const std::vector<std::string>& codecs,
- const std::string& key_system);
-
-// Returns a name for |key_system| suitable to UMA logging.
-std::string KeySystemNameForUMA(const std::string& key_system);
-
-// Returns whether AesDecryptor can be used for the given |key_system|.
-bool CanUseAesDecryptor(const std::string& key_system);
-
-// Returns the plugin type given a |key_system|.
-// Returns an empty string if no plugin type is found for |key_system|.
-std::string GetPluginType(const std::string& key_system);
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_CRYPTO_KEY_SYSTEMS_H_
diff --git a/src/media/player/crypto/proxy_decryptor.cc b/src/media/player/crypto/proxy_decryptor.cc
deleted file mode 100644
index e5bae20..0000000
--- a/src/media/player/crypto/proxy_decryptor.cc
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/player/crypto/proxy_decryptor.h"
-
-// WebMediaPlayer.h needs uint8_t defined
-// See: https://bugs.webkit.org/show_bug.cgi?id=92031
-#include <stdint.h>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/location.h"
-#include "base/logging.h"
-#if defined(_DEBUG)
-#include "base/string_number_conversions.h"
-#endif
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/decryptor_client.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
-#include "media/crypto/shell_decryptor_factory.h"
-#include "media/player/crypto/key_systems.h"
-
-namespace media {
-
-ProxyDecryptor::ProxyDecryptor(DecryptorClient* decryptor_client)
- : client_(decryptor_client), destroy_soon_(false) {}
-
-ProxyDecryptor::~ProxyDecryptor() {}
-
-// TODO(xhwang): Support multiple decryptor notification request (e.g. from
-// video and audio decoders). The current implementation is okay for the current
-// media pipeline since we initialize audio and video decoders in sequence.
-// But ProxyDecryptor should not depend on media pipeline's implementation
-// detail.
-void ProxyDecryptor::SetDecryptorReadyCB(
- const DecryptorReadyCB& decryptor_ready_cb) {
- base::AutoLock auto_lock(lock_);
-
- if (destroy_soon_) {
- decryptor_ready_cb.Run(NULL);
- return;
- }
-
- // Cancels the previous decryptor request.
- if (decryptor_ready_cb.is_null()) {
- if (!decryptor_ready_cb_.is_null())
- base::ResetAndReturn(&decryptor_ready_cb_).Run(NULL);
- return;
- }
-
- // Normal decryptor request.
- DCHECK(decryptor_ready_cb_.is_null());
- if (decryptor_) {
- decryptor_ready_cb.Run(decryptor_.get());
- return;
- }
- decryptor_ready_cb_ = decryptor_ready_cb;
-}
-
-void ProxyDecryptor::DestroySoon() {
- base::AutoLock auto_lock(lock_);
-
- destroy_soon_ = true;
- if (!decryptor_ready_cb_.is_null()) {
- base::ResetAndReturn(&decryptor_ready_cb_).Run(NULL);
- }
-}
-
-bool ProxyDecryptor::GenerateKeyRequest(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length) {
- // We do not support run-time switching of decryptors. GenerateKeyRequest()
- // only creates a new decryptor when |decryptor_| is not initialized.
- DVLOG(1) << "GenerateKeyRequest: key_system = " << key_system;
-
- base::AutoLock auto_lock(lock_);
-
- if (!decryptor_) {
- decryptor_ = CreateDecryptor(key_system);
- if (!decryptor_) {
- client_->KeyError(key_system, "", Decryptor::kUnknownError, 0);
- return false;
- }
- }
-
- if (!decryptor_->GenerateKeyRequest(key_system, type, init_data,
- init_data_length)) {
- decryptor_.reset();
- return false;
- }
-
- if (!decryptor_ready_cb_.is_null())
- base::ResetAndReturn(&decryptor_ready_cb_).Run(decryptor_.get());
-
- return true;
-}
-
-void ProxyDecryptor::AddKey(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id) {
- DVLOG(1) << "AddKey()";
-#if defined(_DEBUG)
- std::string hex = base::HexEncode(key, key_length);
- DLOG(INFO) << "DRM Key Response: " << hex;
-#endif
-
- // WebMediaPlayerImpl ensures GenerateKeyRequest() has been called.
- decryptor_->AddKey(key_system, key, key_length, init_data, init_data_length,
- session_id);
-}
-
-void ProxyDecryptor::CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) {
- DVLOG(1) << "CancelKeyRequest()";
-
- // WebMediaPlayerImpl ensures GenerateKeyRequest() has been called.
- decryptor_->CancelKeyRequest(key_system, session_id);
-}
-
-void ProxyDecryptor::RegisterKeyAddedCB(StreamType stream_type,
- const KeyAddedCB& key_added_cb) {
- NOTREACHED() << "KeyAddedCB should not be registered with ProxyDecryptor.";
-}
-
-void ProxyDecryptor::Decrypt(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb) {
- NOTREACHED() << "ProxyDecryptor does not support decryption";
-}
-
-void ProxyDecryptor::CancelDecrypt(StreamType stream_type) {
- base::AutoLock auto_lock(lock_);
-
- if (decryptor_)
- decryptor_->CancelDecrypt(stream_type);
-}
-
-void ProxyDecryptor::InitializeAudioDecoder(
- scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- NOTREACHED() << "ProxyDecryptor does not support audio decoding";
-}
-
-void ProxyDecryptor::InitializeVideoDecoder(
- scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) {
- NOTREACHED() << "ProxyDecryptor does not support video decoding";
-}
-
-void ProxyDecryptor::DecryptAndDecodeAudio(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb) {
- NOTREACHED() << "ProxyDecryptor does not support audio decoding";
-}
-
-void ProxyDecryptor::DecryptAndDecodeVideo(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb) {
- NOTREACHED() << "ProxyDecryptor does not support video decoding";
-}
-
-void ProxyDecryptor::ResetDecoder(StreamType stream_type) {
- NOTREACHED() << "ProxyDecryptor does not support audio/video decoding";
-}
-
-void ProxyDecryptor::DeinitializeDecoder(StreamType stream_type) {
- NOTREACHED() << "ProxyDecryptor does not support audio/video decoding";
-}
-
-scoped_ptr<Decryptor> ProxyDecryptor::CreateDecryptor(
- const std::string& key_system) {
- DCHECK(client_);
-
- // lb_shell doesn't support ppapi or AesDecryptor, so we have our own
- // decryptor factory to handle cdm support.
- return scoped_ptr<Decryptor>(
- ShellDecryptorFactory::Create(key_system, client_));
-}
-
-} // namespace media
diff --git a/src/media/player/crypto/proxy_decryptor.h b/src/media/player/crypto/proxy_decryptor.h
deleted file mode 100644
index 44465a4..0000000
--- a/src/media/player/crypto/proxy_decryptor.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_PLAYER_CRYPTO_PROXY_DECRYPTOR_H_
-#define MEDIA_PLAYER_CRYPTO_PROXY_DECRYPTOR_H_
-
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/synchronization/lock.h"
-#include "media/base/decryptor.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class DecryptorClient;
-
-// A decryptor proxy that creates a real decryptor object on demand and
-// forwards decryptor calls to it.
-// TODO(xhwang): Currently we don't support run-time switching among decryptor
-// objects. Fix this when needed.
-class ProxyDecryptor : public Decryptor {
- public:
- ProxyDecryptor(DecryptorClient* decryptor_client);
- virtual ~ProxyDecryptor();
-
- // Requests the ProxyDecryptor to notify the decryptor when it's ready through
- // the |decryptor_ready_cb| provided.
- // If |decryptor_ready_cb| is null, the existing callback will be fired with
- // NULL immediately and reset.
- void SetDecryptorReadyCB(const DecryptorReadyCB& decryptor_ready_cb);
- // Notify that the decryptor is going to be destroyed soon. So any pending or
- // future DecryptorReadyCB will be called with NULL and the pipeline can be
- // torn down.
- void DestroySoon();
-
- // Decryptor implementation.
- virtual bool GenerateKeyRequest(const std::string& key_system,
- const std::string& type,
- const uint8* init_data,
- int init_data_length) OVERRIDE;
- virtual void AddKey(const std::string& key_system,
- const uint8* key,
- int key_length,
- const uint8* init_data,
- int init_data_length,
- const std::string& session_id) OVERRIDE;
- virtual void CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) OVERRIDE;
- virtual void RegisterKeyAddedCB(StreamType stream_type,
- const KeyAddedCB& key_added_cb) OVERRIDE;
- virtual void Decrypt(StreamType stream_type,
- const scoped_refptr<DecoderBuffer>& encrypted,
- const DecryptCB& decrypt_cb) OVERRIDE;
- virtual void CancelDecrypt(StreamType stream_type) OVERRIDE;
- virtual void InitializeAudioDecoder(scoped_ptr<AudioDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
- virtual void InitializeVideoDecoder(scoped_ptr<VideoDecoderConfig> config,
- const DecoderInitCB& init_cb) OVERRIDE;
- virtual void DecryptAndDecodeAudio(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const AudioDecodeCB& audio_decode_cb) OVERRIDE;
- virtual void DecryptAndDecodeVideo(
- const scoped_refptr<DecoderBuffer>& encrypted,
- const VideoDecodeCB& video_decode_cb) OVERRIDE;
- virtual void ResetDecoder(StreamType stream_type) OVERRIDE;
- virtual void DeinitializeDecoder(StreamType stream_type) OVERRIDE;
-
- private:
- // Helper functions to create decryptors to handle the given |key_system|.
- scoped_ptr<Decryptor> CreateDecryptor(const std::string& key_system);
-
- // DecryptorClient through which key events are fired.
- DecryptorClient* client_;
-
- // Protects the |decryptor_|. Note that |decryptor_| itself should be thread
- // safe as per the Decryptor interface.
- base::Lock lock_;
-
- bool destroy_soon_;
- DecryptorReadyCB decryptor_ready_cb_;
-
- // The real decryptor that does decryption for the ProxyDecryptor.
- // This pointer is protected by the |lock_|.
- scoped_ptr<Decryptor> decryptor_;
-
- DISALLOW_COPY_AND_ASSIGN(ProxyDecryptor);
-};
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_CRYPTO_PROXY_DECRYPTOR_H_
diff --git a/src/media/player/mime_util.cc b/src/media/player/mime_util.cc
deleted file mode 100644
index 86b0a5b..0000000
--- a/src/media/player/mime_util.cc
+++ /dev/null
@@ -1,1039 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/player/mime_util.h"
-
-#include <algorithm>
-#include <iterator>
-#include <map>
-#include <string>
-
-#include "base/hash_tables.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/string_split.h"
-#include "base/string_util.h"
-#include "base/utf_string_conversions.h"
-
-#if defined(OS_STARBOARD)
-#include "starboard/configuration.h"
-#endif // defined(OS_STARBOARD)
-
-using std::string;
-
-namespace {
-
-struct MediaType {
- const char name[12];
- const char matcher[13];
-};
-
-static const MediaType kIanaMediaTypes[] = {
- { "application", "application/" },
- { "audio", "audio/" },
- { "example", "example/" },
- { "image", "image/" },
- { "message", "message/" },
- { "model", "model/" },
- { "multipart", "multipart/" },
- { "text", "text/" },
- { "video", "video/" },
-};
-
-} // namespace
-
-namespace media {
-
-// Singleton utility class for mime types.
-class MimeUtil {
- public:
- bool GetMimeTypeFromExtension(const FilePath::StringType& ext,
- std::string* mime_type) const;
-
- bool GetMimeTypeFromFile(const FilePath& file_path,
- std::string* mime_type) const;
-
- bool GetWellKnownMimeTypeFromExtension(const FilePath::StringType& ext,
- std::string* mime_type) const;
-
- bool IsSupportedImageMimeType(const std::string& mime_type) const;
- bool IsSupportedMediaMimeType(const std::string& mime_type) const;
- bool IsSupportedNonImageMimeType(const std::string& mime_type) const;
- bool IsUnsupportedTextMimeType(const std::string& mime_type) const;
- bool IsSupportedJavascriptMimeType(const std::string& mime_type) const;
-
- bool IsViewSourceMimeType(const std::string& mime_type) const;
-
- bool IsSupportedMimeType(const std::string& mime_type) const;
-
- bool MatchesMimeType(const std::string &mime_type_pattern,
- const std::string &mime_type) const;
-
- bool IsMimeType(const std::string& type_string) const;
-
- bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs) const;
-
- void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out,
- bool strip);
-
- bool IsStrictMediaMimeType(const std::string& mime_type) const;
- bool IsSupportedStrictMediaMimeType(
- const std::string& mime_type,
- const std::vector<std::string>& codecs) const;
-
- private:
- friend struct base::DefaultLazyInstanceTraits<MimeUtil>;
-
- typedef base::hash_set<std::string> MimeMappings;
- typedef std::map<std::string, MimeMappings> StrictMappings;
-
- MimeUtil();
-
- // Returns true if |codecs| is nonempty and all the items in it are present in
- // |supported_codecs|.
- static bool AreSupportedCodecs(const MimeMappings& supported_codecs,
- const std::vector<std::string>& codecs);
-
- // For faster lookup, keep hash sets.
- void InitializeMimeTypeMaps();
-
- bool GetMimeTypeFromExtensionHelper(const FilePath::StringType& ext,
- bool include_platform_types,
- std::string* mime_type) const;
-
- MimeMappings image_map_;
- MimeMappings media_map_;
- MimeMappings non_image_map_;
- MimeMappings unsupported_text_map_;
- MimeMappings javascript_map_;
- MimeMappings view_source_map_;
- MimeMappings codecs_map_;
-
- StrictMappings strict_format_map_;
-}; // class MimeUtil
-
-// This variable is Leaky because we need to access it from WorkerPool threads.
-static base::LazyInstance<MimeUtil>::Leaky g_mime_util =
- LAZY_INSTANCE_INITIALIZER;
-
-struct MimeInfo {
- const char* mime_type;
- const char* extensions; // comma separated list
-};
-
-static const MimeInfo primary_mappings[] = {
- { "text/html", "html,htm" },
- { "text/css", "css" },
- { "text/xml", "xml" },
- { "image/gif", "gif" },
- { "image/jpeg", "jpeg,jpg" },
- { "image/webp", "webp" },
- { "image/png", "png" },
- { "video/mp4", "mp4,m4v" },
- { "audio/x-m4a", "m4a" },
- { "audio/mp3", "mp3" },
- { "video/ogg", "ogv,ogm" },
- { "audio/ogg", "ogg,oga" },
- { "video/webm", "webm" },
- { "audio/webm", "webm" },
- { "audio/wav", "wav" },
- { "application/xhtml+xml", "xhtml,xht" },
- { "application/x-chrome-extension", "crx" },
- { "multipart/related", "mhtml,mht" }
-};
-
-static const MimeInfo secondary_mappings[] = {
- { "application/octet-stream", "exe,com,bin" },
- { "application/gzip", "gz" },
- { "application/pdf", "pdf" },
- { "application/postscript", "ps,eps,ai" },
- { "application/x-javascript", "js" },
- { "application/x-font-woff", "woff" },
- { "image/bmp", "bmp" },
- { "image/x-icon", "ico" },
- { "image/jpeg", "jfif,pjpeg,pjp" },
- { "image/tiff", "tiff,tif" },
- { "image/x-xbitmap", "xbm" },
- { "image/svg+xml", "svg,svgz" },
- { "message/rfc822", "eml" },
- { "text/plain", "txt,text" },
- { "text/html", "shtml,ehtml" },
- { "application/rss+xml", "rss" },
- { "application/rdf+xml", "rdf" },
- { "text/xml", "xsl,xbl" },
- { "application/vnd.mozilla.xul+xml", "xul" },
- { "application/x-shockwave-flash", "swf,swl" },
- { "application/pkcs7-mime", "p7m,p7c,p7z" },
- { "application/pkcs7-signature", "p7s" }
-};
-
-static const char* FindMimeType(const MimeInfo* mappings,
- size_t mappings_len,
- const char* ext) {
- size_t ext_len = strlen(ext);
-
- for (size_t i = 0; i < mappings_len; ++i) {
- const char* extensions = mappings[i].extensions;
- for (;;) {
- size_t end_pos = strcspn(extensions, ",");
- if (end_pos == ext_len &&
- base::strncasecmp(extensions, ext, ext_len) == 0)
- return mappings[i].mime_type;
- extensions += end_pos;
- if (!*extensions)
- break;
- extensions += 1; // skip over comma
- }
- }
- return NULL;
-}
-
-bool MimeUtil::GetMimeTypeFromExtension(const FilePath::StringType& ext,
- string* result) const {
- return GetMimeTypeFromExtensionHelper(ext, true, result);
-}
-
-bool MimeUtil::GetWellKnownMimeTypeFromExtension(
- const FilePath::StringType& ext,
- string* result) const {
- return GetMimeTypeFromExtensionHelper(ext, false, result);
-}
-
-bool MimeUtil::GetMimeTypeFromFile(const FilePath& file_path,
- string* result) const {
- FilePath::StringType file_name_str = file_path.Extension();
- if (file_name_str.empty())
- return false;
- return GetMimeTypeFromExtension(file_name_str.substr(1), result);
-}
-
-bool MimeUtil::GetMimeTypeFromExtensionHelper(const FilePath::StringType& ext,
- bool include_platform_types,
- string* result) const {
- // Avoids crash when unable to handle a long file path. See crbug.com/48733.
- const unsigned kMaxFilePathSize = 65536;
- if (ext.length() > kMaxFilePathSize)
- return false;
-
- // We implement the same algorithm as Mozilla for mapping a file extension to
- // a mime type. That is, we first check a hard-coded list (that cannot be
- // overridden), and then if not found there, we defer to the system registry.
- // Finally, we scan a secondary hard-coded list to catch types that we can
- // deduce but that we also want to allow the OS to override.
-
-#if defined(OS_WIN)
- string ext_narrow_str = WideToUTF8(ext);
-#elif defined(OS_POSIX) || defined(OS_STARBOARD)
- const string& ext_narrow_str = ext;
-#endif
- const char* mime_type;
-
- mime_type = FindMimeType(primary_mappings, arraysize(primary_mappings),
- ext_narrow_str.c_str());
- if (mime_type) {
- *result = mime_type;
- return true;
- }
-
- mime_type = FindMimeType(secondary_mappings, arraysize(secondary_mappings),
- ext_narrow_str.c_str());
- if (mime_type) {
- *result = mime_type;
- return true;
- }
-
- return false;
-}
-
-// From WebKit's WebCore/platform/MIMETypeRegistry.cpp:
-
-static const char* const supported_image_types[] = {
- "image/jpeg",
- "image/pjpeg",
- "image/jpg",
- "image/webp",
- "image/png",
- "image/gif",
- "image/bmp",
- "image/x-icon", // ico
- "image/x-xbitmap" // xbm
-};
-
-// A list of media types: http://en.wikipedia.org/wiki/Internet_media_type
-// A comprehensive mime type list: http://plugindoc.mozdev.org/winmime.php
-// This set of codecs is supported by all variations of Chromium.
-static const char* const common_media_types[] = {
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- // Ogg.
- "audio/ogg",
- "application/ogg",
-#if defined(ENABLE_MEDIA_CODEC_THEORA)
- "video/ogg",
-#endif
-
- // WebM.
- "video/webm",
- "audio/webm",
-
- // Wav.
- "audio/wav",
- "audio/x-wav",
-#else // !defined(__LB_SHELL__) && !defined(COBALT)
-
-#if defined(OS_STARBOARD)
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- // WebM.
- "video/webm",
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-#endif // defined(OS_STARBOARD)
-
- // currently lbshell supports flv and mp4
- "video/mp4", "audio/mp4", "video/x-flv",
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
-};
-
-// List of proprietary types only supported by Google Chrome.
-static const char* const proprietary_media_types[] = {
- // MPEG-4.
- "video/mp4",
- "video/x-m4v",
- "audio/mp4",
- "audio/x-m4a",
-
- // MP3.
- "audio/mp3",
- "audio/x-mp3",
- "audio/mpeg",
-};
-
-// List of supported codecs when passed in with <source type="...">.
-// This set of codecs is supported by all variations of Chromium.
-//
-// Refer to http://wiki.whatwg.org/wiki/Video_type_parameters#Browser_Support
-// for more information.
-//
-// The codecs for WAV are integers as defined in Appendix A of RFC2361:
-// http://tools.ietf.org/html/rfc2361
-static const char* const common_media_codecs[] = {
-#if !defined(__LB_SHELL__) && !defined(COBALT)
-#if defined(ENABLE_MEDIA_CODEC_THEORA)
- "theora",
-#endif
- "vorbis",
- "vp8",
- "1" // WAVE_FORMAT_PCM.
-#else // !defined(__LB_SHELL__) && !defined(COBALT)
-
-#if defined(OS_STARBOARD)
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- "vp9",
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-#endif // defined(OS_STARBOARD)
-
- "avc1",
- "mp4a",
- "aac51",
-
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
-};
-
-// List of proprietary codecs only supported by Google Chrome.
-static const char* const proprietary_media_codecs[] = {
- "avc1",
- "mp4a"
-};
-
-// Note: does not include javascript types list (see supported_javascript_types)
-static const char* const supported_non_image_types[] = {
- "text/cache-manifest",
- "text/html",
- "text/xml",
- "text/xsl",
- "text/plain",
- // Many users complained about css files served for
- // download instead of displaying in the browser:
- // http://code.google.com/p/chromium/issues/detail?id=7192
- // So, by including "text/css" into this list we choose Firefox
- // behavior - css files will be displayed:
- "text/css",
- "text/vnd.chromium.ftp-dir",
- "text/",
- "image/svg+xml", // SVG is text-based XML, even though it has an image/ type
- "application/xml",
- "application/atom+xml",
- "application/rss+xml",
- "application/xhtml+xml",
- "application/json",
- "multipart/related", // For MHTML support.
- "multipart/x-mixed-replace"
- // Note: ADDING a new type here will probably render it AS HTML. This can
- // result in cross site scripting.
-};
-
-// Dictionary of cryptographic file mime types.
-struct CertificateMimeTypeInfo {
- const char* mime_type;
- CertificateMimeType cert_type;
-};
-
-static const CertificateMimeTypeInfo supported_certificate_types[] = {
- { "application/x-x509-user-cert",
- CERTIFICATE_MIME_TYPE_X509_USER_CERT },
-#if defined(OS_ANDROID)
- { "application/x-x509-ca-cert", CERTIFICATE_MIME_TYPE_X509_CA_CERT },
- { "application/x-pkcs12", CERTIFICATE_MIME_TYPE_PKCS12_ARCHIVE },
-#endif
-};
-
-// These types are excluded from the logic that allows all text/ types because
-// while they are technically text, it's very unlikely that a user expects to
-// see them rendered in text form.
-static const char* const unsupported_text_types[] = {
- "text/calendar",
- "text/x-calendar",
- "text/x-vcalendar",
- "text/vcalendar",
- "text/vcard",
- "text/x-vcard",
- "text/directory",
- "text/ldif",
- "text/qif",
- "text/x-qif",
- "text/x-csv",
- "text/x-vcf",
- "text/rtf",
- "text/comma-separated-values",
- "text/csv",
- "text/tab-separated-values",
- "text/tsv",
-};
-
-// Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript.
-// Mozilla 1.8 accepts application/javascript, application/ecmascript, and
-// application/x-javascript, but WinIE 7 doesn't.
-// WinIE 7 accepts text/javascript1.1 - text/javascript1.3, text/jscript, and
-// text/livescript, but Mozilla 1.8 doesn't.
-// Mozilla 1.8 allows leading and trailing whitespace, but WinIE 7 doesn't.
-// Mozilla 1.8 and WinIE 7 both accept the empty string, but neither accept a
-// whitespace-only string.
-// We want to accept all the values that either of these browsers accept, but
-// not other values.
-static const char* const supported_javascript_types[] = {
- "text/javascript",
- "text/ecmascript",
- "application/javascript",
- "application/ecmascript",
- "application/x-javascript",
- "text/javascript1.1",
- "text/javascript1.2",
- "text/javascript1.3",
- "text/jscript",
- "text/livescript"
-};
-
-static const char* const view_source_types[] = {
- "text/xml",
- "text/xsl",
- "application/xml",
- "application/rss+xml",
- "application/atom+xml",
- "image/svg+xml"
-};
-
-struct MediaFormatStrict {
- const char* mime_type;
- const char* codecs_list;
-};
-
-static const MediaFormatStrict format_codec_mappings[] = {
-#if defined(__LB_ANDROID__) // Assume Android supports everything.
- { "video/webm", "vorbis,vp8,vp8.0,vp9" },
- { "audio/webm", "vorbis" },
-#elif defined(OS_STARBOARD)
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- {"video/webm", "vp9"},
- {"audio/webm", ""},
-#else // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- {"video/webm", ""},
- {"audio/webm", ""},
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-#elif defined(__LB_SHELL__) || defined(COBALT)
- // No other platforms support webm.
- { "video/webm", "" },
- { "audio/webm", "" },
-#else // Default Chrome codec mappings.
- { "video/webm", "vorbis,vp8,vp8.0" },
- { "audio/webm", "vorbis" },
- { "audio/wav", "1" }
-#endif
-};
-
-MimeUtil::MimeUtil() {
- InitializeMimeTypeMaps();
-}
-
-// static
-bool MimeUtil::AreSupportedCodecs(const MimeMappings& supported_codecs,
- const std::vector<std::string>& codecs) {
- for (size_t i = 0; i < codecs.size(); ++i) {
- if (supported_codecs.find(codecs[i]) == supported_codecs.end())
- return false;
- }
- return !codecs.empty();
-}
-
-void MimeUtil::InitializeMimeTypeMaps() {
- for (size_t i = 0; i < arraysize(supported_image_types); ++i)
- image_map_.insert(supported_image_types[i]);
-
- // Initialize the supported non-image types.
- for (size_t i = 0; i < arraysize(supported_non_image_types); ++i)
- non_image_map_.insert(supported_non_image_types[i]);
- for (size_t i = 0; i < arraysize(supported_certificate_types); ++i)
- non_image_map_.insert(supported_certificate_types[i].mime_type);
- for (size_t i = 0; i < arraysize(unsupported_text_types); ++i)
- unsupported_text_map_.insert(unsupported_text_types[i]);
- for (size_t i = 0; i < arraysize(supported_javascript_types); ++i)
- non_image_map_.insert(supported_javascript_types[i]);
- for (size_t i = 0; i < arraysize(common_media_types); ++i)
- non_image_map_.insert(common_media_types[i]);
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
- for (size_t i = 0; i < arraysize(proprietary_media_types); ++i)
- non_image_map_.insert(proprietary_media_types[i]);
-#endif
-
- // Initialize the supported media types.
- for (size_t i = 0; i < arraysize(common_media_types); ++i)
- media_map_.insert(common_media_types[i]);
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
- for (size_t i = 0; i < arraysize(proprietary_media_types); ++i)
- media_map_.insert(proprietary_media_types[i]);
-#endif
-
- for (size_t i = 0; i < arraysize(supported_javascript_types); ++i)
- javascript_map_.insert(supported_javascript_types[i]);
-
- for (size_t i = 0; i < arraysize(view_source_types); ++i)
- view_source_map_.insert(view_source_types[i]);
-
- for (size_t i = 0; i < arraysize(common_media_codecs); ++i)
- codecs_map_.insert(common_media_codecs[i]);
-#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
- for (size_t i = 0; i < arraysize(proprietary_media_codecs); ++i)
- codecs_map_.insert(proprietary_media_codecs[i]);
-#endif
-
- // Initialize the strict supported media types.
- for (size_t i = 0; i < arraysize(format_codec_mappings); ++i) {
- std::vector<std::string> mime_type_codecs;
- ParseCodecString(format_codec_mappings[i].codecs_list,
- &mime_type_codecs,
- false);
-
- MimeMappings codecs;
- for (size_t j = 0; j < mime_type_codecs.size(); ++j)
- codecs.insert(mime_type_codecs[j]);
- strict_format_map_[format_codec_mappings[i].mime_type] = codecs;
- }
-}
-
-bool MimeUtil::IsSupportedImageMimeType(const std::string& mime_type) const {
- return image_map_.find(mime_type) != image_map_.end();
-}
-
-bool MimeUtil::IsSupportedMediaMimeType(const std::string& mime_type) const {
- return media_map_.find(mime_type) != media_map_.end();
-}
-
-bool MimeUtil::IsSupportedNonImageMimeType(const std::string& mime_type) const {
- return non_image_map_.find(mime_type) != non_image_map_.end() ||
- (mime_type.compare(0, 5, "text/") == 0 &&
- !IsUnsupportedTextMimeType(mime_type));
-}
-
-bool MimeUtil::IsUnsupportedTextMimeType(const std::string& mime_type) const {
- return unsupported_text_map_.find(mime_type) != unsupported_text_map_.end();
-}
-
-bool MimeUtil::IsSupportedJavascriptMimeType(
- const std::string& mime_type) const {
- return javascript_map_.find(mime_type) != javascript_map_.end();
-}
-
-bool MimeUtil::IsViewSourceMimeType(const std::string& mime_type) const {
- return view_source_map_.find(mime_type) != view_source_map_.end();
-}
-
-// Mirrors WebViewImpl::CanShowMIMEType()
-bool MimeUtil::IsSupportedMimeType(const std::string& mime_type) const {
- return (mime_type.compare(0, 6, "image/") == 0 &&
- IsSupportedImageMimeType(mime_type)) ||
- IsSupportedNonImageMimeType(mime_type);
-}
-
-// Tests for MIME parameter equality. Each parameter in the |mime_type_pattern|
-// must be matched by a parameter in the |mime_type|. If there are no
-// parameters in the pattern, the match is a success.
-bool MatchesMimeTypeParameters(const std::string& mime_type_pattern,
- const std::string& mime_type) {
- const std::string::size_type semicolon = mime_type_pattern.find(';');
- const std::string::size_type test_semicolon = mime_type.find(';');
- if (semicolon != std::string::npos) {
- if (test_semicolon == std::string::npos)
- return false;
-
- std::vector<std::string> pattern_parameters;
- base::SplitString(mime_type_pattern.substr(semicolon + 1),
- ';', &pattern_parameters);
-
- std::vector<std::string> test_parameters;
- base::SplitString(mime_type.substr(test_semicolon + 1),
- ';', &test_parameters);
-
- sort(pattern_parameters.begin(), pattern_parameters.end());
- sort(test_parameters.begin(), test_parameters.end());
- std::vector<std::string> difference;
- std::set_difference(pattern_parameters.begin(), pattern_parameters.end(),
- test_parameters.begin(), test_parameters.end(),
- std::inserter(difference, difference.begin()));
-
- return difference.size() == 0;
- }
- return true;
-}
-
-// This comparison handles absolute maching and also basic
-// wildcards. The plugin mime types could be:
-// application/x-foo
-// application/*
-// application/*+xml
-// *
-// Also tests mime parameters -- all parameters in the pattern must be present
-// in the tested type for a match to succeed.
-bool MimeUtil::MatchesMimeType(const std::string& mime_type_pattern,
- const std::string& mime_type) const {
- // Verify caller is passing lowercase strings.
- DCHECK_EQ(StringToLowerASCII(mime_type_pattern), mime_type_pattern);
- DCHECK_EQ(StringToLowerASCII(mime_type), mime_type);
-
- if (mime_type_pattern.empty())
- return false;
-
- std::string::size_type semicolon = mime_type_pattern.find(';');
- const std::string base_pattern(mime_type_pattern.substr(0, semicolon));
- semicolon = mime_type.find(';');
- const std::string base_type(mime_type.substr(0, semicolon));
-
- if (base_pattern == "*" || base_pattern == "*/*")
- return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
-
- const std::string::size_type star = base_pattern.find('*');
- if (star == std::string::npos) {
- if (base_pattern == base_type)
- return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
- else
- return false;
- }
-
- // Test length to prevent overlap between |left| and |right|.
- if (base_type.length() < base_pattern.length() - 1)
- return false;
-
- const std::string left(base_pattern.substr(0, star));
- const std::string right(base_pattern.substr(star + 1));
-
- if (base_type.find(left) != 0)
- return false;
-
- if (!right.empty() &&
- base_type.rfind(right) != base_type.length() - right.length())
- return false;
-
- return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
-}
-
-// See http://www.iana.org/assignments/media-types/index.html
-static const char* legal_top_level_types[] = {
- "application/",
- "audio/",
- "example/",
- "image/",
- "message/",
- "model/",
- "multipart/",
- "text/",
- "video/",
-};
-
-bool MimeUtil::IsMimeType(const std::string& type_string) const {
- // MIME types are always ASCII and case-insensitive (at least, the top-level
- // and secondary types we care about).
- if (!IsStringASCII(type_string))
- return false;
-
- if (type_string == "*/*" || type_string == "*")
- return true;
-
- for (size_t i = 0; i < arraysize(legal_top_level_types); ++i) {
- if (StartsWithASCII(type_string, legal_top_level_types[i], false) &&
- type_string.length() > strlen(legal_top_level_types[i])) {
- return true;
- }
- }
-
- // If there's a "/" separator character, and the token before it is
- // "x-" + (ascii characters), it is also a MIME type.
- size_t slash = type_string.find('/');
- if (slash < 3 ||
- slash == std::string::npos || slash == type_string.length() - 1) {
- return false;
- }
-
- if (StartsWithASCII(type_string, "x-", false))
- return true;
-
- return false;
-}
-
-bool MimeUtil::AreSupportedMediaCodecs(
- const std::vector<std::string>& codecs) const {
- return AreSupportedCodecs(codecs_map_, codecs);
-}
-
-void MimeUtil::ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out,
- bool strip) {
- std::string no_quote_codecs;
- TrimString(codecs, "\"", &no_quote_codecs);
- base::SplitString(no_quote_codecs, ',', codecs_out);
-
- if (!strip)
- return;
-
- // Strip everything past the first '.'
- for (std::vector<std::string>::iterator it = codecs_out->begin();
- it != codecs_out->end();
- ++it) {
- size_t found = it->find_first_of('.');
- if (found != std::string::npos)
- it->resize(found);
- }
-}
-
-bool MimeUtil::IsStrictMediaMimeType(const std::string& mime_type) const {
- if (strict_format_map_.find(mime_type) == strict_format_map_.end())
- return false;
- return true;
-}
-
-bool MimeUtil::IsSupportedStrictMediaMimeType(
- const std::string& mime_type,
- const std::vector<std::string>& codecs) const {
- StrictMappings::const_iterator it = strict_format_map_.find(mime_type);
- return (it != strict_format_map_.end()) &&
- AreSupportedCodecs(it->second, codecs);
-}
-
-//----------------------------------------------------------------------------
-// Wrappers for the singleton
-//----------------------------------------------------------------------------
-
-bool GetMimeTypeFromExtension(const FilePath::StringType& ext,
- std::string* mime_type) {
- return g_mime_util.Get().GetMimeTypeFromExtension(ext, mime_type);
-}
-
-bool GetMimeTypeFromFile(const FilePath& file_path, std::string* mime_type) {
- return g_mime_util.Get().GetMimeTypeFromFile(file_path, mime_type);
-}
-
-bool GetWellKnownMimeTypeFromExtension(const FilePath::StringType& ext,
- std::string* mime_type) {
- return g_mime_util.Get().GetWellKnownMimeTypeFromExtension(ext, mime_type);
-}
-
-bool GetPreferredExtensionForMimeType(const std::string& mime_type,
- FilePath::StringType* extension) {
- return false;
-}
-
-bool IsSupportedImageMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsSupportedImageMimeType(mime_type);
-}
-
-bool IsSupportedMediaMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsSupportedMediaMimeType(mime_type);
-}
-
-bool IsSupportedNonImageMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsSupportedNonImageMimeType(mime_type);
-}
-
-bool IsUnsupportedTextMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsUnsupportedTextMimeType(mime_type);
-}
-
-bool IsSupportedJavascriptMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsSupportedJavascriptMimeType(mime_type);
-}
-
-bool IsViewSourceMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsViewSourceMimeType(mime_type);
-}
-
-bool IsSupportedMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsSupportedMimeType(mime_type);
-}
-
-bool MatchesMimeType(const std::string& mime_type_pattern,
- const std::string& mime_type) {
- return g_mime_util.Get().MatchesMimeType(mime_type_pattern, mime_type);
-}
-
-bool IsMimeType(const std::string& type_string) {
- return g_mime_util.Get().IsMimeType(type_string);
-}
-
-bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs) {
- return g_mime_util.Get().AreSupportedMediaCodecs(codecs);
-}
-
-bool IsStrictMediaMimeType(const std::string& mime_type) {
- return g_mime_util.Get().IsStrictMediaMimeType(mime_type);
-}
-
-bool IsSupportedStrictMediaMimeType(const std::string& mime_type,
- const std::vector<std::string>& codecs) {
- return g_mime_util.Get().IsSupportedStrictMediaMimeType(mime_type, codecs);
-}
-
-void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out,
- const bool strip) {
- g_mime_util.Get().ParseCodecString(codecs, codecs_out, strip);
-}
-
-namespace {
-
-// From http://www.w3schools.com/media/media_mimeref.asp and
-// http://plugindoc.mozdev.org/winmime.php
-static const char* const kStandardImageTypes[] = {
- "image/bmp",
- "image/cis-cod",
- "image/gif",
- "image/ief",
- "image/jpeg",
- "image/webp",
- "image/pict",
- "image/pipeg",
- "image/png",
- "image/svg+xml",
- "image/tiff",
- "image/x-cmu-raster",
- "image/x-cmx",
- "image/x-icon",
- "image/x-portable-anymap",
- "image/x-portable-bitmap",
- "image/x-portable-graymap",
- "image/x-portable-pixmap",
- "image/x-rgb",
- "image/x-xbitmap",
- "image/x-xpixmap",
- "image/x-xwindowdump"
-};
-static const char* const kStandardAudioTypes[] = {
- "audio/aac",
- "audio/aiff",
- "audio/amr",
- "audio/basic",
- "audio/midi",
- "audio/mp3",
- "audio/mp4",
- "audio/mpeg",
- "audio/mpeg3",
- "audio/ogg",
- "audio/vorbis",
- "audio/wav",
- "audio/webm",
- "audio/x-m4a",
- "audio/x-ms-wma",
- "audio/vnd.rn-realaudio",
- "audio/vnd.wave"
-};
-static const char* const kStandardVideoTypes[] = {
- "video/avi",
- "video/divx",
- "video/flc",
- "video/mp4",
- "video/mpeg",
- "video/ogg",
- "video/quicktime",
- "video/sd-video",
- "video/webm",
- "video/x-dv",
- "video/x-m4v",
- "video/x-mpeg",
- "video/x-ms-asf",
- "video/x-ms-wmv"
-};
-
-struct StandardType {
- const char* leading_mime_type;
- const char* const* standard_types;
- size_t standard_types_len;
-};
-static const StandardType kStandardTypes[] = {
- { "image/", kStandardImageTypes, arraysize(kStandardImageTypes) },
- { "audio/", kStandardAudioTypes, arraysize(kStandardAudioTypes) },
- { "video/", kStandardVideoTypes, arraysize(kStandardVideoTypes) },
- { NULL, NULL, 0 }
-};
-
-void GetExtensionsFromHardCodedMappings(
- const MimeInfo* mappings,
- size_t mappings_len,
- const std::string& leading_mime_type,
- base::hash_set<FilePath::StringType>* extensions) {
- FilePath::StringType extension;
- for (size_t i = 0; i < mappings_len; ++i) {
- if (StartsWithASCII(mappings[i].mime_type, leading_mime_type, false)) {
- std::vector<string> this_extensions;
- base::SplitStringUsingSubstr(mappings[i].extensions, ",",
- &this_extensions);
- for (size_t j = 0; j < this_extensions.size(); ++j) {
-#if defined(OS_WIN)
- FilePath::StringType extension(UTF8ToWide(this_extensions[j]));
-#else
- FilePath::StringType extension(this_extensions[j]);
-#endif
- extensions->insert(extension);
- }
- }
- }
-}
-
-void GetExtensionsHelper(const char* const* standard_types,
- size_t standard_types_len,
- const std::string& leading_mime_type,
- base::hash_set<FilePath::StringType>* extensions) {
- // Also look up the extensions from hard-coded mappings in case that some
- // supported extensions are not registered in the system registry, like ogg.
- GetExtensionsFromHardCodedMappings(primary_mappings,
- arraysize(primary_mappings),
- leading_mime_type,
- extensions);
-
- GetExtensionsFromHardCodedMappings(secondary_mappings,
- arraysize(secondary_mappings),
- leading_mime_type,
- extensions);
-}
-
-// Note that the elements in the source set will be appended to the target
-// vector.
-template<class T>
-void HashSetToVector(base::hash_set<T>* source, std::vector<T>* target) {
- size_t old_target_size = target->size();
- target->resize(old_target_size + source->size());
- size_t i = 0;
- for (typename base::hash_set<T>::iterator iter = source->begin();
- iter != source->end(); ++iter, ++i)
- (*target)[old_target_size + i] = *iter;
-}
-}
-
-void GetExtensionsForMimeType(const std::string& unsafe_mime_type,
- std::vector<FilePath::StringType>* extensions) {
- if (unsafe_mime_type == "*/*" || unsafe_mime_type == "*")
- return;
-
- const std::string mime_type = StringToLowerASCII(unsafe_mime_type);
- base::hash_set<FilePath::StringType> unique_extensions;
-
- if (EndsWith(mime_type, "/*", true)) {
- std::string leading_mime_type = mime_type.substr(0, mime_type.length() - 1);
-
- // Find the matching StandardType from within kStandardTypes, or fall
- // through to the last (default) StandardType.
- const StandardType* type = NULL;
- for (size_t i = 0; i < arraysize(kStandardTypes); ++i) {
- type = &(kStandardTypes[i]);
- if (type->leading_mime_type &&
- leading_mime_type == type->leading_mime_type)
- break;
- }
- DCHECK(type);
- GetExtensionsHelper(type->standard_types,
- type->standard_types_len,
- leading_mime_type,
- &unique_extensions);
- } else {
- // Also look up the extensions from hard-coded mappings in case that some
- // supported extensions are not registered in the system registry, like ogg.
- GetExtensionsFromHardCodedMappings(primary_mappings,
- arraysize(primary_mappings),
- mime_type,
- &unique_extensions);
-
- GetExtensionsFromHardCodedMappings(secondary_mappings,
- arraysize(secondary_mappings),
- mime_type,
- &unique_extensions);
- }
-
- HashSetToVector(&unique_extensions, extensions);
-}
-
-void GetMediaTypesBlacklistedForTests(std::vector<std::string>* types) {
- types->clear();
-
-// Unless/until WebM files are added to the media layout tests, we need to avoid
-// blacklisting mp4 and H.264 when Theora is not supported (and proprietary
-// codecs are) so that the media tests can still run.
-#if defined(ENABLE_MEDIA_CODEC_THEORA) || !defined(USE_PROPRIETARY_CODECS)
- for (size_t i = 0; i < arraysize(proprietary_media_types); ++i)
- types->push_back(proprietary_media_types[i]);
-#endif
-}
-
-void GetMediaCodecsBlacklistedForTests(std::vector<std::string>* codecs) {
- codecs->clear();
-
-// Unless/until WebM files are added to the media layout tests, we need to avoid
-// blacklisting mp4 and H.264 when Theora is not supported (and proprietary
-// codecs are) so that the media tests can still run.
-#if defined(ENABLE_MEDIA_CODEC_THEORA) || !defined(USE_PROPRIETARY_CODECS)
- for (size_t i = 0; i < arraysize(proprietary_media_codecs); ++i)
- codecs->push_back(proprietary_media_codecs[i]);
-#endif
-}
-
-const std::string GetIANAMediaType(const std::string& mime_type) {
- for (size_t i = 0; i < arraysize(kIanaMediaTypes); ++i) {
- if (StartsWithASCII(mime_type, kIanaMediaTypes[i].matcher, true)) {
- return kIanaMediaTypes[i].name;
- }
- }
- return "";
-}
-
-CertificateMimeType GetCertificateMimeTypeForMimeType(
- const std::string& mime_type) {
- // Don't create a map, there is only one entry in the table,
- // except on Android.
- for (size_t i = 0; i < arraysize(supported_certificate_types); ++i) {
- if (mime_type == supported_certificate_types[i].mime_type)
- return supported_certificate_types[i].cert_type;
- }
- return CERTIFICATE_MIME_TYPE_UNKNOWN;
-}
-
-bool IsSupportedCertificateMimeType(const std::string& mime_type) {
- CertificateMimeType file_type =
- GetCertificateMimeTypeForMimeType(mime_type);
- return file_type != CERTIFICATE_MIME_TYPE_UNKNOWN;
-}
-
-} // namespace media
diff --git a/src/media/player/mime_util.h b/src/media/player/mime_util.h
deleted file mode 100644
index d9779a0..0000000
--- a/src/media/player/mime_util.h
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_PLAYER_MIME_UTIL_H__
-#define MEDIA_PLAYER_MIME_UTIL_H__
-
-#include <string>
-#include <vector>
-
-#include "base/file_path.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Get the mime type (if any) that is associated with the given file extension.
-// Returns true if a corresponding mime type exists.
-MEDIA_EXPORT bool GetMimeTypeFromExtension(const FilePath::StringType& ext,
- std::string* mime_type);
-
-// Get the mime type (if any) that is associated with the given file extension.
-// Returns true if a corresponding mime type exists. In this method,
-// the search for a mime type is constrained to a limited set of
-// types known to the net library, the OS/registry is not consulted.
-MEDIA_EXPORT bool GetWellKnownMimeTypeFromExtension(
- const FilePath::StringType& ext,
- std::string* mime_type);
-
-// Get the mime type (if any) that is associated with the given file. Returns
-// true if a corresponding mime type exists.
-MEDIA_EXPORT bool GetMimeTypeFromFile(const FilePath& file_path,
- std::string* mime_type);
-
-// Get the preferred extension (if any) associated with the given mime type.
-// Returns true if a corresponding file extension exists. The extension is
-// returned without a prefixed dot, ex "html".
-MEDIA_EXPORT bool GetPreferredExtensionForMimeType(
- const std::string& mime_type,
- FilePath::StringType* extension);
-
-// Check to see if a particular MIME type is in our list.
-MEDIA_EXPORT bool IsSupportedImageMimeType(const std::string& mime_type);
-MEDIA_EXPORT bool IsSupportedMediaMimeType(const std::string& mime_type);
-MEDIA_EXPORT bool IsSupportedNonImageMimeType(const std::string& mime_type);
-MEDIA_EXPORT bool IsUnsupportedTextMimeType(const std::string& mime_type);
-MEDIA_EXPORT bool IsSupportedJavascriptMimeType(const std::string& mime_type);
-MEDIA_EXPORT bool IsSupportedCertificateMimeType(const std::string& mime_type);
-
-// Get whether this mime type should be displayed in view-source mode.
-// (For example, XML.)
-MEDIA_EXPORT bool IsViewSourceMimeType(const std::string& mime_type);
-
-// Convenience function.
-MEDIA_EXPORT bool IsSupportedMimeType(const std::string& mime_type);
-
-// Returns true if this the mime_type_pattern matches a given mime-type.
-// Checks for absolute matching and wildcards. mime-types should be in
-// lower case.
-MEDIA_EXPORT bool MatchesMimeType(const std::string& mime_type_pattern,
- const std::string& mime_type);
-
-// Returns true if the |type_string| is a correctly-formed mime type specifier.
-// Allows strings of the form x/y[;params], where "x" is a legal mime type name.
-// Also allows wildcard types -- "x/*", "*/*", and "*".
-MEDIA_EXPORT bool IsMimeType(const std::string& type_string);
-
-// Returns true if and only if all codecs are supported, false otherwise.
-MEDIA_EXPORT bool AreSupportedMediaCodecs(
- const std::vector<std::string>& codecs);
-
-// Parses a codec string, populating |codecs_out| with the prefix of each codec
-// in the string |codecs_in|. For example, passed "aaa.b.c,dd.eee", if
-// |strip| == true |codecs_out| will contain {"aaa", "dd"}, if |strip| == false
-// |codecs_out| will contain {"aaa.b.c", "dd.eee"}.
-// See http://www.ietf.org/rfc/rfc4281.txt.
-MEDIA_EXPORT void ParseCodecString(const std::string& codecs,
- std::vector<std::string>* codecs_out,
- bool strip);
-
-// Check to see if a particular MIME type is in our list which only supports a
-// certain subset of codecs.
-MEDIA_EXPORT bool IsStrictMediaMimeType(const std::string& mime_type);
-
-// Check to see if a particular MIME type is in our list which only supports a
-// certain subset of codecs. Returns true if and only if all codecs are
-// supported for that specific MIME type, false otherwise. If this returns
-// false you will still need to check if the media MIME tpyes and codecs are
-// supported.
-MEDIA_EXPORT bool IsSupportedStrictMediaMimeType(
- const std::string& mime_type,
- const std::vector<std::string>& codecs);
-
-// Get the extensions associated with the given mime type. This should be passed
-// in lower case. There could be multiple extensions for a given mime type, like
-// "html,htm" for "text/html", or "txt,text,html,..." for "text/*".
-// Note that we do not erase the existing elements in the the provided vector.
-// Instead, we append the result to it.
-MEDIA_EXPORT void GetExtensionsForMimeType(
- const std::string& mime_type,
- std::vector<FilePath::StringType>* extensions);
-
-// Test only methods that return lists of proprietary media types and codecs
-// that are not supported by all variations of Chromium.
-// These types and codecs must be blacklisted to ensure consistent layout test
-// results across all Chromium variations.
-MEDIA_EXPORT void GetMediaTypesBlacklistedForTests(
- std::vector<std::string>* types);
-MEDIA_EXPORT void GetMediaCodecsBlacklistedForTests(
- std::vector<std::string>* codecs);
-
-// Returns the IANA media type contained in |mime_type|, or an empty
-// string if |mime_type| does not specifify a known media type.
-// Supported media types are defined at:
-// http://www.iana.org/assignments/media-types/index.html
-MEDIA_EXPORT const std::string GetIANAMediaType(const std::string& mime_type);
-
-// A list of supported certificate-related mime types.
-enum CertificateMimeType {
-#define CERTIFICATE_MIME_TYPE(name, value) CERTIFICATE_MIME_TYPE_ ## name = value,
-#include "media/player/mime_util_certificate_type_list.h"
-#undef CERTIFICATE_MIME_TYPE
-};
-
-MEDIA_EXPORT CertificateMimeType GetCertificateMimeTypeForMimeType(
- const std::string& mime_type);
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_MIME_UTIL_H__
diff --git a/src/media/player/mime_util_certificate_type_list.h b/src/media/player/mime_util_certificate_type_list.h
deleted file mode 100644
index c3d2947..0000000
--- a/src/media/player/mime_util_certificate_type_list.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This file intentionally does not have header guards, it's included
-// inside a macro to generate enum.
-
-// This file contains the list of certificate MIME types.
-
-CERTIFICATE_MIME_TYPE(UNKNOWN, 0)
-CERTIFICATE_MIME_TYPE(X509_USER_CERT, 1)
-CERTIFICATE_MIME_TYPE(X509_CA_CERT, 2)
-CERTIFICATE_MIME_TYPE(PKCS12_ARCHIVE, 3)
diff --git a/src/media/player/web_media_player.h b/src/media/player/web_media_player.h
deleted file mode 100644
index 6ab6c02..0000000
--- a/src/media/player/web_media_player.h
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_PLAYER_WEB_MEDIA_PLAYER_H_
-#define MEDIA_PLAYER_WEB_MEDIA_PLAYER_H_
-
-// The temporary home for WebMediaPlayer and WebMediaPlayerClient. They are the
-// interface between the HTMLMediaElement and the media stack.
-
-#include <string>
-
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/time.h"
-#include "googleurl/src/gurl.h"
-#include "media/base/ranges.h"
-#include "media/base/shell_video_frame_provider.h"
-#include "media/base/video_frame.h"
-#include "media/player/buffered_data_source.h"
-#include "ui/gfx/rect.h"
-#include "ui/gfx/size.h"
-
-// Disable `unreferenced formal parameter` as we have many stub functions in
-// this file and we want to keep their parameters.
-MSVC_PUSH_DISABLE_WARNING(4100)
-
-namespace media {
-
-class WebMediaPlayer {
- public:
- // Return true if the punch through box should be rendered. Return false if
- // no punch through box should be rendered.
- typedef base::Callback<bool(const gfx::Rect&)> SetBoundsCB;
-
- enum NetworkState {
- kNetworkStateEmpty,
- kNetworkStateIdle,
- kNetworkStateLoading,
- kNetworkStateLoaded,
- kNetworkStateFormatError,
- kNetworkStateNetworkError,
- kNetworkStateDecodeError,
- };
-
- enum ReadyState {
- kReadyStateHaveNothing,
- kReadyStateHaveMetadata,
- kReadyStateHaveCurrentData,
- kReadyStateHaveFutureData,
- kReadyStateHaveEnoughData,
- };
-
- enum AddIdStatus {
- kAddIdStatusOk,
- kAddIdStatusNotSupported,
- kAddIdStatusReachedIdLimit
- };
-
- enum EndOfStreamStatus {
- kEndOfStreamStatusNoError,
- kEndOfStreamStatusNetworkError,
- kEndOfStreamStatusDecodeError,
- };
-
- // Represents synchronous exceptions that can be thrown from the Encrypted
- // Media methods. This is different from the asynchronous MediaKeyError.
- enum MediaKeyException {
- kMediaKeyExceptionNoError,
- kMediaKeyExceptionInvalidPlayerState,
- kMediaKeyExceptionKeySystemNotSupported,
- };
-
- enum CORSMode {
- kCORSModeUnspecified,
- kCORSModeAnonymous,
- kCORSModeUseCredentials,
- };
-
- virtual ~WebMediaPlayer() {}
-
- virtual void LoadMediaSource() = 0;
- virtual void LoadProgressive(const GURL& url,
- scoped_ptr<BufferedDataSource> data_source,
- CORSMode cors_mode) = 0;
- virtual void CancelLoad() = 0;
-
- // Playback controls.
- virtual void Play() = 0;
- virtual void Pause() = 0;
- virtual bool SupportsFullscreen() const = 0;
- virtual bool SupportsSave() const = 0;
- virtual void Seek(float seconds) = 0;
- virtual void SetEndTime(float seconds) = 0;
- virtual void SetRate(float rate) = 0;
- virtual void SetVolume(float volume) = 0;
- virtual void SetVisible(bool visible) = 0;
- virtual const Ranges<base::TimeDelta>& GetBufferedTimeRanges() = 0;
- virtual float GetMaxTimeSeekable() const = 0;
-
- // Suspend/Resume
- virtual void Suspend() = 0;
- virtual void Resume() = 0;
-
- // True if the loaded media has a playable video/audio track.
- virtual bool HasVideo() const = 0;
- virtual bool HasAudio() const = 0;
-
- // Dimension of the video.
- virtual gfx::Size GetNaturalSize() const = 0;
-
- // Getters of playback state.
- virtual bool IsPaused() const = 0;
- virtual bool IsSeeking() const = 0;
- virtual float GetDuration() const = 0;
- virtual float GetCurrentTime() const = 0;
-
- // Get rate of loading the resource.
- virtual int GetDataRate() const = 0;
-
- // Internal states of loading and network.
- virtual NetworkState GetNetworkState() const = 0;
- virtual ReadyState GetReadyState() const = 0;
-
- virtual bool DidLoadingProgress() const = 0;
- virtual unsigned long long GetTotalBytes() const = 0;
-
- virtual bool HasSingleSecurityOrigin() const = 0;
- virtual bool DidPassCORSAccessCheck() const = 0;
-
- virtual float MediaTimeForTimeValue(float timeValue) const = 0;
-
- virtual unsigned GetDecodedFrameCount() const = 0;
- virtual unsigned GetDroppedFrameCount() const = 0;
- virtual unsigned GetAudioDecodedByteCount() const = 0;
- virtual unsigned GetVideoDecodedByteCount() const = 0;
-
- virtual scoped_refptr<ShellVideoFrameProvider> GetVideoFrameProvider() {
- return NULL;
- }
- virtual scoped_refptr<VideoFrame> GetCurrentFrame() { return 0; }
- // We no longer need PutCurrentFrame as the the video frame returned from
- // GetCurrentFrame() is now a scoped_refptr.
- virtual void PutCurrentFrame(
- const scoped_refptr<VideoFrame>& /* video_frame */) {}
-
- virtual AddIdStatus SourceAddId(
- const std::string& /* id */,
- const std::string& /* type */,
- const std::vector<std::string>& /* codecs */) {
- return kAddIdStatusNotSupported;
- }
- virtual bool SourceRemoveId(const std::string& /* id */) { return false; }
- virtual Ranges<base::TimeDelta> SourceBuffered(const std::string& /* id */) {
- return Ranges<base::TimeDelta>();
- }
- virtual bool SourceAppend(const std::string& /* id */,
- const unsigned char* /* data */,
- unsigned /* length */) {
- return false;
- }
- virtual bool SourceAbort(const std::string& /* id */) { return false; }
- virtual double SourceGetDuration() const { return 0.0; }
- virtual void SourceSetDuration(double /* duration */) {}
- virtual void SourceEndOfStream(EndOfStreamStatus /* status */) {}
- virtual bool SourceSetTimestampOffset(const std::string& /* id */,
- double /* offset */) {
- return false;
- }
-
- // Returns whether keySystem is supported. If true, the result will be
- // reported by an event.
- virtual MediaKeyException GenerateKeyRequest(
- const std::string& /* key_system */,
- const unsigned char* /* init_data */,
- unsigned /* init_data_length */) {
- return kMediaKeyExceptionKeySystemNotSupported;
- }
- virtual MediaKeyException AddKey(const std::string& /* key_system */,
- const unsigned char* /* key */,
- unsigned /* key_length */,
- const unsigned char* /* init_data */,
- unsigned /* init_data_length */,
- const std::string& /* session_id */) {
- return kMediaKeyExceptionKeySystemNotSupported;
- }
- virtual MediaKeyException CancelKeyRequest(
- const std::string& /* key_system */,
- const std::string& /* session_id */) {
- return kMediaKeyExceptionKeySystemNotSupported;
- }
-
- virtual SetBoundsCB GetSetBoundsCB() { return SetBoundsCB(); }
-
- // Instruct WebMediaPlayer to enter/exit fullscreen.
- virtual void EnterFullscreen() {}
- virtual void ExitFullscreen() {}
- // Returns true if the player can enter fullscreen.
- virtual bool CanEnterFullscreen() const { return false; }
-
- // Returns the address and size of a chunk of memory to be included in a
- // debug report. May not be supported on all platforms. The returned address
- // should remain valid as long as the WebMediaPlayer instance is alive.
- virtual bool GetDebugReportDataAddress(void** /*out_address*/,
- size_t* /*out_size*/) {
- return false;
- }
-};
-
-class WebMediaPlayerClient {
- public:
- enum MediaKeyErrorCode {
- kMediaKeyErrorCodeUnknown = 1,
- kMediaKeyErrorCodeClient,
- kMediaKeyErrorCodeService,
- kMediaKeyErrorCodeOutput,
- kMediaKeyErrorCodeHardwareChange,
- kMediaKeyErrorCodeDomain,
- kUnknownError = kMediaKeyErrorCodeUnknown,
- kClientError = kMediaKeyErrorCodeClient,
- kServiceError = kMediaKeyErrorCodeService,
- kOutputError = kMediaKeyErrorCodeOutput,
- kHardwareChangeError = kMediaKeyErrorCodeHardwareChange,
- kDomainError = kMediaKeyErrorCodeDomain,
- };
-
- virtual void NetworkStateChanged() = 0;
- virtual void ReadyStateChanged() = 0;
- virtual void TimeChanged() = 0;
- virtual void DurationChanged() = 0;
- virtual void PlaybackStateChanged() = 0;
- // TODO: Revisit the necessity of the following function.
- virtual void SetOpaque(bool /* opaque */) {}
- virtual void SawUnsupportedTracks() = 0;
- virtual float Volume() const = 0;
- virtual void SourceOpened() = 0;
- virtual std::string SourceURL() const = 0;
- // Clients should implement this in order to indicate a preference for whether
- // a video should be decoded to a texture or through a punch out system. If
- // the preferred output mode is not supported, the player will fallback to
- // one that is. This can be used to indicate that, say, for spherical video
- // playback, we would like a decode-to-texture output mode.
- virtual bool PreferDecodeToTexture() { return false; }
- // TODO: Make the EME related functions pure virtual again once
- // we have proper EME implementation. Currently empty implementation are
- // provided to make media temporarily work.
- virtual void KeyAdded(const std::string& /* key_system */,
- const std::string& /* session_id */) {
- NOTIMPLEMENTED();
- }
- virtual void KeyError(const std::string& /* key_system */,
- const std::string& /* session_id */,
- MediaKeyErrorCode,
- unsigned short /* system_code */) {
- NOTIMPLEMENTED();
- }
- virtual void KeyMessage(const std::string& /* key_system */,
- const std::string& /* session_id */,
- const unsigned char* /* message */,
- unsigned /* message_length */,
- const std::string& /* default_url */) {
- NOTIMPLEMENTED();
- }
- virtual void KeyNeeded(const std::string& /* key_system */,
- const std::string& /* session_id */,
- const unsigned char* /* init_data */,
- unsigned /* init_data_length */) {
- NOTIMPLEMENTED();
- }
- // TODO: Revisit the necessity of the following functions.
- virtual void CloseHelperPlugin() { NOTREACHED(); }
- virtual void DisableAcceleratedCompositing() {}
-
- protected:
- ~WebMediaPlayerClient() {}
-};
-
-} // namespace media
-
-MSVC_POP_WARNING()
-
-#endif // MEDIA_PLAYER_WEB_MEDIA_PLAYER_H_
diff --git a/src/media/player/web_media_player_delegate.h b/src/media/player/web_media_player_delegate.h
deleted file mode 100644
index e237f6c..0000000
--- a/src/media/player/web_media_player_delegate.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_PLAYER_WEB_MEDIA_PLAYER_DELEGATE_H_
-#define MEDIA_PLAYER_WEB_MEDIA_PLAYER_DELEGATE_H_
-
-#include "base/compiler_specific.h"
-
-namespace media {
-
-class WebMediaPlayer;
-
-class WebMediaPlayerDelegate {
- public:
- virtual void RegisterPlayer(WebMediaPlayer* player) = 0;
- virtual void UnregisterPlayer(WebMediaPlayer* player) = 0;
-
- protected:
- virtual ~WebMediaPlayerDelegate() {}
-};
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_WEB_MEDIA_PLAYER_DELEGATE_H_
diff --git a/src/media/player/web_media_player_impl.cc b/src/media/player/web_media_player_impl.cc
deleted file mode 100644
index e94219d..0000000
--- a/src/media/player/web_media_player_impl.cc
+++ /dev/null
@@ -1,1202 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/player/web_media_player_impl.h"
-
-#include <math.h>
-
-#include <limits>
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/callback.h"
-#include "base/float_util.h"
-#include "base/message_loop_proxy.h"
-#include "base/metrics/histogram.h"
-#include "base/string_number_conversions.h"
-#include "base/synchronization/waitable_event.h"
-#include "media/audio/shell_audio_sink.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/filter_collection.h"
-#include "media/base/limits.h"
-#include "media/base/media_log.h"
-#include "media/base/video_frame.h"
-#include "media/filters/chunk_demuxer.h"
-#include "media/filters/shell_audio_renderer.h"
-#include "media/filters/shell_demuxer.h"
-#include "media/filters/video_renderer_base.h"
-#include "media/player/web_media_player_proxy.h"
-
-namespace media {
-namespace {
-
-// Used to ensure that there is no more than one instance of WebMediaPlayerImpl.
-WebMediaPlayerImpl* s_instance;
-
-// Limits the range of playback rate.
-//
-// TODO(kylep): Revisit these.
-//
-// Vista has substantially lower performance than XP or Windows7. If you speed
-// up a video too much, it can't keep up, and rendering stops updating except on
-// the time bar. For really high speeds, audio becomes a bottleneck and we just
-// use up the data we have, which may not achieve the speed requested, but will
-// not crash the tab.
-//
-// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
-// like a busy loop). It gets unresponsive, although its not completely dead.
-//
-// Also our timers are not very accurate (especially for ogg), which becomes
-// evident at low speeds and on Vista. Since other speeds are risky and outside
-// the norms, we think 1/16x to 16x is a safe and useful range for now.
-const float kMinRate = 0.0625f;
-const float kMaxRate = 16.0f;
-
-// Prefix for histograms related to Encrypted Media Extensions.
-const char* kMediaEme = "Media.EME.";
-
-#if defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
-// On some platforms, the underlying media player can hang if we keep seeking to
-// a position that is near the end of the video. So we ignore any seeks near the
-// end of stream position when the current playback position is also near the
-// end of the stream. In this case, "near the end of stream" means "position
-// greater than or equal to duration() - kEndOfStreamEpsilonInSeconds".
-const double kEndOfStreamEpsilonInSeconds = 2.;
-
-bool IsNearTheEndOfStream(const media::WebMediaPlayerImpl* wmpi,
- double position) {
- float duration = wmpi->GetDuration();
- if (base::IsFinite(duration)) {
- // If video is very short, we always treat a position as near the end.
- if (duration <= kEndOfStreamEpsilonInSeconds)
- return true;
- if (position >= duration - kEndOfStreamEpsilonInSeconds)
- return true;
- }
- return false;
-}
-#endif // defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
-
-base::TimeDelta ConvertSecondsToTimestamp(float seconds) {
- float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
- float integer = ceilf(microseconds);
- float difference = integer - microseconds;
-
- // Round down if difference is large enough.
- if ((microseconds > 0 && difference > 0.5f) ||
- (microseconds <= 0 && difference >= 0.5f)) {
- integer -= 1.0f;
- }
-
- // Now we can safely cast to int64 microseconds.
- return base::TimeDelta::FromMicroseconds(static_cast<int64>(integer));
-}
-
-} // namespace
-
-#define BIND_TO_RENDER_LOOP(function) \
- BindToLoop(main_loop_->message_loop_proxy(), \
- base::Bind(function, AsWeakPtr()))
-
-#define BIND_TO_RENDER_LOOP_2(function, arg1, arg2) \
- BindToLoop(main_loop_->message_loop_proxy(), \
- base::Bind(function, AsWeakPtr(), arg1, arg2))
-
-// TODO(acolwell): Investigate whether the key_system & session_id parameters
-// are really necessary.
-typedef base::Callback<
- void(const std::string&, const std::string&, scoped_array<uint8>, int)>
- OnNeedKeyCB;
-
-static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log,
- const std::string& error) {
- media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error));
-}
-
-WebMediaPlayerImpl::WebMediaPlayerImpl(
- PipelineWindow window,
- WebMediaPlayerClient* client,
- WebMediaPlayerDelegate* delegate,
- const scoped_refptr<ShellVideoFrameProvider>& video_frame_provider,
- scoped_ptr<FilterCollection> collection,
- const scoped_refptr<AudioRendererSink>& audio_renderer_sink,
- scoped_ptr<MessageLoopFactory> message_loop_factory,
- const scoped_refptr<MediaLog>& media_log)
- : network_state_(WebMediaPlayer::kNetworkStateEmpty),
- ready_state_(WebMediaPlayer::kReadyStateHaveNothing),
- main_loop_(MessageLoop::current()),
- filter_collection_(collection.Pass()),
- message_loop_factory_(message_loop_factory.Pass()),
- client_(client),
- delegate_(delegate),
- video_frame_provider_(video_frame_provider),
- proxy_(new WebMediaPlayerProxy(main_loop_->message_loop_proxy(), this)),
- media_log_(media_log),
- incremented_externally_allocated_memory_(false),
- audio_renderer_sink_(audio_renderer_sink),
- is_local_source_(false),
- supports_save_(true),
- suppress_destruction_errors_(false) {
- DLOG_IF(ERROR, s_instance)
- << "More than one WebMediaPlayerImpl has been created.";
- s_instance = this;
-
- media_log_->AddEvent(
- media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED));
-
- scoped_refptr<base::MessageLoopProxy> pipeline_message_loop =
- message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
- pipeline_ = Pipeline::Create(window, pipeline_message_loop, media_log_);
-
- // Also we want to be notified of |main_loop_| destruction.
- main_loop_->AddDestructionObserver(this);
-
- SetDecryptorReadyCB set_decryptor_ready_cb;
-
- decryptor_.reset(new ProxyDecryptor(proxy_.get()));
- set_decryptor_ready_cb = base::Bind(&ProxyDecryptor::SetDecryptorReadyCB,
- base::Unretained(decryptor_.get()));
-
- // Create default video renderer.
- scoped_refptr<VideoRendererBase> video_renderer = new VideoRendererBase(
- pipeline_message_loop, set_decryptor_ready_cb,
- base::Bind(base::DoNothing),
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::SetOpaque), true);
- filter_collection_->AddVideoRenderer(video_renderer);
- proxy_->set_frame_provider(video_renderer);
-
- if (audio_renderer_sink) {
- filter_collection_->AddAudioRenderer(ShellAudioRenderer::Create(
- audio_renderer_sink, set_decryptor_ready_cb, pipeline_message_loop));
- }
-
- if (video_frame_provider_) {
- media_time_and_seeking_state_cb_ =
- base::Bind(&WebMediaPlayerImpl::GetMediaTimeAndSeekingState,
- base::Unretained(this));
- video_frame_provider_->RegisterMediaTimeAndSeekingStateCB(
- media_time_and_seeking_state_cb_);
- }
- if (delegate_) {
- delegate_->RegisterPlayer(this);
- }
-}
-
-WebMediaPlayerImpl::~WebMediaPlayerImpl() {
- DCHECK(!main_loop_ || main_loop_ == MessageLoop::current());
-
- DLOG_IF(ERROR, s_instance != this)
- << "More than one WebMediaPlayerImpl has been created.";
- s_instance = NULL;
-
- if (delegate_) {
- delegate_->UnregisterPlayer(this);
- }
-
- if (video_frame_provider_) {
- DCHECK(!media_time_and_seeking_state_cb_.is_null());
- video_frame_provider_->UnregisterMediaTimeAndSeekingStateCB(
- media_time_and_seeking_state_cb_);
- media_time_and_seeking_state_cb_.Reset();
- }
-
-#if defined(__LB_ANDROID__)
- audio_focus_bridge_.AbandonAudioFocus();
-#endif // defined(__LB_ANDROID__)
-
- decryptor_->DestroySoon();
- Destroy();
- media_log_->AddEvent(
- media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
-
- // Finally tell the |main_loop_| we don't want to be notified of destruction
- // event.
- if (main_loop_) {
- main_loop_->RemoveDestructionObserver(this);
- }
-}
-
-namespace {
-
-// Helper enum for reporting scheme histograms.
-enum URLSchemeForHistogram {
- kUnknownURLScheme,
- kMissingURLScheme,
- kHttpURLScheme,
- kHttpsURLScheme,
- kFtpURLScheme,
- kChromeExtensionURLScheme,
- kJavascriptURLScheme,
- kFileURLScheme,
- kBlobURLScheme,
- kDataURLScheme,
- kFileSystemScheme,
- kMaxURLScheme = kFileSystemScheme // Must be equal to highest enum value.
-};
-
-URLSchemeForHistogram URLScheme(const GURL& url) {
- if (!url.has_scheme())
- return kMissingURLScheme;
- if (url.SchemeIs("http"))
- return kHttpURLScheme;
- if (url.SchemeIs("https"))
- return kHttpsURLScheme;
- if (url.SchemeIs("ftp"))
- return kFtpURLScheme;
- if (url.SchemeIs("chrome-extension"))
- return kChromeExtensionURLScheme;
- if (url.SchemeIs("javascript"))
- return kJavascriptURLScheme;
- if (url.SchemeIs("file"))
- return kFileURLScheme;
- if (url.SchemeIs("blob"))
- return kBlobURLScheme;
- if (url.SchemeIs("data"))
- return kDataURLScheme;
- if (url.SchemeIs("filesystem"))
- return kFileSystemScheme;
- return kUnknownURLScheme;
-}
-
-} // anonymous namespace
-
-void WebMediaPlayerImpl::LoadMediaSource() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- DCHECK(filter_collection_);
-
- // Handle any volume changes that occured before load().
- SetVolume(GetClient()->Volume());
-
- SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
- SetReadyState(WebMediaPlayer::kReadyStateHaveNothing);
-
- scoped_refptr<base::MessageLoopProxy> message_loop =
- message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
-
- // Media source pipelines can start immediately.
- chunk_demuxer_ = new ChunkDemuxer(
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDemuxerOpened),
- BIND_TO_RENDER_LOOP_2(&WebMediaPlayerImpl::OnNeedKey, "", ""),
- base::Bind(&LogMediaSourceError, media_log_));
-
- filter_collection_->SetDemuxer(chunk_demuxer_);
- supports_save_ = false;
- state_.is_media_source = true;
- StartPipeline();
-}
-
-void WebMediaPlayerImpl::LoadProgressive(
- const GURL& url,
- scoped_ptr<BufferedDataSource> data_source,
- CORSMode cors_mode) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- DCHECK(filter_collection_);
-
- UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme);
-
- // Handle any volume changes that occured before load().
- SetVolume(GetClient()->Volume());
-
- SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
- SetReadyState(WebMediaPlayer::kReadyStateHaveNothing);
- media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
-
- scoped_refptr<base::MessageLoopProxy> message_loop =
- message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
-
- data_source->SetDownloadingStatusCB(
- base::Bind(&WebMediaPlayerImpl::OnDownloadingStatusChanged, AsWeakPtr()));
- proxy_->set_data_source(data_source.Pass());
-
- is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https");
-
- filter_collection_->SetDemuxer(
- new ShellDemuxer(message_loop, proxy_->data_source()));
-
- state_.is_progressive = true;
- StartPipeline();
-}
-
-void WebMediaPlayerImpl::CancelLoad() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-}
-
-void WebMediaPlayerImpl::Play() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-#if defined(__LB_ANDROID__)
- audio_focus_bridge_.RequestAudioFocus();
-#endif // defined(__LB_ANDROID__)
-
- state_.paused = false;
- pipeline_->SetPlaybackRate(state_.playback_rate);
-
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY));
-}
-
-void WebMediaPlayerImpl::Pause() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-#if defined(__LB_ANDROID__)
- audio_focus_bridge_.AbandonAudioFocus();
-#endif // defined(__LB_ANDROID__)
-
- state_.paused = true;
- pipeline_->SetPlaybackRate(0.0f);
- state_.paused_time = pipeline_->GetMediaTime();
-
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PAUSE));
-}
-
-bool WebMediaPlayerImpl::SupportsFullscreen() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- return true;
-}
-
-bool WebMediaPlayerImpl::SupportsSave() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- return supports_save_;
-}
-
-void WebMediaPlayerImpl::Seek(float seconds) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
-#if defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
- // Ignore any seek request that is near the end of the stream when the
- // current playback position is also near the end of the stream to avoid
- // a hang in the MediaEngine.
- if (IsNearTheEndOfStream(this, GetCurrentTime()) &&
- IsNearTheEndOfStream(this, seconds)) {
- return;
- }
-#endif // defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
-
- if (state_.starting || state_.seeking) {
- state_.pending_seek = true;
- state_.pending_seek_seconds = seconds;
- if (chunk_demuxer_) {
- chunk_demuxer_->CancelPendingSeek();
- decryptor_->CancelDecrypt(Decryptor::kAudio);
- decryptor_->CancelDecrypt(Decryptor::kVideo);
- }
- return;
- }
-
- media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
-
- base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
-
- // Update our paused time.
- if (state_.paused)
- state_.paused_time = seek_time;
-
- state_.seeking = true;
-
- if (chunk_demuxer_) {
- chunk_demuxer_->StartWaitingForSeek();
- decryptor_->CancelDecrypt(Decryptor::kAudio);
- decryptor_->CancelDecrypt(Decryptor::kVideo);
- }
-
- // Kick off the asynchronous seek!
- pipeline_->Seek(seek_time,
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek));
-}
-
-void WebMediaPlayerImpl::SetEndTime(float seconds) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // TODO(hclam): add method call when it has been implemented.
- return;
-}
-
-void WebMediaPlayerImpl::SetRate(float rate) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // TODO(kylep): Remove when support for negatives is added. Also, modify the
- // following checks so rewind uses reasonable values also.
- if (rate < 0.0f)
- return;
-
- // Limit rates to reasonable values by clamping.
- if (rate != 0.0f) {
- if (rate < kMinRate)
- rate = kMinRate;
- else if (rate > kMaxRate)
- rate = kMaxRate;
- }
-
- state_.playback_rate = rate;
- if (!state_.paused) {
- pipeline_->SetPlaybackRate(rate);
- }
-}
-
-void WebMediaPlayerImpl::SetVolume(float volume) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- pipeline_->SetVolume(volume);
-}
-
-void WebMediaPlayerImpl::SetVisible(bool visible) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // TODO(hclam): add appropriate method call when pipeline has it implemented.
- return;
-}
-
-bool WebMediaPlayerImpl::GetTotalBytesKnown() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- return pipeline_->GetTotalBytes() != 0;
-}
-
-bool WebMediaPlayerImpl::HasVideo() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- return pipeline_->HasVideo();
-}
-
-bool WebMediaPlayerImpl::HasAudio() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- return pipeline_->HasAudio();
-}
-
-gfx::Size WebMediaPlayerImpl::GetNaturalSize() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- gfx::Size size;
- pipeline_->GetNaturalVideoSize(&size);
- return size;
-}
-
-bool WebMediaPlayerImpl::IsPaused() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- return pipeline_->GetPlaybackRate() == 0.0f;
-}
-
-bool WebMediaPlayerImpl::IsSeeking() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
- return false;
-
- return state_.seeking;
-}
-
-float WebMediaPlayerImpl::GetDuration() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
- return std::numeric_limits<float>::quiet_NaN();
-
- base::TimeDelta duration = pipeline_->GetMediaDuration();
-
- // Return positive infinity if the resource is unbounded.
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
- if (duration == kInfiniteDuration())
- return std::numeric_limits<float>::infinity();
-
- return static_cast<float>(duration.InSecondsF());
-}
-
-float WebMediaPlayerImpl::GetCurrentTime() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- if (state_.paused)
- return static_cast<float>(state_.paused_time.InSecondsF());
- return static_cast<float>(pipeline_->GetMediaTime().InSecondsF());
-}
-
-int WebMediaPlayerImpl::GetDataRate() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // TODO(hclam): Add this method call if pipeline has it in the interface.
- return 0;
-}
-
-WebMediaPlayer::NetworkState WebMediaPlayerImpl::GetNetworkState() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- return network_state_;
-}
-
-WebMediaPlayer::ReadyState WebMediaPlayerImpl::GetReadyState() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- return ready_state_;
-}
-
-const Ranges<base::TimeDelta>& WebMediaPlayerImpl::GetBufferedTimeRanges() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- buffered_ = pipeline_->GetBufferedTimeRanges();
- return buffered_;
-}
-
-float WebMediaPlayerImpl::GetMaxTimeSeekable() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // We don't support seeking in streaming media.
- if (proxy_ && proxy_->data_source() && proxy_->data_source()->IsStreaming())
- return 0.0f;
- return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF());
-}
-
-void WebMediaPlayerImpl::Suspend() {
- pipeline_->Suspend();
-}
-
-void WebMediaPlayerImpl::Resume() {
- pipeline_->Resume();
-}
-
-bool WebMediaPlayerImpl::DidLoadingProgress() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- return pipeline_->DidLoadingProgress();
-}
-
-unsigned long long WebMediaPlayerImpl::GetTotalBytes() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- return pipeline_->GetTotalBytes();
-}
-
-bool WebMediaPlayerImpl::HasSingleSecurityOrigin() const {
- if (proxy_)
- return proxy_->HasSingleOrigin();
- return true;
-}
-
-bool WebMediaPlayerImpl::DidPassCORSAccessCheck() const {
- return proxy_ && proxy_->DidPassCORSAccessCheck();
-}
-
-float WebMediaPlayerImpl::MediaTimeForTimeValue(float timeValue) const {
- return ConvertSecondsToTimestamp(timeValue).InSecondsF();
-}
-
-unsigned WebMediaPlayerImpl::GetDecodedFrameCount() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- PipelineStatistics stats = pipeline_->GetStatistics();
- return stats.video_frames_decoded;
-}
-
-unsigned WebMediaPlayerImpl::GetDroppedFrameCount() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- PipelineStatistics stats = pipeline_->GetStatistics();
- return stats.video_frames_dropped;
-}
-
-unsigned WebMediaPlayerImpl::GetAudioDecodedByteCount() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- PipelineStatistics stats = pipeline_->GetStatistics();
- return stats.audio_bytes_decoded;
-}
-
-unsigned WebMediaPlayerImpl::GetVideoDecodedByteCount() const {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- PipelineStatistics stats = pipeline_->GetStatistics();
- return stats.video_bytes_decoded;
-}
-
-scoped_refptr<ShellVideoFrameProvider>
-WebMediaPlayerImpl::GetVideoFrameProvider() {
- return video_frame_provider_;
-}
-
-scoped_refptr<VideoFrame> WebMediaPlayerImpl::GetCurrentFrame() {
- if (video_frame_provider_) {
- return video_frame_provider_->GetCurrentFrame();
- }
- return NULL;
-}
-
-void WebMediaPlayerImpl::PutCurrentFrame(
- const scoped_refptr<VideoFrame>& video_frame) {
- if (video_frame) {
- proxy_->PutCurrentFrame(video_frame);
- } else {
- proxy_->PutCurrentFrame(NULL);
- }
-}
-
-// TODO: Eliminate the duplicated enums.
-#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(player_name, chromium_name) \
- COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::player_name) == \
- static_cast<int>(ChunkDemuxer::chromium_name), \
- mismatching_status_enums)
-COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusOk, kOk);
-COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusNotSupported, kNotSupported);
-COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusReachedIdLimit,
- kReachedIdLimit);
-#undef COMPILE_ASSERT_MATCHING_ENUM
-
-WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::SourceAddId(
- const std::string& id,
- const std::string& type,
- const std::vector<std::string>& codecs) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- std::vector<std::string> new_codecs(codecs.size());
- for (size_t i = 0; i < codecs.size(); ++i)
- new_codecs[i] = codecs[i];
-
- return static_cast<WebMediaPlayer::AddIdStatus>(
- chunk_demuxer_->AddId(id, type, new_codecs));
-}
-
-bool WebMediaPlayerImpl::SourceRemoveId(const std::string& id) {
- DCHECK(!id.empty());
- chunk_demuxer_->RemoveId(id);
- return true;
-}
-
-Ranges<base::TimeDelta> WebMediaPlayerImpl::SourceBuffered(
- const std::string& id) {
- return chunk_demuxer_->GetBufferedRanges(id);
-}
-
-bool WebMediaPlayerImpl::SourceAppend(const std::string& id,
- const unsigned char* data,
- unsigned length) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- float old_duration = GetDuration();
- if (!chunk_demuxer_->AppendData(id, data, length))
- return false;
-
- if (old_duration != GetDuration())
- GetClient()->DurationChanged();
-
- return true;
-}
-
-bool WebMediaPlayerImpl::SourceAbort(const std::string& id) {
- chunk_demuxer_->Abort(id);
- return true;
-}
-
-double WebMediaPlayerImpl::SourceGetDuration() const {
- DCHECK(chunk_demuxer_);
- return chunk_demuxer_->GetDuration();
-}
-
-void WebMediaPlayerImpl::SourceSetDuration(double new_duration) {
- DCHECK_GE(new_duration, 0);
- DCHECK(chunk_demuxer_);
- chunk_demuxer_->SetDuration(new_duration);
-}
-
-void WebMediaPlayerImpl::SourceEndOfStream(
- WebMediaPlayer::EndOfStreamStatus status) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- PipelineStatus pipeline_status = PIPELINE_OK;
-
- switch (status) {
- case WebMediaPlayer::kEndOfStreamStatusNoError:
- break;
- case WebMediaPlayer::kEndOfStreamStatusNetworkError:
- pipeline_status = PIPELINE_ERROR_NETWORK;
- break;
- case WebMediaPlayer::kEndOfStreamStatusDecodeError:
- pipeline_status = PIPELINE_ERROR_DECODE;
- break;
- default:
- NOTIMPLEMENTED();
- }
-
- float old_duration = GetDuration();
- if (!chunk_demuxer_->EndOfStream(pipeline_status))
- DVLOG(1) << "EndOfStream call failed.";
-
- if (old_duration != GetDuration())
- GetClient()->DurationChanged();
-}
-
-bool WebMediaPlayerImpl::SourceSetTimestampOffset(const std::string& id,
- double offset) {
- base::TimeDelta time_offset = base::TimeDelta::FromMicroseconds(
- offset * base::Time::kMicrosecondsPerSecond);
- return chunk_demuxer_->SetTimestampOffset(id, time_offset);
-}
-
-// Helper enum for reporting generateKeyRequest/addKey histograms.
-enum MediaKeyException {
- kUnknownResultId,
- kSuccess,
- kKeySystemNotSupported,
- kInvalidPlayerState,
- kMaxMediaKeyException
-};
-
-static MediaKeyException MediaKeyExceptionForUMA(
- WebMediaPlayer::MediaKeyException e) {
- switch (e) {
- case WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported:
- return kKeySystemNotSupported;
- case WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState:
- return kInvalidPlayerState;
- case WebMediaPlayer::kMediaKeyExceptionNoError:
- return kSuccess;
- default:
- return kUnknownResultId;
- }
-}
-
-// Helper for converting |key_system| name and exception |e| to a pair of enum
-// values from above, for reporting to UMA.
-static void ReportMediaKeyExceptionToUMA(const std::string& method,
- const std::string& key_system,
- WebMediaPlayer::MediaKeyException e) {
- MediaKeyException result_id = MediaKeyExceptionForUMA(e);
- DCHECK_NE(result_id, kUnknownResultId) << e;
- base::LinearHistogram::FactoryGet(
- kMediaEme + KeySystemNameForUMA(key_system) + "." + method, 1,
- kMaxMediaKeyException, kMaxMediaKeyException + 1,
- base::Histogram::kUmaTargetedHistogramFlag)
- ->Add(result_id);
-}
-
-WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::GenerateKeyRequest(
- const std::string& key_system,
- const unsigned char* init_data,
- unsigned init_data_length) {
- WebMediaPlayer::MediaKeyException e =
- GenerateKeyRequestInternal(key_system, init_data, init_data_length);
- ReportMediaKeyExceptionToUMA("generateKeyRequest", key_system, e);
- return e;
-}
-
-WebMediaPlayer::MediaKeyException
-WebMediaPlayerImpl::GenerateKeyRequestInternal(const std::string& key_system,
- const unsigned char* init_data,
- unsigned init_data_length) {
- if (!IsSupportedKeySystem(key_system))
- return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
-
- // We do not support run-time switching between key systems for now.
- if (current_key_system_.empty())
- current_key_system_ = key_system;
- else if (key_system != current_key_system_)
- return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
-
- DVLOG(1) << "generateKeyRequest: " << key_system << ": "
- << std::string(reinterpret_cast<const char*>(init_data),
- static_cast<size_t>(init_data_length));
-
- // TODO(xhwang): We assume all streams are from the same container (thus have
- // the same "type") for now. In the future, the "type" should be passed down
- // from the application.
- if (!decryptor_->GenerateKeyRequest(key_system, init_data_type_, init_data,
- init_data_length)) {
- current_key_system_.clear();
- return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
- }
-
- return WebMediaPlayer::kMediaKeyExceptionNoError;
-}
-
-WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::AddKey(
- const std::string& key_system,
- const unsigned char* key,
- unsigned key_length,
- const unsigned char* init_data,
- unsigned init_data_length,
- const std::string& session_id) {
- WebMediaPlayer::MediaKeyException e = AddKeyInternal(
- key_system, key, key_length, init_data, init_data_length, session_id);
- ReportMediaKeyExceptionToUMA("addKey", key_system, e);
- return e;
-}
-
-WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::AddKeyInternal(
- const std::string& key_system,
- const unsigned char* key,
- unsigned key_length,
- const unsigned char* init_data,
- unsigned init_data_length,
- const std::string& session_id) {
- DCHECK(key);
- DCHECK_GT(key_length, 0u);
-
- if (!IsSupportedKeySystem(key_system))
- return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
-
- if (current_key_system_.empty() || key_system != current_key_system_)
- return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
-
- DVLOG(1) << "addKey: " << key_system << ": "
- << base::HexEncode(key, static_cast<size_t>(key_length)) << ", "
- << base::HexEncode(init_data, static_cast<size_t>(init_data_length))
- << " [" << session_id << "]";
-
- decryptor_->AddKey(key_system, key, key_length, init_data, init_data_length,
- session_id);
- return WebMediaPlayer::kMediaKeyExceptionNoError;
-}
-
-WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::CancelKeyRequest(
- const std::string& key_system,
- const std::string& session_id) {
- WebMediaPlayer::MediaKeyException e =
- CancelKeyRequestInternal(key_system, session_id);
- ReportMediaKeyExceptionToUMA("cancelKeyRequest", key_system, e);
- return e;
-}
-
-WebMediaPlayerImpl::SetBoundsCB WebMediaPlayerImpl::GetSetBoundsCB() {
- // |pipeline_| is always valid during WebMediaPlayerImpl's life time. It is
- // also reference counted so it lives after WebMediaPlayerImpl is destroyed.
- return pipeline_->GetSetBoundsCB();
-}
-
-WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::CancelKeyRequestInternal(
- const std::string& key_system,
- const std::string& session_id) {
- if (!IsSupportedKeySystem(key_system))
- return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
-
- if (current_key_system_.empty() || key_system != current_key_system_)
- return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
-
- decryptor_->CancelKeyRequest(key_system, session_id);
- return WebMediaPlayer::kMediaKeyExceptionNoError;
-}
-
-void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
- Destroy();
- main_loop_ = NULL;
-}
-
-bool WebMediaPlayerImpl::GetDebugReportDataAddress(void** out_address,
- size_t* out_size) {
- *out_address = &state_;
- *out_size = sizeof(state_);
- return true;
-}
-
-void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- state_.starting = false;
- state_.seeking = false;
- if (state_.pending_seek) {
- state_.pending_seek = false;
- Seek(state_.pending_seek_seconds);
- return;
- }
-
- if (status != PIPELINE_OK) {
- OnPipelineError(status);
- return;
- }
-
- // Update our paused time.
- if (state_.paused)
- state_.paused_time = pipeline_->GetMediaTime();
-
- GetClient()->TimeChanged();
-}
-
-void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- if (status != PIPELINE_OK) {
- OnPipelineError(status);
- return;
- }
- GetClient()->TimeChanged();
-}
-
-void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- if (suppress_destruction_errors_)
- return;
-
- media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error));
-
- if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing) {
- // Any error that occurs before reaching ReadyStateHaveMetadata should
- // be considered a format error.
- SetNetworkState(WebMediaPlayer::kNetworkStateFormatError);
- return;
- }
-
- switch (error) {
- case PIPELINE_OK:
- NOTREACHED() << "PIPELINE_OK isn't an error!";
- break;
-
- case PIPELINE_ERROR_NETWORK:
- case PIPELINE_ERROR_READ:
- SetNetworkState(WebMediaPlayer::kNetworkStateNetworkError);
- break;
-
- // TODO(vrk): Because OnPipelineInitialize() directly reports the
- // NetworkStateFormatError instead of calling OnPipelineError(), I believe
- // this block can be deleted. Should look into it! (crbug.com/126070)
- case PIPELINE_ERROR_INITIALIZATION_FAILED:
- case PIPELINE_ERROR_COULD_NOT_RENDER:
- case PIPELINE_ERROR_URL_NOT_FOUND:
- case DEMUXER_ERROR_COULD_NOT_OPEN:
- case DEMUXER_ERROR_COULD_NOT_PARSE:
- case DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
- case DECODER_ERROR_NOT_SUPPORTED:
- SetNetworkState(WebMediaPlayer::kNetworkStateFormatError);
- break;
-
- case PIPELINE_ERROR_DECODE:
- case PIPELINE_ERROR_ABORT:
- case PIPELINE_ERROR_OPERATION_PENDING:
- case PIPELINE_ERROR_INVALID_STATE:
- SetNetworkState(WebMediaPlayer::kNetworkStateDecodeError);
- break;
-
- case PIPELINE_ERROR_DECRYPT:
- // Decrypt error.
- base::Histogram::FactoryGet(
- (kMediaEme + KeySystemNameForUMA(current_key_system_) +
- ".DecryptError"),
- 1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)
- ->Add(1);
- // TODO(xhwang): Change to use NetworkStateDecryptError once it's added in
- // Webkit (see http://crbug.com/124486).
- SetNetworkState(WebMediaPlayer::kNetworkStateDecodeError);
- break;
-
- case PIPELINE_STATUS_MAX:
- NOTREACHED() << "PIPELINE_STATUS_MAX isn't a real error!";
- break;
- }
-}
-
-void WebMediaPlayerImpl::OnPipelineBufferingState(
- Pipeline::BufferingState buffering_state) {
- DVLOG(1) << "OnPipelineBufferingState(" << buffering_state << ")";
-
- switch (buffering_state) {
- case Pipeline::kHaveMetadata:
- SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata);
- break;
- case Pipeline::kPrerollCompleted:
- SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData);
- break;
- }
-}
-
-void WebMediaPlayerImpl::OnDemuxerOpened() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- GetClient()->SourceOpened();
-}
-
-void WebMediaPlayerImpl::OnKeyAdded(const std::string& key_system,
- const std::string& session_id) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- base::Histogram::FactoryGet(
- kMediaEme + KeySystemNameForUMA(key_system) + ".KeyAdded", 1, 1000000, 50,
- base::Histogram::kUmaTargetedHistogramFlag)
- ->Add(1);
-
- GetClient()->KeyAdded(key_system, session_id);
-}
-
-void WebMediaPlayerImpl::OnNeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- // Do not fire NeedKey event if encrypted media is not enabled.
- if (!decryptor_)
- return;
-
- UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1);
-
-#if !defined(__LB_SHELL__) && !defined(COBALT)
- DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_);
-#endif // !defined(__LB_SHELL__) && !defined(COBALT)
-
- if (init_data_type_.empty())
- init_data_type_ = type;
-
- GetClient()->KeyNeeded(key_system, session_id, init_data.get(),
- init_data_size);
-}
-
-// TODO: Eliminate the duplicated enums.
-#define COMPILE_ASSERT_MATCHING_ENUM(name) \
- COMPILE_ASSERT(static_cast<int>(WebMediaPlayerClient::name) == \
- static_cast<int>(Decryptor::name), \
- mismatching_enums)
-COMPILE_ASSERT_MATCHING_ENUM(kUnknownError);
-COMPILE_ASSERT_MATCHING_ENUM(kClientError);
-COMPILE_ASSERT_MATCHING_ENUM(kServiceError);
-COMPILE_ASSERT_MATCHING_ENUM(kOutputError);
-COMPILE_ASSERT_MATCHING_ENUM(kHardwareChangeError);
-COMPILE_ASSERT_MATCHING_ENUM(kDomainError);
-#undef COMPILE_ASSERT_MATCHING_ENUM
-
-void WebMediaPlayerImpl::OnKeyError(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- base::LinearHistogram::FactoryGet(
- kMediaEme + KeySystemNameForUMA(key_system) + ".KeyError", 1,
- Decryptor::kMaxKeyError, Decryptor::kMaxKeyError + 1,
- base::Histogram::kUmaTargetedHistogramFlag)
- ->Add(error_code);
-
- GetClient()->KeyError(
- key_system, session_id,
- static_cast<WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
- system_code);
-}
-
-void WebMediaPlayerImpl::OnKeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const GURL& default_url) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- GetClient()->KeyMessage(key_system, session_id,
- reinterpret_cast<const uint8*>(message.data()),
- message.size(), default_url.spec());
-}
-
-void WebMediaPlayerImpl::SetOpaque(bool opaque) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
-
- GetClient()->SetOpaque(opaque);
-}
-
-void WebMediaPlayerImpl::OnDownloadingStatusChanged(bool is_downloading) {
- if (!is_downloading && network_state_ == WebMediaPlayer::kNetworkStateLoading)
- SetNetworkState(WebMediaPlayer::kNetworkStateIdle);
- else if (is_downloading &&
- network_state_ == WebMediaPlayer::kNetworkStateIdle)
- SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
- media_log_->AddEvent(
- media_log_->CreateBooleanEvent(MediaLogEvent::NETWORK_ACTIVITY_SET,
- "is_downloading_data", is_downloading));
-}
-
-void WebMediaPlayerImpl::StartPipeline() {
- state_.starting = true;
-
- SetDecryptorReadyCB set_decryptor_ready_cb;
- if (decryptor_) {
- set_decryptor_ready_cb = base::Bind(&ProxyDecryptor::SetDecryptorReadyCB,
- base::Unretained(decryptor_.get()));
- }
-
- pipeline_->SetDecodeToTextureOutputMode(client_->PreferDecodeToTexture());
- pipeline_->Start(
- filter_collection_.Pass(), set_decryptor_ready_cb,
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded),
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek),
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingState),
- BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged));
-}
-
-void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- DVLOG(1) << "SetNetworkState: " << state;
- network_state_ = state;
- // Always notify to ensure client has the latest value.
- GetClient()->NetworkStateChanged();
-}
-
-void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- DVLOG(1) << "SetReadyState: " << state;
-
- if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing &&
- state >= WebMediaPlayer::kReadyStateHaveMetadata) {
- if (!HasVideo())
- GetClient()->DisableAcceleratedCompositing();
- } else if (state == WebMediaPlayer::kReadyStateHaveEnoughData) {
- if (is_local_source_ &&
- network_state_ == WebMediaPlayer::kNetworkStateLoading) {
- SetNetworkState(WebMediaPlayer::kNetworkStateLoaded);
- }
- }
-
- ready_state_ = state;
- // Always notify to ensure client has the latest value.
- GetClient()->ReadyStateChanged();
-}
-
-void WebMediaPlayerImpl::Destroy() {
- DCHECK(!main_loop_ || main_loop_ == MessageLoop::current());
-
- // If |main_loop_| has already stopped, do nothing here.
- if (!main_loop_) {
- // This may happen if this function was already called by the
- // DestructionObserver override when the thread running this player was
- // stopped. The pipeline should have been shut down.
- DCHECK(!chunk_demuxer_);
- DCHECK(!message_loop_factory_);
- DCHECK(!proxy_);
- return;
- }
-
- // Tell the data source to abort any pending reads so that the pipeline is
- // not blocked when issuing stop commands to the other filters.
- suppress_destruction_errors_ = true;
- if (proxy_) {
- proxy_->AbortDataSource();
- if (chunk_demuxer_) {
- chunk_demuxer_->Shutdown();
- chunk_demuxer_ = NULL;
- }
- }
-
- // Make sure to kill the pipeline so there's no more media threads running.
- // Note: stopping the pipeline might block for a long time.
- base::WaitableEvent waiter(false, false);
- DLOG(INFO) << "Trying to stop media pipeline.";
- pipeline_->Stop(
- base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)));
- waiter.Wait();
- DLOG(INFO) << "Media pipeline stopped.";
-
- message_loop_factory_.reset();
-
- // And then detach the proxy, it may live on the render thread for a little
- // longer until all the tasks are finished.
- if (proxy_) {
- proxy_->Detach();
- proxy_ = NULL;
- }
-}
-
-void WebMediaPlayerImpl::GetMediaTimeAndSeekingState(
- base::TimeDelta* media_time,
- bool* is_seeking) const {
- DCHECK(media_time);
- DCHECK(is_seeking);
- *media_time = pipeline_->GetMediaTime();
- *is_seeking = state_.seeking;
-}
-
-WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
- DCHECK_EQ(main_loop_, MessageLoop::current());
- DCHECK(client_);
- return client_;
-}
-
-void WebMediaPlayerImpl::OnDurationChanged() {
- if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
- return;
-
- GetClient()->DurationChanged();
-}
-
-} // namespace media
diff --git a/src/media/player/web_media_player_impl.h b/src/media/player/web_media_player_impl.h
deleted file mode 100644
index 2400412..0000000
--- a/src/media/player/web_media_player_impl.h
+++ /dev/null
@@ -1,382 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Delegate calls from WebCore::MediaPlayerPrivate to Chrome's video player.
-// It contains Pipeline which is the actual media player pipeline, it glues
-// the media player pipeline, data source, audio renderer and renderer.
-// Pipeline would creates multiple threads and access some public methods
-// of this class, so we need to be extra careful about concurrent access of
-// methods and members.
-//
-// WebMediaPlayerImpl works with multiple objects, the most important ones are:
-//
-// Pipeline
-// The media playback pipeline.
-//
-// VideoRendererBase
-// Video renderer object.
-//
-// WebMediaPlayerClient
-// Client of this media player object.
-//
-// The following diagram shows the relationship of these objects:
-// (note: ref-counted reference is marked by a "r".)
-//
-// WebMediaPlayerClient
-// ^
-// |
-// WebMediaPlayerImpl ---> Pipeline
-// | ^ |
-// | | v r
-// | | VideoRendererBase
-// | | | ^ r
-// | r | v r |
-// '---> WebMediaPlayerProxy --'
-//
-// Notice that WebMediaPlayerProxy and VideoRendererBase are referencing each
-// other. This interdependency has to be treated carefully.
-//
-// Other issues:
-// During tear down of the whole browser or a tab, the DOM tree may not be
-// destructed nicely, and there will be some dangling media threads trying to
-// the main thread, so we need this class to listen to destruction event of the
-// main thread and cleanup the media threads when the even is received. Also
-// at destruction of this class we will need to unhook it from destruction event
-// list of the main thread.
-
-#ifndef MEDIA_PLAYER_WEB_MEDIA_PLAYER_IMPL_H_
-#define MEDIA_PLAYER_WEB_MEDIA_PLAYER_IMPL_H_
-
-#include <string>
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/message_loop.h"
-#include "base/time.h"
-#include "googleurl/src/gurl.h"
-#include "media/base/audio_renderer_sink.h"
-#include "media/base/decryptor.h"
-#include "media/base/message_loop_factory.h"
-#include "media/base/pipeline.h"
-#include "media/base/ranges.h"
-#include "media/base/shell_video_frame_provider.h"
-#include "media/player/crypto/key_systems.h"
-#include "media/player/crypto/proxy_decryptor.h"
-#include "media/player/web_media_player.h"
-#include "media/player/web_media_player_delegate.h"
-#include "ui/gfx/size.h"
-
-#if defined(OS_STARBOARD)
-
-#if SB_HAS(PLAYER)
-#define COBALT_SKIP_SEEK_REQUEST_NEAR_END
-#endif // SB_HAS(PLAYER)
-
-#endif // defined(OS_STARBOARD)
-
-namespace media {
-
-class AudioRendererSink;
-class ChunkDemuxer;
-class MediaLog;
-class WebMediaPlayerProxy;
-
-class WebMediaPlayerImpl : public WebMediaPlayer,
- public MessageLoop::DestructionObserver,
- public base::SupportsWeakPtr<WebMediaPlayerImpl> {
- public:
- // Construct a WebMediaPlayerImpl with reference to the client, and media
- // filter collection. By providing the filter collection the implementor can
- // provide more specific media filters that does resource loading and
- // rendering.
- //
- // WebMediaPlayerImpl comes packaged with the following media filters:
- // - URL fetching
- // - Demuxing
- // - Software audio/video decoding
- // - Video rendering
- //
- // Clients are expected to add their platform-specific audio rendering media
- // filter if they wish to hear any sound coming out the speakers, otherwise
- // audio data is discarded and media plays back based on wall clock time.
- //
- // When calling this, the |audio_source_provider| and
- // |audio_renderer_sink| arguments should be the same object.
-
- WebMediaPlayerImpl(
- PipelineWindow window,
- WebMediaPlayerClient* client,
- WebMediaPlayerDelegate* delegate,
- const scoped_refptr<ShellVideoFrameProvider>& video_frame_provider,
- scoped_ptr<FilterCollection> collection,
- const scoped_refptr<AudioRendererSink>& audio_renderer_sink,
- scoped_ptr<MessageLoopFactory> message_loop_factory,
- const scoped_refptr<MediaLog>& media_log);
- ~WebMediaPlayerImpl() OVERRIDE;
-
- void LoadMediaSource() OVERRIDE;
- void LoadProgressive(const GURL& url,
- scoped_ptr<BufferedDataSource> data_source,
- CORSMode cors_mode) OVERRIDE;
- void CancelLoad() OVERRIDE;
-
- // Playback controls.
- void Play() OVERRIDE;
- void Pause() OVERRIDE;
- bool SupportsFullscreen() const OVERRIDE;
- bool SupportsSave() const OVERRIDE;
- void Seek(float seconds) OVERRIDE;
- void SetEndTime(float seconds) OVERRIDE;
- void SetRate(float rate) OVERRIDE;
- void SetVolume(float volume) OVERRIDE;
- void SetVisible(bool visible) OVERRIDE;
- virtual bool GetTotalBytesKnown();
- const Ranges<base::TimeDelta>& GetBufferedTimeRanges() OVERRIDE;
- float GetMaxTimeSeekable() const OVERRIDE;
-
- // Suspend/Resume
- void Suspend() OVERRIDE;
- void Resume() OVERRIDE;
-
- // True if the loaded media has a playable video/audio track.
- bool HasVideo() const OVERRIDE;
- bool HasAudio() const OVERRIDE;
-
- // Dimensions of the video.
- gfx::Size GetNaturalSize() const OVERRIDE;
-
- // Getters of playback state.
- bool IsPaused() const OVERRIDE;
- bool IsSeeking() const OVERRIDE;
- float GetDuration() const OVERRIDE;
- float GetCurrentTime() const OVERRIDE;
-
- // Get rate of loading the resource.
- int32 GetDataRate() const OVERRIDE;
-
- // Internal states of loading and network.
- // TODO(hclam): Ask the pipeline about the state rather than having reading
- // them from members which would cause race conditions.
- WebMediaPlayer::NetworkState GetNetworkState() const OVERRIDE;
- WebMediaPlayer::ReadyState GetReadyState() const OVERRIDE;
-
- bool DidLoadingProgress() const OVERRIDE;
- unsigned long long GetTotalBytes() const OVERRIDE;
-
- bool HasSingleSecurityOrigin() const OVERRIDE;
- bool DidPassCORSAccessCheck() const OVERRIDE;
-
- float MediaTimeForTimeValue(float timeValue) const OVERRIDE;
-
- unsigned GetDecodedFrameCount() const OVERRIDE;
- unsigned GetDroppedFrameCount() const OVERRIDE;
- unsigned GetAudioDecodedByteCount() const OVERRIDE;
- unsigned GetVideoDecodedByteCount() const OVERRIDE;
-
- scoped_refptr<ShellVideoFrameProvider> GetVideoFrameProvider() OVERRIDE;
- // TODO: Remove Get/PutCurrentFrame.
- scoped_refptr<VideoFrame> GetCurrentFrame() OVERRIDE;
- void PutCurrentFrame(const scoped_refptr<VideoFrame>& video_frame) OVERRIDE;
-
- AddIdStatus SourceAddId(const std::string& id,
- const std::string& type,
- const std::vector<std::string>& codecs) OVERRIDE;
- bool SourceRemoveId(const std::string& id) OVERRIDE;
- Ranges<base::TimeDelta> SourceBuffered(const std::string& id) OVERRIDE;
- bool SourceAppend(const std::string& id,
- const unsigned char* data,
- unsigned length) OVERRIDE;
- bool SourceAbort(const std::string& id) OVERRIDE;
- double SourceGetDuration() const OVERRIDE;
- void SourceSetDuration(double new_duration) OVERRIDE;
- void SourceEndOfStream(EndOfStreamStatus status) OVERRIDE;
- bool SourceSetTimestampOffset(const std::string& id, double offset) OVERRIDE;
-
- MediaKeyException GenerateKeyRequest(const std::string& key_system,
- const unsigned char* init_data,
- unsigned init_data_length) OVERRIDE;
-
- MediaKeyException AddKey(const std::string& key_system,
- const unsigned char* key,
- unsigned key_length,
- const unsigned char* init_data,
- unsigned init_data_length,
- const std::string& session_id) OVERRIDE;
-
- MediaKeyException CancelKeyRequest(const std::string& key_system,
- const std::string& session_id) OVERRIDE;
-
- SetBoundsCB GetSetBoundsCB() OVERRIDE;
-
- // As we are closing the tab or even the browser, |main_loop_| is destroyed
- // even before this object gets destructed, so we need to know when
- // |main_loop_| is being destroyed and we can stop posting repaint task
- // to it.
- void WillDestroyCurrentMessageLoop() OVERRIDE;
-
- bool GetDebugReportDataAddress(void** out_address, size_t* out_size) OVERRIDE;
-
- void OnPipelineSeek(PipelineStatus status);
- void OnPipelineEnded(PipelineStatus status);
- void OnPipelineError(PipelineStatus error);
- void OnPipelineBufferingState(Pipeline::BufferingState buffering_state);
- void OnDemuxerOpened();
- void OnKeyAdded(const std::string& key_system, const std::string& session_id);
- void OnKeyError(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code);
- void OnKeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const GURL& default_url);
- void OnNeedKey(const std::string& key_system,
- const std::string& type,
- const std::string& session_id,
- scoped_array<uint8> init_data,
- int init_data_size);
- void SetOpaque(bool);
-
- private:
- // Called when the data source is downloading or paused.
- void OnDownloadingStatusChanged(bool is_downloading);
-
- // Finishes starting the pipeline due to a call to load().
- void StartPipeline();
-
- // Helpers that set the network/ready state and notifies the client if
- // they've changed.
- void SetNetworkState(WebMediaPlayer::NetworkState state);
- void SetReadyState(WebMediaPlayer::ReadyState state);
-
- // Destroy resources held.
- void Destroy();
-
- void GetMediaTimeAndSeekingState(base::TimeDelta* media_time,
- bool* is_seeking) const;
-
- // Getter method to |client_|.
- WebMediaPlayerClient* GetClient();
-
- // Callbacks that forward duration change from |pipeline_| to |client_|.
- void OnDurationChanged();
-
- // Actually do the work for generateKeyRequest/addKey so they can easily
- // report results to UMA.
- MediaKeyException GenerateKeyRequestInternal(const std::string& key_system,
- const unsigned char* init_data,
- unsigned init_data_length);
- MediaKeyException AddKeyInternal(const std::string& key_system,
- const unsigned char* key,
- unsigned key_length,
- const unsigned char* init_data,
- unsigned init_data_length,
- const std::string& session_id);
- MediaKeyException CancelKeyRequestInternal(const std::string& key_system,
- const std::string& session_id);
-
- // TODO(hclam): get rid of these members and read from the pipeline directly.
- WebMediaPlayer::NetworkState network_state_;
- WebMediaPlayer::ReadyState ready_state_;
-
- // Keep a list of buffered time ranges.
- Ranges<base::TimeDelta> buffered_;
-
- // Message loops for posting tasks between Chrome's main thread. Also used
- // for DCHECKs so methods calls won't execute in the wrong thread.
- MessageLoop* main_loop_;
-
- scoped_ptr<FilterCollection> filter_collection_;
- scoped_refptr<Pipeline> pipeline_;
-
- // The currently selected key system. Empty string means that no key system
- // has been selected.
- std::string current_key_system_;
-
- scoped_ptr<MessageLoopFactory> message_loop_factory_;
-
- // Internal state of the WebMediaPlayer. Gathered in one struct to support
- // serialization of this state in debug logs. This should not contain any
- // sensitive or potentially PII.
- struct WebMediaPlayerState {
- WebMediaPlayerState()
- : paused(true),
- seeking(false),
- playback_rate(0.0f),
- pending_seek(false),
- pending_seek_seconds(0.0f),
- starting(false),
- is_progressive(false),
- is_media_source(false) {}
- // Playback state.
- //
- // TODO(scherkus): we have these because Pipeline favours the simplicity of
- // a single "playback rate" over worrying about paused/stopped etc... It
- // forces all clients to manage the pause+playback rate externally, but is
- // that really a bad thing?
- //
- // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't
- // want to hang the render thread during pause(), we record the time at the
- // same time we pause and then return that value in currentTime().
- // Otherwise our clock can creep forward a little bit while the asynchronous
- // SetPlaybackRate(0) is being executed.
- bool paused;
- bool seeking;
- float playback_rate;
- base::TimeDelta paused_time;
-
- // Seek gets pending if another seek is in progress. Only last pending seek
- // will have effect.
- bool pending_seek;
- float pending_seek_seconds;
-
- bool starting;
-
- bool is_progressive;
- bool is_media_source;
- } state_;
-
- WebMediaPlayerClient* client_;
- WebMediaPlayerDelegate* delegate_;
- scoped_refptr<ShellVideoFrameProvider> video_frame_provider_;
-
- scoped_refptr<WebMediaPlayerProxy> proxy_;
-
- scoped_refptr<MediaLog> media_log_;
-
- bool incremented_externally_allocated_memory_;
-
- scoped_refptr<AudioRendererSink> audio_renderer_sink_;
-
- bool is_local_source_;
- bool supports_save_;
-
- // The decryptor that manages decryption keys and decrypts encrypted frames.
- scoped_ptr<ProxyDecryptor> decryptor_;
-
- scoped_refptr<ChunkDemuxer> chunk_demuxer_;
-
- // Temporary for EME v0.1. In the future the init data type should be passed
- // through GenerateKeyRequest() directly.
- std::string init_data_type_;
-
-#if defined(__LB_ANDROID__)
- AudioFocusBridge audio_focus_bridge_;
-#endif // defined(__LB_ANDROID__)
-
- // Suppresses calls to OnPipelineError() after destruction / shutdown has been
- // started; prevents us from spuriously logging errors that are transient or
- // unimportant.
- bool suppress_destruction_errors_;
-
- base::Callback<void(base::TimeDelta*, bool*)>
- media_time_and_seeking_state_cb_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_WEB_MEDIA_PLAYER_IMPL_H_
diff --git a/src/media/player/web_media_player_proxy.cc b/src/media/player/web_media_player_proxy.cc
deleted file mode 100644
index 9ebec48..0000000
--- a/src/media/player/web_media_player_proxy.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/player/web_media_player_proxy.h"
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/message_loop_proxy.h"
-#if defined(_DEBUG)
-#include "base/string_number_conversions.h"
-#endif
-#include "media/base/pipeline_status.h"
-#include "media/filters/video_renderer_base.h"
-#include "media/player/web_media_player_impl.h"
-
-namespace media {
-
-// Limits the maximum outstanding repaints posted on render thread.
-// This number of 50 is a guess, it does not take too much memory on the task
-// queue but gives up a pretty good latency on repaint.
-static const int kMaxOutstandingRepaints = 50;
-
-WebMediaPlayerProxy::WebMediaPlayerProxy(
- const scoped_refptr<base::MessageLoopProxy>& render_loop,
- WebMediaPlayerImpl* webmediaplayer)
- : render_loop_(render_loop),
- webmediaplayer_(webmediaplayer) {
- DCHECK(render_loop_);
- DCHECK(webmediaplayer_);
-}
-
-WebMediaPlayerProxy::~WebMediaPlayerProxy() {
- Detach();
-}
-
-void WebMediaPlayerProxy::Paint(SkCanvas* canvas,
- const gfx::Rect& dest_rect,
- uint8_t alpha) {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (frame_provider_) {
- scoped_refptr<VideoFrame> video_frame;
- frame_provider_->GetCurrentFrame(&video_frame);
- video_renderer_.Paint(video_frame, canvas, dest_rect, alpha);
- frame_provider_->PutCurrentFrame(video_frame);
- }
-}
-
-bool WebMediaPlayerProxy::HasSingleOrigin() {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (data_source_)
- return data_source_->HasSingleOrigin();
- return true;
-}
-
-bool WebMediaPlayerProxy::DidPassCORSAccessCheck() const {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (data_source_)
- return data_source_->DidPassCORSAccessCheck();
- return false;
-}
-
-void WebMediaPlayerProxy::AbortDataSource() {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (data_source_)
- data_source_->Abort();
-}
-
-void WebMediaPlayerProxy::Detach() {
- DCHECK(render_loop_->BelongsToCurrentThread());
- webmediaplayer_ = NULL;
- data_source_.reset();
- frame_provider_ = NULL;
-}
-
-void WebMediaPlayerProxy::GetCurrentFrame(
- scoped_refptr<VideoFrame>* frame_out) {
- if (frame_provider_)
- frame_provider_->GetCurrentFrame(frame_out);
-}
-
-void WebMediaPlayerProxy::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
- if (frame_provider_)
- frame_provider_->PutCurrentFrame(frame);
-}
-
-void WebMediaPlayerProxy::KeyAdded(const std::string& key_system,
- const std::string& session_id) {
- render_loop_->PostTask(FROM_HERE,
- base::Bind(&WebMediaPlayerProxy::KeyAddedTask, this,
- key_system, session_id));
-}
-
-void WebMediaPlayerProxy::KeyError(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code) {
- render_loop_->PostTask(
- FROM_HERE, base::Bind(&WebMediaPlayerProxy::KeyErrorTask, this,
- key_system, session_id, error_code, system_code));
-}
-
-void WebMediaPlayerProxy::KeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url) {
-#if defined(_DEBUG)
- std::string hex = base::HexEncode(message.data(), message.size());
- DLOG(INFO) << "DRM Key Request: " << hex;
-#endif
-
- render_loop_->PostTask(
- FROM_HERE, base::Bind(&WebMediaPlayerProxy::KeyMessageTask, this,
- key_system, session_id, message, default_url));
-}
-
-void WebMediaPlayerProxy::NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size) {
- render_loop_->PostTask(
- FROM_HERE,
- base::Bind(&WebMediaPlayerProxy::NeedKeyTask, this, key_system,
- session_id, type, base::Passed(&init_data), init_data_size));
-}
-
-void WebMediaPlayerProxy::KeyAddedTask(const std::string& key_system,
- const std::string& session_id) {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (webmediaplayer_)
- webmediaplayer_->OnKeyAdded(key_system, session_id);
-}
-
-void WebMediaPlayerProxy::KeyErrorTask(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code) {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (webmediaplayer_)
- webmediaplayer_->OnKeyError(key_system, session_id, error_code,
- system_code);
-}
-
-void WebMediaPlayerProxy::KeyMessageTask(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url) {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (webmediaplayer_) {
- const GURL default_url_gurl(default_url);
- DLOG_IF(WARNING, !default_url.empty() && !default_url_gurl.is_valid())
- << "Invalid URL in default_url: " << default_url;
- webmediaplayer_->OnKeyMessage(key_system, session_id, message,
- default_url_gurl);
- }
-}
-
-void WebMediaPlayerProxy::NeedKeyTask(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size) {
- DCHECK(render_loop_->BelongsToCurrentThread());
- if (webmediaplayer_)
- webmediaplayer_->OnNeedKey(key_system, session_id, type, init_data.Pass(),
- init_data_size);
-}
-
-} // namespace media
diff --git a/src/media/player/web_media_player_proxy.h b/src/media/player/web_media_player_proxy.h
deleted file mode 100644
index fc07ba8..0000000
--- a/src/media/player/web_media_player_proxy.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_PLAYER_WEB_MEDIA_PLAYER_PROXY_H_
-#define MEDIA_PLAYER_WEB_MEDIA_PLAYER_PROXY_H_
-
-#include <list>
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "media/base/decryptor_client.h"
-#include "media/base/pipeline.h"
-#include "media/filters/chunk_demuxer.h"
-#include "media/filters/skcanvas_video_renderer.h"
-#include "media/player/buffered_data_source.h"
-#include "ui/gfx/rect.h"
-
-class SkCanvas;
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-class VideoFrame;
-class VideoRendererBase;
-class WebMediaPlayerImpl;
-
-// Acts as a thread proxy between the various threads used for multimedia and
-// the render thread that WebMediaPlayerImpl is running on.
-class WebMediaPlayerProxy
- : public base::RefCountedThreadSafe<WebMediaPlayerProxy>,
- public DecryptorClient {
- public:
- WebMediaPlayerProxy(const scoped_refptr<base::MessageLoopProxy>& render_loop,
- WebMediaPlayerImpl* webmediaplayer);
- BufferedDataSource* data_source() { return data_source_.get(); }
- void set_data_source(scoped_ptr<BufferedDataSource> data_source) {
- data_source_ = data_source.Pass();
- }
-
- // TODO(scherkus): remove this once VideoRendererBase::PaintCB passes
- // ownership of the VideoFrame http://crbug.com/108435
- void set_frame_provider(VideoRendererBase* frame_provider) {
- frame_provider_ = frame_provider;
- }
-
- // Methods for WebMediaPlayerImpl -> Filter communication.
- void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect, uint8_t alpha);
- void Detach();
- void GetCurrentFrame(scoped_refptr<VideoFrame>* frame_out);
- void PutCurrentFrame(scoped_refptr<VideoFrame> frame);
- bool HasSingleOrigin();
- bool DidPassCORSAccessCheck() const;
-
- void AbortDataSource();
-
- // DecryptorClient implementation.
- virtual void KeyAdded(const std::string& key_system,
- const std::string& session_id) OVERRIDE;
- virtual void KeyError(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code) OVERRIDE;
- virtual void KeyMessage(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url) OVERRIDE;
- virtual void NeedKey(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size) OVERRIDE;
-
- private:
- friend class base::RefCountedThreadSafe<WebMediaPlayerProxy>;
- virtual ~WebMediaPlayerProxy();
-
- // Notify |webmediaplayer_| that a key has been added.
- void KeyAddedTask(const std::string& key_system,
- const std::string& session_id);
-
- // Notify |webmediaplayer_| that a key error occurred.
- void KeyErrorTask(const std::string& key_system,
- const std::string& session_id,
- Decryptor::KeyError error_code,
- int system_code);
-
- // Notify |webmediaplayer_| that a key message has been generated.
- void KeyMessageTask(const std::string& key_system,
- const std::string& session_id,
- const std::string& message,
- const std::string& default_url);
-
- // Notify |webmediaplayer_| that a key is needed for decryption.
- void NeedKeyTask(const std::string& key_system,
- const std::string& session_id,
- const std::string& type,
- scoped_array<uint8> init_data,
- int init_data_size);
-
- // The render message loop where the renderer lives.
- scoped_refptr<base::MessageLoopProxy> render_loop_;
- WebMediaPlayerImpl* webmediaplayer_;
-
- scoped_ptr<BufferedDataSource> data_source_;
- scoped_refptr<VideoRendererBase> frame_provider_;
- SkCanvasVideoRenderer video_renderer_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(WebMediaPlayerProxy);
-};
-
-} // namespace media
-
-#endif // MEDIA_PLAYER_WEB_MEDIA_PLAYER_PROXY_H_
diff --git a/src/media/shared_memory_support.gypi b/src/media/shared_memory_support.gypi
deleted file mode 100644
index f91edf5..0000000
--- a/src/media/shared_memory_support.gypi
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-{
- 'variables': {
- 'chromium_code': 1,
- # These are defined here because we need to build this library both for
- # the general media pipeline and again for the untrusted NaCl target.
- 'shared_memory_support_sources': [
- 'audio/audio_parameters.cc',
- 'audio/audio_parameters.h',
- 'audio/shared_memory_util.cc',
- 'audio/shared_memory_util.h',
- 'base/audio_bus.cc',
- 'base/audio_bus.h',
- 'base/channel_layout.cc',
- 'base/channel_layout.h',
- 'base/limits.h',
- 'base/media_export.h',
- ],
- },
-}
diff --git a/src/media/test/data/48_aac_infinite_loop.m4a b/src/media/test/data/48_aac_infinite_loop.m4a
deleted file mode 100644
index 27a6184..0000000
--- a/src/media/test/data/48_aac_infinite_loop.m4a
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/README b/src/media/test/data/README
deleted file mode 100644
index 560115b..0000000
--- a/src/media/test/data/README
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-bear-320x240.webm - WebM encode of bear.1280x720.mp4 resized to 320x240.
-no_streams.webm - Header, Info, & Tracks element from bear-320x240.webm slightly corrupted so it looks
- like there are no tracks.
-nonzero-start-time.webm - Has the same headers as bear-320x240.webm but the first cluster of this file
- is the second cluster of bear-320x240.webm. This creates the situation where
- the media data doesn't start at time 0.
-bear-320x240-live.webm - bear-320x240.webm remuxed w/o a duration and using clusters with unknown sizes.
- ffmpeg -i bear-320x240.webm -acodec copy -vcodec copy -f webm pipe:1 > bear-320x240-live.webm
-vp8-I-frame-160x240 - The first I frame of a 160x240 reencode of bear-320x240.webm.
-vp8-I-frame-320x120 - The first I frame of a 320x120 reencode of bear-320x240.webm.
-vp8-I-frame-320x240 - The first I frame of bear-320x240.webm.
-vp8-I-frame-320x480 - The first I frame of a 320x480 reencode of bear-320x240.webm.
-vp8-I-frame-640x240 - The first I frame of a 640x240 reencode of bear-320x240.webm.
-vp8-corrupt-I-frame - A copy of vp8-I-frame-320x240 w/ all bytes XORed w/ 0xA5.
-
-Vorbis test data from bear.ogv (44100 Hz, 16 bits, stereo)
-vorbis-extradata - Vorbis extradata section
-vorbis-packet-0 - timestamp: 0ms, duration: 0ms
-vorbis-packet-1 - timestamp: 0ms, duration: 0ms
-vorbis-packet-2 - timestamp: 0ms, duration: 0ms
-vorbis-packet-3 - timestamp: 2902ms, duration: 0ms
diff --git a/src/media/test/data/bali_640x360_P420.yuv b/src/media/test/data/bali_640x360_P420.yuv
deleted file mode 100644
index 40d471b..0000000
--- a/src/media/test/data/bali_640x360_P420.yuv
+++ /dev/null
@@ -1 +0,0 @@
-DEHKLLMNNPPPPQSQSRRRSSRRQSQQPONMLJKJGGFCAA>>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~
}wmgXH;/)%$$#$%%'*.178>CLS^emt{|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039<CNV]flqty{|zzvrmd]OE92,+)((''''&((()))+*-01236778=>?CFFJNU\bkptxz{}~}{yzvttx{
~|zxutty
|xpfXJ=4/.-,038=DNZbksx|~}{yvwwzz|ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~~~~wsk`UH<2+*++++++,,+,17?JVamw}
{tk^P?3*&$$$%&(,/258=AEJRZcjr|zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<CMU]flqty{|zzwvof^RG;3.+)((''''&'(()))+,..0035689;=ABDGKOX^dkpty}~~}|{wsrrvyz}
}{yyxy~
{umbRD92/...038=BMXbksx||zwwtssvxzxnbWNIFA><;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{|vqi^QB6.*+***++++++05=EQ_jr{
}xncTE6-&$$%&',/148=>BDHLU^eowzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{
}|zwtrppquvy}
~~{yzz{}yrj\N>51-,,.037<CMW`irx|}{xwtrooruxwk^SNIFA>;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz~|vmbWI;4,*,******++.28ANXcoy~}tj[M=0)$$$%+.168;?BDCFKOXdmu~}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx}
~|yvtqoooppsux~~}{||~
{vofXI;3//0..025:BMU^gpw|~~~~|{xvtqmllnrv{
wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz}xql\OB8/+*****+++,-28?IWamw{vm_RB4*%%%(.249<@CDEDFINU_iqzyri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{
~}xvtplkjjkmot{~}}}~
zslbRC82/..../26;BMWaiqwz~~}}{wusrmiiimqv|
vgZNHGEC@><JJKJLLNNOOPPOOPRRQPPQQPPPOOMLJJJHGEDCA@?>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{}vmcWI<2.-****+++,,07<FS`ku~
yrdWG6-))).158:?BDFEFFLNRYdow~
{tk`\djkeWLQi[ONV\YT&""$$$##$"""$(-4:@EKQW]^bhlqvyz||zzwwwwvutpj`SICA==>>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz}
zxvtokhgeegjms{~~~}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx
~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB><AGQ]fow
{si^PB60,,*+++*++,-49BO[ht||tj]O>1++/479<>BCEFFGGILPX`jr{
|ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~
}{wspnkihdbcgint{~
}wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks}
|voeYG:2.,*+++*+++,17?KXdq||umbRA4//269=?BDGFGGHHHKPV[emu~|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~}yuqoljgda_`cgjnty}
}vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~
~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~}wpj^PA6/+****(**++/7<FT`mxypeYJ;4369;?DFGGHHIIIHILQV_gqz}umd`TUWD;2CZ/-0AQEML8"%%%%$$'*04;AFLRV[^_bdeijjlqtvxxxssutttrmi_QH@<;:;;;;:;;;;;:98876534222100./19=DNZaiquvxz||||ytlcZM@81-+)))*(+++,,,-///26:>DHNSX^cikosu|}|wtpjhfca^]_cgimou{~
|ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov}
|sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}zsmcVI;2-***+*)*++.29DQ^gt}|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27<DNXbiquvxz||||ysnf[NC82+,,++*)+++,-..0139=CHMQW\`fjnqvz|~}wskgea`\\\^behknptz~~~}~
|ulaTF=864200249>FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~
|sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqx~{wpi]PA3.**)**))**,29@KWdny|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA<C:1*2.,0<Ab2%%&&(-06:@FLSXZ^_bcdddbacdjmrtsttssssspme[PD=988889:;:::::998875542221/00/016;EMXaipuvxyz{||yxqh_SD:3.--++,,,,-../139=AFLPV[aejnrvy|
|{sjgb][YZ]_cehkmpsvz|}}}}~
|uk`SF>98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}~}ytmdUG:0+*********07=ES`jw~
|woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9<EIcK%%(*,18;AGLRVY^_aacde`]\\^dhnqtutsssssojdZLA=;887789988888776665553221/0/..26;DNYbipuvxyz{|||xri_SI<71//----..//137;AFKOU[_ejnruw|~
~zumg`[XYZ\_behkloqtw{{}}|~~|uj_RE?:965547;AHPV\cjsx}|||zwsmh`[XURPOOS\fmw
{sh^RMHFCB@?MMOOQQQPOOOOPPOOPPONLLKIJIGFDBAB@@????@@===<====<<;;::::::888898776665443333222022022200/..-,,,,,,++**,,+*****++++,,--.........///....-,---,,,++++****))))))))(())))))))(((((((((((((()))')))***+++++,,---.//1231455665542433121./2569>DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy~}{vph\N@4-*)**)****.6:BP\hq|
}{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~zvqh_YVVY\_cfikmnoqtw{{{{{}{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}~~}ysmcUI90*)))()****07>IWdnw~|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7<BFNYY[`liTMVa[[aRPPUZaipuutstrponh`UI?856656776666665544332220///.../04:CMV_gotwywy{{{|ysmcYL@:62111111348=DIPT\`fimruy|~}}}}|ytld]XWY\^bfikmooprvy|z{{||{tj^PGC?>=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq|
~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5<DS`lv||xuog\RKHHHIKKKKJHEA<82216AP_aniRhdZTRGQQJQYabaa`]U?4<I[rr:6>ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|~~}}~}}{xtphd[YZZ]afilooqrrtvy|}|||{
{rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~
}yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}}~}yslaTE7-*))))****-39BM[fq{}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~wjh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:<FR\hw
{vrkaUMEA@>>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5<HR]gqz}}zvpfYL=3,****))**,16=IWcmw~|zvulg^UOKMMMNMJGB>:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[ntosmhmlepzqj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}~|}}|}~}~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny
{wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~}||xql_SD6-**(())***.49DR_jr{~~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~
|{{z{}|~~~~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz
}yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs}
}|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5<GUcksz~}~}}{{yuph_VQMJIIEA<70*&#" $*;_afkqmeLFU[\VTX[YOXRQZX^`gibR@VNDe~ynf_V^K:8=QVHPSGFGKVdouwxxwyywuli\PC90-++++,---....-----,--,,,,++**,-17>GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx}
~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{}
{slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4<GT^hrx{|{{}|ytmeWH:0))))))*)).28BN\how|}|}||{zxtmbYRLIIFB;63+&#! ##(;MUhfc\Y2@Z[WR]QPZVZUP^[W^`jeW4ONNoordbgXZWM8=GCQWD:6?M[gpvxxyyxxvtni_OB80*++,+++++**+-,,+***)+,,**+)++--07>FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuz
}}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224<FTcnz
|yxxvtneXMC?;99:OOOMLLKJKKIHGFEEB?>;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}}vkdb_][^^acfjorvy|}}{{{{yvpkaUJ=73128>KXds}
~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....00228<AGKQUZ_dfhkmlnnlijda^]ZWWSOE<5.**)((''''''(('',/4=HS^jqwzyz||{yrpdUD8/+)(((((()/3;GT`grx{}}}}|wsk`UOEB>94+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}}vleb_^^_`cfhlotvy|}}yzzyvtmg^OD;6433:@O]iv
{wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158<BGLQWZ^cgilmpqpqmkgc`^]ZWTOH@93,)*)((''''''(('')-18BNXenuxz{{zzzvqgZI=/*)(((((((-17?MXdowz}~~}|zuof[PGA:4/)&"!!! !7YghZ`]c_HMWTROQ^lT5@IKGS[^\WOFEEBHKJV`N]``e[QGDTG9((3:CQ\hqvxxvvvvusoi^O@6.)''''''(((())**)(''''%%'()**,//38>EQWbkqtvxwwwy{{{wunia]\]`glpsw}~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435<DSamy
}ywvvvvtldYMC=89;@NMLKJGIHGFDB@B==:987766667889;::::<<<<<<==>>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/5<IUaisx{zz{|{xskaQ@3+((('''''+.6=IUamuy|}}yrj]SE>6/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy|
}vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{
|xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iuyvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237<CGMRW\_ehjmnpprrponkgc^[ZWTPJC93,)))''&&''&''''&(''&+/5@MXcntxzz{||{xsj\L<0)'&&'''()-29BNYenw|~~wqgVH;1-*%$#!!##\c_uQ>TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy}
|tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lxxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5</&(-2;GT_kty{{{|||{unaQB5+'&&'''()-1:ANYantz~zsiZJ;/(%&%$!!"7uYKB<;KOVaT?12;@?EBGJA8FIKEIEN[^`bdeecZ>=ICEB^NCP[\J(+3<EQ]gqssrrrstusmh^O@6,)%%&&$$$$%%%&&&&&%%$%')+,/01346:>CIOXaiosvxxwvxy{}~}{zwwwy}~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}}vl_O;-$$###$#%<kL;453=LLbbaUB='799:BA=GKJLPTX]_`adbc^T<5@CEEjmMICDH105<FR[gqsttssttttmg^PA5-)%%%%$$%%$$%%%%%%&%'))*,.023579:?EKQYahosvxwuuwz}}~~
}{zwvqnnkeb``abdhjlppruutrrpopppnlmjfb_^]]]\^`bgjpsv{{{{zyvz~{rjfeedcbabfikosvwxwwwxywwtpjd]PD=:88<BJXer|
|wtsssttsqi_RGCFJPVKIGFGDC?<:9754321101002244444788889:;;<=>>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K</('&'&'*+06:AJT`jry}yqbR@1(#$$$$$'BnY>6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<AEKQYahosuyxwvx{~
}|yvsrmlifcaabbcfgkoqrtuutrpooooonlkhd`^]\\ZZ[\acinsv{{}|ywy|
|siedda`_^`cfjosuvvuuuuwwwtpkcZNE><;;?FQ^ivzwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}{tiZF6,&$##%(([kX<6<BAIYentwtVKHCADLPMGCLTXZ]``cba]XTNF>BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~
}|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz}
~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvyvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw}
|{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz}
zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzyurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047<CINRW[_cgiklkiigggfeca]\[YUQI?80,))))(((((('&''&&&&&&%%&')05?O[epwz{z||||xri[K;-&'))-157;?DKOYbkrz~}uhVE6+&"$#$(@NJ>799<JUbqtwtlNCB97>FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{
~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~
}wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{
~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2<IVclwz{zyy{|zuk^N?2*))+/379>@EKOWahpx~~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{
~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~
zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257<?CFJNU]gnv~ysfVC3'%$##"5id,#'/:CSbntusk`6%%*+7LYZ[_`_a_YUQKE=71/.338HPQOUNRW45:ALYcmuwyxuutwtrok`RB91-***(''''''''()+-/012468;=?ACFIKNSW[aflptvwxz}|ytolgd`\\^``acegffgikmnprtusqqoonomkifda]YVUTSTSTUX\dkpux{zxxxz~~ysleb`^]\[]^bejmpqrrnnnorssuqmg_WOMIGINU\fp{}xusttvvwwuqld]]_djoCA<:986543111...//.....0111245688899;;==>>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~
}ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt}
}xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhszxreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47<AOYdnvxwwuuvvwuokbRD:2-**))***'(()*,-.014579:<?BEHILPQSZ\beimpsvy{{unida^\Z[]]__bddefgiklopqttsrqpoonnkifb^[YVUTQPPOOOSYaiovxzzyxyz~{vmb^^][[[Z]adiknqsqpoomorsttpkd]VQQOOQV]fow
|yvutvvvxxvuqjecehls=::86555432200.---..../111435677999:<<==>???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:<?ACBEHLOV`gpz}tkYF5+&&=6&%%!%+/6CQ`mtxzzxrme][Z\^_`_\YTPJD=5.,+,----7uXQNLJS?/6;DQ[cmuwwuvvvvwupj_RD:3.++*)**+*++,,--/024689<>@DGJKNRUW[_cgknstvx{~}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{
ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{
|yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....<jfPQNT^X87>DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{
ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxzyria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~
~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358<BFKORWX\^^^_]ZWVVVVY\accdba\VMC;4-+**++**))**))))('(('''''''''')-4>LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7<FS^gpuwvuvvvvvspi]QE:3..-,,,,,,,+,-./14688:<>ADHJMPSVY\_cdhmqstww{
|wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy||yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw
~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<?DIPRVZ[]^abcc`_^\\\]`baadeca]VMD;4.++**+++++,++****)+))((''''''&&()/5?LXforuuuutvwtpk_SHA@BCEFFFGGGIMMR[dmv}}tiXE5($"$#$##&,4@MZhsz}||yulec^\WQLFA:2-****++,-.//..3YTPIJMV=:AKV_lwyyxuuvxxvsnh]OD=62//....-,..-/023568:<?AFIJMQSVX\_dfjnrvx{|}~
}xrnhaZWSQPORRRSYZ[\_achjlnppsssrrooopolgda\WRONKJGCA><<?ELV_ipx{ywwy
yne]XXXWXZ^`chkmnppppopqruutrnib]ZXZ^bgmv|~|zxxx||zwvuuwxyyywtqmkmqtv65666543321100.-0011113534677789:::;<<==>??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy||xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::<AJU_hovwwuwy~
xqg`ZZZY\[_cfhkooppoooqrsurtrmhb][Z[^djqx~zxttx{{xyvwwyyyyxwtqmknpru774455433211110..122113457777899::;;==??@>@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~
|xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}zrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmsz}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:<???@@AAAAABBBA@A>=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt||vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLE<BNYeovyzxxwwxxvtmg]PD<843222221-.//0213579:=@CGJMPSVXZ^acglprv{
|ytqkc^WSQPOOPRSUUWYZ[^abdhjmooopqtsqqqrpolic\WPLFC@=<986568>JU`ipuwwuxy
ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2<HUdmsvvvvvvyywti_SIEDEGGFEEB>;;<@LXdrzwp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{
xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589<?BFHKJJIJJIOTX]_ehhhedegfeedddceefggfgihhheb[SLA60*))**))(('&''&%%%%%%%$$###########$%%%%%%%&''''()*+,,--..///////0469;AGLQRXZ`dgimoqsswuromjfecacdcbb`[UOF<51/.,,,,,,,,,,-,,++,,,,++++**(())((((),19DR_jqvutttvyzzumbXMGFFGGFEC?;878<FTbmvzseTB2)')+049?EMT]hqwxwwroi\PH@8/+))))**++,,,-..//1122=ghRPIZaJM[hsxzyxwwwwwusmh\OE=85434322000000004679;>ACHKMORTXY]^cglptx~
}ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{
xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247<?DKPTVY_bgjmoqrvuyxwtqmieddddccb_YVQFA92,,,,,,----,,..----..-,----+**())))))),27?N]iqtuutuvwyywpg\QIGGHEGEC=74449AO_ky
~wkZH9.*-04:>CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz}
yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry
|wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249<AFKPUY\cginnrtvxz{|{wtrmjgfdccb`^YVOH@82-,,,,,,------..----....--...,+*))))))),/5>KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~
~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv||wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq|
{vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~
}{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx
yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my
~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpx~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt|
zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4<FOZcmrwusj`RMD=:53-*(&&&&''&&%$#$$#"#"#####$$$$$$$%%&&''()))+,--..///0258<AJPT[_cgjmprsuwwz||yxwqonkgd`b^[XSOKD=61.-,,,,----.-..//./....//022446540/--,,*))*,09BP\gpttttttvxzyqi_VLGFC@<83.+().5?LZiv
}tk[LCACGJNQSV\cirw{zzwqi^PA4.******++,,....//..0022MnaAKHJAMU`mv||{xvvwwvvtoh^QG?:855554411220011133556;>AEEHJMPTVW\]bhow}
~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~
wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow|
wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw}
~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~|yrkdZRH?7248AMZfovxyy{
wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz
}wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~~{qkbZQE<549DP[fpwyyyz~ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}~}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~
~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~~zrjaYOE<9;FQ]hqxzzz}
ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz}~zoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gry
~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}xribVKB>?JS_jqy{{z~
}wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw}~
~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389<CFEFEDB>:7778;<=@CGJPY`iry
~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~|xpg[TIABKT`krwyy{}
{tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowwohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|}~
ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy
}||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|{tlb[PJFNWblsy{{}
}vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goywlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~
zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz}|}{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz}yog_WPOQYbltz{{}~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr|
~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw~}}
~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry
}|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~
|{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~
~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059<CDHKOT\dnv|
|ocRA.$"#$$$$$$%%&&'(*+++--../013467;?DJQX^dgjlonmmh`ZPJCA=:86544311200////----..........//00123344556677777777665533321000///137=HS_jpsspnmnorolg\QE:4/+)('()()*+/5<JZgs|~|||~xpg_YTPHECCGPZfptfM>D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538<BKPV`hou}
~}~~|zvrjbVMFA?=>=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz
xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz~|z{~zrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz
~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKC<?GQYahmuuvqkgdbadjry}~|}{{}{}}}{{zzzyyxyzz|{zuoiaZSNLJLOU_kvzqhdejrwzxvuuuvvvvuvwwwsoolljh99999999998788778888::::::::;<<<<<<<<<;:::9987776666445544553334521100.---*++*()))((&&''&%%%&&%%&%%&''''&&&&''''''''''&&&&''%%&%%%$$&())*)*0<CKT]flrvyz|
~tdS?*!!$$$$$$%&((()*+,,-../0013689>DJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346<BMVblu{}ulbUI<77;BHOV]flsy
}}~|}~}{wqh_TJC?=<;;;<<;;;<<<<=BGNV[`fkkmpqpqromje\RJ?76=EPX`flswrmiifgimtz}~{ywsuw|}~{{zzxyzz|{ytmg]VNHIHIOU`mwxmebhlrwxywvvvvvvvuvwwvsomhhge:::::::9998777778888:99999;;::::;;<<<<:;:;99887765555455544443343201/---+,*)((''&&'&'&%$%%%%%%%%&&%%&&''&&&&&&&'&&''''''&&%'&&&&$$%%%%%&%&'+05?FNTZaeilpx~
yiXD-"$####$%&'())*++-,-../012469>AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137<EPZdpy~umaUI?<>AHPW_emrx}
~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~
|vrniltx}~|{{{{{{}{xqkd\TLHFFIMWdp|
~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbnyym\D-$#####$%&'())***,---/0/1369<BGLTY^egjlnnmgc\UJB;7444443333212100000/....----....001112334555679999::::98877665442222111348;@KValpponlnnnppmg_RC6.***)*****)*/6;IXery~}zxxz}|wl_RF=6148=FR^kd=BgvofXJ=5.,++,...--.//../0Cn{v^ONYT>NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|~ul`THAAFIOW[cjpx}~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~zrkebemrx|~||{{{{{}{vniaXOICDEGMXfr}|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv
zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~~yobSD82036<EQ]jaDFowrh]L@5/---,,--....-.02<_klrXGNNZSQRgjt|~~{yxwwyzxwuogYJ@84211249=GR_jw~uk`TLGEIPVZ`gnv|~~||||~{wqi^PF?986857777787768<BGQZ_gimnmmnmmnjgc[RH=6.+-05=FS\eloqqqqtuxz|~}
~wla\Y^fksy~~}|||}|||yslf\SJEBABFO[ht{pfacfkszyxwuttvvvvvvuspnjhgea^;;;;::;;::987888888899999::;9:;;9999:877756666434465444323444111/.-.-,++**)(('(('&&&%&&%&&%%%%%%$$$%&&''''&&%%&'&&&&&&&&&&&&&&&&%%%%$$$$####"""$&',./6BQdsylX>)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247<AIR]fnrpnlnooprrlf[M?4,,)+)*+++*,05<FUalv}~{xxy|}~{reSB72036:DO[g[ERutoj_NA6/-+++,--......03KjjulLGFHMLNRons{}~{yxwwwyxwtngYJ@84212239>HUbmy
~uk`TLHMSXY\dmt{
~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jwxnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|}yyx{|~{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcozxm`UQQSVX[agpw}
~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~
zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{
uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26<BKT`kt|~zxx{|~|vl[K=5025:BMWdPSgvurmcSD;2-,,+,,,--,--.08lzuubNED>EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~|pdYVVXZ\]dirx~
~~~|||}~~|ytof[OC:644444555444457<DMU\bhkllmmlkiifa[TI@30+))+04:EP\fmrvy{|
xmcUJJNWahpwz}~~}~~~|{ytle^RHA<:>BKWco||ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my
zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}~z{{|~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv
rg]XXY\]`elrz~}~|~~}~|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~
~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}|{{z}{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}}{zyxwyyyxuneVI>843125;AIS^lxth`[[\]]bflrv|
~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~
}vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiuxpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs|
~{z}~xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{~{zyxxyyywtmeYK=752548=ELXcp|}tib^\]]_aejpvy
}}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{
~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_ly}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+2<M]o{vfU=*&%'+-,())**++-/26<BIMSU[^_befhjjjiigc_XOF@965665566553333332211///..-------../1001112345567887789999998789976653554568:<@CGKPU`hosttqppsuvxwslaP@6/..18<?BEJPRW]cks{
v{xo`O>5347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~|rhc`_^]]^`djov}~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{
}vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{
|sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkxwi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{~s^j|yz|seTD8336<FP_JGkvsqnfXK>5.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk|zyyyyyxxvofYJ?;;=ACHLTZbmxznga`_]ZXY\bipw}~|}}|}~~~~}{ztqkcWI=630/...---,,-/5<CLU]djmklkllmjhe]WOD91,**,+/5@HRX^ckrw}
|vl`SQRU[elty}~}|}|||{wpibXMA9688>FXdq~{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>859<ADINRVY\_dhlu|wnky~|p{}ujXI:337=EO^KHarsrnfZK>5.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|uic_\ZURSTX`fnw~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4<CMW_gjmnlklllieb]WOD91,+++/5?GMU\diotx}~
|vlaUQSZ`gov{~~}|{}}|{upiaTI=7669?J\juynd[[^ciqvwsrqqrrrrssrokjhfb]\]^^::::;;;;:99877878877887787776643333322221111110011000///.,..,,,,*))))'&&''&&%%$$%%$$$$$$$$#########$$$%%&&%%&&'%(*''%$""""""""""""##!!"""" !!!"$'+4>Qboy|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz
xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~zre]ZVRMJMOS]fr|~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy|
|ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4<?4))*,/38<CGNSX\adfghhhjigda[VOHD>955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|xmcYSLHDBEMU^iv}~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz||
|ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{
}uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!! !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmryvk_QLD=8;@MYeq{~~}~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejquxvX@PZRP@X`^dlNJA@GKhrICLcsqkf[K>5.-+,,,,,,,,-/Hn}tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}|uiZL@7339CP\hv}~~~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~
}vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!! !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry}
{qeVF;404=HUamx
}|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~
~uj^Z\`hosy{}~~~|xph\SE80-,0<J\iwyumbXTWahosvurprrsrqtsroliecccddefh<<;:::9988777766556655542222100///10////..----/./.----++++*+))))(('&&&&%%%$$$$$$$$$$############$$""####$$""##""##""""""!!!!!!!!!!!! !!!"""#&*3?P_mv|zsj[M6,+*+,/046;AHMTY]adgjmkjd^ZRJEA>9554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~pVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz}
{qeTD8218BM[fr}}|{xxwwvqlbVI<3.,,---,,+**.5<GNXafiiiiiiihe`\XOE92/04<DOYbinsy~~}|}~
~uka\_cipvz{}~|uph\O@5.++3>O_ly
yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! ! !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz}
zqdSC725<ER]jv}|yvvxxvqldWI;2-++,,.-++),/6=GR\bfhihhiiige`ZTMC8127;DKV^ekrv|~~}{}
~uk`Y_dkqw|~}~~{wodYL=4-+,5@Rap
yphZVSWaiortsooqqqrrrsrmkheffgiihge::;;;;9987776665552211210/121221221/..//..------++))+++)***)()(('''&%%%$&&%%$$##$$####$$####"""###""##""""""""""""!!!!!!!!!!!!!!!!!! ! !! !"#%*2?O`jt}}wl\O@222568:?CGMQW[_bfhijgb[TMFA=:7654444444444444543333321100/...--..----..//./111111334444555544555545568:<=@BCDFHJJKLNNOONOSTY^hnuxwusrrqsvzzzwuojfijmt{u^VYP3]s|wj\TWRSMMYWQNU^fd`YMA7.,,,,,,++++*,2A]aPZ[Q]T:BEJT^eqz}|{xwxyyy{|{{vojfehhlpuux{|
zqdSC837>IVaoy
~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{
~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt
xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz
kSyyob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy}
zrdTE:58AN[gr{~~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59<BHPX_dinrvyywvxyz
}ujbabjpuz}}}~~{vndWG:3,*0;HZjxvlbUQRYajqvursqppqrssqomllkljjhddaZ<;:::::8876655643321//001/1458;<:8531100....-+--,,++****))(''(((&&%%%%%$%%%%$$####$$########""""##""""""""####""!!!!!!!! !!!!!""%%)2<IZhr{}vmcWG<;=?BDIMRVX]^beehhf`YSKF=976554664444455665544333333110///....-------..../110012243344444434445578;<<?ACDFFHIIJKLMNNOOOOPORV]dluxvtsrqsvwz}{ysnjjmqsv{
|{|{sg\NGV[XZPLJMOLLY^VH=4.+,,,,,,,,,,-5E]\G[VWOFWVG>Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}ypeTE96:CP^jv
~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz}
~uk``flrxz|}}~~~zskbSD81,+1;M_n{
|si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!! !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz|{{}~{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~
~rgbbgmtyz{}~}xri]NB7.+,3?Obryqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!! !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy|
~rhcfjpuz{|}~|wqh]N@6.+*6CTdtwndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !! !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{
~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{
tkggmsvy{|}}}~~xofXK?5-+.8FXjw~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!! #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx|yywvtnecaZYWVYWMKLJE><CGGGIPSS\L%-.,-,+,,-.--.4LMWf[ST@<7:Iizz{xvvuwyysrurjd\XZ[jhM=D@8]iejsyzxsh]L@;?GT`mw~~}}}}}~}{ywvvvwxxvqjaSF81+(('''((*.4=FNW^eghghiiiifca^ZVPORW[^aeghhifb_^_cimtx{
|sjiiotz|{||}~}wofXH=4--1;L\n}~th^RNPT]elrussrrsstttuuuutqpmhc[SMHJ;;:98777543332101/--.....26;?EILRSSRQOOMKLKJIIJIFDB@=951.*)('('&%%%%&&&&&&&&%&&&%%%%%$%%$##$$$######$%$%%%%%%%$#""!! !! !!!!$&(-5@Q`lv}|xtplkhhhkkkkkjjieb^YRKD?;9999777788988889;<>??>>:64221/110/21132134577;<<<?BBCGGIJLLMOOPRRTSRTTSSSSRRSSRTRPPONNLJKKIIIFCB>;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<<Wc`^S=:3AZp{~yxwwvvusjhppje`\\aZ\UE@7Jcaajswvvph[M@<BJWdoz}}}}}}|~~|zxvtutvwwuqj`TG80+((''&'((06@IOZaeghhhhhjhhfb`\XVVVZ]`dfeedb_YXX\bhotxz~
~wmklqty{|{z|~~|upeXI=4..5>N_o}{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!! !!!!!! !!!"%').4>JZfq{|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=<?@DFFIKPPPRUWWY[Z]aaacdgffeehgeeeeeddba_]\[XTQNLJHGDB?<:855<DR^jtvtssstvwxwwtmcZMGAELU^iov~~zxvwxskc]XSMJMRQJFDDAACCGLOQNLNJ^iL*&--,-,,-,-/07>`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~
xpmntxz{|{|}~~~}wqgZK=4//3?Rcrzod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##"" !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434<GP]irttssstvxvyvtndVLA:>GR[enu~
zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-4<ENW^dhiihhiihjjigfc^\\]_`bcdc]URONPW_gmruy{~
zrqqvz|{zz{}~}|xpfZL>5229DVgvxnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"! !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju}
|xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~
~uttx{{|zz{}~~~~yrh[MA833;GXhxxnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"! !!!!!!""!"$&(*,/26<BNWdmtx|{}}}}~~}}{{yxvsld[RIB?>>><<=<<;=>AGQV_isz}{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~
}zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt|
|ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|}
xuux{||z{}}}~~|xqh^QD;44>K]m|xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"! !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<=<?DIOX`kv}qfYSV\`dgloqstvwwy{{|}}~|}}}{{
|xtmf`YVRONNPV_irtttssstvvw £ z(3EP]gu|zywwxy{zvkiaQRXYRRNKIJJIJOQTQQA6DA<;WT,(121<OgoWb^YTM7<AS^dquwxwvtvrdYOR[SNMMJKQ_[ZZJS_^_\I[aQ^laVHGJR\hu}~|||||||||{zxuromnmpppnjf\OC70+)(((').4<GMU`fhhihhhiiijjihea]]]\\YVQLIEBBFLSZbjpuvz|}|yyz{||z{}}}~~|zsh_RF=66?O`p~wl`VUV^djprsspopqqrtuuwzz{zxvrle[QNZi7765333110//..----+++*))**))**(+--/1325:;<@ADEHLORTVXYYYZXVRNGA;51.,)(****)())(()(((((''(())))))**+)))'((('&&&%%&&$$#""" !!""""$%%&()-.046;@HOW_fimpsw{{}~}~}{upicZSMGB?@>=>?BHOX`kt~|reacgkptuwz|}}||}}~~}}}}{z{{{{}~
|zvsolgd_\\_fksuvuqsrstw£¡¢¥«®¨h,>OZiu|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~
|}||{{{{||~~}{{rh^SG=89BSds}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#""" !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}|ztnha[UMGEC@@DINV`hp|wmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~
}zwwtpnmrtwwwspnnx¡¡¨¬°±®48IZiu~|yxxxzz{{yrbNCPSS^XQQIMUVWQN><MJC::;:Dc^Rlc\pifZ<P[T==@FLBX{oxxzxrbVcbRIMKJJMRT^][VSJD[hgd^`s\QKJKXcny~}|{{yy{{{{|zyurommllooonje[O@70+*)(()+18BKT\dhijihiihhhigeb`[XXXSNIDB>:;?DLSZagmsvy{}~~~~
}}}}{{{{||}}}|zwri_UJ@:;EWfv
ui^VR[bhosttspopqqsvuwz{}~~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"! !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~}zvohaXVQKFGKRXagpzxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~
}}}}~|xsv
¢¦«®¯®«A5GYft||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4<EMW_eikkhgghggfddb`[YWRPKFC=876<BHPU[`hotwz{}~}}~
}}|{{z{|}~~~}}zyvpi`UMB;=J[jy
~tg]WY]flstutsonpqpsvuwz|}ytnjnv55443211..--,,++++****)))(((((('''''(()))())*,.1258>CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"! !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz
|yvpid`ZVUWZahnyywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~
¡¦ªª¨¦¥T1HXgu||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337<BHPW^ekouy|}}~}}~
}|{{z{}~~~~}|zxuohaYPE?AL]l{~tg^YYahovvtsponpqqrvuwz{~
}yutx5554200/.--,-,++**(((())''''((('''((''(''())****,./5:AFNUX\^`a_^[XROJEA<;80--++----,++,,,,-...----..,,+*)))(''''''%%$$$$"! !!""###$$%'')+.1369=>>??@DGLQW\`cgimpuz~}yuroieddfkpx
}xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~
£¤¤¢s3DXgu}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~~}{{z{}}~~}}zyvrngbZPGBEPao}
}rh_Z[ckswwvspmnqqqrtvwwy~
||334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#" !!""###$$%'')*-0377;<<<<?CGKPVY]]`dfjmrw{
}}xusqprt|zurqsrqrqoollgeeeaa^]YXWUUVWWWZ\`abdhhiijjjjihhjjihiknortwz|~
¢¡|AFXgu~|||zz{z}}{v_a^V_^ddZWV^_QWC9329;;978@HE5=IKCAloiUG>:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25<AFMT\cinswz}~
}|zzz{}}~~}}zywtohd\SJEHRcr
}re]\ahmuxwuspoppprqrtuvy~
220000..,++*))****(('''''''''(((((''''&'))))********-06:@GNSZ\\[^_]\[XVSMIC931...0.//..//////////.//---,+++*))((((&&&&%%$#!!!! !!! !!"#$$$%%%&()*+./369<===AEGLPTVXXYX[_chouz
}|zy{}~sqpmmjjhdc`\[ZXTRPOMJHHGECDHHIKMRRVXZ[^```]^\[]\^_\[[]]aejnrux{~
}¡¢|y{px\LXhv}qbozzy|}~i[WXZ^dhYHJNK=KG7C::;95669;D;7?GAMSSvdP?<CCJH?F8Jr}wsnVT=MjPJCFJHGEFCDGOXVONKFDGGEWIQYSS_lt{}}zyxyyzzy{{zyvtqnjjkllnqomgaUH@64579<@EHLRV^dillkkhfgddba\WRLE<72-))*.27<CHOX_gjpuwz}~}{||z|~~~~~}zywsoid]VMHKVfu
|sf^]cirwxwuqpqqppqrrrrsv|110011/,*)**(())))((''''''''''''&&''''&'))))))********-28?EJNSWYZ]_`a``_YSLD:200.//00///000000000/00...-,,,+))''((&&&&%%$#!!!! !! !!"####$%$$$%%%&(+,+.057;<=@@EIJMOSTTUTUUW[^fmu}
~zrkhfdb][XUSPKHFB?>=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz}
p}~~|n|sLYgv
iamcz
wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27<CHOY`eksuwz~}|||}~~~~~}{xvrmie_XOLNXhv{qg`agouzzwurqqqppqrrrrrty~211/00/++++*)(((((''''''&&&&&%&&%&''''(())(())******))+,.4:@EINSXY^`abbde^ULA610/.000001221123311111////..-,++*)))(('&&&%$""!! !!! !#&()('&%#$%%'$&)*+,/259<?BEGIKNMPRUUWWVWWX_cjoz
|qf^]ZURNIC?=:9521223022259=?BFHJMPSUUWWWWVVWSPONKHD?>::>BEJQSY_dipw}
ws{r|{{ZZguwpqroxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{
}}{}}~~~~}|{xuqmjfaYSMQ\lz|rfcgisy{zvvsspooopqrronquz
1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!! !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx
|l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}zvy{nsmlyv~vztp}pxcagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF<a\be[@?JDNZVPHFFHC=>;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy|
|{zz}}~~~~}}{xsolifa[URU_m}{sifimtz{zwussrpoopqppnkmqv{0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06<AFLQX^dglmke[NB711222122233333222222210/.------+***(('&&&%$$"! ! ! "&+/4<>>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{
zhREA=9731000../----..048<?BFIJLORRVXXZZZZXXWSSMKFB;60---++,017;AHRY`fmpvum¡´¼½¾¾¾»°}yqtzY{kzmcuxwj\fjkheb`RTUKEOb\+(=><;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~
~{yyz}}}~~~}{yvspliea\WUXbp~{slkmrw{|zxtssoonnpqqnkhhlpv~/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!! "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx}
{iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuykom¡¶»¹»½½½¼»»´vpihjk_]`vwy{kUVrnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J<ACEIUKH=MieZVJVO;nnniXLE@>=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{}
}{yz{{{|}}}zyvsqnjgd`\WW]es{spnquy}}{wsssponnnnnkihijmqz
--,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!! !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~
o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm}]5c
¸¾¼»¿¿¾½¼¾¶³º¤|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<<IMBDECJVED9Mwpk2B@Auyng`RNF??=>><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~
~|zyxy{{{|}}yvsplihfc`\[Y^gu{uqruy{~}zxtsrppoooopmjgfflpw}--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!! !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~
~zyyz
ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.036<BHNTT
~kyn]px{\]¶½¾½¼½¾¿¾¹¦ls³¸°¡wIZhuz|{{{scRWbOQQ_QAAKX^QNLIOLQX>8FEA<5COQ987<I`JLPNUOCDBRLJTBF<VxkD><<qywqefXHFFDB@=:9;=@CDFGHIJMQRVXWWhuxw{yyvsn`dmjoji_Y_OJKKHDSeklnnmkfa\Z]^bbeglmmooomkjhhhfecb^YUME=4,)'''+/38;BELTZ`hmrvy{}~
~|zyxy{{{{yywtrojggec`\[Zakv{uqtw{}}zvusrppoooopmjgffinu{
,,,+*(''&&&%%%$$$$$$$$$$$$$$$$$$&&''(())))******++--------..1311238>GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~
~yvvslou|wqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTq ¤rYghuwvvo»¾¾¾²°¼¾¿½¸v§º¯
Q<H^ov~xtwzoTAsgZY\^H=?;<MaPMNMKBKOD@@A=8BID89689>6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~
}{ywxy{{{zyxurnjgefdc`^\\dny{usuy|}~}ywttsppoopoolifceintx~++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy|
~~~{wsplgc^agq~xxuqnkd^VL@91./158<?BDHJKMNQRSTVWXYZZXVUTRNID?93/.---,,-.148:?DHKP_¡¢£{xm`yu{ah|£¹¾½º¬·½½½¬°¥|iOTOnzoY^dlmleje]XTL?:9>EITRMKKKTMKEAB?><CE?=;:;A:;KLIA@EFJWXPJCB>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38<?AGNV]dkptwz|}~~~}~
|zxwxy{{zyxwrolhfdecc``^_hp{
|vtu{|~}{yutsrppooponkgfceinpw~)'))'&%%%%%%$$##""""########$$$$&&&'(()))))*++,,++----......141/238>GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~~{xtplgc_XQORYeq||}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<?CEKRR{¢ij¥
hZT^f|¶«»¼±¥¯¤ ²£} }^^tq_YLKShi]iniq^XR::;=?8JPOFLFESKEDHJLSB8<?;:;=PMSHGPLAD@a]RQAD@Jh27;MZ]gtupj_UNJFBCCB><<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~
{yyxxyyyxxvspmhebcecda`aelt
{vwxz}{yvsrqoooopolkgebdglpv}
*)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}~}yvrmhd_[YTGABDMYgv}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^wW\¤§tbYadIZ¾®²µ©®nq¡}egagf]cdYTWf\Teiic\bR;<>D=<FRNJRQCINOQMNRZE<>E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~~{yxwwxxxyyvspjfb`adddaabgox
xvuw{~zwusrqoooopolkfcbdfkpv}
)(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~|zxvqke_[WRNJG@>@BFP`qzuy{~~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPSvYQx~wf_( }½»©}¬®w[e>Z zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}~}{ywxwxxxwvtqlhc_^^cdcbaejqyxsuw{}}|xutrrqnnooqpljgcbeglsx(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}|rgkqv{~}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQd
cQfZ*#x¼»´¥]|bVk{lp¤fC\_czvmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76<DMV]fhiihffccb`\XSKB80,(')+/25:?BGLR]flrux{{||{{{}}|zxvvvwxxvttoke`^]]`cbbcfms{~xsuw{}}|xurqqpnnoopoljfdeeiorz&%&&$$$$$#!! !!!!!!!!""""##"!$$''&''()*++,,------....--....0/01016<DLXfnstmbSH=6566557786666666666677775544442111//-,+*(''&%%#"""!"%&+29AHPU[\XTRV\afjlkiggfhhhlllnprrrpoopprttwxtrrsrsxyyvvsoje_YRLD@?@@?===;=AHVgywhXZaipw{{uof\UQNJIJLLNNOPPNHFDBEDBA>;7557879<>?BDFHKLMOPQT£¢puPW|{vs{ @*#hº¹¶²¦
`dvi`}}pcODfr}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046<BCHOW_hmrwyz|||{{{{}
~}|ywvwvwwwvurmic]Y\[_accdhpx~ytrsw{||{xurppnnnnonmljhgefinq}&%$$###!! !! !! !!!!!!!!""##$$''''())*++,,------....--....///0004:@KWbjqrofYND<786555555665555555566665554443111/,,+*)(''&%%$#""!"#%(06>CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcxucOJOXbkrz
yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQSc¡qY~t^Md}{vqu2**Mµ´±°«rijmohu|mdtvb>>X¡uf[UKMX_^GKBFZUD329>5:NIIC>;FS:589<F;2228@NF=?7NYQYK9BCQAHVUORN7@20IKY@6:>CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3<FNX_dfgfeeca``^[TOH?7/*('),069;?CHOYaiouwyz|||{{{{~
}}zywuvwxwwutqlfa[XWZ_abcflqy~vqpruxz|ywtrppnnnnnmkjjhgeehmq}&$$$###! ! !!!"$$%%''())))*++,,--......-.////....//0027<FQ\fkppibWMC<97556345555555444455466653323311/-++*(((''%%$$"""##%).3:>DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;<?FPaswdM;@ENYcmx
~yuk_VPMLMLMLFC?;::=ACCCCCCCDDCDDDGJKLNOQOQRUUp£¡JLnlPOqxwwpp3105£®§¨¤xj`TYWfjcmlhS8?T¥ tI<^ob[TNLFCCKRD3@D9::8>G43BKK?3A>?713059DC><Pe`i_;=<ANQIEOHMM;<F=C@?817?A@@AA@@AAHIJMMLR`hgfZ_pedcTKDEJEOOJ@BNCIdumgdabddcefhhfc^XPF<3+))/5>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}}|zwutuvvvutroje_ZUWY]`adhptz}tppptvxzxurqqonnnmmnlkkiifghms~$!##"! !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p|jS715=HO^ju}
~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£n:IcxXNYrxzx`854.z¤¢¤~mdXNB?D]jhcJ<EQ~¢cCN|}vmd`SJPYXZPG<:FG:;=EA?ADUPBA705=6,036:?UbYVN<;=D^XHDFHQ]WLJMKM@>@;<?>:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~~|{yxvtuvvxwvtnic\XTTX\`aejqx~
yollnpvwzxurqonnnnmmmmmkjjjhjov"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~q\>*+/7?KVdnv}
yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£j6Ua{}dRLfu}V9788I£
}wl`ULFI\c\ZGAFJrzZsyli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0<Rpwrooihncjhbbfeea[SH<1(('*/6?JS[cefgfdbaa`_^YRMD;2,**)+-1578<@IS\dlqvvxzz{{{zyz}{xxvuvvvvuusqmh_ZVRSW[_bgmu{}tniilpuwyvurqonmmmlnnnmmlkkiiox"!!! !!"!"##%%'(())))*++,,-...///.....//......,-..15=HQ[dhmmhd\XQLGE?<96334444444455666654443311/-,++))(''&&&%$###!"%$(-243578:>CHLMMKKLLMOQW[adgjmprrxyz|~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey
yhL0*),09CMZfqy~vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZs,<Xq}hMGLfu|uJ:;<<:gwrtmeYPPTZUQQIFGGf}|]NQTMOUNRnps
pbZMFKXONF;GO@2.37<8/-..3<?C?KVJMI;78;;:<>>A=8:;<CHJMP@ADA>?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578<AJV_flqvxyzz{{{zyz~}{xvutuuuuvvtplg_XSOQU[_eiov|zohdgkpuwwusqqonmmmlnnnnnmmkkms|!! !!"#$&&'(()**+,+,,.../////.....----//.-,+,-,17=GQ\diiifd_ZUQNID@:653333334444554443332210/.-,,+*)('&&&&$$$#""#$$(-0132689>ADFHHGJLNQUZ[_cfknrrsw{|~}|ywqngc]XPMGB=;:::9::99998876666679>K_r}nW;))')-4=IS]hox{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b}+9N`{ycNKDIPRIA=>>=<9nnmjb_WPPSQJMPIJGEJv~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}~|xvvuuuuuvuuoje]VQOQW^bfkqy}~vmeeejnswwtqoqponmmnmnpppnmnlpu~!!!! !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{
vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY 318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu
z{wF:ABCAA@>;6/2Vvb`UQSI=D98849?=2.1/.-)<A98<86347;<AC:43662<BCG<99;=FFB>8<?@A>AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}zxvvvuuuuuvuuoje]VQOQW^bglrzzrha`bgnsvvsqoooonoonmnpoonmnntz!###! "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s
|pW9'%%'((,/7@KVajojdonSj}th_]clv}~}}unghhie_YVSRPNLKLJIGL E.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|mC;<;899:9763/;f~saSNICEKJ=26J;4,34/+*3?=DE953496<BD<7655434334577::>JVI60,38:<E?;=@FZ\QCL[gb]HAHJE=YmmmprtZClvtFW`WGIP[fghgb[RH:1+)*/4;DNW`dfggfdba`_^\YRIB:0*()*-11479>HSZckquxy{|||zyyy|
zxwvuuvvvvuusmgc\SONSY_cgmt|
ymd]\`gotuusqonponnnnnnnmmnnqqv}
!#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy
yeF,%'%')*06;DSbmhk]dxqxyxyrsrw
}~xpg[QOLLGGECCCAzY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZooUB8;:576421.--Dj|eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{
~{ywvtsvvvvuusmhbZQNOSY`dipw{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrz#$&'% !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;LburY=)%$%%(.1:GS[^Yacfoqqkiopu~~||xy}{l[LA>>B><9;Xq4/=X[IKMKKTFDCE@>:_jg`ecZSPLPOMGHJLMRMM[VKQTdl]QE;2247641.,*-He|zeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etulstwtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}}ywwuuuwwvvvvrlg_XNLOTY`elsxypdYRU[cltwtspnmoonnnnlkkkknoot{#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~|jQ;&%)45.0<KNGGGONNRMQTWVUZhruyvvuw{r[H>889759~Q=?Q_PMNLIJFCDB@>7UvgVZ`YPMRNHEEJHKNNNYYSHJSq]IC43789952-***0J`|nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~~{ywuvvwwvvvvrlg_WMKNTYafnu{uk^SMQYdmsvsrpnoqonnnnlkkkkkmns|
!#&'&# "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4<LezueO9)6SUJGT\ZQPQSTY_PJFEFG?FILU^gp{xyw{aH93467H}jKGWcUOSMEFCBBB<7FrymZTXZVPIFEFGIGHHIKOPIFJQeA52>C>;720)()**5Ld}wturcbXLKLB<MNF>7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENA<H:1^kkjhb\TI;1--/5<EMT\dgighffhjlnqrqtsppptyz}~}|{vuy}|zxwwwwxxwwutrle^TMJLS\ciqx}~vgWMIOYbltutrpnnnnnnnmkjiefiknu "$%%" !!"#%%&&'(()****,-..,---....//.,,,*++*))**))))****++***+-257=BGKRY\_bba]TLF;5222123321010000....--,++*(((((++.14:>EINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZpyiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mzYA7<GLh}rnngXLOMDDB?A?<;b~si^XUQMHDFIJJHGFILEFEGMM~ymF<7BGD@80(##%')*8P`xujuiqxdikYJW^QC<97840/025668:89GOG9CC7**+1Nhkmd_HBGCTPOS\mcL]fZMG9CNj[CcieXIIC??ELF77?MYRMGKQ[4Ogkijhb\TH>51139AHPX_cfijlnpqsvz}
{z|
|zxwwwwxxwwutqke^SKINS\dkry|rcQIGPZeotutrpnnnnnnnmkigbdfhnv"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48<BGNU[addd]UME93311222200011//....---*+*)()*,/58<BGMPT[_dhmprtwyxxxwvtrpmkgb^YTOJF>;511000..////////./1111112211132111/...-,++,./2<Pg|xrww|~ufQ??Pcuy\OWX_nwtaICCCDFCACDGK\q~~}
yc`ny
~qXLMIGCA?=ELVxmb]]ZRIHGHHIHFEFHE<=ESYltmiQ@>BFDB=3&! !$'*+>Q`qtniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz}
|{yxxwwwwwwutrkd\RIHMS\elrzym^MEGS]hqtusrpnnnnnnomkheccdhox !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rtq~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_
~|~
XhXJLKGC@A==Co¡§}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jwxw}~zuj^OUH<DHOXT=:@<67B42>?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw|
|{yxxwwwwwwutpjaYPHGLS\elt{xiVIEJT^jrtusrpnnnnnnomkhebbchqy
!!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxz|
}zlX]nbVOew|n`OEAFFEHEFHJLJA?Snq{y{q
]HJKEA?;::J}£§ek~unlkjiix\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;<CLU_gqy}
|{zwwwwwwwwusog`YOGFLT]fmv~whTHELW`kruurrpommnnnomjgdaachq}! !!$#$%''()()*+,,,-../........-,+******))))))((((((''(()))())++*+06>EPY_gkic[SMEA=876521010/.//----+-/137<CIOU\aejmpsssstrpomjeb^ZWSRNKEA;641,,,,,--,-,,,------+-....-.////////010000/.++++**))()*2@Teos
yvv
~~\Rksnf]o|umg[WPEEJRKEDCEIKGGJ^rwzxycGIIEBCOG8Q¦©£^,X{yxvzz¡ydM>6/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl¡{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<LLHP5:<@FWVFEEON\f_M85GYKSL67RY?DJfp_jrOCWcfghhjjlhd_XOF><>BMV_is}~{yy{~
|{zxwwwwwwwusog_UJFEKR[clw
ufSIJR[emrssqppommllonmjgd``cis~!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~qbphioWXeX\ggd__qtlgbZMDFJPJEECDGIKLLOx
u}y{wvqDLKIIC?>7Z¥ª¥+4Mr}y{¡ t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk¢¤¤¤¢~`VhqfUOaVE<HW>2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8<Y_XL[PKSnh`c`jnNV_]`fegefijkkheaXQHBCHNYbkuxrlfb``bdgkorx{|~
|{zxwxxwwwwurmf\PGDDJPZcmwufVOPW_gostrpnponnnnnnmjgea_djt!!""$%%&&')*+(*+,,,-,,........,++++++)))))))))(''(''''''((())(((((*.3:BLT^dhjhc^XVRNKFC?=:8632/.../0369>CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuuimmgVDAIHJGDEEFFHILMuzvvzKGHFFEA>5c¡§§¥W2=?FI>Aj
ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]nxixsh`[WZKGV`=94;AGFH89>H<E>9<HQYN<;_C=FZ^E1.66<HE@=EHHT^enkgeieimlfPUi`fl`ekiiecfgggkkkjfaZSLKNRZdoy{rj`XRJFEEGKLR[_glnsvz}}~~
|{zxwxxwwwwurne[OEAAGQ[eoywh\RTZclsttrqpponnnnnnmjgebadlv !!"$%&&'&'()**++-.----,-.......-,,+++(**)(((((((''((''&&&&((((&&')**/4;DMW\dhhgdd`[YWRQMIECA:6522237:>DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt|
yudUKLLGDEDFFJMMJi[r
{yut\DIHHCA<:r£¨©¦t0<;974/P~yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~
~uiUPUTUddLB7522;><?@4779<;:=A:60-:;>7gZ3-0>NA6<JPRLcxwrpnkjikii`Vhs`Zqqmihfeffghjmmljida\YY\cju~~ti`UKA>;<;;:<>BJOWagosvxzz{}
~}{ywwxxwwwvvqkdZQFABHR\hqyvj^YZ_gnuvtrpppnnnnnnnmjfdbabn{ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3<GNV]bdfedcc`^YWSSPMJEA;988==AFKQW]`bhjkihhea\YTPJF>:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3Gzqzwtwiikgju`MK[\egebd_g{}££xUCHHEGHHIHIIG`/9Ocqywuwh@GFEA@=Ey¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?<IJ29<881/9KD00794<9=Q+,1692HemRPJqzvronkkjjknmqqu]Jmrnigffffghikklmmljijkpv|}vme]VOGA>=;<<<>?BFMV]fmsvwwvwy||~
~}{ywxxxwwvutnjc[PE@CHQ\hq{vj_[]dkquwupppnnnnnnnnmjfdbaep~!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf
ppffibOMLNMZgm_TOL^hpj¥¨¡xU@IFHIGIJHHHT@;;<Ig}{uvwxzBAFJC>>V~£¦¦}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26<?<99EC=9:;B<<<35<17;?`SK?<<F[ebSOY{xurnkjjiijmnqrsmktpkigffgggihkprtvuvxz}
~{xrmhb\YRME?;:<==>?ACIRYbhptwvvvwyzz{|~
~|zxxxxxvvwvsojcXNE?BHR]kt}wh__bjqvwwtrqpnnnonnoomjfeacfr !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly
}|vsyydUP[[K`gdb][V`cd~¯³«¤G@FILMMLIDBGD4>>?E\yztwy{N?EGD@?h£}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<<GSPV]]Z8-4857;7BC;>B01;CSL8@769<OYID:8Xut\QLp{xsomkiiihilnqpnputokigffggijkpty}}zurnlifc`^]WQKC@<;;=>?@@EKV^dlsyxyywxyyyyz{}
~|zxwwxxvvvusniaWMD?AHT_lv}rg``hmuwyxvrqpnnnonnnnlihgdejv!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07<CFJNQRVY]_^_`_a``a^][[\^__acdecb^ZTNID<52-+**))*)++**++*,,,,,,,,,,,,+++++++)************++--,,--,,++++++++*)****)+-/I~x|~xwwvzvpkffoghYFcpWWfcdefk°¬¯²¬®«¡wC<HKJGJEAABmk.:>@@@Sruowx|aCHHEADy q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H<B>;<h}UM_|urpmkjgghiintoovtspmjhhghjkosx|
~vplgfc_`a_]]ZUOHA><<==>?@BEOW^hpv{}|{xxzyyyz|~~{zxwwxxvvvuqmg^ULC?AIUamxyoeacjouxzxsqqponnoomnmkjihefmy "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?yj|{|yur|}ypvy~vj\QXkgSGdwyzy¨ª³¹´·¹±h7IHGFFCCC=W}S4@?=@BNptqxz~xHHLFBGb,699::8521<KQQSQNMLGGGHMJLKMMIGEEKSWWVMJFGILQRPMQ^]PMC9HVRMD=9666:999510:FNQA=ADD8?><HI>9?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|~{yyzzz{{
}{yxwwxxvvusplf]TJA>BJUcp{xmc^bipuxyvsppoonlmnnomkjhgfgp} !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7kqXowz~|nvspwphcVc}oG4Xv|z
£®·º»»»¹BBELJDBCD>F~}
=8?>>ECawsy~
ONIFAJ^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943<A=;9::=<748>@BJOXcnmliggiililtvuttqrrtvvx|~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy
~}{yzz{{}
{ywvwwvwvuuupke\RG@?CKYgs}ui]]ajouxxusqppnmlmmoonlljigjs~ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|}{qulpmXEdsjRG]ft¨µ»»¼»ºµ]7>AEBCC?@Cd~~f1@@>CHqxxz~{z[@EGHJQ07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq|
|{z{||
~zxvvwwvvvuttoid[OF@?EMZft~
|re\Y^gnswvrqpppnmlmmonnmljihmv !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~x||{{qmvoxu\AVp}ucY]b{£¯º¼»»º¸§p06=BADEDDCN~}H1?BKm~|{e?CCEP@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv
}|z{|{
}zwuuvvvvvvtrmgaYOE?=EQ^kvxn_VU[blsvvtqponmkklmoppomkjjoz
!""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnqim~
{{vprmM?UBbxx|nX_j¨²»»»ºº¦t47<??CECCCFcyzD-C\~|}
zIFAFLI5<9::::9542.+4ELONNKIJLLMKGLPRPKDAE]ONTS?EHJMNQ^b`^^MKR]]Y\_\WPE=86-&'*++-----0125@A><<FGHBF?<79779345631/26137:9?<:><?EKJGA7=Ni`H]x|z}
xlcVNLPTXY[[\\[[[[\\[YWUPJC;:989;<?BEMWajs|{z{{|
|yvttvvuuuutrmg`XKA<=EQ_lxwl]SSZcmtwusqpomlkklmopqpolkmt} !!""$$%$%'((*****++,.......//------,+,,**)())''''((((((((''''''''''((('''''''''''))))))))))*,/0357:?AAABA=;62,)'&''(()))))*++,-,+,,-,-.....//////----------,,,,,,,,,,,,,,-----------++,/1-*****+,AX_{x~RJTettwsupbT>LutrX_o¥®´¹»º¹£i17:<>AA@@FCDm}{};2g}
VKDDK}P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEB<E>FORF?961433453538CF=@A9:@GKIG?7QaV^t
wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw}zzy{}|xvttuuuuuutqlf]UI@:=EQ_mz
~wi\QSZenuwvtrqonlkklmnoppnmkmu
!!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQyTb[OJMi|ty|zyoH?^xxunScq¦©¬°·ºµN/9:>>=@A>ABBK{}zx@w}{mNLEI|U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;<?ALTW`lw}zy{{}
~ywvusuuuutttqke\TI@;>FRan{
}sfXPS[gqvwusrponlklmmnpqppnmqy !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP
bMHEJSh®©¡ vvopkoO:;Hb~iV\y¥§§©³©|729<=<<?A@B>?BQ{{vw|w||{ROGIx`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_C<EPTSL=9:==A8<MXj
|wsrrqssqoke\TJFEHLRTXXXXYYZZZZYY[ZVQMGA<989:<=>DIPYboy}zxzz}
}xxvstuuutttspjd[SG?=>HTcp{qcWQW`jrvvurrponmmmmmnqqqmmot~ !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIhKGEXr¡§¬§¢}okmsr]??83Gmw~Y]p¤¡O4;;:::8;:<?>BMOd
{ywy|x~
~SIHOpj:<;=<=<85400..[}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*<veI?JPPQN<7BDB:CWh
|{vqliiiklnnmhc\SJFFIMQUYYXXYYZZZZYYYXVQKE@:98:;<=AFKT\dqz|yzz{
}xwutussttttrojd\RG?>CMYerzobVSX`jsywurqponmmmmmnpqpomm} !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>a¨¦¡££«ªª©wx|zb[KHCDJqy}o^jwlD:=:<=ACC?=<>BHJRmxuv|
zv{y
t}hEIQgu<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--4<?EYvz
oQDLTUR>9=DB8=Wo{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;<AGLS\gs|
zyyw{
}yvtrstttttsomgc[QG?@FP\huxm`UT\dntwttqponmmmmmmnqqpmp !!""""!"%%%&&'(()****+,,,,....//.-----+**)**))))))))))))))(((()))).9+'''((((((''''''''((((((()()))))****))()))))))))()))))**+-,,//./01001111111111222211/////.------..----,-..............//..----./EtzeCRBX¨¥¤£¦¨¥£¯®«¢
wlj`e[eW_w{{efr{tJB:A>==BIJOJDB??HQRq{wuw|sqy{wknwbs{ECM^wz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqt}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy
}{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[sypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;<BGNV_jt}}yxxx|
yvtsrsstssromfaYOD?AGR^jwvj]TT\entwtsppollnnmmmnprmw
!!""####"#$$%'''(())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))))))))))))))))))())*)**++,-.,-..00111122222212111111110/////...-....-----..-....////......----,,./DmLB@EQ¨ª© ¡¡¬¯¯¥ ¢vnaenihfepl`nv}yT.4>D>?@?@@IEDM>:AFOIx}vsyzuttpWa}wm{UBEdr
~A77998432/0.G{yptpom`ps[:F@:;;;:8=]\PSA^jnx~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[xrlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~{vvwy}
zwsrrsrrrqppme^XNC?CJValy~tg\TW^gqvvtromlmmmmmmmomo !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt³²£¦¨¢¤¥£ª§¥¢¡qdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy}uvzxl[V\wcCCO ¡^?78977744566M~q~P;DD<;:<<;S\NWeqs
xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T~g]SNPVNLM>8=>EZ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~}yvvwy}
{utssttssrqpme^VLC?DKXcn{
|rfZSY`kswvtpponlmnnllmmr !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq¦²±®±²¶·¯¬£~vi[ojdehnvz{jfr{{x_1,01:CDDA>=<9CJH::=HMCR|y||kHQTk~mJCDv ¡sv?:;;<>?@>CB2V }opkU27?D?;<=<J_]gpo}
rcbdfceb^UNTTURRPPNKC/%#$$&+442/-5DP^ZGCIW]\\ar~yqhimievhH:-0064./4R
a^r|viXKGCEGBA@Amqjhhfgijlmlolg`YRMLNQSTVVVVVXZZZ[ZZZWSMF@=;::9;>>DLQZeoy~
|wvvx{}
|wstrrrsssppmd]TJCAENZer}|qcXSZbksuuspnnmnlmmkmlv
!"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp°µµª¯³µ¹¸³ {zjbi`acst|~RbpxuX,(+17<@AB@>><;AA<=;AKH9Z}{{EIV_xtqQDEWvJBBCDFDC@==92n©¥¢~
|yg[IA7548?@;<:Cahkqv
e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8orv
xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz|wwwx{}
}xtsssrttsqpmd]SGAAGP^juzn`UV\dmsuuspnmlmllkngw"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/<DUt
¥³´·¡¡«¥©©§¨£|xqggemnqt SRjutR'(+.86:IDA?=<:9=:=;?GMDF{{y
|GOXzyaFCDi_DA?=?@=9:7R`B
®¢qhiZQC864324>C?<AWkntz
igffimpmkf\B00>LRQRSRJ7&""'+,3659:@KROD7<AIPTWW]`cY\\H=72:GWO6//21/..I_gx{y{|~wkobT>CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz~yvxxy|}
}xtqsrsttrqljd]QE?BHT`kxxm^VU]fotusqpnnmjikjh~"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo®¦¥²p~¦¤¬©
zsljckqut}
©nJejhL*+&,.>MKJBAA?<;>;;=>DHFFw~{{}uJRv
yrNFCKsrLE@?=>=<::5GdX©~f[PMMC6643126>IHBE`osxsqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=<Spuyzub]kqq
m>LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{
}yvwwy|
~~yssrstsssrnhaZQE?BKXcny
wl\SW_hptusqpnlkmjil""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce¡©«pr¥®®¬¬§}pifnrs{©®G_^]G..3.-6G:>FCB@<>=<<<=BKLHzrz|{
lMc{yndk}_EDBLcpTFEA>===;:814Zj¥yvmeWLCBA74334:CKSSSEGOUX\s
|zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z£¦¥¥£~k]WF2=@5?Yosyzj\ayJCG<EKEUcXVUSWZ_ekkmmg_XTRQUVUVVUVXYYZ\[[[XUOLFBA@@@BDHLRV\dmu|{wvwv{|
~~~~~yusssstsrqlf_XOE>DMZeq}
~tgZSW_irtuspollkljj#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx£©²¶ªs{xtvsiqw|©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vj|z`Rfmc^^^k~H?CAABGHDB?=>;95225Td¥|vi_UTLD?834;?GRVY[XYYMIGIOU]izxihry~zmF.,0317IOI</.-.12GY[Z]^YTOG<76<AFJMNK=2)7Xy¦¶»»»»º»ºµ®ªh/1540?Xku}{ws|mbAJEHMNnuhbd[VVW]]ddda[UTSUVUVVTUXYYZ[]\\XUQLGCBABBDFKOTX_fov|}yvuwx{|
~~~~~zuqqrssssplf^VMC@EQ]gs~|sfYT[ckrrtropmkjkk$#"#%%$%&&%%%%%&''(((((()+++,,,,.......-,,,,,+++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////112233433444456666665666554433221010000//.-/+.--.-----,,,,,,,,,,,,,,----+0[ §¬¨rwvwz{rx}p®¥XNPJ5(*7C/-0007CE=A><;;9;@EKH|~sj}wzZ_d[\]\ewo?@A@?BFDB>=;98400;Ub||pskXUPE997<CJOTVYX[]\`ZJDDKS\i|waZu
p6-026628DA1,0221<Waahqm`WPF627@IJLH<3,0>Zz¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw||LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}{wuvvx{~}{{}}~{zvssttssqojd^UKCBIT_jw{reWS[bkrvvqolkihk&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g £¦¤¥¡£yy|~ywtv|{a3LPKE/'(,.,,+.16;FA?C=?<<;<AFEysmo|{~rSZRUWYfvU??=@CCCC==864100=R_y{plj]JJA457@JNSTYZ[\^_bbOEFLR[i{{bm\--13775353++1449T_emx|ueYJ>64@MONH6.,=HWv¦¹¾¾¿¾¾¾¾¾¼º·³°®¥8.4348Uhg~z}~{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}}zuuvxz|}|}~~}}{wssttssqojd]TKCCJVbkyzpcYT[dmtuspnmkij(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{ ¢¡¢
§¦¢xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu||
^XWTT[jz}xx{M>@ABCGH=:8431.0<R\xtqmd]KH=875?MSY[\aa`abc]JKNSTZduw £ A//0258:51--.049M^epz
pYB:99DQWQB//5CMc¤¹¿¾¾¾¾¾¾¾½»º¸µ±¬¨ r018G5>XVou{|{cn>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz{vtuvwy}
}}{}~}zwusssssqnicZPIDGO[cq{xmbVV]fnuwsqolml~'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{££
¢§zvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_vnln
~{}pQPU^dq|xsl}kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao a130146674421036I\eow|l:7:;<<>@=70/7?Ef¶¾½¾¾¾¾¾¾¾»»º¶³§U0LW@0CBaYm}xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|~ztsuvx{}
~~~}zxutssssqnicZPFFHP\es}vl_VX_gpuvsrpkn
~t~''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6jyz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?Puonlr|~|UASdpyzreY`xy\CDB?DH>:74310/:L`
maVTSFBB>8<SX[ahnvywrvgbfegixnD6667986234453E`flu|b27765567530047Bu¬º½¾¾¿¾½¿½¾¼º¸µ²«¡~MKUT19TjX^}~~zywMENNeWSTRPUYVXWVQONMOWUTVX[\[TWZ\b^ZZ`ddf]OLKLLMQTX\`fjpv}~|yuttwx|
}~~}|yuvttsqpmgaZPFEIS^hv|tj]WYairvvtqlm
|jn
''''''''''''(((((()))***)*++,,,,++++++++++++*,++******++****))))((((''((((((''&&&&&&&&&&%%%%%&''&&''(((())))))))))**+++,,-,,--../00002223444554555665566665555543311110///.----..----,,,,,,,++*)**********++/6kzztniajcK9COOQOPOH8'(()***)*,+.5;JG9:@@=><9;Bzwwuw~qXahv{wn\G>HZeWECEC@@9731/0-;I`cGSTVHDGCCHRX_hjnwf^elgmrutw~wpV=:9898543258:Yttuxb675443020..266Eq´»»½¾¼¼¸´¶ºº¹³®¤ZRLUR14MO=h||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~~zvtstwx|~~~}yusrrpnomgaZPIHMW`kx{si\W\ckrwwqpl
|ddt((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h
}yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l~YitxzxjWD97:KaTB?CCCA841/..<KZvRGGWXMHCBLLV[\eu
{`SMby
tit|vsmhaS@<;98766656:Uw~{
c:863200/-.049<JUo¤¬³¶±§¨®°¯¨XIJGWI07E4Kty}wep>LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}}yvusvxx|
~}yusrrpomjg_YPIJQ[epyzqf\X^fnuvwrp~
{d^iw((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a
wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\}qrwwytgTC8548LZODAFDD=51//.<Mezs4@LNPLA<?BSX]dp
tum]Rbx|{nrtrlfc[YPDA>=:97779:Mv
g<75631/.-/39;CU_fks{}w~ZDDIKQP<<Mm}wx~~l_CMYccekhYP\flmmnlga`XPJM[Z]ZVOVbeUHA]dWQQOPPPRVXZ^diov{~}yvsuwxz}
~|xusqqqomjg_XOILS]gp{
ypf\\bipwvuqy
zd^`jv((((((((((()))))********++++++++++++++******))(())))))))))))))))((('&&&(''&&&&&&%%%%$$%%%%%%%%%%&&&'''&''('((())))**++,,,-,,-...000110111233445556565565465554442120//.-------,,,,++++++**++****+,(*))****))),Z}}xvtmd]RE>BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J~
duxvpcO>777:EV\O?>B?@:40./:Kgh.6QdTHB>>AE[ad{vx{p~¤¢¢ ~rmhgd_ZZTOIB@>=;98:4[w{
|{jD6643/../15:?Ph}~ywma_becdlx|{ncY?@7<7EZBAbvxu|dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|}yttvxy|}xutttqomje^VMIOV`iu~xpd]]dksvuou~g__enz((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}xwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@
inwup`LB88<DZf_TMDB@?;51//;H[}Y2Srx`LEA?@AOfnws¥«¥¦¦®°ª¥{rie`]ZVRNKEA==>??>;Z
~sw~{xnQ8753300158?BVv}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz
h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}}zvsuxy|}xtssspnlie^VMLQXakv~wod^`gnswtt
iccbfq~))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~
}
qtyslhaSLHGF<@IGE</(()*)((**+,+-,--*,/54=<7;Lruum[K<87D_lrh]WOF@B?810-8D_rPNwoSJHAA>NUe|
µ²²²¬¥ ®³®¤}slec^YWXTMHB@@BCB@Cbxw|}yvo[@:;;<7257<@E[xqjc\VSMGFHHLXgsoLGI>G8>EDG?HWay8EIXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|}zxwvz{}
~zwssssonlgd\TMNS[cmx
~wme`bhovuq
keebbhv***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+<sz~z~nv}qqqqocYQOMHF@AJEC>0'()*)('+**+,*)*)++-.17:@Kkurtm[ICCCJWeok`UPPIAA@:5/2C[qyNczzeG=E>NVP]y¥±³µ³°«¤ª¦¡¢tnjda\bpg\QHEDDDDDP|{z~wui_\Y]\_P48;>ABWo
ywupcca]\[^afmpfBA=@BC;LAIG?RgvpePPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw||yvwx{|}
zwssqqonkfbZSNPU]eq|
~wmdcgirwr|
rhifbbjx
))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2kz}}sgd~q^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uzz|{vqssu}jY[ZUNGFGA72?Njxl\tL@>JULRm¨±³°¬¨££¤¢£smkhmmjsjcZSKHHICBN}}wriiotzzve=68:>BNazzx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rTGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|~zxxy{|~
~yvrqqqonlgc\RPTY_hr|
~wmfegmuv{oiiidbeoz))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N
|winnftaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.AnzuRPRXRMGEJC64Jmw{£
hiZO@Mf«°°²¥ ¡¬¤ £¤¤¢i[dovqnf_SJHKKGB@]xuxxqvsP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~~|yy{{|~
~zvsqqqonlgb[RPTY`is~
}wmfgjqu{tjnliecgq~**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek
qv[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T ~{z
`VXXZ\UJFGB:>a¤¥¤ _@O¦®°¯¢ ¢¤¥¦¥ [<J`z~sleZJFHLKGADouz
}~wa55:<=@HUflkcgif```[Y\][Z[_`_JQ\g`I:C?7ACFRGHex
}RJT=-GA05eussqnjfa^][ZZZ[[Z[\[[\[ZVSRMMNNORUZ]`cejquz~~|zz|{}~
}yussqppnlg`XTPW\clu~|wmggntzvllmliffju********+*****++**))++****)))))))(((((((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%$$%%%%&%%%&&&&&&''((())))+,-,--.////1110122222332233221111110////------,++**))))''&&&&''&&&&&&&&''%%''&&''')18EYru}
~{z|¡pWOQ]cZXUii_LFLKJGC:-$&'))9=(&'(()(*/0-0/-=a~|}rW[Z]_`YMDDA@n¤ª¦s
¥`lª¬¬©Y<AJr}pibTJHFDEHAP}
~{tg>257<<AFR\][^`\VYYTOSTWXZ]WEBLU_la9;631=Y[eWVp
cIJE8=@4=ftttrnifa^][ZZ[[Y[[\][[ZYUSPOOLOQRUZ^adgjptx}|{|||}
|xtssqppnif`XRQW\dnv|wmiimu
|lmonmkhhnw**********++++******))***)**))))))(('''''''(((((((''(('''&&&&&%%$$%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%&&&&$$%%&&&'''''(()))+++,,-....011111113221123212233000000..----,,,+*+**)))(''&%&&&&&&%%&&%%%%&&''(&')*+7F[u{tdmqtxullkls¢¤¡z^WY]aba^fglp_[SMHHB7*'''((&'''''&'((*+-/,5Ldx~|}
|rdac\[_]VJB@?^h[q¡¦¨}¥©¤
WACJa{qjbQGBAFHD@a
xtohH//28;>@CJRTWXWUTSPNQSTWYP:+<HNOSM@G?94@WJQON[p{eKCE@38E^pttsqnie`]\[[\\[ZZ[ZZ[ZZWSPNMNNOPSVZ^`chjorw|~|{|~
|xusrqpomje_XSRV^enx
|vnkko
nmnomonilqy**********++++******))**))))))))))(('''''''((((((('''(''&&&&&&%%%%%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%%%%%$$$%&&&'''''(()))+++,,-....011111113223321110000//////----,,,,+*)**)))((&&%%&&&%%%$$%%$$&&%&&%&(**-5EYnqa`[X^cd\SX[\]s¡`UWeiidfdghrzqmhe]KA0&&''''&'''''&'((*++,/:Vht~yyy
ujgffd`^\ZQF=;[pfa\e£¢{gMNf¦£zoeUHDUthjyyrkifQ/,./58;=BIMPQQPPQONPRSQC00>?8;>:?CC=>=AJGIX[J^kuXFIRD<NYlttsqnid_]\[[[[[ZZ[\\ZXYURONMNNNOSVY\`cfhmrv{}}~~
}yrrqqpomid^XUUX^gpx
zsnml
qnqpopomjlt{++++++****++++******))))))((()((((('&&'''''(((((((''''((&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'''''(((()+++,,-...-/11110002112210/10/0...//.---,,,++++)))))(('&%%%%$$$$$$$$$$$$%%&&%'&&*.4BZrzjb^VWY\YOCKQTZmgUR[ibdjhcdjtpumplwxgD%'&*'('&''('&)()(*+.7EVft|~xxvpb]ZQH??}|m\_`_cx¢¦zs|`Tyª£vk]LDK]x~|xtnfa^V4-.)*.38<BFJMMMNMNOKH?3,/7BF7?FF@KOLI>BMI@LRT_bt
xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~}~
~{vsqqppnlhd]XSV\`hry
yrnkwulnmnpppmot|++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx|
|¡~h]\YPH=unZ`ppknp¦¡~s~~ph¥yocRDCHnyyskf^[ZV@,+('))+/479<????<81,*+.35EU>6;?EIK?BQNJ<LLNUV_hyr<=ERb[`opqssqnje_\[ZZZZZZZZ[\YWURONJIIKLNRUY]`bdgipty}
~zvrqqppnlhd\XUWZajry~wqkq{mqqrrtvvwx}++++++++++++**********))(((((())(())''('''''((((((''''''&&%%$$$$&&%%%%%%$$$$$$$$$$$$$$##$$$$##$$$$$$%%$$$$$%%&&&&&'''())*+++++-...00001101110011110.-----,-.-,++++**)++)((''&&'&%$##$$$$######$$%%%%&&(+1=UrzvsgUQ]qtc]]QIJ[bcspqSPWlumpmpnmmsmojrtmfsr~`7%(+*++*(19-)(().;MZc
}}}lHXbfw¥¦¬ª¦wa^ZSLIj}r[s|yz¡£yop|~vuzqj`PCT}s
unhaZWQNF0)(('('&')*))(('&')++.168=EJND>:=<HXUHI:0NMECRdj}{dNBBCMV`nrrsrqmid^\[ZZZZZZZZYYWUTQOMJJIKLOSVY[`ceeknry~
~yurppppokhd]WUZ]bkt|
}wqo
qrvz||~,,,,,,,,++++**********))(((((((((((('''(''''((((((''''''&&%%$$$$%%%%%%%%$$$$$$$$$$$$$$##$$$$%%$$%%$$$$$$$$$$%%&&&&'''())*+++++-...//./00/011110000/.-----,,,,,++*****)()(('&&&&&$$###"####""""##%$$&''(-6Nlzuqxsnvwwuib\G/@jos}n}XGNYuqsumks
vggmv~vflyv
a7$(+++*0>0')))-;NZi{zvz`5OYfqx¥°°±©¡nc^\PIcv]u zx~}xtqmt¢ª¦¡~|vqldXEg
|
wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdwve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx}
}yurppoomjfb\WUY_fmu|
{vp
{y,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE<JdryuqlMO]ipszxwrmqtqjgnyz}jozuzxZ.+'*(()))+++.@PYp}~zwusy\GJRZk±®¯©td`ZTNat^v
phtxkt¤ª¨ZPYkztqpneXEg
~wh[SPOJB9.'$&$%%$%&%%''(')('')-./792367?FRD564JOJY>3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{
~ysrqponliea\XV\`fmt{
{p~
,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwzWQWqymr{qqquytwuop~yto_urbmrN3((()**+++4GT^x
{ptx|}vpmqzW:O]s°¬®§]IMNQNN_jP\
{jkox§«§£{SJJGY}sonnofPA]
{
tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJ<QG-+4AZ_RTIOtrvnSQ]hprqpmid^[ZXXWWXXYYXXUSPLJHHHIKMPSVZ\^`acfjpuz
|wsrqponliea\XX\`hov}
~z|,,++++++++********))))((((''''''''''''''(('''((())((((''&&&&&&''&&&&&&%%%%%%%%%%#####$$$$$%%###$%%%%%%$$$$$$$$&&&%&&(())(*,,,,------..////..---------,++**++***))))(((&&&&&$$$####!!!!!!!!""""##$%%$(/>Wsxqtz||z{zzrjf[NIWiv}piKNPcnnhikptuqfuxyposvkeX`plWmdg?2')))(*5ALYc}}|vnpstvrv}{urppz
WQ`r®²°©C=DEDKPP[T:=ds{
{xy|h_aix¢~UFHOktrskhkoaHFSlx{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty
|vsrqonmkhe_[XX^cjry~
||
,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[vxu}}yYGGSinokekfjsmhl~rosohigGjnTnqhft?MD)(,*)2<LYf
zyy{uljnnjkknr
zqtrnve`vª¯¨wiQB==@PYZ[
V8ASgw{vrpopspjls~}T=>N{~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+<SXNVT\WH?/2@JORXR\lsyvmYMWnrqqmhc]ZXWWWWWWXWWWTPNIGFEEGILORUY[]]_aeikqw}
{vsrqonmkhc^[XZ`eltz~
}++++++++********)))))(''))((('''(('''''''''((())))((((((''((''&&''&&&&&%&&%%%%%%$$$$$%%%&&&&%%%%%%&&$$%%%%%%%%&&%&&'())**+++----....--//--------.,,,,+*)))(())(((('&&&%%$$%$##""##!!!!!!!!!""#""$%(+5Gasqsv{|yyy{xusj`UJJcu}z{|nMEL]hiijebdgjmluwrcxoiGPw_Ekdf[hhnvZ:+++4?KW`~vywvsljkhcdifinquwx|uqnnrxlz¢wawQ=7@Xnph``9@aormjikpnjkw¤}{wdHA=MryM/3AQa_PJKUozrlb[dppkJ,/0/+*(('&&%$%%%&%('()+02121574.+*)%&')+)+7AFQQUMVgCA@JMRSMXV_]fk{toaKVirrpmgc]YWWWVVWWWWWVSOMHFDBCEHKNRUXZ]^^`dhkpv|
zurqqommjhd_XZ_agou|
++++++++********))))((''))((('''((''''''''(((()*))(((((((((('''''''&&&&&&&%%%%%%%%$$#%&&&&&&%%%%%%&&%%&&%%%%%%&&%&&'())***++----....--..--------,++++*))))(())(((('&&&%%$$##"""!""!!!!!!!!!!"#$$%'*3C[noov{{wruwvwutg]UJIis|z|}bIGP`acfjffhcUNVYXcgQ]YWQVD>co]dWg]hfYVI9/3>JVZ~}zvuvrpoliecba^_agecby}qlnmlu}sRS[Q>:Jk
rc{pLQkvynifbejioy¦zvqqkM=:<Kcu
pD/,/?cg^Y[hsf]fmj_F*-0-+*('&&&%$%%"#%))**/4568>FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{
}ytprqommjhc^WY_diqw}
++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn
xkxxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJpa~tpnaTKHA;>JPVy}ytqqqssohe_[ZWYZaaWHIqupoifkwu|[OVN?<Lvo_sNceezzwmgb]bfej£ xleh^HB=;GZgv
{`<,,3GivnYXf
qlmeU>)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~
}xsqpqpmljga]Z[`djqx}
++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""! !"!!!"#$%,<VqphntytmmprswxvnbVM=:hrrwrwykNCHUgilvdWGDGAJPKKJJDGA>;;@F?O]:k{
~tcTMIEA@FJQrzvsronprnie_[YONT`^RF2Gjmnjhhnv|c@KNNEHo¢j\o
y51Lwwkd^[^c\f|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~
|vsqqqpmljga]Z]afks{
|y{~,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;jtyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:<w}rbUMFDCCBFLgwusqnlnongb[X[SNNCLOI9,FjjfjhjowzG<BIIGm¢££¡sa]C+={wh_\YXZSu¡p[PY\M;:BIRW[adfjkaM7,7I\inv~vjy
z}k\J,%(***('&%$%%$###"""!&/7:<?>==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~
|vrqpppoljga]Z\`hnu{
~|yyxy|
,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@nzkYNKECBBADG_
xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_£¦¤¦u`eyK1@zsbZXVVNTyjONYP;19BKQWY\_cfe\J60=Qcikku|~z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~
{urqpqonkhe`\[^cimv}
|yxxwwx{}**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81Uvnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDWrgUJGCBAB@>@\ysopokhhhea]YTPH91155556<iligcehm}X02As¢¥§¤
m\vq`7>w}shVRUTTMq
yn^OZaT54;GNTX\^abc`YF9?KZdgfglphysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:<JZZJSOC=@IHLPLZmwnidEWmuunic[XSRQPPSTSSRQOJEB>=>@BFIMNRUVWXXWWX]bhnuz|
{urqppoljfc_\]_ciqx}
}yvvuvvwy{{**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-JtrrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA
}n_QICA>;=<9;T
}qopnhfgec_\VUPD533113356Mplhfecltt>,O~ £¢}cZw`v|r:6oskXNORRQWwmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw}
ytrqppoljfc_\]`fkry~
}xwtuvwwwxz{{
****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""! !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>ivmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|nNR^CavsG7rxlaGGGMNNdwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777<IFFKPGCFNMUdbZTVVOEIezqlf^XRPPQOQQQQRQLHC@>=>@DGJMOSUVVVUTUVY]cjpv|
~xrpqqpnljeb^]]ahntz
}zwwvuwwwxyyz{|****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""! !! ""'-<Vpunnswpifb\TPP^krxvog]PD56Mwtmps^LTEDLHEA?DEGFIIIIPVVOEEF?>=;9D??>=A@JtlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{zmRFFCBW~upU:vtk\BEGLKRkvui^TSTVXC<PZY[Z]`bcdfebVP^giglxl]w|nga_]VUR?+(('('&&%%$##$#"!!!)5<>>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw|
}wrpqqpnljda^]^biqu{
|wwvvuvvwwwxxz{}********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiwxlT>FBAKX}xjbBztfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv|
{trppppnljc`]]_eipu|
zyywwwwvwwwwvxyz{))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""! !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__MuseSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.<R`Z[_alfaPIFSJfrne\W_^^YOPPPPNKHDA>=>@CFILNQQQQSTSQQTX\ckqx~
ztqppppnkic`]]`flsv|
}yxyxxwwvwwwxxxzz{(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLixvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy}
~ysppppomjhca]^bgnsy}
{{xxxxyyxvwwwxxwwz|(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡|wx{hWg{T/`ole[K=62231/./12<dc_[YXVSPLKKKKOMJB61-+-31+/Zf^XU]]_fowjaYC=<Facb`]ZPTdd{dUGHMPQRTUUUQOH?411;N^mwtrwxurqqqprstu]GVODQZ^_[XUURLHE?2&$'''''&%%%$##"#(3<=>?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy}
~ztppppomigb`]^bhnty~
ywyzyxxyxwwwwvwwwxz|))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^¥¥qZb}w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH</),/-&'4_b_[X[^deqn^R=;@^_UZZ\WPGPQk`PCJJMNRX\`]ZNE;313EYfpvuqprqmjloonnlpKOUGFQ[]\ZVUTNJFB:-##$%&''%%%%$##$'29;<>??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~
~ysoooonlhda`_adjpv|
|{yzxyxxwwxxxxxxxwx{~(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81<apefnZ@D@96<CDAACCBDEHEKd ¤¬§}vtj
e3pje\RC2.,,***+,/0/;V\WSPPONMLMKMKJIHE@5.1.)$):Z^abca]^hhaH:BUZW[`aYRLIDGNXMFLQOORU[a_VL@9307J_kotvsnmlgeijgg_\ZGZNIHR]^\ZVURJFC@5*$$$$''&'&&&%##&08;;<?@?@AAAA@@?:/##%!!""""##%'(***++**++++++./38@TbhmoideUMWije^\XUONMNNNLJFDA>==>ABGJMNPPRRPPOONSZ`hpu{~
}uqoooonlhda``bflrx~
~y{yyxxxxwwwwwwwwwwy|(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71EulgjqVAF>99?CDCBBCACEDIa£¦¨ª¬ª¡vyz|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx}
ztpoooomkgecbbchnuz~yyxywxxxxwxxwwwwwwxz}(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{lfiqT?E>99BEEDCCCBCEHYp¤¬«ª®£¡riQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=<?DKQ_qmIGXY_de[SVQNNNNNMLHE?==>?BFHKMMPPPPOONOQV\biqz~
|uqnooooljgebcefjqw{
wy|yyzxxwwxxxwwvvwwxz~
''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk§ªª¬¢££¤¢}zRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689<CFEHUTFG\R]ef[W\SMNMNNMKGD?==?@DGHKMMNNNOPOOPQV_ckrw}
~ytpoppnnkigeecghlsx}
zszzxyyyyxxwwwwwxzyww{''(((((())))))++++,,,,,,,,------,,------....////....................----..----..------,,,,,,++,,+***))))**))))**********++++++++,,++****))((''''%%%%%%$$$$$$$$$$%$####"!""!!!!!!"#'-7Povjknprrqg`VL?2,.2<GR[fmooi`XK?21Iq}lgjlL@CC:8@DEDEGGC?C^}£®°®°®©«tv£¡£`QUSRQQQQQRRQQPPQSTSVQMKGECB@A@?CCBB@>>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz}
~
ytpooonnljgedehiov{sx{{yyyxxxxwwwwwwwxxy|''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No
¡¯¹º»¸ª§|¢sMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~~|wronmmnmjhgdefjlpw}zux|}zyxxxxwwwwwvuuxxy~''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw¤±¹»¼º²¯©ª¬® ¥\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2<EOSV^bbegihkid_QQTOOWZ[XWTOJD@?;95-%""$#"##$$####")6>A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~~ytpnnmmnmjhfefgjmty~utz|{{zxxxxwwwwwvvvxxy~
(())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`t£±µ¶º»¶¹»º»º®¦¢jZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}{wronmmmlkjgffgilqw{
ywx{}}zyxxwwwwwwwwwwwxz(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_v ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}{ysommmmmlkifgggimrx|
yy{|||zyxxwwwwwwwwwwwx{))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu¥¦¬¹¾¼³³²³±¬¤¨¦^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~~ytommmlmmkjihghhjnty~yyy|}{{zyxxvvwwwwwwvvvy}))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pachtu@=:5<@ACLf ¤²¶·´±¯¬nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz~~~
{wrnmmmlllkjhhghhjouz}zxz{}{zzxwwvvvvwwwwvvvy}))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}sbchte5968?CB[|¤£©«¯³´©£tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..<KHEFCBBGRA6872111=LTWWSQPOKKKKMQRSNTSE<=?@CHORUTONNLPPPPRRPNKID>6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|~|
ysomkkklkkkigfghimqv{
|yy{|{{yxxwvvuuvvvvwwxz~))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp~H776=AHj£¤¢¦¬®±¦kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<???@???@><:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_py^XNKLIA@BBCFGIKMNNNMNOU^gr{~
{wrnlkkklkkkigfghimqv{|zyz|||{yxwvvvuuvvvvwwxz~))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5<GQ[dieaVJA96?]~zv{{hbbl~t195<@Ov¢¡¤ ¤¥¤¡ {txu{ikklmjkigghffda[V[\ZXZXTQPPOMMMMNQSRGA<;9:89886551/0/2FGEEBDEIRP633216GQTTUURROMKKKKKMMPONOLD62:?AEFGHFBCCCB?CFIJHFA<0+(')'''&&%%$%$%''&&&$$%%%&*07;>?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}~}}~
}wtqlkkkkkkkjhfffgimqv{|yy||}{zxwwuuuuuuvuvuuxz
)))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3<GR\bfd`UKDCL`{zwvz|ia`j|Z+26>Uz¥¦¥§¢ ¢uiba\nv_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|}
|wsnmlkkkkkkjhffffhlqv{{vxyzz{ywvwuuuuuuuuvuuy{****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}xxv|}k`^dzC+2<Tz§©ª©¥¡
~wlghiaq}]bfhfdb_dbb`^][YVVVTQOKIGGEFHHIJJJKMOOHC<97765665333/0CKGFGGCFIM?112:GRTUSSRSQPNMLLLKKJIGFD:,#$$%)-6<=><>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~
{wsnkkkkjjkjhggggghlqv|
~zyxy{{zxvvutusuuvvvuwvx}
****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?qtryufYF1$#"""#"#%'*19DOZ`cdaZ]gq~y~j`\^vz6/5Ow §©©¨§¢|yuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}z
zvpliklljjjigfffgijnrw{
}yxxy{{{yvvutstuuvvvuuwy~**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outyp_Q=+$###"#"#%(,08CNX^ac__ku}
|
|m_[\t
{nv~b21Dp
¥¨¨¨¨£wuugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;<?@A@CEFFECCA<756446555662;KICDCHHHIN=16FPTUTTUSPONNMMLKKIIGE?5%##$$%&''(*-2;BFDA;2--,---,-,*+*((((((&&%'%%%%%%%$####""""""""##""#"#""###""####%*18AGIHD<3*$#%%&&&&'')*++)+,0;ELJOPV]c^UNJEB@?@ACFHHJKKMMKJIJOT]hry~}
}wsnjijjjjjjhgffggilptx|
{xwyz{zyxvvuttstuvvuuvwz~**+++,--./..//0000001111111100110011001111111100111100//////.../..////////....0000//..//.....----..-,,,,,,,,------..........----,,+***)((('''&&&%%%%%%$$%$$$####$$$$$$$$%%',/>juszzeWI3'$#####"#$'*-6ALV]`ba_n|
xx{}}o]Z[gf_i{|H)>d}¤¦¨¦¢u|vprpoleahhdgpeN^Ic
YMQSTUSUUSRRPNHIKJJGEB@=;98999;=<?BCCEEBB@<764456666643>IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;<?HKP]llg_YQNGC??@ACFHHJKKKKJIHILR]gpx~
zvpkjjhjjjjjhgffghjlptx|
}zwwyz{zyxvvuttstuvvtvwx}
**+++,....-.//11000000001111001100111111111000//00000000////////....//////............//..--....,---,,,,,,,,------..--......----,,++**)(((((('&&&&&&%$%%$$%$######$$%%$$%%'-3>eusv|paP=+$#%$######&)-6@KV\adebn
uwx{~o\YZfcgpzx|=-Vs ¥§¥£z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grz
~xrnjjiijjjjihhgfhjjmrvz~
}yvvyz{zxwuutsrrtuvvvvvw~**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz}kpwvxzr`]^dguz{y}x/>hz£¢¡ zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4<HY[Puwsvvutqmf]TLFB?AACEGIIJJJKKIIHHJP[grz
{vpkiiiiiijiihhggjkkosxz
}yvvxzzyxwutssrrtuvvvvwz~,*+--,--..-.11002233222222221111001111112200122211110/00////////////////////.........---..-,--,,....,,,,--------------......------**)))(((''((''&&&&%%%%%%%$$$$$##$$$$$%&'+1:B\|uqqqlV?-&$#%$%$###$&).2;EQZbhmvz_Omjippsda`fs~xvy
a3[rpgaaehgb^fowpovxxovo8DGBAA?AEEDAC>;=<;<=:853211/./11159<=>>>>===;8556776888:<DHFGEFFFHMTNHQUSQPOONMMMMLKKJKFA5&%$%%%&'&'))(&%$(+-//...00//01/'""#%',*(((''''''%%%%$$##"""""""###&-*$"!!!!!""%/=KZiv~zrg\M>1)'),04<JTMDOO_~|vvuutqmf^VMFB?DABDGIIIJKIIHHGGHOYgr|
~xtnjhhiihhjjihggfikmpty|
~{xwvxzyxwvtssssstuuuuvy|
,*+-..--../011112233222222221111001111112211333311110/00////////////////////.........---..-,--,,----,,,,--------------......----,,**)))(((''''''&&&&%%%%%%%$$$$$##$$$$$%%(.4;FWwuqqonS7'%%#%%%$###$&)-39CPZcmt{|ub<Gkppsqwhdcfs|wvPCetp\`aghc`]cly}ukl{||L9BC<;;<@><<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{zqf\MA8436ASalk[]bgy}wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~
|vqljhhiihhjjihgggiknquz}
}yxvwyzyxwvsssssstuuuvwy|---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<IXmxkhkmY2$%%$%%%%%%%&&(+28AM\hu|~|{wp\;(?fyrqtzjdcbmt{
}{z|uIRmym_\`a^adcgt}pktxtwj-9><867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899<DKA>:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it
~xrnjihhhhhhhhhggghjkprv{~
}zwwyzyzvvutsttttttttsvx|
,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagirywwzzdK]pxyvlhddgaY\binv
|rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.<K^n}yupkf`WWX]fg_[SO]dRJkqmrvuuqmd\RJCBCDEGIIIIHHHHGGFGLS]jv
ztqmkihhhhjjjjhgggijmqty{~
|ywvxzywwvutsssssstttvvz
........000111222233332223333322333333333333333322110000/////////////..............-,,,,,,,,,,--,,----------------....--.///..--,,+)*****)(())('((('&%&&%%%%%%%%%$%%&%%%*/16=MZfonhdcfV+!%$$$$%%%&'(().6FXo}xofUE?69Tupl||kcbeiv
usuvyzbL`pprv|{rjgihhmlkmryxz~ymaVKKGMK.03111112310..//-//---++,,'$))'(,,03588::9:;:::;:=??BCCBADFILIFHGDFIJH@>FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mx}ytoljhhhiiiihhhgggiknrtx{~~zxvuwxxwvuutsssssssuuvy{........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl
{tj_VPIDFRabf~zncahk~{ssuvw|yOQgljkoustkgjklot|}yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj|
{un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nx
|wsmkihhhhhiihhhgggiknruy{
}zxvuwxxwvuutsssssssuuvy|--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx
wld`YWRNOXadn{wngfhpyknrprw{WAWbfacegwnalos{~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/1345533333788<BFIJIJJHILLPRQPOQNKHIHCABEFBBCGGHIHF@?9,%%%'(+-..-*(%##)-..1:@EIEBB=1$#%+2:=?=:8642+*)%$%$###%%#"$$%%%%$%&%&''())('),2EVgy
|pdkoji[]h[5Adv|wrbcpjaXNFEEGIIIJJJIIGGDDFGKUamx
|xrljhhhhggiihhhggijlosvz}
~{yxwvxxwvtttsrrrssssuuuy~
--..//0/111111333333333334444433333333343333333333222221100000//////..--........,,++,,,,,,,,,,,,,,++----------------............++++++++++++))))(()()'&&&&&&'''(-FZL00+')-28ANZenrsliccfQ)##%%$#((.9K`tyjcaa`\XVUYhuvsxrhfiounnnopwyY8KW\^]Z[nyjwyr}
oW>9448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.00<CEIIEB:+! "%07;;;<;:741.,+'#%%###$$#"""!!!##$$$%&'())+,.18FYjz
yrh^OG>40031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw
|vpkihhhhggiihhhggijlosvz~{wvuvxxwvtttsrrrrsssuuw{~
--////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|xcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~z}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx
{voljihhhgghhhhhghjknotw{
}|xwtvxwvvtssrrrrrrsstuy}
..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih
|oXJNPX^bb``\Zc}~{ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-# )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWany
zumjhhggggghhhhhghjmorux~
}zxwuvxwvutssrrrrrrsssux}
////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~
w]B5<ELRX\_adbbds{nkjijlrtpkklouu_2,.9HKHNKJLBB?4;dxzpbN;4579;;:968:;;6(%$$%%%$$##""""""""""""""!!!#$%&&(+---,--/.;A=BEILORSTUUUX\^]^^]]\XUSSTRQPIFD@=;>=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/36<Qhnjp{ytlf\RE732249?GEDGIKWZY]aTIHKMIKLIJIIHFEDEFLW`nx
zsligggggggghhhhhijmosxx
}zyvvwxwuttssrqssssrstw|
////002222112233333333333333444455555333444444443344431121000000..........----,,,,,,+++++,,,++,,++,,,,,,,,,,,,------......//..--++,,,,,*+++*))**))(())**+*-.////111/GA1128?HP[cije[QZmgbagqT$#),2HlyiI.)24;DJRXZ]adfhuqigfhlmkiikloqv_,)+.4FHEGF>65IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;<CHNORRRSSTUW\]]^bb`^YVXYXXWWUMIC=978730,+)))(((*++*****(*,/5AFFEC=/# !%-6<?;71.+)&%##"#$&#"$))%%#"###"#%')*,022/1=\u~zy|~|wpic\PFA:768:??ADFINKNHJ^eSUZHFJJJJJGFEDDHOXboz
}vrmigffffggghhhhhjlprtw|~|ywuvwxwuttssttrrrrrstw|
00112233332222223343234444444444555555334444445555443322322200000.......--,...--,,,,+++++,+,+++,*,,,,,------..------..........----..--,+,,,+****))*))++-..00121100119B=57>ENT]fjjbUKHakecdnq>!(*6Qy}~sW9&(+,15<AHPTZ_dlr~z|shdbgmqmkijkoop^+)+**7FIEC>3,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;<BGLORQQQSVVZZ\[`aca^\]^]Y\^WSNID<75640..-,*)())**++++,+,-17@DEDA7+!""#(.4;>:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{y|
|xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy
{vqmhgfeeeggghghhimmpqtv
}{yxyyyutssrrrrrrqqssuv{001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx
yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!! "#$%&)))*,,,/3689<AEJORRPPTVX[\\_^`ab`___\[[[ZWINH<74220/.-,+)))**+++++-,-0;@DED?5& !"$).39;950+'&$#""""##$%(***$%###$&('*++++*+3APWB=9<:Zlr~|us{~xpkg_UNDB@@CFKKMLLQNH?KKLOXglbIIKIHFDCEIP\epy
zuqmgffeffgghfgiiilnqruv
~{yxyywttssrrqqqpqqssux~
001222332222333333433444444455555555555555444444556644444222100000/.....------,,,,,,++++++,-++++,,+,,,--,,,,,,------....--------------,++++***++**,,//1233456656567788DLDHRY`fmmi[P>05VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}}ukhqx~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqy
~xsokggedffggghghikmprtvx
~|xxzxwtsssrrqqrqqqstsy222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|zvh^\[^chggegmprZ+'*-5<Cc[B793,,(%(()*,.5<;557887876577:2++-,++,-./,*)(&&$"! "##$&'('((()-110/05:>AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/19<ED>3+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~|qjeiqx}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx
}xsokffedffggghiijlnpruv}
~|yxzuwtrssrrqqqqqqrst{333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryoszvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$ ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p|pc\`hry}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz}wrnjgfedfffghhiijmorsty
~}|zyzvvsqrrrrrqppqqrsu{333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98<FLRSTPTXUTQOQOQSUVW^ab_bfc]UTRQM:5;9400....--/025684489;60'##%%+6F[s
zm`TB.%$,9AFGFEACB<4442221000/-*'$$$$$&(4H_u{qaWY`jsy|wqjghddda`afkllc[WXW`efaa[LUk\JEFKOXbisz|
}{wrnjfeedffffffiijmorsu|
~|zyxvvsqrrrrrqppqqrsv}2222223344465544334444445555554466666666556666666666766544442111110/..----,,,,----,,++,,+,,,-,+,----,,+,..--------....------....//.--,*+,,--/0125789<>?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^pzkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7Ndw}ncUSYckt{
zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~
}yupnjggfffeggfgijkmprvv
~|zyxvvsqqqqqrrqqppqrx2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<azwpc=%'&'()*+-0A`xvklx~xsmlovd]\[aceffikl]/&*,-3Sg[Y[XN9*'&&')(-00/.++-012311699Ie6&.00/11..-,++('&%$$%%"!" ! !! !#""')-/7?DFINQLNPRPMJLNQRRSW]effhgdZUVKIZN67=:62111111278864230(%%',Cax
wO0:BMB><@LJID?967:;<<9998-$ !$$$*?Si}tdSKPZeov|
{zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz}
~|xtomifeeefeggfgijlnqqtw
~|zzyvtsqqqqqqqooppqry3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxw+13234321/-+((%#"####" !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')AilB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmyeNBHR_ipx
~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz|
}zvsnlieeeefffffgijnosst{
~|yxyusrpqqqpppoopppu{
333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix[-4566321/-+*)(&#"""! !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b{R<FIJJKNSVXX\aa^UUQONMGA4&##%&((.AWmudI9=JW`kv}}~~tjada^`h]aYUWafaWZa
ACW_[`diosvxy|
~|yurmkheeeeffeefgijnosst|
~|yywusrpppqqppooppqv|
33444444554444445544444455444455555566666677666666667664554433331100..----,,---,----,,,,,,,,,,,,,,--..,,,-,,----......------..//..014577667799>@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]qysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z
^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}|pbE/1>JXelw
xjaZY^\WWW\^^_jeXi}LZa^ZYforsvz|
~~~~}zxtqkkgedeeedeeghilnpsss
~|zxvuqqpppoooooonopw}
44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxxa3(*(**+,.<Zxsmls~vjiiiijpma]\^cabfhfY0+.34?fd^]^ZMJPTSC,'&$$&*.*-317AKWhp|x(/244310/,--++)%#! "!#&(*,,-279<@BGMPQIBHKNSY__ejglni\TVMRdXSOPPMHA80/13666666<8=YzlFFIILMOQSVY\^__\\[\[XQB,$"##$$&)7McxynX=,)/@LYet~yvtdefeZZ^jjg_LHNk
tSPLQYgnsuuw|
~||}}{xtqkieedddddfffgilnpqtu
~zywtsqqpoooooooonnrx
44444444554444444434444444554455555566666677777777778AC;6644443231000/....,,---,------------,,----..--------......------....--//13558;=AABB@==@CFHKNPRTUXY[[[\]\\Z[[\\^_bhnsvtqg]TLQUPB5211/>enhfhqy~yi>%'*())*0Bdyolkqukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx
U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345OspsrHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw~uiR3(56>DO^o|
ztneZWVX^djdedRL[{}of[Valonw}
{|{{{yxtokheddccddeeeghloqrty
|yvvusqpqppoonnnnlot{
44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp}
zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy ;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//DiwU=AYx
uPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r}sfR/&7;?AMany
yskcYSQPKOVX^_^_v}WU[TU[`cp~~|{zz{|ywsnjgdddccddeeegimpstu~|yxvtrqqpoooonnnnnpu|
44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~
mK*()=cvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]r{obM-)+/6?Malu|
yskfYUQPR]_]\bgo{ ¢¥¤WLNSQJG@;ALVl~~zyyz{zyvsmhedddddddeeegjmqstu}xwuspoppppnnnnmnopu~
44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<gidfegOLOMNNMNNK8((.6:K\ZLQY]dmw~
h(//./-+)(())('$!!""#%%),/112FWdlqrvxzvrttvtrtsstrmaUXeaTONNNLIFDBCBDFIA0/Aoz{~
}a7%()%2W}
sIDIJKKKMNNMMIFKLNNNOKIB-!!""$%%(3I_t~wn^F(+3/*3M_iqx|}xpieb[U[\^\]agq
iSHVaain\MJHGGDBJf||zyz{zyxurmhddddddddeeegjmqsuw|zxwuspopooonmnnmnopv
44444444444444445555554454554455456666677766666655665563455543333222110./.---.............//..........////..--------..--------00368;?CFKNRVXWSOPQTWZ[^bcfehijkkkkkjkllmnrsssutpkb]TNKDC@><:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|C*,,***''&&&&&%# !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{{||~m8&(%'/NwhCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w
~{tl\G.&1)(:P[elqw{~
{smijkhaZ^cgkpxB.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{~|yvtrqpoooomlllmmosy
4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9<KV^fnr|,!&(''&&&%%$$#"! !#"'?Zfgdcdegrxxutvtsromkkllnpprv{}t\PLNNLHFECBCDHSamrz~{}}}~y],$%&$$/NtZ9DHLMMLMNMLKFHKLMMKJIG>.#!"""%(8Pfz~~{tiX>+,0,.EQZ^gkquz|}~|yutspngefhkqx\2DL\ba]nsm£©¦ogmvwxzywxsojedbbdddddefgimooru~}{yvtrqpoooollllmmosz5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213<Wjcfeorghgfjkjifgiffed`fxsomiifeghh}rhfdaga\N,*)<gkhilmSBKOPQQONLIGCB@B>?9;54BQZbglus $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptzwZLONLKGECCCCJT`kt}y{{xuoJ)&&%&&.Nt
zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,<Tk~}yrgS8"'18=LQZ\`fknqrwy|~~~|wxwuqqqojnszA'7EQ[]QUb}³¼»º¹±¢~pwt|zzzyvsnkgcbbdddccdegilnqss
}zxvtrpooonmllllnlot|5544443333444444444555444444445555665556666666666666665555454444332111000//..///..////0/////////....../////.....--..--..----..00146:>AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~X&&$$$$%%""!! 4Zkje``bcclvyuvuxxxxusqpnnlljjjklnszy\ONLKGECBBEJRant}ywwsp`9,,(&'(/OsvI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@XmzrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~71<ES_VQMn ¶¾º¶·£«´fXp{yvrmgccbccccccdehjmoqsu
}zwutrpooonmllllmmpu}
554444444433334444554444444444446666555677666666666666555344444444321100000////.//////////////////////.////.//.//.....---------/1358;@EJOT[aejiea]_bdfjljmnnpnnnnnqrtwyzxwvtrnid`\WTROLHFB@<97312:^ia`bkX04;:62/7Nhd\ZWS]xrigbdhjhilr}mfcbjo\G'(@hkijpp]?HNSTUSQPNIEA?=<>EB=8@ARdQYafku}=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu~p=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[q
}ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy|
33<DT[LEX´½½«¨¤¬¹²
w_nxwvokgecbbbcccccegjmoqsw
{xwusrpoooomkklllnqv
554444444433334444554444444444446666555677666666666666555344444444322210000///////////////////////////.////./////.....--------/01357:=BFMS[aejmjfcbdehkkjmnnpnnnopqsvx{{xwtsqmgd`\XVSPMJGDB>:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkntiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{- ()'''%%%""!! /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw
~~i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_w
}wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~
y77;AMRJDo¦·¾¶| ´º¸¶~uvutokgecbaabbbbcegjmost|
}zvtrrqpnnkkkklllnpw
445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679<AELRZ`fknnjgfgiiijiklnpooorswx{|yxxwvsnige_\ZXSQOKIFB?:8622/?cc^afjG&(*)+/;Te[WRMNTtwfghiiifhlp~mkjhuzpgihhlpnphCGMQUWURRNIFEA>=;859FI?:9BIPPV^fmu~w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy~~d0#$*;FHJH?53<CIKLKKJHHE8%""""$$&3Md{
}vm]C)"*:LUWZ]\\[ZYTUZdhlqttuwx~~}}~~~rG9@CKICM©¶½¶{Y©²ª¡£uqrokfbbaaabbccdfhkoqqr~~zwtsqoooonmlkklljmpx
555543455544444444333344444455444455555665555566667777664444444444222211111100////////0000//./00////00.///0000/..-....----..//0135789<AEKQX^djppmjgfhhijlknoqpqswy|}~{yzywrmjfc_\[XTRPMIFB?;962304[g`_bhW(%)),0<VfZUQOPUrxheeebekhimzskjio|zxoloppq\AHNPSUTROLGC@><:95328CG>15=CFQ[^enu{k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{
xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}|mVCFEIE@a¨²·¸ª|¦ronhfdbbbbaacddfglnqrs~zwusqooonmlkkklmknry
65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689<AEIQU[biptrnjggfhikloqvyy|}~{zwqmjfb_\[XUROLJGC?<973213Li^]aca0$()+0>XdWSQRTXirfabcaailrpqujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37<?DT\acnu|
X"$!! "#$&# -Yokedhhinrusvwustuvyzyz{yrjiihquqprvwrolhfilpswrSIFFBEIP]krwvonmnpmprqojeiooko|~}wS$!#$#"%#+*(/:?EGIIJEFF?1%#$""$%-?VmylX:&*;JU[ZZ]__]ZWUSV[`eikljkowz|||}~mVOLGIBAl¥««¨¡ ¡skidbbbabbbcceggjnqss
}zwsqonnmllkkjjjkkns{
655555665555554444554433334455545555666666555555666766666555444433333311111100000/////0000//0000////00//////00/..-....----..00013568:=?CHNTZ`gnrusnjighhinqv|
~{zwqnkfb_\[XUROLJGC?<973200@c`X]be@%((*/A[bVRQTVVdsg`ccchmmokgqsh^_cimrx{xttm]GIKMNOOOKKJEA>;9653321/2<=437AGRYYaemuy
M "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq
whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}n[ZZTQHDq¦¥£jedabbabbbcceghkppsv~}zvsqnnmlllkkjjkjlnt~
55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{
}zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}|}}rC#$$"##"%%',.**4=BGHGHE</%$$$$&)2G^u
tgS1&6DKOV[\_^]_\YVTTX\`caceikppmuw|pdgcZVQOu¦¥£¡ |fbca`abaaccegilqquy
~{xuqnmmllkjkkjhkkmow
9999886676555533445544333333444455555555666666666666667666664444333333221122111100///0//////000000/////////////..-....----..//013579:=?CGLPW\ciorutpmifhltx|
{vspnlhdb^[ZWTQNKIHC?;86220/1Cb[XZ`]2%+.0@Z]VSUWXY`smdhpqqnnqmnk_]bidfgklngaUQSSTQQOOMKIHGEB?>;762100//,-7FE?CHNidU_glqx}~8 """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx||rnkge`\\|¤¢z¢}kcbbbabaaccegilqrt{
~{xupnmllljjkkjkjjmqx
9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz}
~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|yn_A)(<EJNRVY[]]\\[[ZZ[]_`a``_beedhlpx{}|wsspkgfe
~sVoyydaccabaabcegjmpru}~
{wtpnmkkkkjkkhijkmsz
;::::::877665554444444333333444444445555556666666666666665554444443321221111110011//////--//0000..//////00/////..-....----.../013579;>@BDHLRV\agmrvvspljnuy{~|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?VoynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}{uttrsqopv{|o`ababaabcegjmqst
}zuspmmkkkkjkkijilns|
<;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\syjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv
¬¬¢z{¡xdb``aabacehknrtt
{wvuqnmlkjjjkkkkjkow
=<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqwwvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~+('&%&&$#%$!(Onxi`c^K<ATntdY[]^bbbfe_`hdO=?ICGHNUVUWZ`hoty|{xuqlt|tlllkjhhhXKR[mwvqpqrqpeW[fosplkkjmt}||wY)'(())*))('''(*-4.(()****))*)))3Kat
xfL-,<GJKLNQRVXYZZ\\\\^^\Z^]Z]__\YZ\^biostwywqnpuuuuvw ³¶®£{ |rabaa```bfhknrtv~
{wtqpmmlkjjjkkkkkmrw
==>=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut|
ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{
&!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=<BHEHFMQOQT_hosstvyywups}xolkkjhif[RTapvtprssrm^VYbnrnjjjimt~}~uT'()(()*)(('()'),21)'(*+*+,,0--/;Si|}scC+/>IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz®³«
¡~f`_``a`adhmprtw
}{wsqnmlkkkkkkkllkmqx
@@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}|v|unf`UK=:9<?AFHMRSTSTSRPMJGEB@>=;:;:7544456;>=<;;3/+*<Yfgjrthcdktz~,"))'''#@rvnjjkijfZA8IMEOUVY\acdeg^C4E?AQKEHHILNS]flrttstuxyxvx}}umjlhffcaWZiuwsprstphVTXalrnjkijnu~|}rK$')))()((***)*')03-''(****-0339G\s
|r`A+3BJKKMPPPPRTVYYYZ\___]\\[Y_^[TUY_`ZbjjmodZ`gnswy|
¨©~o_`__`_cfkmppuv
~yurplllkkkkkkklllory
A@ACB@?><<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|pmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}(!'+('"3suokkmmloo[C8<ODJUXZ[\^^[L94586EMIBIJJLT\cgimprqppuxzzz}}unhjgcaab\`pzxqqqrslbPPW`jpkjljjqy|~qC&''(''()*))**)(*-150'(())*,234>Rfz
}yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{¡ yda`aaadgiloswz
|ywtqpomkjkkjjjjjjkns}
BAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqsmtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||m<%'()))*+*+**++*++/471&'()),18<I\r
wjV4,:DIMOOPRQONOPRUYZ]^_a`]YWUU\_]YSONaggfgfZYagrv|~ ¢tfeddegilmpsw~~|vsqmllmlkijjjjjjlou
FFEDDCA???<:998877777755333344443344555566666655555566555544333322211101111000////....--,,,,--..//------........--...........//112356;;>>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bqxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|}m@2.*++++****+,--,--08</&(),..9ASgy
~udH-+:CGKNOPRSRONNORSX[]___^ZVTQZ`c_ZWTU_giie[_hnuz~¤¤¢~}lhkiikmnrsuw~~{wurolllkjikiiiijjmpw
HHFECCBBA?>><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr
sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~
w $"B{qhjovy|w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}rRIE<0,,+*++*+..,+--.2:92)-8>7=Mbt
ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|¢£ vpnmooqrswxy
~~{wurpmlljiihiiiijjmpx
MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`|
{uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvzj7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv
ylc[SD4-+++++++,++++,.4;=033:>DZn{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{
}wustvwwxwz|~~~|ywtqpnlljjjiiihhkklqy
NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{
ypd[I6,+++*++,++++*,/5>735=BQiz}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}{wyzzz||
~~~{ywrqpnlkiiiiiihhjklq{
ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-148<ACDDEGGC@AEFGGGFEA;40000155766655633.0Ibgs{|xX $+kvlnqoryscW\\[^\UA556530-/4DIFFXdTQVZZY^^bded```dcdc\WTM?:CUbggkg_ZY[]]n~wpprqqsXFEKNQW]ejlmrxsk^L4-,,,-,,-+++++-16:;;BLZr|sh`]bfcbbYGOMG>9=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|}
~~~zxvrppnlkiihfhiiiikms}
PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuyvk[D0-.----,,++++,/4;;=@Nbv{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~
~~~|zwurppnlkiihfffhhijov
TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\l~uqqrsp_DBFHLOV_cgr|
tgV=//......++,+-.07<@ERj|pdcP=^gbeehhhhlolbODKQQYafgknrsx{~~~~|}~ ¡¢£¡£¢
~~~~~~}~{yvtqonllkihgfeeggijpw
UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]aw}vtutsqR?BFGKOV_hou}sbM5---....,,,++-,27K\dtylcbcZ^eccefiiggjooppokdgmrsuuyz~|{zxy{|}
£¦ª««««¬®°ªª©£
~~|~||}}{yvtqomllkihffefggijqz
XWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[fzwvtstvkK=BEJQU\ky~ym]C2.,//00...++-..4Caw
ug`_aabacddggeehnrrvy~}||~~~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥~zzy{{{|}
~~}}~~zxurqnmkjiihffffggijs|YXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655<FOUSR^g^Z\mxvustsqvtSAGLMP_s}~sfW>149;;<862-*+022C_y
|ob_baabbdeeeggjkprtx}
zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡
~|ywvuvvvwy{~
}}~|yvsrnmlkkjihffhhhhimt}
[[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v}yvtprrjjwxXEIMVcw|qcSEGLPPPMHC@81214Ed|xk`_cbcaabcfghjossu{
~yvspohfimpmn²³¸¼¼½½¾¾¾½¼»»»»¶°§{yxvsrorpprrvw{~
~~{xwsplllljiiihfiihhilu[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@<AIRfppk`[^]]T+!!"%*6QdbceZF0,)%$"##$$&(*/9;844447;==?BBE@;:86766750Ku}tkimqvzua[_Z_gK((+.-1038:7<FE>;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i|wususj`bkuxXFKXo}~zzxsid_bfffb]VQJA846Ml|rdb`bcbb`abdglptw{|
~}}}|xsnjff_hmpko¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦zz}~zxvtromkhhjjljoy}v{
~~zwupnkjiijiiihfhhhhjpw[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^s
}qotvy}zxy{}|zwrkf\VKFK[q
|xkaadbaccehiknquy}}
~zvvwvvusmhb\_dacgm¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª
styyyz{zxurpkgffijhdemnw{ww|{wtrpllkkjjiighhhiikq|
¡¡^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;<DXv}sjgbnt]/ !$'1Hhmml]7*-)'%#"!#%#$&*-46666679=@DFHHGC@<985441:suonqstyyb^`]agI%%*/9:78>>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{xkhmtrx
{tlf\\_m{
}tf]^_bdcfgklquw{
wnjjkljd[WTQRUSW`¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuv
}wurqllkkjiiighiiiikq{
£¤]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt
yecholm{
|wolqx{o`Z_`^bgjnqsv|}~wz}|{sfZZXTPMWPGQ^jl£¨µ¹»²¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emz
~xurolkjlhhiiihhhhjou}£¢^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|wdbdljbq}
{m^[^^^einrwz|
{ytquy|vvld[T^aFDl§²·¸¸¹¾¦w»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t
}xtpmlkihhhiiihhhkpw ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgstbccii^j{
whZTX^bhkos{~}zzzug\armnge^_qsIZ¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWo
~zwrnllihhhhhhhhimqz
^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkx~qbacfg]h{~vh^WUajghov}}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp
|yupnlkhhhhhhhhimr{
^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0<MROKGKa]QJKMYnc.%1Rd`V?,&""""!!#%%##""$(),.2449:<>?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<<?CFHJLMMQSVYYYWX\[ZYXXTPNIECDMSW[ennpruvsuvng\X]_hs
oaaabeal{~vlb^_gossy}}{wutskqo_YpsmklsjZX[Wuª¸º¼¾¾¼¸±ªµº¼¾¾½¾¿»´rQKTTQMNNNSV\`dilooppsyvwwssmiprpoaBKp
~{xusoljhhhggghjms~[[[[[ZYWVSQOMJHEDB?=;97764331111//.-------------,,------,,,,,,+++++***))))))))))))'&&&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334445555777787666667777666777765664333333322222211000000//..----,,,,)***0QfUPV\b[-! &+9HQTMIQe\NNPSetK!$/NaYUK;'""""!!#%%$##"$')*,/14668;;;<;7Icrsmfgjopu{wmaZY\^`d@-,/02589736AR\UKHPRUUWZUL@=;::=?DGLLMMORVYYYXYZZXUUUSOLIDABHOVXbjjjlpsqsrke_YZ`o{oaaaekiq
yrnjhny~
}zwuvrrronpqrnoqmkhfYS_c|©·º½½»º¶¯¥°¸»½¾¿»¶©~UMRRQOLMNOOOQV[_cgkpqqrt||wqsxqj`mvxwn\ay}}
}{wtqmlhgggghhimu~ZZZZZYYWTSQOMJHEDB?=;:9764322000//.-----------,,,,,,,,,,,,,,+**(++++))((((('''(((('&&&%%$$$$$$$$$$#$&&&&&&&&''(())))**++,,,,../000112222233323444466666677666667777666667766554333333333221110////////.-----,,++,,+()5_bSPU\fa2 "(*7CRTOJ[i[PKQZpk5 ,Jc]TMG*""""!"$%%&%#$$',+**.//1269841Owpmgdfimppwpmc\YZ]^`Q1//2258<;:8<=KE=;EQWVXZ\ZQF>;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\Zdssb`bhru}
{yxsrx||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬¸»¼¹±eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}py}y||}~
}{wrpnjihfgggkox\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hwub_dkv{~~
~}~xuqmmrsppqonmlklonnncY`ed`ZU`´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx
zy
zx{{{|}}
}{zwsomjhggghkpz}YYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ</%!! !!"%&'&$(&(+/,('&(&$$!*Lrnmhhlmnmpr[Khf`\ZYQYL*.1699788:75786328DKMKKMMHEB>:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp}
wd_fpz
}{voga\_chklpqroonllnmlU@MX_[H@Y°¶¹¶¯µ·«¯±¶¶ªNOQOQQSRRSQPNNLOONNSUX\bfimoqt|
vvyzzzz{~
~zuqolkjihjls|
}|YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:<?DGLOSXYVVXZ[\XVPJKKHGFFFGGGFCIPX]`dgijjcX[cinw
yd_ft}|}uhXLIORT\`bcfmpoonnjeN=R_^V>3Np¥´¶¯°¦²°± oMOT]WSTXWURQQPOOOOOPQUX[`hq}¡¢
wuyzzzz{|~
~~}xvtpnkihilt
w}WWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt}h_jv
|zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^¯²R¤}³·ª
v\MNRXQTYa]TUVVTQPRRPNOPR]n~¢£¢ £~xvxxzz{{z|~
~}{wutrollkny
}rxWWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<?DIMOSSQQSUVWTRNJKKKKIGJMMLIFACIOW\acffcYZepz}h_ly
~|xy~
~wod[mm[RWVY`\XXY^dffc\UF51GQORZ]z¢ª_s²µ¨vVIILOOOSVVROTUTUTRQQRQNMa~¥«°¯ª§¡ywxxzz{{{{|}~~|zywtromnqzvoxVVVVTTTRQONLJHGECAA><:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv
~jbp{|wqosy|
xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤
£¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTf¦²¹¼º¸°©¥ ¡{v{zzz{{{zy|~}}~~}}||ywrqppu}
{rnw
UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC"" !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#<sjjgikpt_<;ZegjfggaMV_F29E?DCEC<;756>FEA955564332599;;<<;:99<AEJPRTRMLMOPQQMLKKKLLNNOOOOMGDA@BGKRYaec[^o|
|jcq~~ypggmsv{~
~vrpppqstqoc]aSEEPWTNHINPMKQQPTSfa`|¥¨©¬ªmLHHMNMMNPPSTTSRSTRQQPUnª·»½¿¼ºµ¨ ¤£¡ |u{{zz{{yyzy}{{|}~~}}}}{zwtrrxxnmx
TTRRSQPOONLIHFDCA?><;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]<HgfaabcccWRVI65=;=C??=97532:BDB<543230312589;=>=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fx|y{
{jes~vl`_dkosw|
}|zwtqqqpqqn\9489<DINMKKKMIJFMRRPllj §¨©¨UFGHJJILLOKKPOMMOPPQOXw¸»½¾¾¾¾¼´¤ ¤ ¦¥vz|{zzzzyxy|~wwy}}|}}}}}||yxtrz}uonz
RRQQPOONMMKHFECBA?=<;976653210/.-,,,++*)****))))((((((((((((((((''''''&&%%%%&&%%$$$$##########$$$$%%$%%&''&&'())))**+++,,-.../0101223345555555557766666656665566666655555555554433221111000/.---..-,++*)))))''*)))*+-,)),,-013Egj]TRSY^dbF$!!!"'fxb[]`nJ""! !&(*++***--.120,,/cpjedhkn_M`jhdaacb[_[J?47899;<:97565108ABB=6310//012237:;=>>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|}vqu
{jiv
~}xma]`eimqv}~|{zwvtpqqpqnM)5688<CDJPSOKHKMVZZV^_l¨ª§¤wDHIIKJILLMJJLKJLMLMMTs¯¸¼¼¾¾¾¾¾¼¹³¤ ¥¥¡|z}{z}|zzxx{}vsuwz{|}}||||{ywtzyqon{
QQPPOOMMMKHFECBAA?;;986654320///-,-+,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%$%'''''()))***++,,-,-./01123223455555555445577666666665555555555555544333333111100////...-,,,+,,++****))))())-/0.+*+/3477Bcn^SQSU]cj[1 $$3lrb^[am0 "!""$(*,*,+++./012/,.`oghgijphdjkhe``acb]VH8205768>@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTarznkvyjjw
}zvkd_^_ehmsy
|}~~~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm§©¦ mAFJILKJIJIJJJJJJIKJSl®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡££z~}{{{{yzxxz~sptuww|{|~~~}}{yv{
uoon{
OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN% %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[p
~zqhltymlx
|{|vkb_\[_`fmu~
{vqpqou|~
~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{§§¥cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡¢¢{{|{{{zxxy{}nlnqttxy{|}|||{zy{}uoon}LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:<BEA>Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz
~}|ujcivyomy
~||zufefed^^ait
zvmbaees{|}|}~~yphdehiidW[`a``bcfgjkohbl§©¤SCFKKIJIHFGGGHJGGHI]£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³¥
zyzz{zyxxyy~pjllqrtvy|~|z|||z}|tnon|KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|~}|~|wnbbkw{omy}z||yulhjhe`^^iv
}woeWNQXmvy{}~}wqssqnjdW_a`__`eeghlogcg{¥¥£JEKJIHFHGGGGGGGIHFLn¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡
{yzyyzzyyz{qjggnqpsvxz|z{{}{~
ytnno}IIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?<PbeZRRUX\_inR1)))))SsbZ`j[ "$$&***.024545775=ikhhkklqgdeghhcdeih^G@B332227FD@732121111466:@C<;6..//1234569;<=ACDEFFGEGHILNNMMKIHHGGILNPROLJIGEDC@@@BDDDDEIS`ktxz{{|vqh^`jw}on{|{{{|{skjjhebcn~
ztf^MCDVmswz|~}~tppmmcX\___`abdfgeckgdiu ¤ G?GIKIFFFFFFFHEGFFZ¨·¼¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§¤
zyyxxxxzzz{qgehhmoou{|}}{{{z~}yrnmnGGGGEEEDCCA@@?=<:987755443000//----,,+*)))**))(((((((())))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,+,,-....//011122334444566666666666666644445554334544443334222222100//..---,++*))))((''&&&&''&&%%&'(*/7<=;8<BHJLLJIED?=GXje[XXXY[bhfR*"()(Ztd]blL&$&**+-0378:;987fjgikjophcfggghfgilWA?B7353337CKE731///0124699799951.././025789;<>ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky
{oo{|{{{{}}ypoljiffq
}ztgZTKHPctvvy{}~wponj[_`a``cacefgc^efhnqF@FGHGHEEEEEFEEDFOt²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£
{yyyyyyyywy}rgeggjnnsvyz|{{{z
|xpmlsEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{|qp{~|{yxyy}}tonnkilv
~~ysha[WQQZhsuwxz}wpljb]`abbbacdfgd^Ydfemrzx@BFEDHGGECDFDDCEJd¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢
}{{zzyxxwvw}sddgefkloswx{{{{z{yutw~BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en|
|pqz}~}yyzzz|wtqonlny~~~{xriea[WW]isuwwz}
xpgc``abccccdfffa]]hhhkruu<BEFIGFGDCDECCBEW¥´»½½¾¾¾¾¾¾¾¾¾¾¾¾¾º¶²®©¢~}{~
~{{zz{zxwvw}wgefgejlopsuy{{}|
@@AA@@??=>><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<?=;;:;=><;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ympw{}{zyyyz|~wqqoor}
z{z~
~|}}}|uqmida`cbisvww{|ypebbcdccccccfdcc]^ijjmrstr>AEDECCEDDDEDBDKm®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®«
~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}|
@@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShu
tmqxxzzzyyyz{|xurqqw
{zy{~~ywxzyzwxpnlmmlhmtwwx{}
unjfbfeddcccccdb`_floljpxyoCBGDEDACDDDEDBCW
¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²«
~vuyyyzzyxwwuvyzgeedefiklnrvy}}|????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7<ET]djosjjlmmppllmmgP<BMQOHILIA:5432.-055540/,+253389989@DCB<51---,,.//134468898777;::98989::9;=<=@ABDABBB@?@@>>????@@AAAAABBBCCCBBBETiv{oiovvwxxxyzuuvyvtrx{xxx|
xpptwuwvusrsutqlktwxx{
}uspkfcefffdccbc`[]cgkqnoru{pFCCEEEBABCDEFCJj¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢ ¡vuwxyzzywwwvxz}heecfdghknrvy}{}
<<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrglzxvsy|yyy|
xoimmopsvvuuwvutpmrwz{
ytpnijgffecccca]Z]cgmuuuomppLDDDEDACCCDDEFV£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£zuwxyzzywwwwwz}hedcdeghimqtz|{}
<<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867<BFCA<71/-,,-/1324435555666668777799887788;=>?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn}yx{}zz{|
|sb_dghlnpsuxxvtstttuz~
{wxusoigfedcbb_^Z^fmtwvuplivpQGFFFDCBCDEEAJdª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦zuwyzyyywwwvwz}lccdddfhjmrs{z|~
::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+<Ye_UZUVWZaefjjjgefb`jspihkntwo\8'-6/Ifg`]WO76JC851///..2:9421.0111567;857=BEEC=82.+,,-/024543444445554566779988777788;<==??>===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m}{~}zz|xiYY]`dfglrvwwvvuutwz|
~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~
{99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<?@@@AA@@><==AES_isvrrqldUB>FPX\bgmstthYVLf~~}q_QSUZ]`fjrttuuwvvyyz}~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥scdeinsxyzzyxwvwwy~ncbbdfegjlou{|}
yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<=<?@?@??C@>==?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr
zmVMOPTW]dkpqsttvwxyz|}}~}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU
£°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©hY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~
~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]t
ylVJHJLR[gnnoprtwyxx{}~~~~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»·
\PW]`dghijrwxxxvvwx|yqprssssttw}~wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi
~o[HDEHQcikkmprxywwy{{{|}}~~}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDX¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±\EPY\^bfgihltxyyzz|
~~~~ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA><?B@@=;4/----/1122222222222244321122332121156775677777888899::<=?@=@HWanqqrog\P>0.-,--//011248<BHIKPXj~|y|~s\HBCJ[cffhmryywwyzzzzz{|~~~{zwusole_ahd_dmoonlhfbaa_[NNHFCBBBB>DY¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹FEOV\^`ddgiiow{{|
}xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz}
vcG?HWcfdekpzyvwyz{yxxxz{|}~}||zvsrooplknkkoooljhfbab`\KLOGCBBBBABV£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{
uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !! "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz
yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«´¸·®o_cdddgjjkmnnry}
vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!! !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±¦¦«¯®¤xzwwvvwwwvyy}}
}xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!! !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026<CIOU\bjnrvx{}~qeabfnvwxvuuutrmhggjmswyz|~~}~~|ywuvstrppomifdda___a[KIJLIECD@AGm©²¹ºº¼¼ºº¹¸µ³±°®«¨¦£¡ ¤£
{vqiaZMC=<===<:989<???CDDDDDD77777766666554333222310/000///..--..-------/00//0000000000110000000000//00////..------,,,,,,,,--....../.......////////////0000//0000112221021122111110000000////....----+++**))))))(''''&&$$##""!!""!! !&)/6>DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><JNIW_c__`d`TTZ]aOGB;48879114<CCBB?:99778;FLME=74435567:<><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx
sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£°±±±±¬ª©§¤¤¡
}ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!! #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£ ~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!! !#%)/4<BGHIIIPUVWZYZWF-''''((((((((((++,,,)&&=md[[afhS>@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{||||}}{yyvvttqmjhecaa```TLNQX_ks|
}wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:<?BB?:62011001234452200//..../......--..////////......../3>IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}||}||}|{yxwwsqomjgebcacb\_kq{
wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!! "#&),04<ADFGJLNPRRSOG7-'''('))(((())))++*,+(,ekhhfcgW7AHG8-/7:=>D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1<FSVRNI@6-,,,-..--,,,,,-.../10////0011100/.-..--.//.0///000////./0../10468=>?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|~
zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!! !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,<FRQJ=337;55:Rfqnme^abc_]af[ROKKJHJIHB80/125@GD:1-----16<;;755544568==>?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~}|}~}|{{{zvvtsqonotz
}~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!! !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<?ENYcnw}~~}|zy{{zywuvwrrz~
|xsoh`QB=<><;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! "" #%(),15<CIJIHIJKNMLLE?.'&%&'((('')()(((()*(Qmcedac]./7=FPWXYM729:?<\trrpcYa^[`fhe]WSQQRQPNLJC933/18=CA81-**+*,.135445678879<>@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx}~zyxz{{{{||uolhffkmpqs{thnz
|z{{}
}wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !! !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1<ENMHE@94*())))**))+++,++--..////00001111110/////////00000000000/....--..///000//00011223443333:GVblv}}~~{vsvz~
wpic[UPMHHMQ`]_dnsqlgs
£¢¡¡¡¡}ytrssx{~
~~~~yqj]L@===<;;:<<<;<>>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!! %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==><KlpstUV`cabeeegc]YXWSPPQQMLF>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksy}|wqotx}
~woh`XSNLFCDDDEM]XROTZ_gn |toiddeimsw|~}~~~}~~~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!! "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;<ACTmqrne[Ycaekmkhe^][YVRPSROMJE=30/24993,+*))))+.01489:;<97779=@BBA=::6520013345655555432110/////--,+++,,,,,,++**)),2:@HHC@<6/('''''''())))()**++,--.../100001111110/////////00//00//..---------.112311111123555522247CQ^ipz}|zvpoquy}}}}~{ri\TLIIKLONLJHHIKQQOKIKT_o{sme`\YVVX[beipsv{~~~|{{|}}||~~|wodWF===@AA?@@ACFGFFGGGGFFFFFFEEEFCC6666665544554432210000////000010//////.---............//////////..--------,,,+++++++**,,++,,,,,,--....////////....-,-...----........---/...///////////../...-,,,,,++)))(''''''&&%%%$$####""!! !! "$%),.27;@CHHGFA>>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<Zqqsomi]eghlppida^^\WSPQPONKGC:3005473.+*))((*,-045789:86777:=ABB><;:752111224567797553442213330/.,,,----++**+)))*-6<DJA?:51*'&'''%%''&%&''(****+++./..001111221110//....////00..00-----------04455322012456766333347?NZeov|~{zxunkloux{||zzyxtsqnmlg^VNJJIIKMLNNJGGHJLKHFGKNZm{}|{}yzvqjb[UMGDBEGOUY^_deilqtyxy}~}|{{{zz{{|}||{|}|tkbUI@AABCCDEEFFFGGGGFGHHGGFFFFFFGG6666665544555422210000////000010//////.---............/////////...------,,,,+++*****++,,++,,,,,,--..//////////...-----------........---....//////////////...-,,,,,++)))(''''''&&%%$$$##"#""!! !#%(+/38<BEGIHFCA>>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!! #$(+-18<DGJLLJGB?ABCEFB=2&('&&&''''(')))Gog`[^abjU!.:<==;EGXOLJHB=EOWWXdjhgiiabiqrojchhd_]ZVTQLIKIFDA:614675/,,++))))*,-015988855569:;<>===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:<BHMSX[^`^]_^behlpux}~|yyyxxzz{y{{|{||~|ytldZMFFDFFEECCCCCEEEEDEEFGFHHGFHF4433332233323311211100///.......//......-.--..--,,,,,---/-----,,,,,,----++,,+*))))***)*+,,,,,,,,+,--..////..........--,-----------------......//....//....------+*++)))(''&&&&&&%%%%$#$$""!! "%*,.29AFKPOLJGDBABEFIF<,('&&&&((')%(')Vlc_\bhglS2JLLNSUJGJNKIA<D@BGQ^aeghji_gjnplbejgbac^WTPLHGFEDB=834675/++++****))++0269995534457:=>>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344<FR[eov{}}ywtrkdgjlquy{{||{yxwtrokgc`\WVTQNJFA<:88887677::::9;;;<=>><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#"" "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?<FT[dWVcinonlopklpeW_jjhica_XRRMIDA>>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779<BGLQW[]__][WQMMTY`gptwzzwwxyyzzy{|||{zy{}}xribYNEDCCCABAACCDEFFJKJJJIGFED1132222211111/.//........-------,.,,++*,+++,+++*****++**++++**,,*)****++****(******+*)****++,,,,,,++------......--..--------------,,------.......-..-,--------,,++++))((''&&&&&&&&%$$$###""" #$'*,-.26;@BFIJIGGDAAA@?8-*,('%''&(''<pkddegjlh=+/3?ZLCFG<PRNKIGFLQ_f`X_gmoonpqopof_ejhgiffa]XRNGB=;:;:74110-,++**)*,+--,*,./01345420037589::<>A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999<??>=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#"" "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<<????>><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#"" "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<<B=53/)((((()+,//./.-----/2466321135689;;=??@@@@??=<;:998888755410....,.-..-,+*)))(''&'(-147:;97630,($####""""###$%'&'(*,,,-....--,,,-----////....--,,,,--,,,,,,,,----..///00000//..00....../128DNX`hpuzywoljgeb`agnqssuqomiedb`[\^`cfhihd_YPG>988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!! $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6<HbcZYhqmrrqmnmiijkgda_b`_^]\USUSQMIC@@C?8:9561(&''((())*./00.,+++,,-03101/025788:;=???@@AAB>@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669<AGMSWZ\_]\YSNHFEHMT]ekpuwxxvspmnopqsx{zyyy{zwtqjc[UMIHIHIJIGIGHGFC@=;9....-----,,--,,,,+**))((('''((((''''''''(('(''''''(((())**))(((()(((&&&&&&&&&&&&''&'(())))**(())))+++++++,+**+,-,,+,**+,,+**,,,,****++++***+++********+**,++++++******))))('''((''%%&&%%$$"" "%),/147;=BHNRQOMLKKMLH7*&&('&'&&($Ntiejjle2#120.306EIOWYVVVWVMD;08F^RU[akjruqmprnjiggdb`]^^YUTNMPRNLMF??A<763551+'''(((()*-.//.-,,++*+,-.00/0035778::;<>@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!! $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1<HZZTW^hjmqqswvqiaafd_ZY^_VPNIHJNMMMIDB@<854774-(%&'()))),/..-...+)(('))*--.014566899;<=>??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!! $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467<DLT_eltvsmhc_YUTX^cgkmmmje]XRNHE?<:<>>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~
~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;<?BGMSUVXYYVRMLJLNRTX\afkqsvwzywtrqqtvxx}
wocVGC@<;:964332/.**))****))(())(('''&&&&&%$##$$$$$$%%%%%%%%%%%%%%&&&&&%&&(&'(('&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((*((((((((())))((((((((''''''(((((((((((((((())(((((())****))))))*)()))((''(('%&&%$##""! "%(,/4;?AEHIGFEFLNONMNG7'')**)((]{mceiph@!.20/.,*)+-7AFJRRTX^c^OEA=M_ffjnwq_YS`edkrxyumjd[VSPOJDEFHFDDDHHGFFFC>:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz|
zqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;<?ADA@AEKLMMLHC4(('))*%UwolhnrpW&+/3/..,+**+8@KORRUVVY^QCCO^gkjgmjVYa^ZWWcortplni_YVRNHC@CEGFEFGEFHIIFD?;94.**-**.11.+(')+,,,,-+,+,))()-164345688;:988764222333332222210//-----....,--****(&%%%$$$$%(*.100210.+(&%&&&&&'(((()*++++,,,,......--//..,---++++****++,,--/...//////00112343233334455666778999888998;AIQ[bnrsole`[[YXY_cgikjjhe^[XSPJGEDEFECCCBBAAAABBBBA@?=<;::9999:>EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~
~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###! $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz}
~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##! "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666<CNXaiqtvtqjgb`][Z]dgkjhhfc^YVTPKHGFEDCBAAABBBBBBA@?=<;:977889<AEJOQUWZ[[[Y[[Z[\_acceffhknqtx{|zwxx{|{{yuqg^M=200.-,,+++++(((((())))(''''''&&%%%$$$$$$##%%%%$%&%%%&&%%$$%%%%&&'''''%((''&&''''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&''%%%%%%&&&&&&&&&&&&%&%%%%$$$$$$$$$$$$$$%%%%&&&%&&&&&&'''''''())))))***)(())('''(('&&&('$%#""" #')./12237:=@BCFHJMLGA4)(*'6uvkkjpqj8%-.----++))*0FCBIQSQPOSRMXY[`k]Knovnigd^YYVRYabe`\][[\SKFA?>?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::<?CHNSWZ\^^____^^_bdffffcbehkorvz||zz{}~{yvtsni^QA2..-+,,-++**(((((())))('''&&''&%%%$$$$$$##%%%%%&&%%%&&&&''%%%%&&''''&'((''&&&&''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&&&%%%%%%%%%%%%%%%%%%&$$$$$##############$$##$$$$%%%%&&&&&'&''()))))))))(((((('))(('&&&'&'%$##"" ""'),.01358<BCDDFJLOLGA2((,,l|kkqrnlA#*------++*))-/9FLQPPPPKLPW^`[b^W_eproiiec][X]`aa]\ZUXXSOLE?<=>?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!! !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((~~~~~~}}||}}~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}
|{zzzzz|}
~~~~~~~~~~~~}{||{}~~~~~~~}}}
~}}}}||{{{{||{}||}~~||{|~~~}{xwyz~~}|}~~~~}|~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}~~~|{zzzzz{}~
~x{
~~~~~}{||{}}}~~~~~}}}~
~}}}}||{{{{||}~~~}~~|yy{|~~~~~~}{zyz{~~}|}~~~~}{y~~~~}}{{}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{}
~~}}|{z{{zz{~
yssu~~~~~}|}}}}|||}~~~}}}}~
~~}}}|{zz{|}}~}|{yz{|~~~}}}}zzyz|}~~|~~~~~~~yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy|
}|{}}{z{{{{{}~yvuvz~~}|}}}}|||}~~~~}|}}
~~}}}|{{{|}|{xwxy{|~~}|}~|{zy{|}}}|~~~~~~{yy~~}}|{}}}}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~}}||{{yyyyyyxxxxyz|
}|{{||{zzzz|}~~}|z{~|{}}}}}}|}~~}}}}|~
}~~}|||}~|yxwwxz|}}}|{|
~|{{{|~}{z}~~~~~{yx}}||{z||}~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~
|{{{|}{zzzzz|~}|}~}}}~}~
~~~|{}}}}}}}~~~~}}||}
}~~~~~~|}}zywvvxz|}}}|{|
~}{||}}{yz|~~~~~~}{y~~{|{{{{|~}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~
}{z{{}~{{{zz{{}~|}~~~~}}}}~|||~}|||}}}}}}~~~|{{}~
~}~~~}zyvuuw{|}}||z|
~}~~~|zwxz}~|z~}}{{{zz{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{}
|{|{{{}|{{zz{{|}~}}~~~}{{
~}|~}|||}}}}}}~~~~}{{|
~}~~~}zxuttw{~}||z|
~|wvw{~}{~~}{{zzz{~~~~~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy}
||||||||~~|{zz{{|}}~~~~~~}||y~{vvz|}|~|{{{|||}|~}}~~}}{|~
~~~~~~}~~~~{xusstw{|}}|{{}zwvy|~~~~~z~}|{{{{{|~~~~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxz{
~||||||||}~~}zzz{{{|}}}~}|}~~}|zxyzx|~xtqqrtw|~}|}}||}}|~}}}}||{|~~}||}}~}~~}}yvsrrsw{}~}|{{}
}yvwy~~~~
{~}||{{|}}}~~~~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{|zzyxxwy|~
~||||||||}}~~|{{||{}~
~}}~}|}~|{{zzywvv|~xtvxz}~~}|||||}}}}}}}}|{{{}}|{y{|~~~}}}zxusqqrvz}~}|z|~
{xvxy~~~~
|}}|{|||}}}~~~~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~}{||||||||}}~|{{||{|}~~~}~~|zz{{zxxvw{~zz{||||}~}||||}}}}}}}}|{{yz}~|yyxy{~~~}}|ywtqppsvz}~|{{}~
{xvx|~~~
|~~}}|}}~~~~}~~~~}|{{{{{{{{{{{{||||}}~~~~}~~~||||||||}}}}}}}}}}{{{{{{zzyyy{~}||||||}}|}|}}~}y{||{||}}~~~|yz~}}~~||}}|{wvyxuy~{z{~~~}}}}}}}}}||zyzyyz}}|{ywvwx|}~}~~}|xuspnpruy}|{|~
}zwvz}~~~~~}}|}}~~~~~~~~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|}||||||}}|||}}~~}}||}~}~~}ztsw}~}|}~|tswwy~{z{~~~}~~}}}}}}{{yyxxwx{}~{zywvvwz}~~|zwurpnnpuy}|{|~
|zwx{~~~}~~~~~~}|{{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}~~}||||||||}}}}}~~||}~}~~||yy|}
~~~wsuwz}}|z|}|}}}}~~}|zyyxwutvy}~}|zxwvuuvy~|zusqpnnpuz}}|{|~}zvvy|~~~}~~~~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~}{z|||||||{|}}}}~~}~~~~}}{}}
}ysprw{|z{|~}|}}}}}}|zyxwwutrtx}}|zywvusuvz~|yutrpnnpuz}}|{|~~zwuuy|~~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy|~~}}~}||{{||||||}}}}}~~|||~}}{xtolu|~{{|}|||||||{yxwwvtsprv{~}||}~~}|yxvvusstw|~|yvtsrompty}}||}~~~~}zxusvz}}~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}~}}~}{||{{||||||}}}}}~~{xy||~}{ywuoks|}{{|}{z||||{{zywvsrponouz}~~|zz{|}|zxxvtsrrtw}~|xttrqonpty}}||}~~}}{xtrsv{~~~}|{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{}}~|{{{{{||||||}}}}~~~|{||~
}{zyzxtmqz|z{}~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}~|wtsrqonpty}}||~}|yxtpqrw}}}~}
~}}}}~~||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~|{{{||||||||}}}}~~~~}}}~
~|zyyxxvpnw{z{}~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpquy~~|vtsrrpnpv{}}||~}|zxtqnorw}}||}~}
~
~}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyy{~}|{{{||||||||}}}}}}}~~~~{{zzyxwwpnuyy|}}{z{{yyzxxvtqomkifiov|~{xvtsstxz{zzyvtrqpqtyzvtssrqqrx||||}~~|zxvrokouy~~z|~~
~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{
|{{{{||||||||}}}}}}}~}|}~}~|{{zzyxwwvsuyy}~}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{{wutusrsux||||}~~|zwtqnkou{{y{}~~~}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxzy{}
}{{{{{||||||||}}}}}}~}|~~~|{zzyxxwwwxtsxz|}{z{{{{zzywurolifeflu|~|yxuvvwyz|{zyxvsrstx}zxvwvsuuvz}{{{|}yvqollpv}}zx{~~}
~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz|
~{z{|||||||||||}}}}}}~~{{~|{zzzyxxwwwwssy{|~}{z{{{{zzywurolifeflu|~~}{zxxz{}~~|{yxxy|~~{yxwvutvy{{{{|}yvqnkkry~}xwz~~}~|}}
~{{~~~~}}}}}}~~~~~~~~~~}}|{{{zzyz{}
~||{{{{zz{{{{{{||}}||}}|}}||}
|{zzzyyxxvxwtrvz~|{yz{||{zyxutoljgfhlu|~~~}|||~~}~~~{yywutvz|||}~~}yupljltz~{wuz~~|}}~}xwxxyy|
~~~~~~~~}}|{{{zzz|}
|zzz{{{{zz{{{{{{||}}|||||}~}{{|
}{zzzyyxwxxxtqry||{yz{||{zyxutqnkhgimu|~~~~}~~{xvuwz|||}~~~}yupllpu|}yvwz~~~~~~~~~}}||}}~zxxyzz{{|~
~~~~~~}}|{{{zzz}
|zxxxzz{{zz{{{{||||}}||{{{||~}||{|
}|{zzyyxxxyvsnpw||{zzz}}}{zywtrokhhinv|~}}~~~~~~zwuwy|||}}}~{wtojkpw}}yux{~~~~~~~~~}}||||{{xxy{{{||}}~
~~~~~~~~}}|{{{zz{~
~~||{zxwyzzzzzzzz{{{{zz{{zzzz{{{|}}|{{z{~
}|{zzyywwx{}ytrw|}}{||}}|~~}ywspoljkov{}~}~~|zyxyyz|~}}|~|wuwy|||}}~~|~~~~~}{wrmkmtz~}yux{~~~~~}}}}}}}|~~}}||{{{{{{}}}}~~
}~~~~}}||{{{{{|}
~}|yyyywwwyzzzzzz||{{{zyzzyzzzz{{z{~~}}{{{{|~
~~{zyyxxwwxx{{utx|}~}|||}~|zvsonnnrz}~~|zxwvtrqruy~~~|||}~xwxz|||~~~~~~~}{urmjnv}~|xux|~~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~
~}}~~~~}}||{{{{{|~~{zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~~}|{{{{{}
|{}}{yyyxxwwvvwvwvwz}~}|||}}xvtrstx{~~zyxwvusqpqty~~{vxz~|xyz|||~~~~~~~~~~}yupnmqy~~|wux|~~~~~}}||||||||||||||||||||||||||}|{{}}
}}~~~}}}||{{zz{}~{xvvwwvvxxwxyyzzzzzzzy{{zyzzzzyyyyyz{{|}~}{||zz||~~~~|v|~|zyyxxxwvvuvywvy}~}||~
|zyy{{{~~yxxwuutrrsuz~}yutw{}{yz{{{|}}}~~~~|zuqoot{|wux|}~~~~}}||||||||||||||||||||||||||}}|{||}||~~~}}}||{{zz{}|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~~|{||{{||||}{xy}{yyxxxwxzxvvtvy}~}|{||}~~}~~~{yxwuutsstw{|vsty~|zz{{{|}}}~}|}~yuqnou||vux|}}}}}||||||||||}}}}||||||||||~~~~~~}}{}~}|{|
}yz}~}}}}{zzz{{}~zxwwwwwwwxxyxyyzzzzzzzz{{yyxxyyxxyyyyzz|}~|{|||}|~}|z|{x{}{yxxxwx{zussv{}}|zzz||~~~~{yxxxvvsuuz~}yutw|~z{{{{z{}}~~}{yz}~|xtpnpw}~~xttz}~|}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~|yz}~}}}}|{zz{~|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~|{|}}~~}|z|{x{}{yxxxwy}zuttv{}}|zz{|||~~|yxxxvwwxw{~}zvtw{~|{{{{z{}}}~~{wvy||wtposx~~~|wrtz}~|~}}||{{||||||||||}}~~~|}}|{||}}}}~~~~~~
~{{}~}}}|||||~
|wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~}|}|~|z{zv~}{yxxxyz}ytrrtz~|zzz{{|}~|zyyyxxwxz~}{xuvz}}|{{{z{}}}}yvx{}}~~~{vsopty~{uruz}~{~~~||||{{{||||||||||~~~~~|||}}}}~~~~~~~~
{{}~}}}||}~
~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~~|~
||yxx~}{yxxxy||wuurtz~}{{{{{|}~}|{{{zyxy{}{xwwx{|{}||{{{|}}}}~zwx{}}~~~{wsppu{~~~~zuruz}{x~~~~~~}}{{{{{{||}}}}}}}}||~}|||~~~~~~~~~~~~~
|z|}}|}}
~|yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~
|{xvv}}{zzyyyz}~yuuqtz~~}~}|z{|~}~~~{z{{{zyyz}~|{yyyyyz{{{{{||||{}~~{wy{}~~~{vsqrx|}~}}~~|yssu{}zu~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~~~}}~
{ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyz{|}|yvv|}{zzyyxxx|~{xsu{~}|{|~}z|~~~}{{{{zyyz}}{{{{{{{yy{{{||||{}~~|zz|~~~{wsqsx|}~~~~~}ytsw|}yw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~
}~~
~zxwwvvvwwxxxxzzz{zz{{{{{{zzzzyyxxyyyyyy{}
~~~vu{}{zzzz{zwx}}xtu{~}||}||{{~}{{{{zyy{}}|{|}~~}{zz{{{zz{{z|~||||}}~~~{wsruy}}~~~}xttw|{xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~
}}}}~
}zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyz{}
~}zvz}{zzzz|~ysu{~
~||~~|z{|~~}{{{{zyy|}}|{|}~~}{xzz{{zz{{z|~||||}~~~{wsrwz~}~~~}xttx||zx~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~}~~~~
~~~~|yxwwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyzz|
~|{y{}|{zzzxxww{zvrv{
}}}{{|~|}}{yyz{~~{{}}~~~}{xyz{{zz{{z|~{{|}}|~~}wstx|}~||||wuty~~~~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~
|zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz{|~
}~}|{zzz{{}}{xttv{}~}}}{{|}{{{{zyz{~~}}~~~~~}{xyz{{zz{{z|~}{{|}~|~~}wttx|}~||||wuuy~~~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~
|yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz||~~~|{{{zy|}|xvux{~~}||{{}~}|{{zzyz}~~~~~}yxxy{{{{zz||~{{|~~}}|wtvz|~}}}}~|wtv{~}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~
~zwwvvvvwwwwwwxxyyzz{{||||||~~~~~~~~}~~~~~|{{{zyzz{usv{~~}{{}}{{{}~}||{zzy{}~~~|{yxxyzz{{||||~}{{|~~}}|vtuz|~}}}}~|xuw{
~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~~|zwuuuuuvvvxxyzyyyy{|{|~~}~~~}{}~~~~~~{z{zzyy{vvz{}~}zzy}~~}{{}~}|{{zz{|~||{{yyyyz{|||{|}|||~~}}{wuuz}}}}~~~yvx|~~~~}~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~yvtttttuvvz~~}{{{|~
~|}~~~~~~~~{{{{zzyz||}~|zz~~|zz}~}|{{zzz|~~{{z{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~|
~~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~
}xtstsrrsw|
~~}~~~~z{{|zz{{}}~
{zz}~}||z{y{}~{z{zyyxzxz||||{}~~||}}~}}zuuy{}}}}}~{y{|}~
|
}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~
~zwtsttx|
~~}~{x{}}~{zz}~}||z{z{}~}{z{zyyxzxz||||z{|~||}}~}}ztvz|}}}}}~{y{|}~|~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~
zvvw{
~~~~~~~~~{|}~{z|~~~}|zz{{}~}zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}}}z|}~~~}}~|~~~
}}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~~}}
~~
~~~~~~~~}|~~}}}}}~|}~{z|~~~}|zyz{}~}zzzyyyyz{{{{zz|}~~}{||{|{~ytvz|}}}}~~{}}~~~}}}~}~~
~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}}
~~
{}~~}~~~~}|{}}||~}z{~~~~~~}}~}|}~~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ztvz|||||~~}}~~}{{|~~~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~}
~||{{|||~}}}}z}~}}}}z{
~|||}~~}~
}|z{~~~~~||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z{|~~~~~~~}}
~{|||||{{{{{zz||||||}}~~~~~}}~~~
~{zyyyyyz|~}{{{{}}~~}~}|~~
~~~~}zz|}~~~{{|~}{{}|~~|{zyzzyyy{{{{{{{yy{{||||{||}zuw{|||{}~~~~~~|zxz{}~~~~~}}}~|z{{||{{{zzz{{{{{{|||}~~~~~~
~|{zyy{{|~~
~}}}|}~
~~
~~~{yz}~}~~~~~~~~~}}{||}|~|{|~}~~~~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}zvx{|||{}~~~~}zxxz|}~~~~~~~~~~~}}}}
zyzz||{{{zzzzz{{{{{|||}}~~~
~}|}~~~~~
~|~~~}}|~~~}~~}}}~}~~~}}~~~}z{|}~}}~}||}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~}}||}}}}
{yzz||{{{zzzzzzzzz{{||}}~~
~{zz~
~~~~~~}~~~~}wz~~}|~||}~||{z{{{}~}}}|{zz{}}}~~~~~~}z{~~~~~~}~~~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zxz{}||{}~~}{xwy|}~~}~~~~~~~~}}||||}}{yz{{{{{{{{{yyzzyyz{|}||~~
}|zwvuz
~~}zwy|}}||~~~|vtw|~|{~}|z{zzz{{{|~~}~{{{zyyxwz|}}}~~~~z{}}}}}||~}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~}zwx{~~~~~~~~~~~~}}||||}}{yz{{{{{zzzzyyzzyyz{z{||~~
zwtsrruz
{trtxxwxyyzuxzy{}||{zzz|{||||zzzyz|||~}{z|{yyy{{zyy|}}~~}~~~~zz}~
~{{|~}||~}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~}}||||}}
|{|{zzzyyyyzzzzzzzyz{{||}~
~wtppqqquz}
|vtsuvustuy{xx{xxwyz|{{zxyzxwyyxxz|||}{{{{}~}}~~~}~~{{~}|||}~|{}~}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{zz{|z{{{}~~}zwx{~~~~~~~~~~}}||||}}}
}||{zzyyyyzzzzzzyxyz{||}~
|vsrqqrrsvx}{}
~ywwtuvvustw|}||{}}}{zwvxz|{zxwz|~}}|{zz|||}||{|}~~~~~}}~~~~~~~~|||{|}~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~
||yyyyyyyyyyyyyyzzz|}}~~
~{wsssssrstux~{z|~
~zzywtuvussuyz|~~{{zyvvz{zxvsu{~~zz|zyzz{{}}~~~~}}|{}~~~~~|xvx{zz{~~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~~}}~~yxyz~~~~~~~~}}}}}}}}}}|||||}~
|{zzyyyyyyyyyyzzz|}}~~
~{xvttttttstuvx|||}}~
~{{ywtturqruvy||yxwvtwywusqpqsux{|}}|{zz|||}}}|{|||~~~}}~{sry{z{}~~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~}}}}}||||||||||}}~
~{zyyyyyyyyyyyz{{|~}~~~
}zxvvwxvvvuuuuvvw|}
|{zzwtuvuttwxz~{ywvtttsrrrssuuuuuy{}}~~}~~||~~~}}}|~||~~~~~~}|st|{z|}~~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~{{}~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}|}
~{yyyyyyyyyyyz{{{|}~
~{zxwuvwyyywwvvvvwwx{~~}{{~
~~}}|{|}zxwwtutv{|xx{}|zxtsuusolmrwwvvwvtvz~~~}||~~}~~~~}~|y{}zvx}||}~~~}|zzyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}~xy|}~~}}}}zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}
}zyxxxxwwyyyyzz{|
~}|{yxxxxxxxxxwwwwwwwxy{~}vuwxxwz|}
~}}xvvvtwxwrpuxyzwustwwphhpvyxyyyxwy{~~}{|~}~}~{zz|~}|{z{}||}~|z|
~}}~
}|zyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|zvy|~~}|wwy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}}zzzzzyyyyyyzz|~
}|{zxyyyyyyyyyyyxxxxxxxyy{}}|xusrsuvvxz|||}}}
}yuvvtrqrnknsw{zwtttuoecjswxz{{zyvx{{z~}{zz}}}}~~}{zz{{|~~{zz{|||||}~
~~~|{z{{yxxyxwwxyzzz|~{yxxyyz{{{{{|~yvy|~~}{wvy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}}
~|{{zzzyyyz|}|{zz{zzzzzyyzzzzzzyyyyyy{{z{{}wolotuqsyy{{z}~~zwwuupklnlmpw}}xuttsoihlswy{||{{xwwwtx|z{~~{||}{xuvxz{}{xxx{|~}~}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}~zvx|~~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~
}}|||}~
~}||||{{||||{{{{{zz{{zzyyyy{{{{{|~yrlinrrnpux|||~~}ywwunmiopnnrvsmmptusomptwy{||{{yxwvstx}}|{{}}|}zyz{{x{~~
}|~~~~|zyyyyxwwxyzyz||xwvwyz{{zz|}|x{|~~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~~~
~|}{{{{||}}|}||||{{{{{{{{{{zzzz{{{{|~|smjlmrvux{}}}}|zxvrmiglqttromlmquvtqoruwxz{}{zyyxvtppty}}
}}~}}~~}yx{{||~~~}|{{}~~|yyxxvvwzyy{|}|xwvvxzzzzz{}{|}~~~|yvw{}}~~~~~~}}||||||{{{{||}}}}~~~~~~
|zz||}}}}}}}|}||||||||||||||{{{{|||||~
~|ztlhikr|~~~~}||
|yvohdelrvvtroooqruutstuwwwz{~|zwvxxwuuvwxww|~}}}{z~~~~~}|zy{{|~~zvwwvvvxxyz|}|xuvvxzzzzz{}|}}~~~|yvx}}}}}~~~~~~}}}}||||{{z|||}}}}~~
~{{zz{|~~~~~}}}}|||||||||||||||||||}}||~
|unlmv~}}~}~}z|
|ytnf`emrtwwtrrssrtuusstustyz}}xtsuwxwstwwwx|~~}||yz}}}~~}||
~~~|{zzyz||ywvvvvxxy{z}}xttwyzzzzz{~}~~~~~~~}{ywy|}~~~~~~}}}}||||{{z|||}}}}~~~~~
}|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}}
~{vtw}~}{{{|~~|z||y{
ztnfcfmsvxxwwvuttuvwvwutqruvyywrqsuxvttvvwwz|}~~}}}zz|zwvtw{~}|~
~{{|}zyyxwy{~|yvvvvxxyz{~}xttwyzzzzz{}{|~~~~~~~}{ywy|~~~~~~}}||||||||||||||~~~~~~~~~~}}~~|{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~|
zy{~~~~zyyzz}~{xzztojegnruvvxxxutttwxyz{yusuwwvrporssrqrtuvxzyz|}}}}~~~|xtns|~|yyyyzxxxvvz}yvttwxxxy{~zustwyzyyxz}~{xz}}|||zwwy}
~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||}~~}{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~~
}z{|zyyyxz}~|wtxztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}~~~}zvtx~zwwvwxwxxuvy{}}xuuwxxxx{~ytqtwyzyyxz}}xwz|~}}|yvz
~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}z|
}|~|||zyvwy~}~|zurw}unkhhnsstuxywsqsuwyy||zxxyxvsommljgkmqtuxz{|{z{|}~}}~zvuy|yuuuvvvvuvwwyz|~|ywwxxxx{}xsrtwxyyyx{~}uu{}~~}|{y|~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||}
}z}xwz|yssx{xttx~}|~~}~~{ywvyz{zwtqw
}umkhjnstuxxwpjfgijmrsvwvvwwtqokgddhgkruxz{|}|{{z{
~}zwyz|}~}}}{wz{wtsstvvvuuvwyy{~|wwwwwx{~|wrqtwyzyyx{ysu|~~}||~~}}}}}|}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}{z}zrttuvtuvspoont||zxw{~~}zwvvxyzwrry}wolihlruxxwrg_]]\`joqsqrrtsrqmhdddijmrx|~~~}{z{}|z|~}ywwwywvsuz}}yw||yvssrsuuvutuwwxz}~zxwwwy|~~zuqqtwz{yxx{}vty}~~}{|~~~~~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruz||zrnoonnt}}xvvz}~}{xwwxyyxvvz
}xqnjimsvxvvpcTOOMWdorsqponllikjiggjmnosvz}~|zyz}~{{|}{ywx}~||~~|wvx||wtrrrsuuuttuwwxx|}|ywvww{~~zuqqtwz{yxx{~{vty}~~}{}~~~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvnqwwz~~tqoopos|~vuv{}~}{zxwxxwwwxz~unjkpsuuuvqiTLNV_irttsqoifeefhigefmqrsuwz{{yxxz|~}|}|{}
|zwz}|urrv||}~|vtqqrstttstvvvwwz||}~ywuvx{~yrpquxyyyyx{{uty}}~}|~}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xpnqplmrssw{ztqqqooq{yvw{|}}~}|{yxxxvvuwy|~z}ztpqtuuvttpka\[bhmqrpnmnnkigeccccgntttuxyzywutwz}}}~
{y{|~|yxtorv{~{urqqrstttstvvvvvyz|}zwwwx{~xrpquxyyyyx{~ysuz}}}|}}}}}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||{{{{|}|wohhklosqory{uprtsqu||wx||||~~||{zwvuttvww{{{}zwvvututrnifbchopoljgfimqpmhe_Z[dpvxxxxxvsnmprw|~~}
~z{|}~~}vqpuz}~~
}yurpqssssstvvvuuuvx{}|wvvx}}vqqrwxxxwxx{zttz~~}~}~~||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xpkkjkmpmmpv|ytsvvtw|~|yz||||{|}|zywvtstuvvx||}~~|yxwvwwwtoljhkopolgb``chnpnjgc_`hrvxxxxurmhhnsvz~~~~~~~}|~~ztqotwz{|~}
|xtqpqssssstvvutttuwz||wvvx}~{uposwxxxwwy{~~xtvz|}~{y{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}zrljklmlghks{zxvuuvy}~}{{|~|z|{{{zzwvvsrruux|}}}|{yxvvwtpnlnoqrpkd]XX]cjomidb^_fptuvxxtnhdfovwxyyz{~~~~}~}{sopqqty}|wtrqrrrrrstvvutsstwy|{wuvy|ztppswyxxxwy{~|urvx{{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy}zywxxz}}|||{||z{zzz{zyxvussstty}}}|{yyyyyvnjmoqqqokcZVW_fhecbaaacgnqrtuurlfadkruwzz{}{xxz}
~}~zwvy|~{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux{vuy{{{{{zz}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||~~}}~~~{rkhkmkedhnr{~{zzzz|}|{{||||{zzyzzzzywtsrrsv~~}|||yxyyxuoklopppmib[XZdc^VXY\^cgjmpqrstqkgcciouwz{|}|zwvy~~}~}~~~zupoopqqssstvvusssrv{~{vsuz}|wrnpsyyyyvvy{~ystw}}vrtz{{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}skijkieflsv|~|}}z|}|{{||||{{zz{{zyywsqqrsv{|vuvwxupmljjlmnkf__iok[KNNOWagjkloqrqoiebcimrvz{|}}~~}}~~~~~}ytpoopqqssstuuussstx|ytsuy||wqpqtwwwwvvy{|wstyyrrw{}{{zzz}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~}ukhhijgipuy|~~}~}||{{{{|}}{{{zz{||zxrpnorsx~}wrstvwtpiecfjkkhdhjlh\RKHHPZefbagopokgc``cgmv{|{{}
~}~~}}}~~~~~~~~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsw~zurrw{{zyz{|}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~|pjjkkoquy}~}||}|{{{}~||{{{yzzwtpnmnqqtz~xrqqtvsohcdghg^_cjlmhaWQNMOYfaWW`imnkfb_]_bmu|}{{}
||}~~~~~~~~~~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wu|~{wurrw{{zyz{~||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~vpnmovxz|~}{|}~~}{{|}}}}|{{zyvtrqmjkoqqu{|zuqnorrqoheehlh]UXckig_WTVY^bi`WPYekjga_^\`dmv||{|
}}}~~~~~~~~~}{wsollmprrrssttssrrvy~|vssuz{xsopruwvuuuvx{yw{zvvrotyzzzzz}||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~}xvvtux{}~}|}~
}}}}}}}|{zyxvsqnmlkmopqtwwxvuqpnnppqogbempm`VWYacd^YZ``_`d^ZPS`hcUSV]]`dnty~~|{}~
}}}~~~}}}}~~}{wsolkloqrrssttssrrvz{vssuyzvqnoruvvutuvxzw{{xxzxx|~||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}||~~zzz|~~}{|~}}|~}||zyxvspnnonklorsrrqpqtrnkmopogcfkigdYQV\bca`bc_\XYVRQU^fcSJNY\`fnsx~~}}|}|~}{}~}||||}~~{yvsojklopqrtttrssqrvzzsrsuyyupnpruuttstvxxz~~||||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~~z}~}|~
~~}||zyxvromkmmkjnprrttqnoqqlklnmiegic]Z[UW[a`^\_fga\XTOTX]baWMOX_ekrvz}}~~}~
~~~~}~}||||}~}zxvsojjknoqrtttrssrtx|~ysrsuwwtonpruuttsuvvz
||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~
{z~~}~
~~|{zxwtrnjklmjjlorrturmonjinoonlhiicYY\Z]aa^TOZiojbZXZ__`de[PT\dkqvz|}}~~~~
~}~~}|||||~|{xurniijnpqrtttrrrruy}~yrqsuwvqnnpsutsstsvy
}}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~
zz~}}~~
}|zxvsqmjhjjkkkoqrtsqnmklprqppljjlf^[]abc`VMUalojb]^enmhig`Z]ckrwyz|}}}}
~~~}|||||~~{zwurniijnpqrtttrrrsvz~}wrqsuvtpmnpsutsrstv|
~}}}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~
}yy||{|yyy|}}
~~|{wqnmighjnonnqrttsomnrrppopljlke]]aefaYNS]gnnjgdbfooiiiaY[dnuxzz|}}}}~
}~}||{{{~~|zywvspljknoqrtttrrrsw||uqqsuvromnrsutsssvzzxz{|y{}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~|{}|zyxwwwwy{||
~}}{xqnmkiikoqsrsssstsrssqrrrppool_Y^ehe]UT^gloligecipkcbfc\]epvy{z|}}}}~~~~~~~}||{{{~~}{yxvvspljknoqrtttrrrty|zuqqsuupmlnrsttsrry}{vqrtwz{y|~~||}}}}||~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~
~|~|xxxxvvuw{||
}|||zrnmlllmprvvvtttttuvtqrttrrqpi`[bkne[V\ejmnkgddgmmidegdbekruz|}}~~~~~}||{|~
~~}{zzz{}~}{zxxwvtqnllmoqsssrrrruz}yspqrttplloqtssrqu|}vsqomorxyz|}{}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~
}{|zxxxxvvuvz|}~
}|}|zrnmopoprtxxxwvvttvtsrsuusrqqj`_gnph__ejmnmjfefikkhiihffjmquz|}}~~~~~||{z|~
~~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~~wrpqrtsommpstssrtz}wsponmlmsyz{~~{z~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~
~|z{yxxxxvvuvxy{|}}z{|yuomprtuvx{{{ywuttvurruuttrqrngekoolhilopnhgfeghffimlihkmprvz||}~}~~}}{{z{}~~~}~}{{{{|~}|{zxxwvupnmmoqqrssrrruy}~wrpqstqnlmpsssrsy~xsrponnmloswy{~{zy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~
|ywxxwwxxvwwwtnlmx
}zyzzxroqsuwz}~~}|yxvvvuvvssqppoqokkoppmmnqqogaacbaaadjmmklnpqux{||}|}}}zzzzyz}~~~}}~}{{{|}|{{zxxwvuqnmnpqqrssrrruz}{tqpqstpllmpssssv|yrppooonkjmqwz{}}zxx~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~
}ywwvvvwyyyxxqmjjr~y{{{ywtrtwz|~yxwvvutsssponloqpprrommprqmd__aba``glonmnpprvy|}}}}}|{{{zz{{}~~~~}~
~}{zz{||{zzzxxwvuromnqqrssrqqruz}~ysqpqqqnkkmqssuv|spoooomlkjnuwy|~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~{ywwvvvwxyxwurnmmp|
~|zxuvwz|~|{zxvvutsqnmnnpstuutpoprsrngcccdcdhloplloqrtwz|~}}}}|{{{zz{{}~~~~}~
}~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkloqssw{wponnnnmlkjnuyz}~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~
}}
{ywwvvuwxxutttrqmn{
~|zxxxy{|~~{yyyxvuutrooqsttvvwvtrqruuqjedeeefhnponoprsuz|}}}}|||{zzzzz{}~~}~~~~~~~{zxx{||{zzyyxwwtqonnpqssrqpqty|}|||vrppppomllortv{
~qnlnommlkkkntxz{yxvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~
}|~}ywwvvwvvurrry{vtu}
~{zzzzz{{|}~}|{zyxwvwvtsstuwwwxzzywstvvsokihfegipqppqrtuw{|}}}}|||{zzzzz{}~~}~~~}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpsux}~vllmmnmmlkkkovz}~zxvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~}}~~~~
}|~~}ywwvxwvuropsy|{}~
~}|{zzzz|||}~~~~|{zzyxxvvvvvwwyy{}}|xvvwwupnkihjlrqrrtwwx{||}}}}}|{z{zzzz{|~~~}}{zzz}{{z{{zxvtttsrpopqrrrrorw|}}|zuroooonmlmnprw|zollmmmlkkjilrx{}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~
{{~
~}|xxxxxvsspmosw}~|{{{|||||}~~~~|{zzyxyyxxwxxxz{|}}|zyvwwwurqomnosttvwzzy|||}}}}}|{z{zz{z{|~~~~~}{xy{}}{|||{{{zxvtsssrpopqrrrrrux|}|zwrpoooonmlmnptz{rmllmmmlkkjjmsy|~{vuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~
~||}yyzzyxurpmmrw|}{{~~~|}{zyzyzyzxxwwxy{}~~~}{xwvvyywutrtvuwxz|||}~~}}}}}|||{zzz{{}~}}||{xuuy~}{{zzzzxvtrrssqpprrrqqrvz|{{ytqoooooomkmoru{yqmkllmlkkjiinuy}|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~
}|}}~|y{{|zxuqonqv{}~{x}~|{zzyyxyyzyxwwxz{}~~~}{|z{zz{zyxxxyz|}}}}z|||}}}||||||{||{|~~~||}|zxz}}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns}|tnkkllmlkkjiintx|{wuuuuu~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~
||}}~~zz}}{zxuqorvz{|{|~|{zzywwyy{{yxwx|}~~~~}}}}}||}|{z{~|z{yyz|}}||}}}}}}}}~~~~~~~}}{{|{|~zyyyxxwusqrtrqqsrrssvz||zxtrpnnnommkikov}yrmklmmmkkkjjkou{}xuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~||}~
~}}ywx|}|zwsqrtwxx{}
~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}||}}{yxyyzz|}}||}}}}}}}}~~~~~~}}{{|}}}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkkmsz}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~
~|}}~|{~{vsty~{yvsrquyz}}}{{{{zzxxxz{{xwwy}~~~~}}}}~{xxz{}~|~}|||}}}}}~}}~~~~~}}{|{{}~}|yzzxxywusqstrqqsrssu{{||zvroooooomlkjnxzsnljkllljjjiimrv|{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~
~||~}~
~~}~|wuvy~}{xurruz{}~}~~}}||{{zzzzz{{{xwvw{~~~~}}}}}||yxy{}~}|||}}}}}}}}~~~~}}|{|~|yzzxxywurpqrrqqsrstx{{{{xuqoooooomlknt{~xpmkjkllljjjjjmry~~yurrrrrr~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~|~{||~~~~~~|~}xvwy}~}{wuvy{y{}{xz~~~~~~~~~~~~~~~~|{{z{zx{|{zyxxwy|}}~~}}~{zz|~}}|~}}}}}}}~~~~~~}~|{zzyxxwuqooprrrrrsw{||zyvspooooonlkknw~|upllkkllmkkkijptz~{xusrqqqs~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~~
||~}~~
~|~}usu{|~|yyz|{xyyywx~~~~~~~~~~~~~~~~~~}}}}|{y|}}|zyxwxz|~~}||}}~|{zz~~}}}}}}}}~~~~~~~~}|~~|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlknr{~yspllllmmmkkkjkpv}zvsrqqqsu~~~~~~~~~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~
|||~}
~z}|vsx{}}~}}}}zxxxxy}~~~~~~~~~~~~~~~~~~~~}{|}}||zzzzyy{}~}}~~|zz
~~}~~~~~~~~~}~~|~|zyzzyxvtonoprrrstw|||zvvsqpooooonmlnv|uqomllmmmmkjjjnqw|ytsrrpqvy~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~
{{~
y|~zvx|~|}zyxyz{|~~~~~~~~~~~~~~~~}}|}~~}~}|{z{{z{{}~}}}}}~~}{{
~~}~~~~~~~~~}}}~
}|zzzyxvtonoprrrsw{}}{yussqpooooomknrzxsqomllmmmmkjjkosz~|wsrrrrtx{~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~}
}|
xx~yy||z|~~zwxyz{}}}}}}~~~~~~~~~~~~~||||~}}~~||||||z{||}|||{}~}}||}
~~~~}}~~~~}||||zyxwtpopqqrrvy|~|{xurrqonnnmmklov~}urpnllmmmmmmiimqx}{vsrrtvx|~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~}
|
||
uv}{{}}zy~~~|zxyz{}}~~||}}}}}}}}~~~~~}}}}~}}~~~~}}||{{{{{{{{z|||}||}}|}~~~}}~~~~}
|yywtpopqrrtx{}~{ywtrqppnnnmmklqz~ztromllmmmmmmiimry~yutssvy}~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
~zz}~~usy}{~|{z{{zyxz{}}~~|z||||||}}~~~~~~}}}~~~~~}}}~}|||||z|{{zz{{{{}~}|}~~}~~~
~{xwspooqqswy}}|ywusrpooooonmlms|~xsqolllllmmmmkjns{}yvtrsw|~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
~
}z{}}}~usy~~{z{~~|{{zz{}}}~~}||||||||~~}}~~~~}}|}~~~~~~}~}}}|}}}}|{zzzz{{}~~|}~}||
|wtqpqrsuz|~}{xvsrpqonnmmmklov~~xqomlllllmmmmjjou|}wttsv{~~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~}}~~~~~~}}}}}}~~~
{{{|~}svy~|z}~~}}~}}{z{{|}|zz||}}~~~~~~~~}~~~~~~~~}}}}}~~}|||~~|{{yzz{{|~~~~}}}
ysrrsqty}~}{zvsrqqppnnmkkjkqz}wqonlmmmmlllkkkou~{trtux|~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~
}z{|tqu|~|z{}~~|{z|}~|{{|}{zz||||}}~~~~~~}~~~~~~~~}~~~~~}||||}~~}|{{{{|~}}}~}}}~yusqqv{~|ywsrpqqppnnmkkjmt|}wponlmmmmlllkkkrxzutvy{}~~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~~~~}}}}}|}}}}~
~z{}
xxy~}{}}}{zxxx|~}}}~{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|~}~~~|||}}|~~}{z~|}~
xtrrz}{yvqppqqpponnllknw~woommmmmmmmljimsz~ytuxz~~{~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~}xy}~~~~}}}{zxvtuy|}
|{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}~||||}~|z|~|{}~
||{z{~zwtpppqqpponnljinw{tonlmmmmmmmljinuz~}xux|{x~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~~}}}}}||}}~~}
~|}
~~}~~}|}|{ywtttvx~{zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~}}~~|{{{|~~}}|~~~{|~~
}
}{|zyvrooqqqooonmkhjpz|unnlmmmnnnmkikow}{vvy}~wv~~~~~~}}}}}}}}||||||||||||||}}|||~~~~}||}}}}}}~~
}}}~~~|{{{yvursrtw|{{{||||}~~~~~~~~~~~~~}|{|}~~~~~~~|{zz|~~~}|||}}}~}~{|~
~zyyyxyxwsqoqqqooonmkklt}{rmmlmmmnnnmjhkrx}{xy}~xtu~~~~~~}}}}}}|}||||||||||||||||||~~}}
}|||}~
~|~~~~}{zzzwvtsqqqtx|
}{|}|}}~~~~~~~~~~~~~}}}~~~~~~~}}{xz{|}~~|{|}{{|}||~
}{vs||tv}{tqpqpomnmmkjmszqmmlmmmnnmliiltz}~zy{~}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}}
||}~~
~|}~}~}|zyxxuutrsrqsv{|{||}}~~~~~~~~~~~~~~~~~}}}}||}~~}}{yy}~}{|}~}~}}~~~}||~
~vqq|}wy|wsqqpomnmmkinwxpmmlmmmnnmliinu{~~||~~xssw~~}}~~}}}{||||||||||||||||}}}}~~~~~
}|~
}}
~|||}}|zwvxvtuusssrsvx||{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz|~{||{|~~~}{}
}uos}~~vv|zsrpppnmkkjpy~wpmmllkmmmkjhinv|~}zqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~
~
~z}
}{}}}}|zzyvtsuvutttuvxz|~}}~}~~~~~~~}}}~}||{|{{{{|{yzz}~}{zz|~{|||~~~~~}~
vqqy}|z~|||~|yspppnmkkkqz{uommllkmmmmlgipx}~~~}vqty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~
~|}~~~}{{zzxuvvwwvxuvyy{{}
~}~~~~~~~~}~|{zz{z{{}~~~}|{|}~{xz}|z{|}~~~~}|~
}tuy|~vpppnmljkr}|rlmmllllmmljhkty~}vrtx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~
~~~|
}|{{|||yxvuuvxz{{{||}}~~~~~}{zyyzz{|~~}~~{xz}|yy{|~
~~~~~}|}
||~~uppnmljkr}|tnmmllllmmlihltz~zutvz{}~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~}
~~~~}|{zz|}}zxuttuyz||~}~
~~~~~~~|{yyxy{}}~zw{}~zwyy{~}}~~||}~
}|}
zroonkkms~}uollllkmmmmjjmw}~}~~wtuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~}
~
}zxxxz||{xusrsvz||~~~~~~}|{zzyz|}}~~
~~|{}~~zwyy{~}}}}~~}||}
}uponkkmv~}tnllllkmmmmjiow}~}~ysuvxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~~~~~
{wvuuvy{{{wusssuy{}~~|
~~}}~}||{{{|}~~~
~~||}}xvwy|~}|||~~}}{{}
zspnkjnx|umllkkmmnljijqy|~}}~}xuxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~
~~~~}~{xrooruy{{{yvsssuy{}}}~|{|
~~~}}|||{{||}}~~|}~yxyy|~~~}}~~}}||}
{wtqpsz|slllkkmmlkjhks{}~}}~zwvyzyy{~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~
~~~~~}|
|}|xqnoty|}}zzvwvwyz||}|{|}
~}}}}||{{{{}
~}}}|}|zwxx{~~~~~~|}~
}{yy~~{rnkkklmmlkjimv|~~~{wuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~~~~~~~|~xrorvz~~~~|||||}~~}|{z|}~
~~}}}||}}}~
~||}}|zwxz}~~~}
~
~{rnkkklmmlkihmw}~|}{wwyyywwz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~~~~~~~~}}}}}~~|}}vpqvy|}~~~}~~}|{{||{|
~}||{{{||}
~||}}|zxy{~~~~|}
~
yrnkkklnnlkgipx~}}}zwyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~~{~}usux{|~~~~~}|z{{||{|}~}|{|{{{{{~}zz|}~||zxyz}}}~~~~
~
yqlkkkllllkikqy}~}}|yxyyxwvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~}z}zsrvyz}~~~}|{{{{{zzz|~}~}|z{{{z{{|
~|zyz|~~|z{yzzz{}~~|}
yux|}~
yqmkkllllkjilt{}}}|yyzzxvvvy}~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~{z}xtuxy{}{|}}~}||{{{zzz|~}~~}|{zzz{|}~}zyz|~}}{|{zyy}~
}tnpx}~~}~~
}tnllllllkjimu|}|yy|xtty}~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~~xy~~ww{yy}zy||}~~}}}||{{zyz||~}|{zz{z~|zy}~~~~~}|zy{~~zrnntz~~}}~~
ysonmmmmkihnw~~}{yy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~|xz~~~{z|~|z|}~~~}}}||{{zyz|}~}|||{~
}zy|}||~~~~~
~usu{{}}y{~~~zsponmmkiipx~~}~zzz~ytty~~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~{xz~
~{}}}~~}}|||||{zy{~~~~}}}|~
}{||}}~~~~~
}sw}~~||~zzz}~~}~|vronmjhhqz~}zwxz{wttx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}}}~~~~}}||}}~~}}}}}}}}}}|xx}|~}~~}||||{{{|~~}~~}}}|~~
|||}}~~}}~
uw|~~yy~zyyyyz{~}}~xtqnkglv{}{x{}wustx|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}|xz}~
~~~~}}{zyz~~~~~}|}}}}}
}|}}~~}~||yw{{{xz
zwvwyyyyy|~}{}~
}|~ztolkmu}}zx|xrqssw|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}zy{}|{}~~~~}}{z|}}|{|||{}}}||
~~}}~~|}
}wzzwu{|vuvwvvvvuqqsz~{~{umjpx~|y{}vttspty}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}~zxy}}{|}~~~~~~}|{{~~~~{zy{||~~~~~~
~~~|}~~~ywtrrz}vtttutttuokrz
~~~zqnty}||y}ysrrqoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||{vx|~}|}~~~~}}~~~||}}~~~~}{||{|{}~~~~~~
~~
~y{|~~{uqqryzutttuuvtnlt|~~
~|vsuz}~||{vsssnnuz~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||}yxz|}||~}}~~~|}~}}~~}}|z{zz{||~~~~~}~~
{wy|}~zposz{xuttuuttojq{~~~~
|y~zuw{~~}~{vssurlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~~{zz{~}{{|~~~~~}~~}}~}}|{{{zz|~~~}~~
~
~sprz{wtttuuuqkr{}~~~~}
yty}|xy|~~|vssssqkmv{~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~}zzzxyz|~~zvy}{z{|}~}~~|}}~{z{~~|||}~|}~~~~~~
xssz{wuutturlly~~~~~~~
zpsx}}zz|~~|xusqrrmjmw}~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~}zz|{z|}}}||~~~~{{{zz{|||||~~}}|}~~~~~~
~
}wuyyvtttttojr}~~~~~~~
{qpt{|{{}~|xtqqqqrqsx}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy|zx||y{{zzy}~}}zzxyzz{{||}}}|}~}|||}~~~~~~
~
zxyyustutrjlx}~~~~~~~|sqrx}}|}~|xxxwustyxz~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxusstvx{|~}||}}zxxz|~~~|||{zyxxz{||}~~}}}~~}||||}~~~~~}~~~~
~
ywyxtsssrnirz}~~~~~~~{qpquz~~}|}~|xwy}~~
~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzvtqrrvwy}~~||{zz}~~~||{xxxy{{{{|}}~}}~~~~}|||}|||}~~||}}}}}}~~~~~~~~}~~}~xvxwusssqkku}~~~~~~~~}~~uoquw|}}}{xuw{~
~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||~|{z|~}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~
~~~}~~|}ytrxwvtssqjnz~~~~~~~~~~
~vrswy|}}}~zxyz
~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvusrqqsv{}~~|~~{{|~}zyvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~
~}}{xuuuwwuusojoz~~~~~~~~~
~yy{|~{y|~~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}}
~{vvuutvvvutpkqz}~~~~~~~~
~{{~
~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~}}~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwtsstz|||z{{||}|{}~zxvwxyzyzzyz{}}||}}~~}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~
~~~~
}{|}~~}{xuutuvwwvsqnqz}~}}
}|}
~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~}{yvwxyzzyxyz{{|}}}}~}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~
~~~~~~~}~}}}}{yutttuvvsolnx
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyyxvwy|||||||||{|~}||zwyzzyyxyyz{|~}}}}~}~}}{{{{{||zzzzzzzz{{{{{{||||||||||||}~~}}}~~~~~~~~~zwtsttopoov
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|~zxxyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}~}|}}~~~~~~
|zwusvwz~
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~}}}}~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}}yttvw{{{|}~}|zzzz|{|zz{||}~}}{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~}}~
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}{vttw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~
}|z~~
~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyyzz|||{{|~}zwuuvz|}{}}|{xxxyyzxy{{||||||zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~
~{zzyy|
~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~~|zwuuux{{{{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~
}{|
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|z{{zzzzz{{zz|~{zz|zwuvvvy|||}|yyvvwxzz{zzzz{{{{{z{{zz||||{{{|}~}}~~}}}}}}~~~~~~~~~}}}}~~~~}}}}}}~~~~~~
~
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~|z{{|{zzz{||~||{{yxvqrv|}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~}|{{~}|{z{{~|zyzzywvtqw|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}~~~~~
~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{||z{{zyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~|||{{{{{z}
}|}|{zxwspotx{zyvvvwwxyyxwxxyzzy{{{{{{{||}}}|||}}}}}}||}}}}~~~~~~~~~~~~~~~|}}|}}}~~}}}}}}||~~~~~~}}
~~~
~~}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~}{{{{|}~||~|ywuqnnrvyyyvvvuwy{{{ywwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~
}~~~
}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~}|{z{z}}{||~}yxwtrpquyyxwuuwy{}}||zyyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~~~~}}}}}}}}|}}}}}}}~~~~~~~
~~~~~}}~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~~|{z{~~}|{||~~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~~~}}}}}}}}|}}}}}}}}}~~~~~
~}~~}}}}~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{{~|{{{|}~}ywwsooqsuwxvwxxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~~}}}}}}}}}}}}}}}||}}~~~~
}}~~}}|}}~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{|{{{|}~~zyxtpopoqsuvyzzyy{{{||zzzxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~~}}}}}}}}}}}}}}~~~~~~~~~~~
}}}}|||}}~
~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|}~|{||}|~~{xurqponoqsvwzyzyz}}}}{{zyyyyyzzyz||||||zz{zyyzyxxzzzzzz||||||||}}~~~~~}}}}}||||||}}}}}}~~~~~~~~
~}{z{{|||~~~~
~~
~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~}~|{{||}|~~{vtrononnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~
~|{{{|||~~~~
}|~
klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~}~~}}}}}}}}{{||||||||||||}}~~~
|sjd^[]_ada_][^cehhe``bglsz
{qf]YX]bjtz~{urppmifaadgknpquyz|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~
mooppporssttuvwwxzzzzxvtrpqqppppqqrsuvvxzzzzzz{{||{}~~}~~}}}}}}}}}}||}|~}||||||}}~~~
}wpgc_^^`bb]ZYX[_dghfc`acfmu~
{si_\Z[bhpw}}{wtrpmnifcbbejmppqsv{|zupkihhhhhhiklnortuuuuuvvuwvwyxupg]VUW\^cgpw~~{wtoljihgeegiiijjkjmprttttsrppqpoqsrpsw{||ytqmhdccb`\Z]dlsuusrrsuwz~
moqqssrstutuvwxxy{{{ywusqpppppqqsssstuuwyyy{}}||||}}}~}~~~~||~~~~}}||}}~~~}|}}~~~~~~
}vmg`]^`cda^[XVY]fmojea``emt{
vmfcaadhot{yusqnmnjiihedehpyzwz~}{wrnjhhhjjiiijlnosvwvvvvwwwwwxxyupg]WUVZ^dhpw}|ztrojghhgghhjjjkklnnpstsuuropppoprrqrtxz||wsqkgecca_[]aelsutsstuvxy}nonprrqrtuvwxyzz{|{{yvvsqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}~~~}|}}~~~~
wnc^__dddb`YTT[epuvneaabfkqw
{smgbbeilostpnmllllmmkigglw
ynqyzvqkihjiikkjjjkmoqrtvwwwwwwwwwxyzxsj`XTW[`cgnu|~|yurnlihhhhffgkmmmnonllpsvtssroooomoqportxz{yvrlhca`adaacdhnstrqqtuuwznnmnpqstuuuvwx{||{ywvurqqqppqrrttvxxxxxxzz{{y{}}}}}~~~~~~~~}}~~~~~~~~~
~yohfbeffc^\WUWamv|{rkca`chnu{
}vrjeefikopoomllkknonnjijov}xqqw~|wrnkhhiikklkiiknoqruvxxxxxxxvxxyyyukbZWVZ`chms|~~|xsnjihgheefgijnoooonljkpsssuuroomnppommptxxzxspmida_aebaejnsuurpqtuwwy}nnnopqtuuvwxz{}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~~
zslhdcdec`^ZX^iv|~wpic``dkry
yrkdbehjlnonlllllppppnorsxzwtonv||vrpkhjlmoopnkijlpptuxyyyxxxxyxyyz{ytlcZWY]_bglsy~}ywsonliggfddefiklnoooolijmpsssssromlnppommruxxuspmkfbabeefimpuxvsqpqtvvtuy
jlnopqsuvxxzz}~~}{zxvutssqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~~
ztnhecca`^]\]fr{zukeb^bgms{
|unlgdfjkjiikkkmorttrtuvx{|xqmmppijtusnjjklmoprspkhikmoswxyxxxxyyyyzzz{zunf_ZZ\_bglqwz{}|zwrokigeecbbdegimopqponmljhlortsrsronlnpqnmnqtutrqnnkgdabffkpsvxzwrpqsuvtstxjlnopqsuvyz|~~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~
~xrlfb``ba`^_dpy}{vrlda`fkqy
~xqnigikjhiklllnqtvuttxyyyzvonprokjjnniiloqonrttpkhikmoswyzzzyyyyyyzzz{zvnfa^[]_bgjowz{zvsqmiggecbceeefilnoqsponmjhhkpstsrsspnlnpromnoppnlkkkhdcdehkrw{~~{usrstuwtsrvmmopopqwx{}}~}}{yvutsqqqqppqstuvxyzzz{{zzzz{{}}yz|}|}~~}}~~{tnie`\]``^^ckv}|umga`bgpv|}|wqljgklihknmnpruvvwv|~zwwwwxzvkhiinsooqvytmnqrpkiijmprwyzyyyxyyyyzzzzyxohd`^^acfkpsxytrnlhgddb_abdfgimmqqrrpmmnjiilrvuuttspnlmopmkkkkjijhfecbbegkou{~~xtsrtvxwtqpu|mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~}xqje_]]^__`cir{xqkebagmtz~yrmjgghkjkkmnrtwyzz{ysuz~}vqoieeiosu{{tqrrpkiijlorw{|||zzzz{{{{zzzxrkd`^^acfhlpsspkjfdcbbdedfhikmnnnnppnlkllkjnrvuuttspnlnqpkhgfffededccbbehlrx}}{wsprtvwurqprz
llnooruw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~}xrmgc``_```afnx|vnh``chou|ysnjfehjjklpsuxzz|z|}~vqx~yvtolkjijnpx{wqpvwupkghkmprw|}||{{{z||{{|{{xrkda``bcegjnpoigegdbabbcgihjnoonmnoomnnomkkosuuuuuspmmopoicbeea`adcdeeffhmry}|{zuqqssuurqqppxlllnqtvx{{}}~~{{zwvutrrrqqqrstuvwxxzzzzzzz{{zz{zz{|}}~}
{vrmhdb_\Z``bekr{~xrlc_bflrz~xtnjfehjjklqty{|{zxvvvvw}|xsqpoonpt{xtsx}}ytlhhklorw|}||||{{zz{{yyxvplgc``adghikjjhbacddcegfgijknoonnoqqpnnonlmqtvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqt{kmmoqswxyz{|~~~|zyxwvutrqqrrstuvuvxxwxxx{{z|zz{{{{||}}~~{uplgec`^]]__bgqz{vof`bekqw}xvqjfdfhjkmtx}|}~{vvuv||zyy{vtvstw|wqt|~}xrlhhjloqv|}}}}{|||zywwwvtsojea``cijjgggecacddihijkkklnonmmmpmmprronnruvttvvspnopplf`aa^^__abccehhjnsx|{xvrprstvtqrrqqsy~lnmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~{vojfeec`__`ceinv|~|voga`dhnu{}{uqnhfghkkpvzz~~~~~|xvssqopqruxzxuv}~}xrlhhjnpty|}}}~}}}|zywvutspmidbaadefedggebceggjjkjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^^\_adefijjkosy{zxvrprstvtqooppqwzymmmotuvxxz{{||}{xwwvvusrppqqrsstxxxxyyzzzzzzzz{{{{||~~}}wqlieeecbabefhks{}vohbaaglrz~|wtpmifegklqvz{
ytruvwtrqmknoosuuxzyvw~~xqokiklnqv{}~~~~~|{ywussttqnhebbbeffgggghhggjkjlmjijkknmmllnmnopoqrortvttruvtqmnqokc``ccbaacdefijlmqtxzywtqqruuvsqpppppuutmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}|wplifeeeeefghlqx~zsmhbcfkpw}|ytrpnkhehlpptx{}wsqmnpqppnmmmpuy{zyzwvz~xqmiiknprw{}~~~~|{zxvstssrolhgcaadgghhhhiijjmnmnnkjijjkkkjlmnmlprssstwwutuvwtqopqokfbbdddbbefhjjijnrvyyxurqqruwtpnppppoqqpnoqqrqrswxyyyxxxvvutsrqppppqssttuvwwwxyz{{{{}}~~~~~~~~}wpjhhghhhiiklpw~{uojfceipt{}|zvppppmifgjnoswz~~zuppnmnoonnnmpux{{xuu|~xpkiikntuy|~~~~}|zxvvtutsrmjijfbbdhklmljllnoqrrpnljeggijkknmmnnprtttwyzwtuvvtpopqolfddeddcbdhjlllkosxxxvrqppsvxwspnponnnoonoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~~ysnjiiklllmmmotz|vpkfceimswzywtqppomkgdgloquz~~xsomoqpmmmooqw{
{wvz~xpljklorwz|~~~}|{yxvvtutspolkhgcdhmptutqpqrsrttqmkjhhhijiigiklnprsuvyzxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnppqqrssuvwwvwwwwvuttsrrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}{tnjijknopoponrv|~yrmhddglouwwvsqoomlkiehknosw}|vrplkjfgklnqu{{vuz~wpkklmoqv{}~~}{zzywutrqonkjhgghotw{zxvtrsvwwvsnlijjjhiiiggjlnqssvvxyyustvuqoorrpledbcbabcehknnmmosvwutrqoqsvwwtqomkjjjlmppooqrrtvwwvvvssttsssrrqqqqrrsttuuvwxyyzyyzyxuuussux||{zzyzz|}~~wpjihiloprqqpnrw|zuoiecekorstsqpoomnljfeinpqw}~}|{xspljhhiknosy
}vrw}~wpkklmoqw|~~~~}}|zyxvtspooljihinu|}zvtvxzzvspmlhgghiiikjjmnqsstvz{yustvwrporrpgddb```acehknonmpswutspprstuvvtqomkjhhklnoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{|ywtrokjhilpwwusrpqrrux|~~zslhghjkooooooqu||vqjdadimopqqppppnpokhginpptz~}~|xuqmjiiimnrw}
zvwwpkkjkoqv}~}|zyxvuqpnoljhhkt{{wvw{|zxtpnljihiigiiillnoqsuxy|yutuwvrrrssme`bbaa_`cfhknnnnquvtrqqqstuvxwtomljihhjknoppprstuvutttuuuussqqrrrrsssttuvvwwxyy{{xuqljihfefjnmifcbdehloswz|~~~}yqjgeccdimpppoprw|~xrkecceilmpppqqqqrrmhfilpqsx|}}{wtqmljjjnrw|
}uv}|vplllmoqv}~~}{zxwvrqopmkjjnv~}yy{||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqppqsttuvvqnomljhhjkklnopqqqssssttssrrrrrrrrssssststuvvvxyz{yxvrmljigecbccbbbb`adeehou{}~}}~~|xsmfc````foqrqpqw|xrnfacfkjjkmoprsstspjghknpqtz|{ytpqlkklmqw{~
{tu|~wpllklnrx}~~~}||zxvusqoqomllqw}}{z{zyxwtrpmjigffiihgijnpqrsw{|zvuxwurrrrqia^_]^`bdfhkmnmlmrsuttrprtttuwwrnmmkifehkklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmkjhfdcbcdbaabdddccflqw{}}~~~~zupkhd_]\`ekpsrqrtz{uojeacehjkmopsuxxvslihjnoqtxyxsnlljkkmqvz~
wsuy{uokkklosw|~|{zxvtrpqomlkqwz}|yuuustsrppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqqsvwuwyxtollkiedfhmnqqppqqrsttttssrrsrrrrrrsttuuuuwwyyxyz{{{xurrrrnjgggfcfffffdeba`cjtxz||}~~~}wqleb_][\^blnqsrtwz~wqkeceggijmpqsx{{yvojhhoppsuurmkkjkllnqx
|srwxupljklnrw|~}}}|zyvtsrqonnqsvyz|zwppqmkkmkmmkifdceiiihgggjoppswz|zvtuutrrsrne^\\[`bcehkoqnkilpqsrpppsuttuxxqmnnlhgdefmnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zzxxyyurmlijmqsuttqmhbbbcksvwxz|}}~~|vojdb_^]`aeimprsuy|~xrleacdhjjmqtw{}}{xslhhloppplifhhjlllnqx
ysrsvtoljjknrx}~}}}}|zzxvusrpnpoqtvwvqmhfecbadgjnkhddceijjhjlljjjotxz|wstuutrrsrne^\\[_befloopnkjmopppppqsutstzysnmmkgeccelmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||||wuonnquxy{{{ytmecbcehkoruxyz{{~}~~xokgfcaaabdglorsuz~unhb`dghikrvy~~~{vohgknqlieebdehkkmnrx
ywxxupmkijotx}~~}}}||}||{zxvtqnpnnnpsusof```^]^cfjllkgdcgjklmpponmknqw{{xuuvvussrqog]Z[_bgiikopomjjmpqssqqsuvvtsxxqnmlihebefklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zxvvvxyz|{|||ytokhdb`bdgijijlqv|}~~ysnigdbabfglnpquy|
~vrlfcchjknswz~{yrjfhlnjebbceehlmnpru|
}xuzzupkiijnsx}~~~~{xxy||}|zwusqnnnmnpqqlda`aaabgknppmhdcfikopsuronlkou{{wtuvvusstsme\Y[afgkkloqplhijnprsqqsuvvruyvqnljjiebdbkjjklmoqrrqqppqqrrrrsstuuuttvvwwxxxxyz{{||{|||||||{{||{{||||{zvsqmieabd``]_bdgnwz~{vqljhebaejlqsvw|~
{slfdegjlpv{~}yslfehlkc`acfhmoonoty~|srwzupllmnnqx}}xtpqwy{|}|zvspnonnnqrnkfggjmnrtuvupkfbbeilosw{xrnjint{{vtuvwtrstrlc[YV_hlmmoprnjggjmqtspqtvxwstxupolkjhedefjijkmnnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zyyurmkhgda^\\]^`gpx}}xrmkhgghkptxyz}
}umfcdgjosw}~xtoigilkhcbfknrsqmou|
yroovyupllklnqx}~xrieemty||zwspoommkmoppooqvxyyzzzzwrlfbcegjmqx{|vofejszzvtuvvsrstsnf_]\ahlnooppmighjostrstvwwutvxuomlkjhfefhjjjkmnppqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz||zyxusomigea`````enw}|wqmkkiimsy|~
|wphefhloszzvqkijknifdiotvxsmmqw}
~vooqwyvokkjknsy~|rh`\]ent{{wsqponlmnopqppqv|}||~~~|zskfcdfhjlsx~|vnfadnwxvuvwwtuutroiba^agkoqssqmigikqsststwxxuuxyvolllkidfhijjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{yxwtqnkjhfda^bkt}~ytnmjjkqvz~
}yskffgjosx~~{vojhjmljjqw{|{uommr||usrquxvokkjknsy~zpha]_fntxwsommonlmoppqrruz}~~~~~|xqjgdehikmqv{{vld`bltxvuvwwtuuvtpifebejnpsssqnigkmrvurstwxxuruxuoljjkigilmijjjlmoqppppppppqrsstuvwxxxxwwxxzzxxxy{}||{{zz{{zy|zyyyy{{{{||{{zz|||{wusqmga^agr|~xrkjklqw|
|vpheehmrx~|wsliimnmnu}~ytomnrx{uuwuvvvnlkmlotz|riccdhorssolllkklmnoppqquz}~~}vngcdefgjmrvyyulc_bkuxvuvwvutvvsnigihhloprtrnjgfjnquuttuxxwsquwrnkkjhfgjkmijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||}}{{zyxyxxyyzz{{||||||{{{{zzxvqha^`jt|
|wupmklmrw~
~zupgedgjov|~yuvtmjimopqxztollrx|wtz}zvvwplkknnsx}zsjggklnpppnnmmllnnpqqqqqsw}~zsmebcehijnswyytmf`ajsvtuvxvutvurnjijkknprsutnjiimrtussuvxxvrswxsnkkjhfgjoqijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{y{{{{||||{{{{{{|zukb^ajt|}wpnlllnqtz~
|wpjfdeimr{|ustuokkmopry}zvqkjovzvw{|xtttqnlmmosy|uqmmnnmoppmmomomnoqqqqppsx}~~~~}ysmhcdegjlpsy|ysmfbckrttuwxwvuuuqnklmmnpqsttqmlkmqvwvstvvxxtptyysnlkkiegilqijkklklmnppqqqqqrsvvwxwxyyyyyyyyxyzz{{{{||||zzzzyxwvuuuwwxzz{{||||||||{{|yvnd`ags{~ztplkkllmqw}
}xurlfdeimrw}~zumlorokjjlorw}|vqnmqw~}z|}xusrrsrnlllnry{vrsssnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghkosu{|ytoieenqrtuwxwvvvvqlijmpqsttuusomnotwxvtuvvwwsrtyxrnlkkihjlothijllmmnmoqqrrrrsuwwvvxxyyxy{{{{{{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~}wqnkhhiijotz
}xsrmgddfiptz}}ysmefmqnjhllnsv{}vromrx~z{{vsopqtpmmmlmqw{xssvvtommpppqqomnoopprrssuz|~~}}zvoiecdghlosz}|wrlighorttvxwwvwwwrnlmoqsututvspoorvywsstuuwwttvxvqonljjjknqwhijllnppqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{ysja_clu{}wrkgggfgilrw}
zuqoniffhhnsxxuoidcegnnnnpoqstz}xsolou}
~|zxurqrsqmjlklnuz|yvsuwzzvppprrrtqomnpppqrrssty|~}|zvojeddfhkpty{zvpmkjnsvuuwxxwvwwvsnlmoqstvvuvsqpsuxzvutuxxxxttxzwolkljjjmprrijjllmopqqqrrsttuuxxy{{zzz{{||{{||||||||{{y{yyzzzzzzzzyywwxxxy{{||}}|||zz{ztmd`cjtz~wsmgccbdgiqu}
zwtpnnkihiilprqlgdba`cioqrv{{xwz~|xrmnqw
{urqstpljkjkmpsutppty{zurqttuvupnmnnoqqsqqruy}~{xuojedceimquzzxronnprvyxvxzzyyxxxrljmprtwwuvwurswy}{wuuvxyzyuuxytnkkkkkkllnmijlmlmopppqstuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zwqkbaenw}|smga`aceiow|
|xsqpnolhgiijlkjgda_ceimquz}~}|{wspnrx
||}xrquwvpljkjiilpqpmlqvz{vsvwzxvrpnnprrrrrsrtvy|~~{wrnieefiknrxzyvqonnpuxzwxz{{yyyyxrklnprtuvvwwutvy|}{wuwxxyywuw{yrmkjjjkkkljjjklmnopppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{ztoheektz~~ysje``_cjqv}
|tqpponmkhghhiieadcdcjlnptx}yw|xqpsy
~ysrw{yqkjjkjknnmlilru{{ywx{|xxspopqrrrprttuwy|~~{ywqlhddfgjnrvvtqqqppruzzxwy{zyyyzzsnnqrsuuvuvvtsvy{}zwwyyxzyxuvz{smjiiijjjjhjjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzzzyyzzyzzz{|{{{{{{{{{zwrmgcfnuy{{zwqhc`aciqx{~
}{vtqppnnpqlhgghhfbcbbdjnrtvvyvqsttx}zwx|
}yww{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosx}|yyz{zyyz|xrmnrtuvvvwwvtux{}}zwwyyz{{yvy|zrmkhiijjikjkjkmnnoppqstuvuustuvy{{{}{|}~}}||||{{{zzyxxyyyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{ytqjebflqtutqmhecciov{
}zwusqqppnnpqpgeefgfdccbdhlpswwqijlmprv{}|
~{yz|wqmkjkkkmkmkkouxzxxwyz{xvtppqrssrrrttuxz}~}|zxsnkhghkmmopppppppootz~{zz{{zz{{xplotuuvwwwwvuvy|~zwxxy||}zv||xqljjkjgfikilijjknopprstutsolkmpsvvvwy|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|zumf`cfkmlkhdbbbgms|}|yvtssqppppqrstpjffghgeffb_`fmsuwvpnknprv}
}{z~wqmkklllllnopqux{zyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|~{zzzzzz||wolrvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgiknpjjijloqrrstusqmhgfgjnnoqrvvy||{{||{{zzzzyyxxyyyyyyxxxxxxzzzz{{{{{{{{zzy{||||||wqhb_bccddcaabfmqx~~~~~}}}}{ywwtsprpnnooqsuvurkhhiiiijjd_[aluwxwqmnqvz~
|z~
yrmkjlmmlqsx}}xxzzxxz{zvtqpppsutqqrttuvy~~zuqmjhhilljhjkmnpomorw|~}|zzzzz|~{tontvstwwxwvqsw|~|xwyy{||ytu}}yqmjjjggghkqvjjkkloqrrstusqkfdceeggghklmpvxz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrkdca__``aaabfltz~|zxwy}~}{xwtsqqpponnnnnlijjhgghijjlorvxywrkgghikkpokaVXeouvwrnot{
~}
xpmknprqpt{yyyyxxz{{xurrrrstsqqrttuwz}zuolighjigdbehjlmnprvxz}}|||{{|}}zsnouwuvxxxwtqsw||yyzy{{{wqsz}yqmlkkigghltyhjjlnprrrsuxvuplhegfffddffghlpvyz{{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjgc_\_^^^abglu{}yrkgfhpx}{xuommlkhffffedccdcbccdc`````bfkrvyywrlgfijhjqtqgZYdouwtqonrv}}z
|vpmlnsx{|}~{xtuvuwxx{|zwsrrrssrqppprruy|~}xsmifgiifd``cfjmooqvxz{}}|}}||}~}ztpquuuwxxywuqrv}{xzz{{|yrmqz}womlmkjhfgmv}hjjlmoppqrtvvuroljjigggdddedeglprvwxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zwsokgb`_^[[]^cgow~~}xnc\\]bhnrolhda`_]]]]\\[ZWWXWYZ\]^^]]___bgjmnpqpkjhffdegmsodairuwurpnnprx~
~|
zvpmlot|
|tohfimuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnruuxz~}|}}||}~~{xtrvxuwxxyvspqv}{xyy{{{xqlqz{uomlmmkjiglv}ikjlnoppqqstuvurpppmmkjihgecdefilpruwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}}{yvspnkhfc_\]\^bhmt{~}ype_YY[^beheb^\[XWYXWWVVUSTTRSVWWXYUXVX[_adggfgimkigdb_`dimrpmrwvvtqpooorx|z|~}~zsmlkow~~slgijltuyz{wsqpprsqponnnqsux{}zuoighjkkgcaabfknqqtvwy~}|~|{|~zusvvvwyy{tnlnu|{xyz{{yvnkpyxqonnnkifehlu{ikkmnoppqqrsstttuuvuusrqnmkhfdccfjnprtvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{ywvtqmifc`_]\\^bgnqqne^[[[]_`__][ZYXY[\^``aa_^]]\^`abca^\[ZZZ\_behikijgb`ceggdhqvswxwwurponnory}~~{|z{|xy~ulefjompsy|yurrrstsokknortwx{~~{vqligghjjieeeimqrssuvxz~}|~|{|~{xvvwwyzzxsmlnu|{xyz{{xrjipwvqonnnopollptyhjkmmnnoqqqrrtuuuuwwxywwurpkgecabcfgkqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}}|zwxwvsljgdc`b_\\^dihd__]^\\\\\\^_aabdegllkkkigedbdggijifda^]]\Z\_`dgihd`bhmoi]\luvyyxwvtpnmnosy|}~~|zz}y}
}thdejlpu|}ytrsrsvspmlmoqtvyyyxsrplggikllkhjlpvwvutuwx{~}}~|{|~~{zyxxyzyxsmlmt|zyzzzzxogipttonmnoprromosvikkmmnnoqqrtsuuuttwwxyxxxxvqliecba`_ahnqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnnlie`\[^]\\]____^^^^^ehmnpqqrtuutrplihgefggimonnmjiedbb`_``abceknnog\Vetwxxwutrrqollr{}xv||}~ytx
vpkkptvz{wsrsrsromllmprsuvvtsqnmkkkklmmnnotw{{xtrtvy~~}}~||~~}{zxxyzzysmmnt|~zy{{zzwogirvtqrqprsuxwtqrtjllnmnopqrttvvttttwwwwxxxxwwsojgb`^\`dkopprrrrssssuuuvwxxy{||zz{zzzzzyywwvw||}{yvtssonnnmjb^\[]^\[[\]^__`adgmtwy}}~~~{{yurnlihgghikpuuvvtrnmjhgdfdadhnqnklojeioruxwtqlonhfgpz{wt{~~{~
wrv~
|vruz{wsqrssromnqryxuroqpnlkkhigjlnoqsv|~|wuuvy{~|}}}~}yzz{{yyrmlmr{zy{{zzvqgjruvtsrqpsuz{xtttkmmopppqqrttttttttwwwwxxxxxxwsqmjfa]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzwpiikpuy||yusqqpqqmgdbba_]\][[\^]^]`aadgnu{|yuromjihiiimouy|}||zvqmifdcfhnsrjghnw{~{upqtronpmmlosy~}{}
|vy}}z|zy{zvspnnnoot}xooqpnljihijknpqsuy{|{xsorvy|}}}~}yyz{||ysnkls}{z{{{{umejswtrsrqpsu}~{wuumonopprrssuuuuuuvvwwwwxxxxxxwvsrpjc[Y^fopprrqqppqqssrtuvvxz{zzzzzzzytmd`bdinrvutrpqpnlke`[XZYWXVYYY]ceeeccefgkqwz~~zupmkkkjhhiimry}}yunhdbinrsrkhhk|~wlhgmurroqv~
}wry
|~ywtqljmoxxpnopqpkjhghilnppsu{~~{uqprvy|}}}}~zz|}||xrolmt~~{{{{{{thdjrusrrrooqx}}|yvunpprttttttuuuuuuvvwwwwxxxxxxwvustnd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_adimnlklmlljhaZUTVVWUTTX]dkpqqmjgdbceilpux||zvrolkkkkjklnrv{~~~|zwqmgglsutsslhpy}rnsz}{vuz~}
zpov~~~ysrqqrv}
|tomnnopqpmlkiiklnpqsvzzzwspptwz~~~~}{|}~||xromnt~~{{{{{ypgdjqssrrrqqqx{{zxwvqprrttstttwwvvttuuuuwwxxxvwwusttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^``abeegggfgged_[TPORVWZY[`elrx{{wnhe_]]`eijlpqtqpnmmkkklmpqty|~}ytroomtz~{{|wy
|rv}
{yysqw~}w{}{sppq{wmnmoopprrrpnmjjlmnoqsvz{wtqqrtx|~~|{{}}{vsonot}|yz{{yunebjturrssrrrvzyyyvvpsuuuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtohc_aabdddcca_``^][WOMLPRTX]ckry}}tkda]^]^aefgijlnmmmllmoqtwy}}|{ywuqnmpu}|v{
|vttz
xpnqy}ztnkruonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|~~|}}}}zvsonot}~|{{{{yukbdmssrrqqrruyyxyyvvpsvvuvvvxxxxxxxxvwvwxwwwxxwwvvuuqnh_XY]djmmnnnoonnooppppqrtuuwxxxxxuqnjhdcbabdb__]^]ZXVUPMJHMU]dnwvlfddfed_^_bdfhklmnoopqtvxzzzwwsqppnnqz
~rw
|yxvv{wolry}xnhn~|tnmmnnoopruvurpolllopqrtw|zvsppsux}~}~{}}||yvsomnu}}{z{}|ztjdgousqqopprvy{zyxxwvwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspke^Z]_dilnoooonnoopppppqrrtvxxxxywtpnlkigdacddb`_[XVUSQPOOQ]gq{
wmhfjprojgb`adddhknsutuutsrrrqqppnmmpy{
}|y}~qhljf_`hz~yspnnnnonptwyxtqnmnnopqrux{yvsqqsux}~{||||yvuqmnu}}|{||zyrjfluusqqoppruxyzyxvwwwxxwwyyzzywwwwxzzzzzzyyyxxxwvvvspmha^^^_aehknoommnnpppppprsuvwyxyxxvtroonljhhid`^\VTTTTUWXZ^it|~xoffjsvurnme_^`bgimqrsqqonmomlonopomq{
{te]Z_it|}xyuqprromlmrvyywtqnnnnopprx|}{xwtttvy~}{{{||ywtrmqw}~}|~}|xtlhrywqppppqqsxxyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutrkgcbbaa`agjlnmmnnnnooooqrsuvwwxzzxvvtrpnmigcb_][XWY[\_aeipx~zskfhov|zusokghiiihjlihhghknmllnprqrx{}
}|rnu~xvtwvtsuvsmlmrwz{ytqnnnnpqstw{|zxwuuvxz}{}}}|zwurptz}~}|{}}ytlkvyvpppppqqqruyzzzzxxxxyyzzzzyyyyyyxxxxzyyyyzyxywwuwwuqmiiea`__bcfjmonnnnnnnnqqrtuwxz{{zxxtpmlhfc^\[Y\^_bcfinty|}|tnffjqw|{wpmpsqkljiijjjooponjiptvxz|
~{|wr|vtw~}xrpsqmlnsx{{wtqnmmoqqssvwwvuuuwuw||{|}~|zvurpu{}~}|}~}{ros||tpppoopppqvzzzzzyyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxyxtqnjgda_]^bglmnnoopppppprtuwwyzytqnjgea][[YZ[^acgknru{~}wpheejpwuxz{~wsrpmkihffhlmjhhpw{}
wu~~~~}~}yx||zwt{uqrqnnoquxyvtqnmlmmmkknonlmmopuw{|{~~~|ywvtsy}~}}{tpx~{soppqqrrqsvzzz||zzyyzz{{{yzzzzyzzyx{{{{{zz{{yyxxwwxxwwvrnjfb`__bgkmonoppoprttuwvssqnkfd`[YWX[]^`behlqux{~yskegjkqsz
~zslddd_]bjlnnrv{|~
volov
~}yxuz|xtrrppqrvzytpnmmmjkkknmmonooortuy{z|~}zzyvv{~~~~|wv~zrppppqsttu{|{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzzzzyzzyyyywutqnjd`^_cglmnoooopqrsttslida^ZYWWWYZ^chjmpuz{}|upifkosuz
}zsjfgjkhhijkrz|tuzw||{~|||~}|}~|{|yvssrrsuxzzurlkjjjklmnmlllkmmmlnsuwxz{{zzy~~~yux|tqppqruuz~}zz{{yy{{{{{{zz{{{{zz||{{zzzz||{{|{{{zzzz{{{{xvtqlfa__cilmnopoqssutojd^ZXWVVX[^_bhnruy{~|yroru}|zrioma\ahlqyxqm{{xz}~~
~~}|yyyyzzwttstx}~ysomnnkkklnprrponlkijjijorw|~z{{{z~xsw{qpqrsstw||y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxvqkda_^`cefghloppolhb][[\^^^cgkoruy{~{ww~
wttj`]`is|}unt
yy{{z}
{yyzy{}|zwxvwuuux|zspoljjkknrtvwyyyyvtsssokjlntz~
|{|~}}tov~ytsrstuux}~|{|||{{{{||||{{{{zz{{||||{}}}||}}||||{{||||zz||{ztokgd`_^^_``dgihec_]Z[]`bgimrvy{~~z|
|ww{slfguxpt
|zzzxxz}
zyy{z{}~}{xyxwvststtqoklloqoprvz}~~}{yyy{ztnihkq{}{|~
zqnt}}yvuttvvvz~~|{||}{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ytplhe`^^\\\^ab`__^__bfjotx||~}~
{|
}vzxnn
~y{yusuy~}yx{
{zy{z{|~|zyxxxuronppoorstttstx|~wnhhpz
~~zokr||wusuuvvvz~~|{|}{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzyxwsnjea]]][\\^`__`bglswy|}}
|}}whfs
yz{wutvy|}zxw}wz{yz{||yzxwtqnlknoqqruuuvwxz}
|rjhp|
zqnw~|xuuvvwvwy~~|~~~~{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}|||||||{ywvrpmkgba^^`befjnrxz|~
|{||
~tmt
|yyzyz|~|{{}ywwxz}~zuqqonkjjmprrrsvxxyyz{
{pfhr~
|uu{}xwwwwywwy~|ywusq{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||{{||ywspniedehknrv~~|{{}
|tot
|{zyy{
}vuvw{zvsqpmmnoruutuwxy||}}}}~
zkdiu
~z{zwvuxxvw{{rjiii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{zxzxvsrqsvx{}~~~
~zz~}pko}
~vsru{
zxsrqqsrqsuvwwx{zzz{}}}}}}~
uhejx
}zxw{{yxy{skgjljj||{{zzyyyyzz||}}||~~~~}}}}||||||||zz{{zz||{|}}}}||z|~~|z{|}~
{{
|omr~
vliku|y{sqrrtutwyyzz}}||}~~
~peeq
}{{}}|zxvojlmkllozzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyyxzz{{zz}}~~}}}~~
|sqsy
tgbehnvzzzvv|}zwyz{|}|{{{}~}|}~~~~~
{megq~
~|}}~ztlinrrpqruyyyyyyyyzzzzyyz{|}~~~~}}}}}}||}}{{||zzzzzzyz||}}}~~~~~~
|vy}xhhnkhnv~~zxxxy{}{z~|||{{||}~}||~pgdis
}tklnprtuwx{xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~}}}|}}~~tpvyqrusonr{|{yuuwwuttrrv|~~||}|}||||}~}|}~
yjcenz
{pkmosvy{|~xxxxxxyyyyzz{{||}}~~}~~~}}||||||{{zz{yzzzz{|{{|||}~|{{|{{{||~
~ztnr{ ¡ {|{wsqt||xwsnqvvtpmqx}|}}|}}|{}}}}
pgcjx
vljlpsvz}~xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~~~|||{z{{||~
{tt¢¢{{{~}{{wrlknsuusqqt{zxxz|||{}}}}}}}~
znikx
wlkmosw{~~wwwwwwxxyyzzzz{|~~~~~~}}}}}||}{zyzzzz{{zzzzz{||}~~~}{{{{{{|}}~~
|yy~
|qeampsvxywuv|zxyz||z{{~|{||ypnv
xnlnprvzwwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{zz{|}}~~
}
~yqbOViotwvusqv
~{z|{}}||zzxwx{}vpt
ymkpruy|xxwwxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{{yywwwxyzzz{||~~}}{zz{{z{|}}
}
|ylU73Whptvwwst||zz{zwuttutuw|wpt~
{nlpsuy~
xxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~~}{zz{{z{|}}
}ucC,/Vknnrwyupv}x{~}zyvutux~wsv
~smqtuzxxzzyyxxxyyz{{}}}}}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~~}}|{{{z{{|}~~
¡taJ<F`qrnorvusuz}ztvx{~~}{vtsvzusw
smqtvy~sxxxxxxxxyyzzzz||}}}}~~~~~~}}~~}}}}|zzzyxxxyyyyyy{{|}}~~}}|{{{zz{|}~~
~~
}ul[JNgwyuqotvutw}{y}~}}ywvvw{utx
xpqvvx
xmxxxxwwzzzzz{||{|||}}}}}}~~~~~~}}||||{zyyyyyyyyzz{{|}}~~~}|}|}{zz{{|}}}~
~xvwx}{tkaVO\v{vppuwtx~~~~~|ywvvz~wv{}pruvx~rixxxxyyzzz{z|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~~}|}|}{{{{{||}}~~zx~yxxy {wrmga`k{|{wpirywx}
~{ywvvz~vu|
pquvw~
ynjxxxxyzzz{{{{||||||~~~~~~~~}}}|{|}}}}||{||||zzz{{{{~~}}~~~~{{}}||{{||}}~~wom|
zurqroorwyuuuogjsxy|~~{yyy}
}vv~snrvy~
}pikxxxxyzzz{{{{||||||{{~~~~~~~|}~~~~}||{|}}|zzz{{{{||||||~~~~~~~~}}||}}||}}~xndgyz~
¡ ¢¡¡¡ ~{z{zxqllmknstmhkpw
~{yzz{
}wyxprtv}}zshgjyyxyyzzz{{{{||||}}}}~~~~~~}}~~~~~~||{{}}{{zz||}}}}}}}}|}}||}~~}}}}||||||}}~|qd_fw}{||y¡ ¡¢¢¥¥
}xnbafknqrpjfju}|{|~{xzvrtuvy
~{vqnooomlfeflzzyz{|||}}}}||||}}}}}}~~~~}}||}}||||{{||||||{||{z|}}}}}}||||}}~~~~~zshb`ehjknzorz¨¦¦§¢¡¡ ¡ ¡£¢
~}ytqnicbglornhen|~}}
zx}wqtvutvspnjihhfghhhgfecehzzyz{|}}||}}||||}}}}}}~}}~~}}||||{{{{zz{{{{||}}}}}}}}|~~~}~~~|{vmfabdehin{tjlw¡¤®©¥ £¡~{vux|yla^bjoojfkuyz{~
~yz~
}stvsonieccdehilnoponkhhffzzyz{|}}}}}}||||}}}}}}~}}~~}}}}}}}}{{{{{{||||}}}}}}}}|~~~~}~~~|ukeehfeefgo|}trv|¡¢«§¢ ¢~|zvx{ync^`hmkgmy
~y{tsrligdedehlouy}~xqlmlnn{{{{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~~}{||||{{||||}}}}}}}}}}~~~~~~~xofbehddedgp{{utz¨«¦£§¥¡
}yyy|}wkb]blmlmt{
|{{vnlhgdghkkntz
zrmoqvx{{{{{}}}~~}}}}~~~~|~~~~~~~~~~~~~~~~~}{||||{{{{{{||}}}}}}}}~~~~~~|xpfeffgggccirvtz ¡¤¤¡
~~~}xqe^elqroqu|
|{{xnkikkmmqv|~rnnt{
||{}|}}~}}}}~~~~~~}~~~}~~~~~~~}}}}|z||||||{{z{||||}}|}~~}~}}}}}yshdfgikkibbjs~
|y|¢¢¡
~||}zqcfjoojelx~
zz}mifhmqrv~yqnrx~
zzz{|}}~~~}}}}~~~~~}}|z{{{{zzzz|||||||||}~~~~~~~~~}}}|zshccfilmkgbbit
{~
}{z{}zurqrogjv
}|{pffhkty~
~tpps{
{{||}~}~~~~~~~~~~~|~}}}}|zzzzzyyzz{{{{||}}~~~~}}}|||}}}}{uicaehmqpnibclu~
~|{y{{|}~}{{{urw}|{|uhfknrz
yqqsy
||}}{|}~~~~~~~}}~~~~~~~~}}}}|zzz{{{{{{||{{{{z|~~~~}}}}~~~}}}}}zrjddcekpsqngbcir|
}z~
¢
~|{z{{|}x~
ywx
|{|zlfinr{
uprt|}}}}}}~~~~~~}}||}}||~~~~}}{{{{yzzzz{{||||{{{|~~~~~}}|}}|ztjddeeiossqlfaelx
xnu|~{|§¬¦
~||{{|}y|z{}
~}}~mdfknu
{rnqx~~~~~~~~~~~~}}||||||}}~~~}|{{{{yzzzz{{||||{{{|}}}}}}}}}}~~}}}{ukdcegimqttpkhgip|
|giu||~
|{|§«¬©£
~}}}}}~
~{
~~
qjiilruoqv{~~~~~~~~||}||||||~~~}|}|{{{z{{{{zz{{{{{{||zz||}}~~~~~}|ulcaehlnqvwsnjghlsl`lvvxwx|
~¨ª¥¢~~}}~
yx{~
ykjklr{|spty~~~~~~~~~~}|||||~~}}|||{z{{{{zz{{{{{{||}}||}}}~~~}wmd`dhknsvxwqlhgks{
qacpvwwx|
}tsw|{lklmpxxqrv{~~~}|||{}}||}}|||}~~~~}|}}{{z||{zzzz{{{|}}}}}}}}|}~~}}~}zshbbhkmsyz|wqlihnt{~vf`mtwxz~
~~
{totz
qkonnw
|trtw}
~~~}}}}}||||}}|||}~~~~}}~~}|||{{z|{zzz{{{{{|||}}}}}}|}~{tkcafikqwyzytmihlpw{
~yk`m~
|tkchu
vlknps|xqrvz~~~~~~||{{{{||||||||}~~~~||~~|}||||||zz{{{{{{||{{|||}}}}}~~~~zpgbeilpw{||yunihnuxz
~q`i
xslb[gz
pjkptzvqsw}~~~~||}|{{{{{{|||||||}~~~~~~~~~~~~~~}}|}}}}}||{{{{||||||{{{{{|}}}}~~~~wlbchjnty{}{yuqmmnmjm~
wee{
}wqlcRPgz
unmnqu}ursx~~~|~~~~{{{{{{{|||||||}}}~~~~~~~~~~~~||}}||}}||||||{{{{||||||}}~~~|vkegikov{}}}{wspplc_i
zkew
~vmbOLZp
}olnpt{|ssu{~~~~}|}|{|{{{{{{|||||||}}}~~~~~~}}||}}}}}}||||||{{{{|||||}}}~~}{vnhjkmsx}~{{zxuspg^_m
{mbq
~zwv{zrnotz~
smnprw~xssv|
~~~~}|||{{{{{{{{||{{{{|}}}}}}}}}~~}}}}}}}}}}||||||{{||{{}}|}||}}~~|ztpknmpw|~~}}zwsld]_n
|tpgg
ujcgqz~|||~{}
{omops{tqsv}~~~~}|{{z{{{{{{{||{{{{|}}}}}}}}}~~~~}}}}}}}}}}}}||||||{{||{{{{z|||}}~~{ytpmnory|~}zwpgZXfu
{rl`e|
|qc^]eoz}yx|~||~
rllnpv}{ssuz}}}}||||}}||{{zz{{{{{{zz{{}}||}}|}}}}~~~~~~~~~~~~}}}}}}}}}}||{{zz{{zzzyyz||z||||yplmknry~~zsk]Z]js}
{unbo
}
|qe^Y\emt{}zyyy{~}|~
znnlmqxwsru}}}}}{{||}}||{{zz{{{{{{|||||||||||}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}||||{{||zyyz{{yyz{yunkkjjnx|~{tkb[\aemx
}~~ywz}~~
}re^\]`ipwyzzyvvx{|{z}
~qmnmqu|tstx}{{{{{{y{{{{{{zzzzzz||{{}}}}}}zz}}}}}}}}~~~~~~~~~~~~}}}}}}}}}~~~~~}}||}}}}{zy{zywvxywrlggghmu{~ypiedcecags
yvv{}yyy|
~{tib`_^fmqtx|{zxx{{|{~
ynnnou{
~uuvy|}{{{{{y{{{{{{{zzzzzzzz{{{{||{{}}}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}||z{{zzyvuvvuqjfcfghowxphdejlgb`bk}
{vw}zwx|~
}{tmheeb``_`aiq{{tnmqtxy~
rmopqx}{tuu{xn{{yyzzzzzzyyzzyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||}}}}}}||~}}}}}}||{zzzyyyyvvtsrmgabchehryzridhnsrqh`^f}
|z|~|z{~
zkRFN\eca`a``eluwrnhb][dou~
|qnppuyywwyzok{{{{zzyyyyyyzyyyyyyyyyzz{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}~~~~~}}~~}}||{zzzyyxxwwtspkebcgiihjswrklqx|{wqhch
~}}|~~ueYX`egebabeecdfllhd^YUOU]^fr
wqsruy}|vwwz|rmk{{zzyyyyyyzzxxwwxxyyxxzz{|||||{{}}}}}}||}}}}}}}}}}|||||||~}}}}}}~~~~~|}|{yxxyyyyxwurohcadjmlgekrqpru{{rmq
~{~}wqs}|vnidcgkjeacdfeaXSONTSOQdy
uttty|~|wxx{tmmnzzyyxxyyyyyyxwyyyyyyyyzzz{||||}}}}}}}}~~~~~~}}~~}}|||||||~}}~~~~~~~~~|}|{yzzzzzzxwvtokfchnrrojkqqompyztv
yrrw||tporw{{{||wogcdhpojdb`_ZSKGFF@KJLWer~
|tssty}yvyy}
xpmpsyyyywwwwxxwyxxyyzzzzxyzz{{||||}}}}}}~~}}}}||||{{}}||||||}}~~}|}|{{{{||||zxxwvsnjgfjqturnkmnnihr}|wx
}
}
zpjgggjpuuqnjjnpsqpsx|}sjbbadnpleb\YTL@:68=EC@HUfu
vqrtv{~~wvyyvonquvxxwwwwwwxxwyxxyyyyyyxyzzzzzz||||}}}}~~}}}}||||{{}}||||||}}~~}|||{{{{{||||zxxwurnjeehotutpkjjjdcmzzvv}|
rcn}
|vy
vliigefginturmhcdghgis|ukgcabjrqjc]ZUOIB<>GMNRWPMcq}~ustvx}~~}wxy{{qmnty|wwvvwwwwwwwwwwyyxxyyzzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|zzyyy||||{{{{{yxvspkdcenswxuplkjgcjwytu~~
vfXYs~
srv|
yqoppljijjmtvvnieb`_`fs}xmhebcmzzrkd`]YVZ\ZYYTY_H,@_t
yppruz|}~
|wxy|}toonu}uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}~zwvuux{{{|{{{{{{zvsolfcbhnrwvtpkjgcet}~xvsv}~}{|zujaTN[y
xqssy
~wustvwupnklswwvrnjfffkv}rlfbfszxsked`^]^cfe_^_Y=,1ATi|
xqqruz|}~yvyz~xqpqu|ttvvwwwwvvxxwwvvwwyyzzzzzzyyz{||||{|}{}|}}}}}}||||||}}}}}~~~~~}{wsqsvz|~|}||{z{yyvrmgbcjosuvtroljdaiqropsv}
yoinqlf`TKLc{
vnnruy|~|yxtu}vpnoquvuutqpnnqv~{rld_cpwwtoiccbbcgjlkd[M:8FOZprooovz~}zwxy
ztpqrxvvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||~~}|}|}}|{||}}||||}}}}}~~|zwsqqsw{|~~~~|{zyxvrmecfnqvxxwvpmkf_biiimrx
}nggkid^VOGMl|
{tqruyzyyzywyx{
zqorvyxwwwvsqqtz}smfbht{{uqjhhgeddccb^TD:?Xw~rqoov|}yyyz
}ursuwuuvvuuuvwwxxxxxxyyyyyyyz||||{|||~}~~||}||||{{{{{{{|||||||}~{wrqqty}~|{wvttqoiefjpuvxyxvtqokghjmlosz
ulihhc[RNHEPu
zvruxzyz{yyzx|
zspsz|zywwxvssw}
|tkednx|ytqmllhbbcb_a`TKHPixuxxwz
{spoqx~~
~{zy{vrquvzuuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~~{wrqprx{|{vtokighihfeinsvwvxxyxurprtuwqnqxvnhdb^WPKCCXy
}wtwyy{{||{z}
ysqv}~}|zzyxuvz}yqkefowysnklmmjebcfcbaZSPXpzzwtrnlvxqppry~||zzy{ytrsw{uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z|||{{{{||{}~~}~~~~|ytnnov{zwqid`_^\]`chntuxxxxy{xwspty||sosx
unjfa[SLG@Db{
|vuxyzz~|{}~wppv|zwwy~uokeht|ztnihjjmgdefdab_YX_t yuqlfcrxoppry}}}{yzx{}ztrsvxttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~~|vqnorxwsjeaceb_^_elrvxxxxxy{ywtqsy|{rnrv
|xqh_YQG@<Ghz
|yy{{{~|{}}zxrpqx~zxz~~toicht|zulhgimkhhihfdb___ds
yphdb^bu
wqrsu{}}{{zxxxvqsuyttuuuuuvxxyyyywwxxxxyyyyzzzzzz{|{{{{{{{{{{{{{{{{||||||~~}~~ztqpptuofdhmpmfdfnsvyyxxyyzzyxtstx|zroqu{
}tjaZWME?CRp|
~{||||~|~|yurpqz~|~
|smgcju}|wplhklkmnnkgeca`bdp~
xnd^]\[dzursrt|~~
}{}}xuussux|
vvvvuuuvwwxxwwwwxxxxyyyyzzzz{{{|{{{{{{{{{{{{{{{{}}||||}}}uqmloqnhimuzyrkinuwyyxxyyyy{yvttwzyspqrwypg^YVOIDFTr
|||||~}|~|uqps~
zrlcdlx}|xtplkknooomkkheddflq}
}ujb_`^\ispont|~~
~|~~xsqqrtx
stuutuuvuuvwwwxxyyyyxxyyyyzz{{{{zz{{{{||{{{{{{{{||||||}}|~tmikliggrz|ywtruzzyxyyyyzzzzyxuuxxuqqrw
zreZXYRNIL\w
}||||}|}{tqss}
wplghr{~|xxupomlmpnmopnjhilmms|~xrh_`bbaas|roqqv~}
}{~}xqossu|~stuuvvvvvvwxyyxxyyyyxxyyyyyyzzzzzz{{zz{{{{{{{{{{||||~~}}~~~yokkgcaeow}|{zzz{{zyyyzzzzzzzyvvxytpnqw}
r_UTWWTR[gy
~~}|{{urqt}
}rmifjs}|yxxvtponnrsrqpqpnpsttwx|}yzwoe`_bdb`l|rooow~}
}}~zuqoquy~rsttuuwwwwvxyywwxxxxwwxxxyyyzzzzzzzzzzzz||{{{{{{{{{}~~~~~~~~~~~|wqlgb^_isz~~~}|{{{yyy{{zz{{{ywvvwuros|
yfWVZ\\Y_k|
~~}~|zz}vqpw
ynmjflw}}{ywwxsqppstrpqrqpquxxtpsuussojc^^begfiwzrqppx~|~~~vsnort{oqtttuvwwwvxwwwwxxxxwwxxwyyyzzzzzzzz{zzz{{{{{{{{}}{}~~~~~~~~~yrkfa^dpz~~~}|zzz{zz{{zz{{{yywwxyyy}}rcZ[^_`fr}
}{z~wqpv~
|qlljhpy|yxyyzxvtqtvtnmlkjjnppnlllmmnolia\]cghho}
xqpppx~~~{tpnoswoqrssvwvwwyyyyyyxxxxxxyyyyzzzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~~xpkfdhpz}~}}{yz{{{{{||{{{{{zxxw{|~
wl^X]bkt{
~{yz~zrlkmr{
|smkmkikoqvyz{}}}}|xyxumf_]]]_ehgfegjmpmkgd`_ahijmsy|}pppoqx~}~~~~yropqt|noprruwvxxyyyyyyxxxxxxxxxxyyzzzzzz{{yzzyyyyy{{||}}|||}~~~~~~~~~~{zuqprx~~}|{z{{{{{{{{{{{{{zxxy|~
wja`ky}}
~{yz~|qicacdht~
vljkjiehlkjlorwy}}yvrle\VTTTV[dhjmnppqnmiea`cfhiighlqvvrmllmoy~~}vqpqsv}mmopqttuvwwyyyyyyywwxxxxwwxxyyzzyyyy{{{{{{{{||{{||}}}}}}}}||}}~~~~~~~~|||}}|}|||{z||}}|{{|||||||yz|
}{y
|y{}}~
zyy~~ticbcbabkv
}rhghiiejllgecbeipvvrmhc^WROPSY_lv{zzzxxspkedbcddefebbbfhhhhhknw~}{sqqsuymmopprstvwwyyyyyyywwxxyyyyxxwwxxzz{{{{{{{{{{||||||}}}}}}}}}}}}}}~~~~~~~~~}||{zzz{{|}}|||||{z{||}|zz~
}
}|~{pjjiieb`cm|
{skjjhhfhiiigeecdbccdcb`]\YYZ^hy
}|wsppokikmopmkigdacccccgqxzy~}||xruusu{~mmnoqrsuvwyyyyyyyyxxxy{{yyyyzz{{{{zz{{zzzzzz||||||||||||}}}}}}}}~~}}{{}{}}}~}|}}|{zz||{{{{||||}}||}}{|
~|||}
~{~~
~ysrqpnlhaacp||{z
xnjlkgfhjijihggebbbbabccefediv~{xvvvtooty~}{ytnhdeddeekoru}zzwrssux{nnnopqrsvwyyyyyyyyxxxyyyyyyyzz{{{{||||{{{{||||||||||{{{{||||}}}}||}}}}}{||}~~~}|}}|{zz||{{||||||||||}}~
~|z}
~|~{}
}xtvxwtstoe`_flihims{{vomnmkggfefgecbcbddfdbagnsv{}}|ywz
}umkigeefjlq{|{ytrssuy~
~~nonopqrswxzzyyyyyyyyzzzzyyyyzz{{||||{z{{{{||||||{{{{{{{{{|||||||}}}}{{{{|||||}}}{{}}||||||||||||{{||||~~~~{|
}~
{zqptvrptyvnfaaded`bflqolmnnjkiidcddddccddfhhedmw|
}~}vrpkhefhiiszzvssssvz
{zlnnooqrsvwwxyyyyyyzzzzzzyyyyzz||~~~~}{{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}||{{{{||||||||||}}||}}||~~
~
{{snoqroptx{ulbaddeeffegiklkklpssoigfddddeeegmun_Ycw
}ytnkjjggny{yxussrsw|}nnnnnpqrvxyxzzyyzz{{{{{{{{||}}}}~~}||||||||{{{{{{{{zz{{z|}}|{|||||{{{zzz{{{}}{{}}{{{{||||||}}~~}}}}}~
|}unklooorvyyuleehijjecccegfdejnvupjfefeebbcbejjcWIVu
}zvsnlhjr{
{xtsttsuz~}
nnnnnpqrtvwyyyyyzz{{{{{{{{||}}}}~~}||||||||||}}||{{{{{{z|{{z{{{{{{zyyzzz{{{zz{{{{||||||{{{{||||}}}}}}}~
{zxrlklmlnqvyysmhgikmponjihgfedfhjkkgggfeca_]][]]ZTRXe
¢¢
{yvqmiosw}wvtsttsv{zoonnnpqsvvwxyyyyzzzz{{|||||||}~~~~~~~~|{{{{{||}}||||{||{{{||{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~}||}}}~
wrolnppolmmty{snkhhlppuwupolhfcccbcghijihhie]ZXYYXY_aev ¢ {zwplprty~~wutssssx|}
oonnnpqsvvwxyyyyzzzz{{{{{{||}~~~~~~~~|{{{{{||||||||{||{{{{{zzzzzzzzzzzzzzzzzzzzzz{{{{{{{yzz{{||~~}}}}}~
{snopqpmiiiowxrmihinqtvutsqokebaaacdglopqrsrjeddb`agilv¢
~~wnrvttv|}ussqssvz
|ppoonpqtvvwxxyxxyyyy{{{{|{}}~~~}|||}|{{{}}||}}}}zzzyyyyyxxxxzzzzzzyyzzzzzzzzyyz{zz{{{{z|||}}~~~~}
|sqrtspnihgnyxrkgcgpssstsqomjedbbacehmqrsvtutropmllonqz¡¤¢¡
zw}~~zty|vurv|ztuttttx||
ppppnpqtvvwxxyyyyyzz{{{{{~~~~~~~~}}||}|{zz}}||}}}}||zyyyyyxxxxwwxxyyyyzzzzzzzzzzz{{{{{{{z|||~~}
zqstuusqnhejsuoieclruvutsomkfdcbdegkmoqstwvtrqsvtssuwvx¤¦¤¢¡¡¡ vnr{~{z
yvuu}
ystuuuv|
|
ppoonrsuwwwwwwxzzzzzz{}}~~~~~~~~|}}||||||||}}}}}{zyyyywyyxxxxxxxxyyzzyyyyz|{{{{{{||{{{{||}~~~}}~~
{tsyxusuphfkqqlgdentwxvuqokjgedefhlqqrrruuqpnpw{{xvvvspr}¥¥¥£¢¢¢¡¡vjiu~}|ywxzzuvvvux||~
ppppqqsuuuwwwwwxyyzzz{||}~}}|}}||||||||}}}}{zzyyyywwwxxxxxxxxyyyyyyyyyz{{{{{{||{{{{||}~~~~~
{xyxxwwrljptrmjdgqvwzzwrpiiifeeinvywuuusrmhgkry{vsttpmmt¢¥¥¥£¢¢¢¢
qgm{}{xsu{
vtwuuvyy{zppqqprqttuxwxxvwxx{{{||}}}}}}~~~|}}~~||{{||}}}}{{{{zzywxxwwvvwwwyyyxxyyzz{{{{||||{{||}}}}}}~~~~~
zvwyzvonpsqmhflvxuxxtqmjiiggflq|}xxuronidbluxutrrrnkq¢¥¦¥¤¤£
siky~zyx{|uvwuvvxwz~xqqqqrsttttutvvvwxxyyz{|}}}}}}}}}~~~~~~~~}}||||}}}}}}}}{{{{zzywxxxxxxxxwyyyxxyyzzzz{{||||||||}}}}}}~~~~}
}~}wrpqsplhfpzzxyxuojjllllklpwz{yvurrpkfelttstsssoor|£¦¦¥¤¥¤¢|qgju~~z~{}xsuvuvwy{yqnrrqrssttstuuwwwxyyzzz{||||}}||}~~~~~}}}}}}||{{}}}}~~}}{{|z{{yyxxyyzzzzyxyyxxxxyyyyzz{{{{z{{{{|}}}}}~~~~~
vqqrrolhgs~z{zvrmmmpooonkmorstxtssolklpsqtutrrssy
¢¦¦¦¤¤¥¢
zrmnw}}z}}}xuvvwwx
ytmigkqqqrsstttuuvwwwxyyyyxyzzzzzz{{{{{{{{}}}}}}||}}~~~~~~}}||}|{{zzyyyyzzzzzyyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~}
xrrrqnkgfs}~~zussstrrqpljjjjkopqqqqplloqtutuussw¢¥§§¥¥¤
zrmntz~~z}}xuvvvv{}vtxwpjdbcejnrrssrrttttvvwwwwyyxxwwxxyzyyzzy{zzz{||}}~~}}}~~~~}}}{yzzzzxxxxzzzzzzzzyyyyyyz{{{||||||||{|}}}}~~~~
zvuupnjgdp}|zuttuutqswtomhgfgjmmmpnmlnrvtustuw~
¡¥¨§¥¥¢
~wolquz}y{
zvuuuux~vnkligfebbeimuuttttttttttuuuuvvwwwwwwwwxxyyxzzz{|||}}|||~}}~~~~}}}|zzzzzzzyyzzzzyyzzyyyyyyz{{{||||||||{|}}}}~~~~
|{|xrjfs}zwuuuttromu~{wpkgefggilljjkmpuvvwx}£¥§§¦¢{sqsy{x{~wtuuuwzxqponoqrsrrtxzttttttttssuuuuttvvwwwwwwwwvwwwwwyy{|||}}||||}}~~~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~}}|
|~
you{xuuuwvtrmhn{{snjc``dfgiljknpruwz
£¦¦¦ysqvyvu|}}~|vvvuuwzwprtwwwz|~
ttssttttttuuttttuuuuuuuuvvwxxxxxxxz{{{{{|||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~
yustuwvsmgfo|}xpdbbabcehikmoqs|¢¥¦¦{ttqnkjkhiiinstsvvuuy{ztsuwz{~ttssrsttuuuuuuvvuuttstuuuwwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyyy{{{{zzzzz{zz{{{{|z{{{{{|{{||||~~}}}}}}~}
|z{yxtqlhehozysjhgc``afgghimr{
¤§§¡yqlifebaa__bgmpqrtuvx~}vuvvx~
zttssrsttuuuuuuttttsssttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zy{{{{{{zz{{{|{{|||||{{{{{{|||||||~~~~}}}}}
|umjhhmttnkjnokjiiggffghkrx}£¥£~~~||{xxwvtooifedccb`]\_`cgmpsuuxz{ww{
ssttttttuuuuttsssssstuuvvvvvvvvvvvxxyyyz{{{{{{{|}}~~~~~}}{z{{zzzz{{zz{{z{{{||{{{|||{{{}||}}{{||~~~~}}|~
|tpqqomjmrssqtrnjgeddhmrv|
¡}}|zyyzyxvuttsqolhgfeddcceffbaaadgkntw{}
rrssttttuuuuttttssssttuvvvvvwwwwxxyyyyyz{{{{{{{|}}~~~~~}}}|||{{{{{{{{||{|||||{{||||}}{}||||{{{{~~~~}}|~
~{wrjgddfinrwz}|yyxzyyzzzz{{xywvtrpliidcacefhhgfdddefhlu
ssssssssttutssttssrsrsttvvwwwwwwxxyyzz{{{{||zzz|}}~~~~}}}}~~}|||||||{{}|||||||{{||}}~~}}}}}}||||~~~~}|~
~unhddfijlory|{zyxxxxyxx{}~}yvuspliea`cgghjjigfcccfkv
ttttuuuuvvutttttssrsuuvwwwwwxxxxxxyyyyyy{{|||||}}}~~~~}}}}}}|{{{||||}}}|||||||||||}}~~}}}}}}||||~~~~~}}zoigghihhklnoqvvxxy{}~zwwurpnkjieeimoomkifccedem|ssttuuuuuuutttttssrtuvwwwwwwxyzzzzyyzzzz{{|||||}|~~~}}}|}}||||}}}}}}||||{{}~~~~~}}}}}{{{{{||||{{{|{}
~~
{phjiijllggjmossw|~yxxyywyyvuojjlmqqookjfcddbepuuuuuuuuuuutttttsstuvvwxxxyyxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|~
|}
{rlihillheeghlqu~
}tppoqrrpppnjgddcbfrvvuuvvuuvvvutssstttvxwyzzyzz{{{{||{{{{zz{{zz{{}}}}~~}||||}}}}}}}}||||}}~~~~~}||||{{{{|||||||||}
~{~
zqkfgjmkjfcffgjnrv{
wpmrvyyuqponjgffecix
uuuuuuuuvvvutssstttvwxzz{zyyzzzz||||{{zz{{{{||}}}}~~~}||||}}}}}}}}~~||}}}}}}}}|{||{{||}}|||||||}{z{
zpjijmqpnfcccdehhimr{|rmmt}}ytpnljihffgmx
ttvuttttvvvussrrsuvvwyz{||||}}}}}}}}||||}}}}}}~~~~~~~~~~}}}}{{||}}}}}}~~}}}|}~~~}|}|||||{|||||||{~
ywy~
}qffgorokffcbdfdacehow
xpnrzyqlknnligghnxwwvuvvvvvvuvttsstvwwwyz{||}}~~}}}}}}}}||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~}|{}~~|||{{{zz{|||||||{~yx{
uhehmqtqkfa`dikjfdejr~wppw
xnklpqpkhhjmuwwxxwwwwwwvvvvuutuvwxxz{}~~|}}}}||}}||}}}}}}~~~~~~~~~~}}||}}}}|}~~~~~~~~}}||}|}|{{{zyxyzz{}}||}zw{
~whdfkqsqlfeimsvurnqv~|vqt{~tmkptytmiijlt~xxxxwwwwwwvvwwwwvwyz{{{|}~~~~}}||}}}}}}}}}}~~~~~~~~}}~~}}}}|}~~~~~~}}~~}||{zzzyxwxyz{||||}
yxz
~
zngefnvxsmhjov
wppxyrllovzwpjhgglv
xxwwwuwwwwwwxxxxvwz{|~}~~~~~}}~~}|||}}~~~~~~~~~~~~~~~~}~~~~~}}}}}}}}|{{{{yyyxxxwyzz{{||xuz}|~~ujcdny|xsnjmv~wppv~}wrqpuy{xqjggehq~
xxxxyxxxwwwwxxyywxz{|~}~~~~~}}~~}|||}}}}}}~~~~~~~~}|}}~~~}}}}}}}}|{{xxwwxwwwvxzz{{|||uv}z|
{nbajx~|tljmt}
zspt{}yxsruy{voiecchuzzzzyxxxxxwwxxyyzyy{{|~~}~~}}}}}}}}}}||}~~~~~}}}}}}}}}|||{zzyxxwuxxwwxyxz{||wx|
}{|{j^\fu|{uoons}
wrry~|xtvxwrjfebem}zzzzyxxxxxxxyyzzzyz{{|~~}~~}}}}}}}}}}}~~~~~~~}}}}}}}}~~~~|}||zyyyxxwuvvvvvwxzz{~
~uvz
~
~n_\^gq{|vpns}{uru{zwwxxrkjgejw{{yyxxxxyyyyyyzzz|||}}}}}~~}}}}}}~~}}~~~~~~~}}}|||||||{zywxxwuuvvuuvvxyy{~xuy
}h^YZcu}zuqnt}}vrru{xvxvsomiefq{
{{yyxxxxyyzz{{zz{|}}}}}}}}}~~~~~~~~~}}}||||||{zzywxwvuuvvuuvwwxxz}~vx}
{h\WXct}~ytor}ysmov{}|wvuqnkgfis~
\ No newline at end of file
diff --git a/src/media/test/data/bali_640x360_P422.yuv b/src/media/test/data/bali_640x360_P422.yuv
deleted file mode 100644
index a7616ca..0000000
--- a/src/media/test/data/bali_640x360_P422.yuv
+++ /dev/null
@@ -1 +0,0 @@
-DEHKLLMNNPPPPQSQSRRRSSRRQSQQPONMLJKJGGFCAA>>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~
}wmgXH;/)%$$#$%%'*.178>CLS^emt{|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039<CNV]flqty{|zzvrmd]OE92,+)((''''&((()))+*-01236778=>?CFFJNU\bkptxz{}~}{yzvttx{
~|zxutty
|xpfXJ=4/.-,038=DNZbksx|~}{yvwwzz|ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~~~~wsk`UH<2+*++++++,,+,17?JVamw}
{tk^P?3*&$$$%&(,/258=AEJRZcjr|zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<CMU]flqty{|zzwvof^RG;3.+)((''''&'(()))+,..0035689;=ABDGKOX^dkpty}~~}|{wsrrvyz}
}{yyxy~
{umbRD92/...038=BMXbksx||zwwtssvxzxnbWNIFA><;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{|vqi^QB6.*+***++++++05=EQ_jr{
}xncTE6-&$$%&',/148=>BDHLU^eowzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{
}|zwtrppquvy}
~~{yzz{}yrj\N>51-,,.037<CMW`irx|}{xwtrooruxwk^SNIFA>;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz~|vmbWI;4,*,******++.28ANXcoy~}tj[M=0)$$$%+.168;?BDCFKOXdmu~}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx}
~|yvtqoooppsux~~}{||~
{vofXI;3//0..025:BMU^gpw|~~~~|{xvtqmllnrv{
wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz}xql\OB8/+*****+++,-28?IWamw{vm_RB4*%%%(.249<@CDEDFINU_iqzyri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{
~}xvtplkjjkmot{~}}}~
zslbRC82/..../26;BMWaiqwz~~}}{wusrmiiimqv|
vgZNHGEC@><JJKJLLNNOOPPOOPRRQPPQQPPPOOMLJJJHGEDCA@?>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{}vmcWI<2.-****+++,,07<FS`ku~
yrdWG6-))).158:?BDFEFFLNRYdow~
{tk`\djkeWLQi[ONV\YT&""$$$##$"""$(-4:@EKQW]^bhlqvyz||zzwwwwvutpj`SICA==>>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz}
zxvtokhgeegjms{~~~}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx
~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB><AGQ]fow
{si^PB60,,*+++*++,-49BO[ht||tj]O>1++/479<>BCEFFGGILPX`jr{
|ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~
}{wspnkihdbcgint{~
}wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks}
|voeYG:2.,*+++*+++,17?KXdq||umbRA4//269=?BDGFGGHHHKPV[emu~|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~}yuqoljgda_`cgjnty}
}vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~
~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~}wpj^PA6/+****(**++/7<FT`mxypeYJ;4369;?DFGGHHIIIHILQV_gqz}umd`TUWD;2CZ/-0AQEML8"%%%%$$'*04;AFLRV[^_bdeijjlqtvxxxssutttrmi_QH@<;:;;;;:;;;;;:98876534222100./19=DNZaiquvxz||||ytlcZM@81-+)))*(+++,,,-///26:>DHNSX^cikosu|}|wtpjhfca^]_cgimou{~
|ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov}
|sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}zsmcVI;2-***+*)*++.29DQ^gt}|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27<DNXbiquvxz||||ysnf[NC82+,,++*)+++,-..0139=CHMQW\`fjnqvz|~}wskgea`\\\^behknptz~~~}~
|ulaTF=864200249>FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~
|sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqx~{wpi]PA3.**)**))**,29@KWdny|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA<C:1*2.,0<Ab2%%&&(-06:@FLSXZ^_bcdddbacdjmrtsttssssspme[PD=988889:;:::::998875542221/00/016;EMXaipuvxyz{||yxqh_SD:3.--++,,,,-../139=AFLPV[aejnrvy|
|{sjgb][YZ]_cehkmpsvz|}}}}~
|uk`SF>98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}~}ytmdUG:0+*********07=ES`jw~
|woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9<EIcK%%(*,18;AGLRVY^_aacde`]\\^dhnqtutsssssojdZLA=;887789988888776665553221/0/..26;DNYbipuvxyz{|||xri_SI<71//----..//137;AFKOU[_ejnruw|~
~zumg`[XYZ\_behkloqtw{{}}|~~|uj_RE?:965547;AHPV\cjsx}|||zwsmh`[XURPOOS\fmw
{sh^RMHFCB@?MMOOQQQPOOOOPPOOPPONLLKIJIGFDBAB@@????@@===<====<<;;::::::888898776665443333222022022200/..-,,,,,,++**,,+*****++++,,--.........///....-,---,,,++++****))))))))(())))))))(((((((((((((()))')))***+++++,,---.//1231455665542433121./2569>DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy~}{vph\N@4-*)**)****.6:BP\hq|
}{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~zvqh_YVVY\_cfikmnoqtw{{{{{}{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}~~}ysmcUI90*)))()****07>IWdnw~|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7<BFNYY[`liTMVa[[aRPPUZaipuutstrponh`UI?856656776666665544332220///.../04:CMV_gotwywy{{{|ysmcYL@:62111111348=DIPT\`fimruy|~}}}}|ytld]XWY\^bfikmooprvy|z{{||{tj^PGC?>=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq|
~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5<DS`lv||xuog\RKHHHIKKKKJHEA<82216AP_aniRhdZTRGQQJQYabaa`]U?4<I[rr:6>ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|~~}}~}}{xtphd[YZZ]afilooqrrtvy|}|||{
{rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~
}yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}}~}yslaTE7-*))))****-39BM[fq{}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~wjh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:<FR\hw
{vrkaUMEA@>>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5<HR]gqz}}zvpfYL=3,****))**,16=IWcmw~|zvulg^UOKMMMNMJGB>:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[ntosmhmlepzqj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}~|}}|}~}~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny
{wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~}||xql_SD6-**(())***.49DR_jr{~~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~
|{{z{}|~~~~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz
}yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs}
}|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5<GUcksz~}~}}{{yuph_VQMJIIEA<70*&#" $*;_afkqmeLFU[\VTX[YOXRQZX^`gibR@VNDe~ynf_V^K:8=QVHPSGFGKVdouwxxwyywuli\PC90-++++,---....-----,--,,,,++**,-17>GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx}
~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{}
{slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4<GT^hrx{|{{}|ytmeWH:0))))))*)).28BN\how|}|}||{zxtmbYRLIIFB;63+&#! ##(;MUhfc\Y2@Z[WR]QPZVZUP^[W^`jeW4ONNoordbgXZWM8=GCQWD:6?M[gpvxxyyxxvtni_OB80*++,+++++**+-,,+***)+,,**+)++--07>FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuz
}}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224<FTcnz
|yxxvtneXMC?;99:OOOMLLKJKKIHGFEEB?>;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}}vkdb_][^^acfjorvy|}}{{{{yvpkaUJ=73128>KXds}
~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....00228<AGKQUZ_dfhkmlnnlijda^]ZWWSOE<5.**)((''''''(('',/4=HS^jqwzyz||{yrpdUD8/+)(((((()/3;GT`grx{}}}}|wsk`UOEB>94+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}}vleb_^^_`cfhlotvy|}}yzzyvtmg^OD;6433:@O]iv
{wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158<BGLQWZ^cgilmpqpqmkgc`^]ZWTOH@93,)*)((''''''(('')-18BNXenuxz{{zzzvqgZI=/*)(((((((-17?MXdowz}~~}|zuof[PGA:4/)&"!!! !7YghZ`]c_HMWTROQ^lT5@IKGS[^\WOFEEBHKJV`N]``e[QGDTG9((3:CQ\hqvxxvvvvusoi^O@6.)''''''(((())**)(''''%%'()**,//38>EQWbkqtvxwwwy{{{wunia]\]`glpsw}~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435<DSamy
}ywvvvvtldYMC=89;@NMLKJGIHGFDB@B==:987766667889;::::<<<<<<==>>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/5<IUaisx{zz{|{xskaQ@3+((('''''+.6=IUamuy|}}yrj]SE>6/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy|
}vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{
|xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iuyvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237<CGMRW\_ehjmnpprrponkgc^[ZWTPJC93,)))''&&''&''''&(''&+/5@MXcntxzz{||{xsj\L<0)'&&'''()-29BNYenw|~~wqgVH;1-*%$#!!##\c_uQ>TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy}
|tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lxxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5</&(-2;GT_kty{{{|||{unaQB5+'&&'''()-1:ANYantz~zsiZJ;/(%&%$!!"7uYKB<;KOVaT?12;@?EBGJA8FIKEIEN[^`bdeecZ>=ICEB^NCP[\J(+3<EQ]gqssrrrstusmh^O@6,)%%&&$$$$%%%&&&&&%%$%')+,/01346:>CIOXaiosvxxwvxy{}~}{zwwwy}~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}}vl_O;-$$###$#%<kL;453=LLbbaUB='799:BA=GKJLPTX]_`adbc^T<5@CEEjmMICDH105<FR[gqsttssttttmg^PA5-)%%%%$$%%$$%%%%%%&%'))*,.023579:?EKQYahosvxwuuwz}}~~
}{zwvqnnkeb``abdhjlppruutrrpopppnlmjfb_^]]]\^`bgjpsv{{{{zyvz~{rjfeedcbabfikosvwxwwwxywwtpjd]PD=:88<BJXer|
|wtsssttsqi_RGCFJPVKIGFGDC?<:9754321101002244444788889:;;<=>>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K</('&'&'*+06:AJT`jry}yqbR@1(#$$$$$'BnY>6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<AEKQYahosuyxwvx{~
}|yvsrmlifcaabbcfgkoqrtuutrpooooonlkhd`^]\\ZZ[\acinsv{{}|ywy|
|siedda`_^`cfjosuvvuuuuwwwtpkcZNE><;;?FQ^ivzwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}{tiZF6,&$##%(([kX<6<BAIYentwtVKHCADLPMGCLTXZ]``cba]XTNF>BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~
}|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz}
~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvyvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw}
|{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz}
zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzyurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047<CINRW[_cgiklkiigggfeca]\[YUQI?80,))))(((((('&''&&&&&&%%&')05?O[epwz{z||||xri[K;-&'))-157;?DKOYbkrz~}uhVE6+&"$#$(@NJ>799<JUbqtwtlNCB97>FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{
~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~
}wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{
~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2<IVclwz{zyy{|zuk^N?2*))+/379>@EKOWahpx~~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{
~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~
zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257<?CFJNU]gnv~ysfVC3'%$##"5id,#'/:CSbntusk`6%%*+7LYZ[_`_a_YUQKE=71/.338HPQOUNRW45:ALYcmuwyxuutwtrok`RB91-***(''''''''()+-/012468;=?ACFIKNSW[aflptvwxz}|ytolgd`\\^``acegffgikmnprtusqqoonomkifda]YVUTSTSTUX\dkpux{zxxxz~~ysleb`^]\[]^bejmpqrrnnnorssuqmg_WOMIGINU\fp{}xusttvvwwuqld]]_djoCA<:986543111...//.....0111245688899;;==>>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~
}ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt}
}xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhszxreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47<AOYdnvxwwuuvvwuokbRD:2-**))***'(()*,-.014579:<?BEHILPQSZ\beimpsvy{{unida^\Z[]]__bddefgiklopqttsrqpoonnkifb^[YVUTQPPOOOSYaiovxzzyxyz~{vmb^^][[[Z]adiknqsqpoomorsttpkd]VQQOOQV]fow
|yvutvvvxxvuqjecehls=::86555432200.---..../111435677999:<<==>???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:<?ACBEHLOV`gpz}tkYF5+&&=6&%%!%+/6CQ`mtxzzxrme][Z\^_`_\YTPJD=5.,+,----7uXQNLJS?/6;DQ[cmuwwuvvvvwupj_RD:3.++*)**+*++,,--/024689<>@DGJKNRUW[_cgknstvx{~}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{
ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{
|yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....<jfPQNT^X87>DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{
ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxzyria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~
~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358<BFKORWX\^^^_]ZWVVVVY\accdba\VMC;4-+**++**))**))))('(('''''''''')-4>LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7<FS^gpuwvuvvvvvspi]QE:3..-,,,,,,,+,-./14688:<>ADHJMPSVY\_cdhmqstww{
|wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy||yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw
~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<?DIPRVZ[]^abcc`_^\\\]`baadeca]VMD;4.++**+++++,++****)+))((''''''&&()/5?LXforuuuutvwtpk_SHA@BCEFFFGGGIMMR[dmv}}tiXE5($"$#$##&,4@MZhsz}||yulec^\WQLFA:2-****++,-.//..3YTPIJMV=:AKV_lwyyxuuvxxvsnh]OD=62//....-,..-/023568:<?AFIJMQSVX\_dfjnrvx{|}~
}xrnhaZWSQPORRRSYZ[\_achjlnppsssrrooopolgda\WRONKJGCA><<?ELV_ipx{ywwy
yne]XXXWXZ^`chkmnppppopqruutrnib]ZXZ^bgmv|~|zxxx||zwvuuwxyyywtqmkmqtv65666543321100.-0011113534677789:::;<<==>??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy||xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::<AJU_hovwwuwy~
xqg`ZZZY\[_cfhkooppoooqrsurtrmhb][Z[^djqx~zxttx{{xyvwwyyyyxwtqmknpru774455433211110..122113457777899::;;==??@>@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~
|xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}zrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmsz}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:<???@@AAAAABBBA@A>=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt||vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLE<BNYeovyzxxwwxxvtmg]PD<843222221-.//0213579:=@CGJMPSVXZ^acglprv{
|ytqkc^WSQPOOPRSUUWYZ[^abdhjmooopqtsqqqrpolic\WPLFC@=<986568>JU`ipuwwuxy
ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2<HUdmsvvvvvvyywti_SIEDEGGFEEB>;;<@LXdrzwp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{
xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589<?BFHKJJIJJIOTX]_ehhhedegfeedddceefggfgihhheb[SLA60*))**))(('&''&%%%%%%%$$###########$%%%%%%%&''''()*+,,--..///////0469;AGLQRXZ`dgimoqsswuromjfecacdcbb`[UOF<51/.,,,,,,,,,,-,,++,,,,++++**(())((((),19DR_jqvutttvyzzumbXMGFFGGFEC?;878<FTbmvzseTB2)')+049?EMT]hqwxwwroi\PH@8/+))))**++,,,-..//1122=ghRPIZaJM[hsxzyxwwwwwusmh\OE=85434322000000004679;>ACHKMORTXY]^cglptx~
}ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{
xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247<?DKPTVY_bgjmoqrvuyxwtqmieddddccb_YVQFA92,,,,,,----,,..----..-,----+**())))))),27?N]iqtuutuvwyywpg\QIGGHEGEC=74449AO_ky
~wkZH9.*-04:>CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz}
yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry
|wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249<AFKPUY\cginnrtvxz{|{wtrmjgfdccb`^YVOH@82-,,,,,,------..----....--...,+*))))))),/5>KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~
~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv||wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq|
{vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~
}{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx
yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my
~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpx~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt|
zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4<FOZcmrwusj`RMD=:53-*(&&&&''&&%$#$$#"#"#####$$$$$$$%%&&''()))+,--..///0258<AJPT[_cgjmprsuwwz||yxwqonkgd`b^[XSOKD=61.-,,,,----.-..//./....//022446540/--,,*))*,09BP\gpttttttvxzyqi_VLGFC@<83.+().5?LZiv
}tk[LCACGJNQSV\cirw{zzwqi^PA4.******++,,....//..0022MnaAKHJAMU`mv||{xvvwwvvtoh^QG?:855554411220011133556;>AEEHJMPTVW\]bhow}
~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~
wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow|
wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw}
~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~|yrkdZRH?7248AMZfovxyy{
wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz
}wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~~{qkbZQE<549DP[fpwyyyz~ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}~}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~
~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~~zrjaYOE<9;FQ]hqxzzz}
ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz}~zoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gry
~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}xribVKB>?JS_jqy{{z~
}wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw}~
~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389<CFEFEDB>:7778;<=@CGJPY`iry
~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~|xpg[TIABKT`krwyy{}
{tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowwohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|}~
ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy
}||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|{tlb[PJFNWblsy{{}
}vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goywlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~
zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz}|}{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz}yog_WPOQYbltz{{}~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr|
~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw~}}
~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry
}|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~
|{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~
~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059<CDHKOT\dnv|
|ocRA.$"#$$$$$$%%&&'(*+++--../013467;?DJQX^dgjlonmmh`ZPJCA=:86544311200////----..........//00123344556677777777665533321000///137=HS_jpsspnmnorolg\QE:4/+)('()()*+/5<JZgs|~|||~xpg_YTPHECCGPZfptfM>D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538<BKPV`hou}
~}~~|zvrjbVMFA?=>=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz
xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz~|z{~zrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz
~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKC<?GQYahmuuvqkgdbadjry}~|}{{}{}}}{{zzzyyxyzz|{zuoiaZSNLJLOU_kvzqhdejrwzxvuuuvvvvuvwwwsoolljh99999999998788778888::::::::;<<<<<<<<<;:::9987776666445544553334521100.---*++*()))((&&''&%%%&&%%&%%&''''&&&&''''''''''&&&&''%%&%%%$$&())*)*0<CKT]flrvyz|
~tdS?*!!$$$$$$%&((()*+,,-../0013689>DJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346<BMVblu{}ulbUI<77;BHOV]flsy
}}~|}~}{wqh_TJC?=<;;;<<;;;<<<<=BGNV[`fkkmpqpqromje\RJ?76=EPX`flswrmiifgimtz}~{ywsuw|}~{{zzxyzz|{ytmg]VNHIHIOU`mwxmebhlrwxywvvvvvvvuvwwvsomhhge:::::::9998777778888:99999;;::::;;<<<<:;:;99887765555455544443343201/---+,*)((''&&'&'&%$%%%%%%%%&&%%&&''&&&&&&&'&&''''''&&%'&&&&$$%%%%%&%&'+05?FNTZaeilpx~
yiXD-"$####$%&'())*++-,-../012469>AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137<EPZdpy~umaUI?<>AHPW_emrx}
~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~
|vrniltx}~|{{{{{{}{xqkd\TLHFFIMWdp|
~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbnyym\D-$#####$%&'())***,---/0/1369<BGLTY^egjlnnmgc\UJB;7444443333212100000/....----....001112334555679999::::98877665442222111348;@KValpponlnnnppmg_RC6.***)*****)*/6;IXery~}zxxz}|wl_RF=6148=FR^kd=BgvofXJ=5.,++,...--.//../0Cn{v^ONYT>NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|~ul`THAAFIOW[cjpx}~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~zrkebemrx|~||{{{{{}{vniaXOICDEGMXfr}|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv
zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~~yobSD82036<EQ]jaDFowrh]L@5/---,,--....-.02<_klrXGNNZSQRgjt|~~{yxwwyzxwuogYJ@84211249=GR_jw~uk`TLGEIPVZ`gnv|~~||||~{wqi^PF?986857777787768<BGQZ_gimnmmnmmnjgc[RH=6.+-05=FS\eloqqqqtuxz|~}
~wla\Y^fksy~~}|||}|||yslf\SJEBABFO[ht{pfacfkszyxwuttvvvvvvuspnjhgea^;;;;::;;::987888888899999::;9:;;9999:877756666434465444323444111/.-.-,++**)(('(('&&&%&&%&&%%%%%%$$$%&&''''&&%%&'&&&&&&&&&&&&&&&&%%%%$$$$####"""$&',./6BQdsylX>)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247<AIR]fnrpnlnooprrlf[M?4,,)+)*+++*,05<FUalv}~{xxy|}~{reSB72036:DO[g[ERutoj_NA6/-+++,--......03KjjulLGFHMLNRons{}~{yxwwwyxwtngYJ@84212239>HUbmy
~uk`TLHMSXY\dmt{
~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jwxnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|}yyx{|~{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcozxm`UQQSVX[agpw}
~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~
zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{
uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26<BKT`kt|~zxx{|~|vl[K=5025:BMWdPSgvurmcSD;2-,,+,,,--,--.08lzuubNED>EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~|pdYVVXZ\]dirx~
~~~|||}~~|ytof[OC:644444555444457<DMU\bhkllmmlkiifa[TI@30+))+04:EP\fmrvy{|
xmcUJJNWahpwz}~~}~~~|{ytle^RHA<:>BKWco||ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my
zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}~z{{|~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv
rg]XXY\]`elrz~}~|~~}~|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~
~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}|{{z}{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}}{zyxwyyyxuneVI>843125;AIS^lxth`[[\]]bflrv|
~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~
}vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiuxpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs|
~{z}~xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{~{zyxxyyywtmeYK=752548=ELXcp|}tib^\]]_aejpvy
}}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{
~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_ly}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+2<M]o{vfU=*&%'+-,())**++-/26<BIMSU[^_befhjjjiigc_XOF@965665566553333332211///..-------../1001112345567887789999998789976653554568:<@CGKPU`hosttqppsuvxwslaP@6/..18<?BEJPRW]cks{
v{xo`O>5347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~|rhc`_^]]^`djov}~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{
}vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{
|sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkxwi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{~s^j|yz|seTD8336<FP_JGkvsqnfXK>5.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk|zyyyyyxxvofYJ?;;=ACHLTZbmxznga`_]ZXY\bipw}~|}}|}~~~~}{ztqkcWI=630/...---,,-/5<CLU]djmklkllmjhe]WOD91,**,+/5@HRX^ckrw}
|vl`SQRU[elty}~}|}|||{wpibXMA9688>FXdq~{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>859<ADINRVY\_dhlu|wnky~|p{}ujXI:337=EO^KHarsrnfZK>5.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|uic_\ZURSTX`fnw~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4<CMW_gjmnlklllieb]WOD91,+++/5?GMU\diotx}~
|vlaUQSZ`gov{~~}|{}}|{upiaTI=7669?J\juynd[[^ciqvwsrqqrrrrssrokjhfb]\]^^::::;;;;:99877878877887787776643333322221111110011000///.,..,,,,*))))'&&''&&%%$$%%$$$$$$$$#########$$$%%&&%%&&'%(*''%$""""""""""""##!!"""" !!!"$'+4>Qboy|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz
xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~zre]ZVRMJMOS]fr|~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy|
|ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4<?4))*,/38<CGNSX\adfghhhjigda[VOHD>955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|xmcYSLHDBEMU^iv}~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz||
|ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{
}uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!! !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmryvk_QLD=8;@MYeq{~~}~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejquxvX@PZRP@X`^dlNJA@GKhrICLcsqkf[K>5.-+,,,,,,,,-/Hn}tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}|uiZL@7339CP\hv}~~~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~
}vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!! !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry}
{qeVF;404=HUamx
}|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~
~uj^Z\`hosy{}~~~|xph\SE80-,0<J\iwyumbXTWahosvurprrsrqtsroliecccddefh<<;:::9988777766556655542222100///10////..----/./.----++++*+))))(('&&&&%%%$$$$$$$$$$############$$""####$$""##""##""""""!!!!!!!!!!!! !!!"""#&*3?P_mv|zsj[M6,+*+,/046;AHMTY]adgjmkjd^ZRJEA>9554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~pVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz}
{qeTD8218BM[fr}}|{xxwwvqlbVI<3.,,---,,+**.5<GNXafiiiiiiihe`\XOE92/04<DOYbinsy~~}|}~
~uka\_cipvz{}~|uph\O@5.++3>O_ly
yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! ! !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz}
zqdSC725<ER]jv}|yvvxxvqldWI;2-++,,.-++),/6=GR\bfhihhiiige`ZTMC8127;DKV^ekrv|~~}{}
~uk`Y_dkqw|~}~~{wodYL=4-+,5@Rap
yphZVSWaiortsooqqqrrrsrmkheffgiihge::;;;;9987776665552211210/121221221/..//..------++))+++)***)()(('''&%%%$&&%%$$##$$####$$####"""###""##""""""""""""!!!!!!!!!!!!!!!!!! ! !! !"#%*2?O`jt}}wl\O@222568:?CGMQW[_bfhijgb[TMFA=:7654444444444444543333321100/...--..----..//./111111334444555544555545568:<=@BCDFHJJKLNNOONOSTY^hnuxwusrrqsvzzzwuojfijmt{u^VYP3]s|wj\TWRSMMYWQNU^fd`YMA7.,,,,,,++++*,2A]aPZ[Q]T:BEJT^eqz}|{xwxyyy{|{{vojfehhlpuux{|
zqdSC837>IVaoy
~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{
~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt
xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz
kSyyob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy}
zrdTE:58AN[gr{~~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59<BHPX_dinrvyywvxyz
}ujbabjpuz}}}~~{vndWG:3,*0;HZjxvlbUQRYajqvursqppqrssqomllkljjhddaZ<;:::::8876655643321//001/1458;<:8531100....-+--,,++****))(''(((&&%%%%%$%%%%$$####$$########""""##""""""""####""!!!!!!!! !!!!!""%%)2<IZhr{}vmcWG<;=?BDIMRVX]^beehhf`YSKF=976554664444455665544333333110///....-------..../110012243344444434445578;<<?ACDFFHIIJKLMNNOOOOPORV]dluxvtsrqsvwz}{ysnjjmqsv{
|{|{sg\NGV[XZPLJMOLLY^VH=4.+,,,,,,,,,,-5E]\G[VWOFWVG>Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}ypeTE96:CP^jv
~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz}
~uk``flrxz|}}~~~zskbSD81,+1;M_n{
|si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!! !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz|{{}~{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~
~rgbbgmtyz{}~}xri]NB7.+,3?Obryqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!! !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy|
~rhcfjpuz{|}~|wqh]N@6.+*6CTdtwndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !! !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{
~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{
tkggmsvy{|}}}~~xofXK?5-+.8FXjw~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!! #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx|yywvtnecaZYWVYWMKLJE><CGGGIPSS\L%-.,-,+,,-.--.4LMWf[ST@<7:Iizz{xvvuwyysrurjd\XZ[jhM=D@8]iejsyzxsh]L@;?GT`mw~~}}}}}~}{ywvvvwxxvqjaSF81+(('''((*.4=FNW^eghghiiiifca^ZVPORW[^aeghhifb_^_cimtx{
|sjiiotz|{||}~}wofXH=4--1;L\n}~th^RNPT]elrussrrsstttuuuutqpmhc[SMHJ;;:98777543332101/--.....26;?EILRSSRQOOMKLKJIIJIFDB@=951.*)('('&%%%%&&&&&&&&%&&&%%%%%$%%$##$$$######$%$%%%%%%%$#""!! !! !!!!$&(-5@Q`lv}|xtplkhhhkkkkkjjieb^YRKD?;9999777788988889;<>??>>:64221/110/21132134577;<<<?BBCGGIJLLMOOPRRTSRTTSSSSRRSSRTRPPONNLJKKIIIFCB>;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<<Wc`^S=:3AZp{~yxwwvvusjhppje`\\aZ\UE@7Jcaajswvvph[M@<BJWdoz}}}}}}|~~|zxvtutvwwuqj`TG80+((''&'((06@IOZaeghhhhhjhhfb`\XVVVZ]`dfeedb_YXX\bhotxz~
~wmklqty{|{z|~~|upeXI=4..5>N_o}{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!! !!!!!! !!!"%').4>JZfq{|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=<?@DFFIKPPPRUWWY[Z]aaacdgffeehgeeeeeddba_]\[XTQNLJHGDB?<:855<DR^jtvtssstvwxwwtmcZMGAELU^iov~~zxvwxskc]XSMJMRQJFDDAACCGLOQNLNJ^iL*&--,-,,-,-/07>`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~
xpmntxz{|{|}~~~}wqgZK=4//3?Rcrzod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##"" !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434<GP]irttssstvxvyvtndVLA:>GR[enu~
zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-4<ENW^dhiihhiihjjigfc^\\]_`bcdc]URONPW_gmruy{~
zrqqvz|{zz{}~}|xpfZL>5229DVgvxnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"! !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju}
|xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~
~uttx{{|zz{}~~~~yrh[MA833;GXhxxnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"! !!!!!!""!"$&(*,/26<BNWdmtx|{}}}}~~}}{{yxvsld[RIB?>>><<=<<;=>AGQV_isz}{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~
}zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt|
|ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|}
xuux{||z{}}}~~|xqh^QD;44>K]m|xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"! !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<=<?DIOX`kv}qfYSV\`dgloqstvwwy{{|}}~|}}}{{
|xtmf`YVRONNPV_irtttssstvvw £ z(3EP]gu|zywwxy{zvkiaQRXYRRNKIJJIJOQTQQA6DA<;WT,(121<OgoWb^YTM7<AS^dquwxwvtvrdYOR[SNMMJKQ_[ZZJS_^_\I[aQ^laVHGJR\hu}~|||||||||{zxuromnmpppnjf\OC70+)(((').4<GMU`fhhihhhiiijjihea]]]\\YVQLIEBBFLSZbjpuvz|}|yyz{||z{}}}~~|zsh_RF=66?O`p~wl`VUV^djprsspopqqrtuuwzz{zxvrle[QNZi7765333110//..----+++*))**))**(+--/1325:;<@ADEHLORTVXYYYZXVRNGA;51.,)(****)())(()(((((''(())))))**+)))'((('&&&%%&&$$#""" !!""""$%%&()-.046;@HOW_fimpsw{{}~}~}{upicZSMGB?@>=>?BHOX`kt~|reacgkptuwz|}}||}}~~}}}}{z{{{{}~
|zvsolgd_\\_fksuvuqsrstw£¡¢¥«®¨h,>OZiu|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~
|}||{{{{||~~}{{rh^SG=89BSds}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#""" !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}|ztnha[UMGEC@@DINV`hp|wmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~
}zwwtpnmrtwwwspnnx¡¡¨¬°±®48IZiu~|yxxxzz{{yrbNCPSS^XQQIMUVWQN><MJC::;:Dc^Rlc\pifZ<P[T==@FLBX{oxxzxrbVcbRIMKJJMRT^][VSJD[hgd^`s\QKJKXcny~}|{{yy{{{{|zyurommllooonje[O@70+*)(()+18BKT\dhijihiihhhigeb`[XXXSNIDB>:;?DLSZagmsvy{}~~~~
}}}}{{{{||}}}|zwri_UJ@:;EWfv
ui^VR[bhosttspopqqsvuwz{}~~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"! !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~}zvohaXVQKFGKRXagpzxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~
}}}}~|xsv
¢¦«®¯®«A5GYft||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4<EMW_eikkhgghggfddb`[YWRPKFC=876<BHPU[`hotwz{}~}}~
}}|{{z{|}~~~}}zyvpi`UMB;=J[jy
~tg]WY]flstutsonpqpsvuwz|}ytnjnv55443211..--,,++++****)))(((((('''''(()))())*,.1258>CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"! !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz
|yvpid`ZVUWZahnyywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~
¡¦ªª¨¦¥T1HXgu||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337<BHPW^ekouy|}}~}}~
}|{{z{}~~~~}|zxuohaYPE?AL]l{~tg^YYahovvtsponpqqrvuwz{~
}yutx5554200/.--,-,++**(((())''''((('''((''(''())****,./5:AFNUX\^`a_^[XROJEA<;80--++----,++,,,,-...----..,,+*)))(''''''%%$$$$"! !!""###$$%'')+.1369=>>??@DGLQW\`cgimpuz~}yuroieddfkpx
}xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~
£¤¤¢s3DXgu}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~~}{{z{}}~~}}zyvrngbZPGBEPao}
}rh_Z[ckswwvspmnqqqrtvwwy~
||334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#" !!""###$$%'')*-0377;<<<<?CGKPVY]]`dfjmrw{
}}xusqprt|zurqsrqrqoollgeeeaa^]YXWUUVWWWZ\`abdhhiijjjjihhjjihiknortwz|~
¢¡|AFXgu~|||zz{z}}{v_a^V_^ddZWV^_QWC9329;;978@HE5=IKCAloiUG>:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25<AFMT\cinswz}~
}|zzz{}}~~}}zywtohd\SJEHRcr
}re]\ahmuxwuspoppprqrtuvy~
220000..,++*))****(('''''''''(((((''''&'))))********-06:@GNSZ\\[^_]\[XVSMIC931...0.//..//////////.//---,+++*))((((&&&&%%$#!!!! !!! !!"#$$$%%%&()*+./369<===AEGLPTVXXYX[_chouz
}|zy{}~sqpmmjjhdc`\[ZXTRPOMJHHGECDHHIKMRRVXZ[^```]^\[]\^_\[[]]aejnrux{~
}¡¢|y{px\LXhv}qbozzy|}~i[WXZ^dhYHJNK=KG7C::;95669;D;7?GAMSSvdP?<CCJH?F8Jr}wsnVT=MjPJCFJHGEFCDGOXVONKFDGGEWIQYSS_lt{}}zyxyyzzy{{zyvtqnjjkllnqomgaUH@64579<@EHLRV^dillkkhfgddba\WRLE<72-))*.27<CHOX_gjpuwz}~}{||z|~~~~~}zywsoid]VMHKVfu
|sf^]cirwxwuqpqqppqrrrrsv|110011/,*)**(())))((''''''''''''&&''''&'))))))********-28?EJNSWYZ]_`a``_YSLD:200.//00///000000000/00...-,,,+))''((&&&&%%$#!!!! !! !!"####$%$$$%%%&(+,+.057;<=@@EIJMOSTTUTUUW[^fmu}
~zrkhfdb][XUSPKHFB?>=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz}
p}~~|n|sLYgv
iamcz
wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27<CHOY`eksuwz~}|||}~~~~~}{xvrmie_XOLNXhv{qg`agouzzwurqqqppqrrrrrty~211/00/++++*)(((((''''''&&&&&%&&%&''''(())(())******))+,.4:@EINSXY^`abbde^ULA610/.000001221123311111////..-,++*)))(('&&&%$""!! !!! !#&()('&%#$%%'$&)*+,/259<?BEGIKNMPRUUWWVWWX_cjoz
|qf^]ZURNIC?=:9521223022259=?BFHJMPSUUWWWWVVWSPONKHD?>::>BEJQSY_dipw}
ws{r|{{ZZguwpqroxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{
}}{}}~~~~}|{xuqmjfaYSMQ\lz|rfcgisy{zvvsspooopqrronquz
1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!! !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx
|l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}zvy{nsmlyv~vztp}pxcagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF<a\be[@?JDNZVPHFFHC=>;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy|
|{zz}}~~~~}}{xsolifa[URU_m}{sifimtz{zwussrpoopqppnkmqv{0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06<AFLQX^dglmke[NB711222122233333222222210/.------+***(('&&&%$$"! ! ! "&+/4<>>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{
zhREA=9731000../----..048<?BFIJLORRVXXZZZZXXWSSMKFB;60---++,017;AHRY`fmpvum¡´¼½¾¾¾»°}yqtzY{kzmcuxwj\fjkheb`RTUKEOb\+(=><;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~
~{yyz}}}~~~}{yvspliea\WUXbp~{slkmrw{|zxtssoonnpqqnkhhlpv~/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!! "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx}
{iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuykom¡¶»¹»½½½¼»»´vpihjk_]`vwy{kUVrnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J<ACEIUKH=MieZVJVO;nnniXLE@>=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{}
}{yz{{{|}}}zyvsqnjgd`\WW]es{spnquy}}{wsssponnnnnkihijmqz
--,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!! !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~
o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm}]5c
¸¾¼»¿¿¾½¼¾¶³º¤|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<<IMBDECJVED9Mwpk2B@Auyng`RNF??=>><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~
~|zyxy{{{|}}yvsplihfc`\[Y^gu{uqruy{~}zxtsrppoooopmjgfflpw}--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!! !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~
~zyyz
ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.036<BHNTT
~kyn]px{\]¶½¾½¼½¾¿¾¹¦ls³¸°¡wIZhuz|{{{scRWbOQQ_QAAKX^QNLIOLQX>8FEA<5COQ987<I`JLPNUOCDBRLJTBF<VxkD><<qywqefXHFFDB@=:9;=@CDFGHIJMQRVXWWhuxw{yyvsn`dmjoji_Y_OJKKHDSeklnnmkfa\Z]^bbeglmmooomkjhhhfecb^YUME=4,)'''+/38;BELTZ`hmrvy{}~
~|zyxy{{{{yywtrojggec`\[Zakv{uqtw{}}zvusrppoooopmjgffinu{
,,,+*(''&&&%%%$$$$$$$$$$$$$$$$$$&&''(())))******++--------..1311238>GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~
~yvvslou|wqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTq ¤rYghuwvvo»¾¾¾²°¼¾¿½¸v§º¯
Q<H^ov~xtwzoTAsgZY\^H=?;<MaPMNMKBKOD@@A=8BID89689>6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~
}{ywxy{{{zyxurnjgefdc`^\\dny{usuy|}~}ywttsppoopoolifceintx~++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy|
~~~{wsplgc^agq~xxuqnkd^VL@91./158<?BDHJKMNQRSTVWXYZZXVUTRNID?93/.---,,-.148:?DHKP_¡¢£{xm`yu{ah|£¹¾½º¬·½½½¬°¥|iOTOnzoY^dlmleje]XTL?:9>EITRMKKKTMKEAB?><CE?=;:;A:;KLIA@EFJWXPJCB>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38<?AGNV]dkptwz|}~~~}~
|zxwxy{{zyxwrolhfdecc``^_hp{
|vtu{|~}{yutsrppooponkgfceinpw~)'))'&%%%%%%$$##""""########$$$$&&&'(()))))*++,,++----......141/238>GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~~{xtplgc_XQORYeq||}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<?CEKRR{¢ij¥
hZT^f|¶«»¼±¥¯¤ ²£} }^^tq_YLKShi]iniq^XR::;=?8JPOFLFESKEDHJLSB8<?;:;=PMSHGPLAD@a]RQAD@Jh27;MZ]gtupj_UNJFBCCB><<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~
{yyxxyyyxxvspmhebcecda`aelt
{vwxz}{yvsrqoooopolkgebdglpv}
*)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}~}yvrmhd_[YTGABDMYgv}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^wW\¤§tbYadIZ¾®²µ©®nq¡}egagf]cdYTWf\Teiic\bR;<>D=<FRNJRQCINOQMNRZE<>E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~~{yxwwxxxyyvspjfb`adddaabgox
xvuw{~zwusrqoooopolkfcbdfkpv}
)(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~|zxvqke_[WRNJG@>@BFP`qzuy{~~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPSvYQx~wf_( }½»©}¬®w[e>Z zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}~}{ywxwxxxwvtqlhc_^^cdcbaejqyxsuw{}}|xutrrqnnooqpljgcbeglsx(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}|rgkqv{~}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQd
cQfZ*#x¼»´¥]|bVk{lp¤fC\_czvmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76<DMV]fhiihffccb`\XSKB80,(')+/25:?BGLR]flrux{{||{{{}}|zxvvvwxxvttoke`^]]`cbbcfms{~xsuw{}}|xurqqpnnoopoljfdeeiorz&%&&$$$$$#!! !!!!!!!!""""##"!$$''&''()*++,,------....--....0/01016<DLXfnstmbSH=6566557786666666666677775544442111//-,+*(''&%%#"""!"%&+29AHPU[\XTRV\afjlkiggfhhhlllnprrrpoopprttwxtrrsrsxyyvvsoje_YRLD@?@@?===;=AHVgywhXZaipw{{uof\UQNJIJLLNNOPPNHFDBEDBA>;7557879<>?BDFHKLMOPQT£¢puPW|{vs{ @*#hº¹¶²¦
`dvi`}}pcODfr}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046<BCHOW_hmrwyz|||{{{{}
~}|ywvwvwwwvurmic]Y\[_accdhpx~ytrsw{||{xurppnnnnonmljhgefinq}&%$$###!! !! !! !!!!!!!!""##$$''''())*++,,------....--....///0004:@KWbjqrofYND<786555555665555555566665554443111/,,+*)(''&%%$#""!"#%(06>CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcxucOJOXbkrz
yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQSc¡qY~t^Md}{vqu2**Mµ´±°«rijmohu|mdtvb>>X¡uf[UKMX_^GKBFZUD329>5:NIIC>;FS:589<F;2228@NF=?7NYQYK9BCQAHVUORN7@20IKY@6:>CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3<FNX_dfgfeeca``^[TOH?7/*('),069;?CHOYaiouwyz|||{{{{~
}}zywuvwxwwutqlfa[XWZ_abcflqy~vqpruxz|ywtrppnnnnnmkjjhgeehmq}&$$$###! ! !!!"$$%%''())))*++,,--......-.////....//0027<FQ\fkppibWMC<97556345555555444455466653323311/-++*(((''%%$$"""##%).3:>DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;<?FPaswdM;@ENYcmx
~yuk_VPMLMLMLFC?;::=ACCCCCCCDDCDDDGJKLNOQOQRUUp£¡JLnlPOqxwwpp3105£®§¨¤xj`TYWfjcmlhS8?T¥ tI<^ob[TNLFCCKRD3@D9::8>G43BKK?3A>?713059DC><Pe`i_;=<ANQIEOHMM;<F=C@?817?A@@AA@@AAHIJMMLR`hgfZ_pedcTKDEJEOOJ@BNCIdumgdabddcefhhfc^XPF<3+))/5>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}}|zwutuvvvutroje_ZUWY]`adhptz}tppptvxzxurqqonnnmmnlkkiifghms~$!##"! !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p|jS715=HO^ju}
~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£n:IcxXNYrxzx`854.z¤¢¤~mdXNB?D]jhcJ<EQ~¢cCN|}vmd`SJPYXZPG<:FG:;=EA?ADUPBA705=6,036:?UbYVN<;=D^XHDFHQ]WLJMKM@>@;<?>:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~~|{yxvtuvvxwvtnic\XTTX\`aejqx~
yollnpvwzxurqonnnnmmmmmkjjjhjov"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~q\>*+/7?KVdnv}
yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£j6Ua{}dRLfu}V9788I£
}wl`ULFI\c\ZGAFJrzZsyli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0<Rpwrooihncjhbbfeea[SH<1(('*/6?JS[cefgfdbaa`_^YRMD;2,**)+-1578<@IS\dlqvvxzz{{{zyz}{xxvuvvvvuusqmh_ZVRSW[_bgmu{}tniilpuwyvurqonmmmlnnnmmlkkiiox"!!! !!"!"##%%'(())))*++,,-...///.....//......,-..15=HQ[dhmmhd\XQLGE?<96334444444455666654443311/-,++))(''&&&%$###!"%$(-243578:>CHLMMKKLLMOQW[adgjmprrxyz|~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey
yhL0*),09CMZfqy~vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZs,<Xq}hMGLfu|uJ:;<<:gwrtmeYPPTZUQQIFGGf}|]NQTMOUNRnps
pbZMFKXONF;GO@2.37<8/-..3<?C?KVJMI;78;;:<>>A=8:;<CHJMP@ADA>?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578<AJV_flqvxyzz{{{zyz~}{xvutuuuuvvtplg_XSOQU[_eiov|zohdgkpuwwusqqonmmmlnnnnnmmkkms|!! !!"#$&&'(()**+,+,,.../////.....----//.-,+,-,17=GQ\diiifd_ZUQNID@:653333334444554443332210/.-,,+*)('&&&&$$$#""#$$(-0132689>ADFHHGJLNQUZ[_cfknrrsw{|~}|ywqngc]XPMGB=;:::9::99998876666679>K_r}nW;))')-4=IS]hox{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b}+9N`{ycNKDIPRIA=>>=<9nnmjb_WPPSQJMPIJGEJv~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}~|xvvuuuuuvuuoje]VQOQW^bfkqy}~vmeeejnswwtqoqponmmnmnpppnmnlpu~!!!! !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{
vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY 318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu
z{wF:ABCAA@>;6/2Vvb`UQSI=D98849?=2.1/.-)<A98<86347;<AC:43662<BCG<99;=FFB>8<?@A>AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}zxvvvuuuuuvuuoje]VQOQW^bglrzzrha`bgnsvvsqoooonoonmnpoonmnntz!###! "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s
|pW9'%%'((,/7@KVajojdonSj}th_]clv}~}}unghhie_YVSRPNLKLJIGL E.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|mC;<;899:9763/;f~saSNICEKJ=26J;4,34/+*3?=DE953496<BD<7655434334577::>JVI60,38:<E?;=@FZ\QCL[gb]HAHJE=YmmmprtZClvtFW`WGIP[fghgb[RH:1+)*/4;DNW`dfggfdba`_^\YRIB:0*()*-11479>HSZckquxy{|||zyyy|
zxwvuuvvvvuusmgc\SONSY_cgmt|
ymd]\`gotuusqonponnnnnnnmmnnqqv}
!#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy
yeF,%'%')*06;DSbmhk]dxqxyxyrsrw
}~xpg[QOLLGGECCCAzY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZooUB8;:576421.--Dj|eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{
~{ywvtsvvvvuusmhbZQNOSY`dipw{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrz#$&'% !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;LburY=)%$%%(.1:GS[^Yacfoqqkiopu~~||xy}{l[LA>>B><9;Xq4/=X[IKMKKTFDCE@>:_jg`ecZSPLPOMGHJLMRMM[VKQTdl]QE;2247641.,*-He|zeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etulstwtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}}ywwuuuwwvvvvrlg_XNLOTY`elsxypdYRU[cltwtspnmoonnnnlkkkknoot{#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~|jQ;&%)45.0<KNGGGONNRMQTWVUZhruyvvuw{r[H>889759~Q=?Q_PMNLIJFCDB@>7UvgVZ`YPMRNHEEJHKNNNYYSHJSq]IC43789952-***0J`|nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~~{ywuvvwwvvvvrlg_WMKNTYafnu{uk^SMQYdmsvsrpnoqonnnnlkkkkkmns|
!#&'&# "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4<LezueO9)6SUJGT\ZQPQSTY_PJFEFG?FILU^gp{xyw{aH93467H}jKGWcUOSMEFCBBB<7FrymZTXZVPIFEFGIGHHIKOPIFJQeA52>C>;720)()**5Ld}wturcbXLKLB<MNF>7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENA<H:1^kkjhb\TI;1--/5<EMT\dgighffhjlnqrqtsppptyz}~}|{vuy}|zxwwwwxxwwutrle^TMJLS\ciqx}~vgWMIOYbltutrpnnnnnnnmkjiefiknu "$%%" !!"#%%&&'(()****,-..,---....//.,,,*++*))**))))****++***+-257=BGKRY\_bba]TLF;5222123321010000....--,++*(((((++.14:>EINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZpyiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mzYA7<GLh}rnngXLOMDDB?A?<;b~si^XUQMHDFIJJHGFILEFEGMM~ymF<7BGD@80(##%')*8P`xujuiqxdikYJW^QC<97840/025668:89GOG9CC7**+1Nhkmd_HBGCTPOS\mcL]fZMG9CNj[CcieXIIC??ELF77?MYRMGKQ[4Ogkijhb\TH>51139AHPX_cfijlnpqsvz}
{z|
|zxwwwwxxwwutqke^SKINS\dkry|rcQIGPZeotutrpnnnnnnnmkigbdfhnv"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48<BGNU[addd]UME93311222200011//....---*+*)()*,/58<BGMPT[_dhmprtwyxxxwvtrpmkgb^YTOJF>;511000..////////./1111112211132111/...-,++,./2<Pg|xrww|~ufQ??Pcuy\OWX_nwtaICCCDFCACDGK\q~~}
yc`ny
~qXLMIGCA?=ELVxmb]]ZRIHGHHIHFEFHE<=ESYltmiQ@>BFDB=3&! !$'*+>Q`qtniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz}
|{yxxwwwwwwutrkd\RIHMS\elrzym^MEGS]hqtusrpnnnnnnomkheccdhox !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rtq~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_
~|~
XhXJLKGC@A==Co¡§}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jwxw}~zuj^OUH<DHOXT=:@<67B42>?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw|
|{yxxwwwwwwutpjaYPHGLS\elt{xiVIEJT^jrtusrpnnnnnnomkhebbchqy
!!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxz|
}zlX]nbVOew|n`OEAFFEHEFHJLJA?Snq{y{q
]HJKEA?;::J}£§ek~unlkjiix\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;<CLU_gqy}
|{zwwwwwwwwusog`YOGFLT]fmv~whTHELW`kruurrpommnnnomjgdaachq}! !!$#$%''()()*+,,,-../........-,+******))))))((((((''(()))())++*+06>EPY_gkic[SMEA=876521010/.//----+-/137<CIOU\aejmpsssstrpomjeb^ZWSRNKEA;641,,,,,--,-,,,------+-....-.////////010000/.++++**))()*2@Teos
yvv
~~\Rksnf]o|umg[WPEEJRKEDCEIKGGJ^rwzxycGIIEBCOG8Q¦©£^,X{yxvzz¡ydM>6/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl¡{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<LLHP5:<@FWVFEEON\f_M85GYKSL67RY?DJfp_jrOCWcfghhjjlhd_XOF><>BMV_is}~{yy{~
|{zxwwwwwwwusog_UJFEKR[clw
ufSIJR[emrssqppommllonmjgd``cis~!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~qbphioWXeX\ggd__qtlgbZMDFJPJEECDGIKLLOx
u}y{wvqDLKIIC?>7Z¥ª¥+4Mr}y{¡ t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk¢¤¤¤¢~`VhqfUOaVE<HW>2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8<Y_XL[PKSnh`c`jnNV_]`fegefijkkheaXQHBCHNYbkuxrlfb``bdgkorx{|~
|{zxwxxwwwwurmf\PGDDJPZcmwufVOPW_gostrpnponnnnnnmjgea_djt!!""$%%&&')*+(*+,,,-,,........,++++++)))))))))(''(''''''((())(((((*.3:BLT^dhjhc^XVRNKFC?=:8632/.../0369>CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuuimmgVDAIHJGDEEFFHILMuzvvzKGHFFEA>5c¡§§¥W2=?FI>Aj
ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]nxixsh`[WZKGV`=94;AGFH89>H<E>9<HQYN<;_C=FZ^E1.66<HE@=EHHT^enkgeieimlfPUi`fl`ekiiecfgggkkkjfaZSLKNRZdoy{rj`XRJFEEGKLR[_glnsvz}}~~
|{zxwxxwwwwurne[OEAAGQ[eoywh\RTZclsttrqpponnnnnnmjgebadlv !!"$%&&'&'()**++-.----,-.......-,,+++(**)(((((((''((''&&&&((((&&')**/4;DMW\dhhgdd`[YWRQMIECA:6522237:>DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt|
yudUKLLGDEDFFJMMJi[r
{yut\DIHHCA<:r£¨©¦t0<;974/P~yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~
~uiUPUTUddLB7522;><?@4779<;:=A:60-:;>7gZ3-0>NA6<JPRLcxwrpnkjikii`Vhs`Zqqmihfeffghjmmljida\YY\cju~~ti`UKA>;<;;:<>BJOWagosvxzz{}
~}{ywwxxwwwvvqkdZQFABHR\hqyvj^YZ_gnuvtrpppnnnnnnnmjfdbabn{ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3<GNV]bdfedcc`^YWSSPMJEA;988==AFKQW]`bhjkihhea\YTPJF>:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3Gzqzwtwiikgju`MK[\egebd_g{}££xUCHHEGHHIHIIG`/9Ocqywuwh@GFEA@=Ey¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?<IJ29<881/9KD00794<9=Q+,1692HemRPJqzvronkkjjknmqqu]Jmrnigffffghikklmmljijkpv|}vme]VOGA>=;<<<>?BFMV]fmsvwwvwy||~
~}{ywxxxwwvutnjc[PE@CHQ\hq{vj_[]dkquwupppnnnnnnnnmjfdbaep~!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf
ppffibOMLNMZgm_TOL^hpj¥¨¡xU@IFHIGIJHHHT@;;<Ig}{uvwxzBAFJC>>V~£¦¦}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26<?<99EC=9:;B<<<35<17;?`SK?<<F[ebSOY{xurnkjjiijmnqrsmktpkigffgggihkprtvuvxz}
~{xrmhb\YRME?;:<==>?ACIRYbhptwvvvwyzz{|~
~|zxxxxxvvwvsojcXNE?BHR]kt}wh__bjqvwwtrqpnnnonnoomjfeacfr !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly
}|vsyydUP[[K`gdb][V`cd~¯³«¤G@FILMMLIDBGD4>>?E\yztwy{N?EGD@?h£}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<<GSPV]]Z8-4857;7BC;>B01;CSL8@769<OYID:8Xut\QLp{xsomkiiihilnqpnputokigffggijkpty}}zurnlifc`^]WQKC@<;;=>?@@EKV^dlsyxyywxyyyyz{}
~|zxwwxxvvvusniaWMD?AHT_lv}rg``hmuwyxvrqpnnnonnnnlihgdejv!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07<CFJNQRVY]_^_`_a``a^][[\^__acdecb^ZTNID<52-+**))*)++**++*,,,,,,,,,,,,+++++++)************++--,,--,,++++++++*)****)+-/I~x|~xwwvzvpkffoghYFcpWWfcdefk°¬¯²¬®«¡wC<HKJGJEAABmk.:>@@@Sruowx|aCHHEADy q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H<B>;<h}UM_|urpmkjgghiintoovtspmjhhghjkosx|
~vplgfc_`a_]]ZUOHA><<==>?@BEOW^hpv{}|{xxzyyyz|~~{zxwwxxvvvuqmg^ULC?AIUamxyoeacjouxzxsqqponnoomnmkjihefmy "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?yj|{|yur|}ypvy~vj\QXkgSGdwyzy¨ª³¹´·¹±h7IHGFFCCC=W}S4@?=@BNptqxz~xHHLFBGb,699::8521<KQQSQNMLGGGHMJLKMMIGEEKSWWVMJFGILQRPMQ^]PMC9HVRMD=9666:999510:FNQA=ADD8?><HI>9?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|~{yyzzz{{
}{yxwwxxvvusplf]TJA>BJUcp{xmc^bipuxyvsppoonlmnnomkjhgfgp} !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7kqXowz~|nvspwphcVc}oG4Xv|z
£®·º»»»¹BBELJDBCD>F~}
=8?>>ECawsy~
ONIFAJ^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943<A=;9::=<748>@BJOXcnmliggiililtvuttqrrtvvx|~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy
~}{yzz{{}
{ywvwwvwvuuupke\RG@?CKYgs}ui]]ajouxxusqppnmlmmoonlljigjs~ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|}{qulpmXEdsjRG]ft¨µ»»¼»ºµ]7>AEBCC?@Cd~~f1@@>CHqxxz~{z[@EGHJQ07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq|
|{z{||
~zxvvwwvvvuttoid[OF@?EMZft~
|re\Y^gnswvrqpppnmlmmonnmljihmv !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~x||{{qmvoxu\AVp}ucY]b{£¯º¼»»º¸§p06=BADEDDCN~}H1?BKm~|{e?CCEP@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv
}|z{|{
}zwuuvvvvvvtrmgaYOE?=EQ^kvxn_VU[blsvvtqponmkklmoppomkjjoz
!""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnqim~
{{vprmM?UBbxx|nX_j¨²»»»ºº¦t47<??CECCCFcyzD-C\~|}
zIFAFLI5<9::::9542.+4ELONNKIJLLMKGLPRPKDAE]ONTS?EHJMNQ^b`^^MKR]]Y\_\WPE=86-&'*++-----0125@A><<FGHBF?<79779345631/26137:9?<:><?EKJGA7=Ni`H]x|z}
xlcVNLPTXY[[\\[[[[\\[YWUPJC;:989;<?BEMWajs|{z{{|
|yvttvvuuuutrmg`XKA<=EQ_lxwl]SSZcmtwusqpomlkklmopqpolkmt} !!""$$%$%'((*****++,.......//------,+,,**)())''''((((((((''''''''''((('''''''''''))))))))))*,/0357:?AAABA=;62,)'&''(()))))*++,-,+,,-,-.....//////----------,,,,,,,,,,,,,,-----------++,/1-*****+,AX_{x~RJTettwsupbT>LutrX_o¥®´¹»º¹£i17:<>AA@@FCDm}{};2g}
VKDDK}P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEB<E>FORF?961433453538CF=@A9:@GKIG?7QaV^t
wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw}zzy{}|xvttuuuuuutqlf]UI@:=EQ_mz
~wi\QSZenuwvtrqonlkklmnoppnmkmu
!!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQyTb[OJMi|ty|zyoH?^xxunScq¦©¬°·ºµN/9:>>=@A>ABBK{}zx@w}{mNLEI|U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;<?ALTW`lw}zy{{}
~ywvusuuuutttqke\TI@;>FRan{
}sfXPS[gqvwusrponlklmmnpqppnmqy !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP
bMHEJSh®©¡ vvopkoO:;Hb~iV\y¥§§©³©|729<=<<?A@B>?BQ{{vw|w||{ROGIx`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_C<EPTSL=9:==A8<MXj
|wsrrqssqoke\TJFEHLRTXXXXYYZZZZYY[ZVQMGA<989:<=>DIPYboy}zxzz}
}xxvstuuutttspjd[SG?=>HTcp{qcWQW`jrvvurrponmmmmmnqqqmmot~ !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIhKGEXr¡§¬§¢}okmsr]??83Gmw~Y]p¤¡O4;;:::8;:<?>BMOd
{ywy|x~
~SIHOpj:<;=<=<85400..[}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*<veI?JPPQN<7BDB:CWh
|{vqliiiklnnmhc\SJFFIMQUYYXXYYZZZZYYYXVQKE@:98:;<=AFKT\dqz|yzz{
}xwutussttttrojd\RG?>CMYerzobVSX`jsywurqponmmmmmnpqpomm} !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>a¨¦¡££«ªª©wx|zb[KHCDJqy}o^jwlD:=:<=ACC?=<>BHJRmxuv|
zv{y
t}hEIQgu<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--4<?EYvz
oQDLTUR>9=DB8=Wo{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;<AGLS\gs|
zyyw{
}yvtrstttttsomgc[QG?@FP\huxm`UT\dntwttqponmmmmmmnqqpmp !!""""!"%%%&&'(()****+,,,,....//.-----+**)**))))))))))))))(((()))).9+'''((((((''''''''((((((()()))))****))()))))))))()))))**+-,,//./01001111111111222211/////.------..----,-..............//..----./EtzeCRBX¨¥¤£¦¨¥£¯®«¢
wlj`e[eW_w{{efr{tJB:A>==BIJOJDB??HQRq{wuw|sqy{wknwbs{ECM^wz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqt}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy
}{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[sypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;<BGNV_jt}}yxxx|
yvtsrsstssromfaYOD?AGR^jwvj]TT\entwtsppollnnmmmnprmw
!!""####"#$$%'''(())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))))))))))))))))))())*)**++,-.,-..00111122222212111111110/////...-....-----..-....////......----,,./DmLB@EQ¨ª© ¡¡¬¯¯¥ ¢vnaenihfepl`nv}yT.4>D>?@?@@IEDM>:AFOIx}vsyzuttpWa}wm{UBEdr
~A77998432/0.G{yptpom`ps[:F@:;;;:8=]\PSA^jnx~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[xrlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~{vvwy}
zwsrrsrrrqppme^XNC?CJValy~tg\TW^gqvvtromlmmmmmmmomo !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt³²£¦¨¢¤¥£ª§¥¢¡qdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy}uvzxl[V\wcCCO ¡^?78977744566M~q~P;DD<;:<<;S\NWeqs
xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T~g]SNPVNLM>8=>EZ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~}yvvwy}
{utssttssrqpme^VLC?DKXcn{
|rfZSY`kswvtpponlmnnllmmr !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq¦²±®±²¶·¯¬£~vi[ojdehnvz{jfr{{x_1,01:CDDA>=<9CJH::=HMCR|y||kHQTk~mJCDv ¡sv?:;;<>?@>CB2V }opkU27?D?;<=<J_]gpo}
rcbdfceb^UNTTURRPPNKC/%#$$&+442/-5DP^ZGCIW]\\ar~yqhimievhH:-0064./4R
a^r|viXKGCEGBA@Amqjhhfgijlmlolg`YRMLNQSTVVVVVXZZZ[ZZZWSMF@=;::9;>>DLQZeoy~
|wvvx{}
|wstrrrsssppmd]TJCAENZer}|qcXSZbksuuspnnmnlmmkmlv
!"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp°µµª¯³µ¹¸³ {zjbi`acst|~RbpxuX,(+17<@AB@>><;AA<=;AKH9Z}{{EIV_xtqQDEWvJBBCDFDC@==92n©¥¢~
|yg[IA7548?@;<:Cahkqv
e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8orv
xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz|wwwx{}
}xtsssrttsqpmd]SGAAGP^juzn`UV\dmsuuspnmlmllkngw"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/<DUt
¥³´·¡¡«¥©©§¨£|xqggemnqt SRjutR'(+.86:IDA?=<:9=:=;?GMDF{{y
|GOXzyaFCDi_DA?=?@=9:7R`B
®¢qhiZQC864324>C?<AWkntz
igffimpmkf\B00>LRQRSRJ7&""'+,3659:@KROD7<AIPTWW]`cY\\H=72:GWO6//21/..I_gx{y{|~wkobT>CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz~yvxxy|}
}xtqsrsttrqljd]QE?BHT`kxxm^VU]fotusqpnnmjikjh~"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo®¦¥²p~¦¤¬©
zsljckqut}
©nJejhL*+&,.>MKJBAA?<;>;;=>DHFFw~{{}uJRv
yrNFCKsrLE@?=>=<::5GdX©~f[PMMC6643126>IHBE`osxsqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=<Spuyzub]kqq
m>LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{
}yvwwy|
~~yssrstsssrnhaZQE?BKXcny
wl\SW_hptusqpnlkmjil""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce¡©«pr¥®®¬¬§}pifnrs{©®G_^]G..3.-6G:>FCB@<>=<<<=BKLHzrz|{
lMc{yndk}_EDBLcpTFEA>===;:814Zj¥yvmeWLCBA74334:CKSSSEGOUX\s
|zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z£¦¥¥£~k]WF2=@5?Yosyzj\ayJCG<EKEUcXVUSWZ_ekkmmg_XTRQUVUVVUVXYYZ\[[[XUOLFBA@@@BDHLRV\dmu|{wvwv{|
~~~~~yusssstsrqlf_XOE>DMZeq}
~tgZSW_irtuspollkljj#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx£©²¶ªs{xtvsiqw|©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vj|z`Rfmc^^^k~H?CAABGHDB?=>;95225Td¥|vi_UTLD?834;?GRVY[XYYMIGIOU]izxihry~zmF.,0317IOI</.-.12GY[Z]^YTOG<76<AFJMNK=2)7Xy¦¶»»»»º»ºµ®ªh/1540?Xku}{ws|mbAJEHMNnuhbd[VVW]]ddda[UTSUVUVVTUXYYZ[]\\XUQLGCBABBDFKOTX_fov|}yvuwx{|
~~~~~zuqqrssssplf^VMC@EQ]gs~|sfYT[ckrrtropmkjkk$#"#%%$%&&%%%%%&''(((((()+++,,,,.......-,,,,,+++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////112233433444456666665666554433221010000//.-/+.--.-----,,,,,,,,,,,,,,----+0[ §¬¨rwvwz{rx}p®¥XNPJ5(*7C/-0007CE=A><;;9;@EKH|~sj}wzZ_d[\]\ewo?@A@?BFDB>=;98400;Ub||pskXUPE997<CJOTVYX[]\`ZJDDKS\i|waZu
p6-026628DA1,0221<Waahqm`WPF627@IJLH<3,0>Zz¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw||LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}{wuvvx{~}{{}}~{zvssttssqojd^UKCBIT_jw{reWS[bkrvvqolkihk&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g £¦¤¥¡£yy|~ywtv|{a3LPKE/'(,.,,+.16;FA?C=?<<;<AFEysmo|{~rSZRUWYfvU??=@CCCC==864100=R_y{plj]JJA457@JNSTYZ[\^_bbOEFLR[i{{bm\--13775353++1449T_emx|ueYJ>64@MONH6.,=HWv¦¹¾¾¿¾¾¾¾¾¼º·³°®¥8.4348Uhg~z}~{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}}zuuvxz|}|}~~}}{wssttssqojd]TKCCJVbkyzpcYT[dmtuspnmkij(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{ ¢¡¢
§¦¢xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu||
^XWTT[jz}xx{M>@ABCGH=:8431.0<R\xtqmd]KH=875?MSY[\aa`abc]JKNSTZduw £ A//0258:51--.049M^epz
pYB:99DQWQB//5CMc¤¹¿¾¾¾¾¾¾¾½»º¸µ±¬¨ r018G5>XVou{|{cn>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz{vtuvwy}
}}{}~}zwusssssqnicZPIDGO[cq{xmbVV]fnuwsqolml~'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{££
¢§zvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_vnln
~{}pQPU^dq|xsl}kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao a130146674421036I\eow|l:7:;<<>@=70/7?Ef¶¾½¾¾¾¾¾¾¾»»º¶³§U0LW@0CBaYm}xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|~ztsuvx{}
~~~}zxutssssqnicZPFFHP\es}vl_VX_gpuvsrpkn
~t~''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6jyz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?Puonlr|~|UASdpyzreY`xy\CDB?DH>:74310/:L`
maVTSFBB>8<SX[ahnvywrvgbfegixnD6667986234453E`flu|b27765567530047Bu¬º½¾¾¿¾½¿½¾¼º¸µ²«¡~MKUT19TjX^}~~zywMENNeWSTRPUYVXWVQONMOWUTVX[\[TWZ\b^ZZ`ddf]OLKLLMQTX\`fjpv}~|yuttwx|
}~~}|yuvttsqpmgaZPFEIS^hv|tj]WYairvvtqlm
|jn
''''''''''''(((((()))***)*++,,,,++++++++++++*,++******++****))))((((''((((((''&&&&&&&&&&%%%%%&''&&''(((())))))))))**+++,,-,,--../00002223444554555665566665555543311110///.----..----,,,,,,,++*)**********++/6kzztniajcK9COOQOPOH8'(()***)*,+.5;JG9:@@=><9;Bzwwuw~qXahv{wn\G>HZeWECEC@@9731/0-;I`cGSTVHDGCCHRX_hjnwf^elgmrutw~wpV=:9898543258:Yttuxb675443020..266Eq´»»½¾¼¼¸´¶ºº¹³®¤ZRLUR14MO=h||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~~zvtstwx|~~~}yusrrpnomgaZPIHMW`kx{si\W\ckrwwqpl
|ddt((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h
}yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l~YitxzxjWD97:KaTB?CCCA841/..<KZvRGGWXMHCBLLV[\eu
{`SMby
tit|vsmhaS@<;98766656:Uw~{
c:863200/-.049<JUo¤¬³¶±§¨®°¯¨XIJGWI07E4Kty}wep>LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}}yvusvxx|
~}yusrrpomjg_YPIJQ[epyzqf\X^fnuvwrp~
{d^iw((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a
wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\}qrwwytgTC8548LZODAFDD=51//.<Mezs4@LNPLA<?BSX]dp
tum]Rbx|{nrtrlfc[YPDA>=:97779:Mv
g<75631/.-/39;CU_fks{}w~ZDDIKQP<<Mm}wx~~l_CMYccekhYP\flmmnlga`XPJM[Z]ZVOVbeUHA]dWQQOPPPRVXZ^diov{~}yvsuwxz}
~|xusqqqomjg_XOILS]gp{
ypf\\bipwvuqy
zd^`jv((((((((((()))))********++++++++++++++******))(())))))))))))))))((('&&&(''&&&&&&%%%%$$%%%%%%%%%%&&&'''&''('((())))**++,,,-,,-...000110111233445556565565465554442120//.-------,,,,++++++**++****+,(*))****))),Z}}xvtmd]RE>BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J~
duxvpcO>777:EV\O?>B?@:40./:Kgh.6QdTHB>>AE[ad{vx{p~¤¢¢ ~rmhgd_ZZTOIB@>=;98:4[w{
|{jD6643/../15:?Ph}~ywma_becdlx|{ncY?@7<7EZBAbvxu|dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|}yttvxy|}xutttqomje^VMIOV`iu~xpd]]dksvuou~g__enz((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}xwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@
inwup`LB88<DZf_TMDB@?;51//;H[}Y2Srx`LEA?@AOfnws¥«¥¦¦®°ª¥{rie`]ZVRNKEA==>??>;Z
~sw~{xnQ8753300158?BVv}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz
h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}}zvsuxy|}xtssspnlie^VMLQXakv~wod^`gnswtt
iccbfq~))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~
}
qtyslhaSLHGF<@IGE</(()*)((**+,+-,--*,/54=<7;Lruum[K<87D_lrh]WOF@B?810-8D_rPNwoSJHAA>NUe|
µ²²²¬¥ ®³®¤}slec^YWXTMHB@@BCB@Cbxw|}yvo[@:;;<7257<@E[xqjc\VSMGFHHLXgsoLGI>G8>EDG?HWay8EIXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|}zxwvz{}
~zwssssonlgd\TMNS[cmx
~wme`bhovuq
keebbhv***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+<sz~z~nv}qqqqocYQOMHF@AJEC>0'()*)('+**+,*)*)++-.17:@Kkurtm[ICCCJWeok`UPPIAA@:5/2C[qyNczzeG=E>NVP]y¥±³µ³°«¤ª¦¡¢tnjda\bpg\QHEDDDDDP|{z~wui_\Y]\_P48;>ABWo
ywupcca]\[^afmpfBA=@BC;LAIG?RgvpePPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw||yvwx{|}
zwssqqonkfbZSNPU]eq|
~wmdcgirwr|
rhifbbjx
))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2kz}}sgd~q^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uzz|{vqssu}jY[ZUNGFGA72?Njxl\tL@>JULRm¨±³°¬¨££¤¢£smkhmmjsjcZSKHHICBN}}wriiotzzve=68:>BNazzx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rTGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|~zxxy{|~
~yvrqqqonlgc\RPTY_hr|
~wmfegmuv{oiiidbeoz))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N
|winnftaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.AnzuRPRXRMGEJC64Jmw{£
hiZO@Mf«°°²¥ ¡¬¤ £¤¤¢i[dovqnf_SJHKKGB@]xuxxqvsP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~~|yy{{|~
~zvsqqqonlgb[RPTY`is~
}wmfgjqu{tjnliecgq~**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek
qv[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T ~{z
`VXXZ\UJFGB:>a¤¥¤ _@O¦®°¯¢ ¢¤¥¦¥ [<J`z~sleZJFHLKGADouz
}~wa55:<=@HUflkcgif```[Y\][Z[_`_JQ\g`I:C?7ACFRGHex
}RJT=-GA05eussqnjfa^][ZZZ[[Z[\[[\[ZVSRMMNNORUZ]`cejquz~~|zz|{}~
}yussqppnlg`XTPW\clu~|wmggntzvllmliffju********+*****++**))++****)))))))(((((((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%$$%%%%&%%%&&&&&&''((())))+,-,--.////1110122222332233221111110////------,++**))))''&&&&''&&&&&&&&''%%''&&''')18EYru}
~{z|¡pWOQ]cZXUii_LFLKJGC:-$&'))9=(&'(()(*/0-0/-=a~|}rW[Z]_`YMDDA@n¤ª¦s
¥`lª¬¬©Y<AJr}pibTJHFDEHAP}
~{tg>257<<AFR\][^`\VYYTOSTWXZ]WEBLU_la9;631=Y[eWVp
cIJE8=@4=ftttrnifa^][ZZ[[Y[[\][[ZYUSPOOLOQRUZ^adgjptx}|{|||}
|xtssqppnif`XRQW\dnv|wmiimu
|lmonmkhhnw**********++++******))***)**))))))(('''''''(((((((''(('''&&&&&%%$$%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%&&&&$$%%&&&'''''(()))+++,,-....011111113221123212233000000..----,,,+*+**)))(''&%&&&&&&%%&&%%%%&&''(&')*+7F[u{tdmqtxullkls¢¤¡z^WY]aba^fglp_[SMHHB7*'''((&'''''&'((*+-/,5Ldx~|}
|rdac\[_]VJB@?^h[q¡¦¨}¥©¤
WACJa{qjbQGBAFHD@a
xtohH//28;>@CJRTWXWUTSPNQSTWYP:+<HNOSM@G?94@WJQON[p{eKCE@38E^pttsqnie`]\[[\\[ZZ[ZZ[ZZWSPNMNNOPSVZ^`chjorw|~|{|~
|xusrqpomje_XSRV^enx
|vnkko
nmnomonilqy**********++++******))**))))))))))(('''''''((((((('''(''&&&&&&%%%%%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%%%%%$$$%&&&'''''(()))+++,,-....011111113223321110000//////----,,,,+*)**)))((&&%%&&&%%%$$%%$$&&%&&%&(**-5EYnqa`[X^cd\SX[\]s¡`UWeiidfdghrzqmhe]KA0&&''''&'''''&'((*++,/:Vht~yyy
ujgffd`^\ZQF=;[pfa\e£¢{gMNf¦£zoeUHDUthjyyrkifQ/,./58;=BIMPQQPPQONPRSQC00>?8;>:?CC=>=AJGIX[J^kuXFIRD<NYlttsqnid_]\[[[[[ZZ[\\ZXYURONMNNNOSVY\`cfhmrv{}}~~
}yrrqqpomid^XUUX^gpx
zsnml
qnqpopomjlt{++++++****++++******))))))((()((((('&&'''''(((((((''''((&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'''''(((()+++,,-...-/11110002112210/10/0...//.---,,,++++)))))(('&%%%%$$$$$$$$$$$$%%&&%'&&*.4BZrzjb^VWY\YOCKQTZmgUR[ibdjhcdjtpumplwxgD%'&*'('&''('&)()(*+.7EVft|~xxvpb]ZQH??}|m\_`_cx¢¦zs|`Tyª£vk]LDK]x~|xtnfa^V4-.)*.38<BFJMMMNMNOKH?3,/7BF7?FF@KOLI>BMI@LRT_bt
xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~}~
~{vsqqppnlhd]XSV\`hry
yrnkwulnmnpppmot|++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx|
|¡~h]\YPH=unZ`ppknp¦¡~s~~ph¥yocRDCHnyyskf^[ZV@,+('))+/479<????<81,*+.35EU>6;?EIK?BQNJ<LLNUV_hyr<=ERb[`opqssqnje_\[ZZZZZZZZ[\YWURONJIIKLNRUY]`bdgipty}
~zvrqqppnlhd\XUWZajry~wqkq{mqqrrtvvwx}++++++++++++**********))(((((())(())''('''''((((((''''''&&%%$$$$&&%%%%%%$$$$$$$$$$$$$$##$$$$##$$$$$$%%$$$$$%%&&&&&'''())*+++++-...00001101110011110.-----,-.-,++++**)++)((''&&'&%$##$$$$######$$%%%%&&(+1=UrzvsgUQ]qtc]]QIJ[bcspqSPWlumpmpnmmsmojrtmfsr~`7%(+*++*(19-)(().;MZc
}}}lHXbfw¥¦¬ª¦wa^ZSLIj}r[s|yz¡£yop|~vuzqj`PCT}s
unhaZWQNF0)(('('&')*))(('&')++.168=EJND>:=<HXUHI:0NMECRdj}{dNBBCMV`nrrsrqmid^\[ZZZZZZZZYYWUTQOMJJIKLOSVY[`ceeknry~
~yurppppokhd]WUZ]bkt|
}wqo
qrvz||~,,,,,,,,++++**********))(((((((((((('''(''''((((((''''''&&%%$$$$%%%%%%%%$$$$$$$$$$$$$$##$$$$%%$$%%$$$$$$$$$$%%&&&&'''())*+++++-...//./00/011110000/.-----,,,,,++*****)()(('&&&&&$$###"####""""##%$$&''(-6Nlzuqxsnvwwuib\G/@jos}n}XGNYuqsumks
vggmv~vflyv
a7$(+++*0>0')))-;NZi{zvz`5OYfqx¥°°±©¡nc^\PIcv]u zx~}xtqmt¢ª¦¡~|vqldXEg
|
wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdwve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx}
}yurppoomjfb\WUY_fmu|
{vp
{y,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE<JdryuqlMO]ipszxwrmqtqjgnyz}jozuzxZ.+'*(()))+++.@PYp}~zwusy\GJRZk±®¯©td`ZTNat^v
phtxkt¤ª¨ZPYkztqpneXEg
~wh[SPOJB9.'$&$%%$%&%%''(')('')-./792367?FRD564JOJY>3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{
~ysrqponliea\XV\`fmt{
{p~
,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwzWQWqymr{qqquytwuop~yto_urbmrN3((()**+++4GT^x
{ptx|}vpmqzW:O]s°¬®§]IMNQNN_jP\
{jkox§«§£{SJJGY}sonnofPA]
{
tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJ<QG-+4AZ_RTIOtrvnSQ]hprqpmid^[ZXXWWXXYYXXUSPLJHHHIKMPSVZ\^`acfjpuz
|wsrqponliea\XX\`hov}
~z|,,++++++++********))))((((''''''''''''''(('''((())((((''&&&&&&''&&&&&&%%%%%%%%%%#####$$$$$%%###$%%%%%%$$$$$$$$&&&%&&(())(*,,,,------..////..---------,++**++***))))(((&&&&&$$$####!!!!!!!!""""##$%%$(/>Wsxqtz||z{zzrjf[NIWiv}piKNPcnnhikptuqfuxyposvkeX`plWmdg?2')))(*5ALYc}}|vnpstvrv}{urppz
WQ`r®²°©C=DEDKPP[T:=ds{
{xy|h_aix¢~UFHOktrskhkoaHFSlx{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty
|vsrqonmkhe_[XX^cjry~
||
,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[vxu}}yYGGSinokekfjsmhl~rosohigGjnTnqhft?MD)(,*)2<LYf
zyy{uljnnjkknr
zqtrnve`vª¯¨wiQB==@PYZ[
V8ASgw{vrpopspjls~}T=>N{~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+<SXNVT\WH?/2@JORXR\lsyvmYMWnrqqmhc]ZXWWWWWWXWWWTPNIGFEEGILORUY[]]_aeikqw}
{vsrqonmkhc^[XZ`eltz~
}++++++++********)))))(''))((('''(('''''''''((())))((((((''((''&&''&&&&&%&&%%%%%%$$$$$%%%&&&&%%%%%%&&$$%%%%%%%%&&%&&'())**+++----....--//--------.,,,,+*)))(())(((('&&&%%$$%$##""##!!!!!!!!!""#""$%(+5Gasqsv{|yyy{xusj`UJJcu}z{|nMEL]hiijebdgjmluwrcxoiGPw_Ekdf[hhnvZ:+++4?KW`~vywvsljkhcdifinquwx|uqnnrxlz¢wawQ=7@Xnph``9@aormjikpnjkw¤}{wdHA=MryM/3AQa_PJKUozrlb[dppkJ,/0/+*(('&&%$%%%&%('()+02121574.+*)%&')+)+7AFQQUMVgCA@JMRSMXV_]fk{toaKVirrpmgc]YWWWVVWWWWWVSOMHFDBCEHKNRUXZ]^^`dhkpv|
zurqqommjhd_XZ_agou|
++++++++********))))((''))((('''((''''''''(((()*))(((((((((('''''''&&&&&&&%%%%%%%%$$#%&&&&&&%%%%%%&&%%&&%%%%%%&&%&&'())***++----....--..--------,++++*))))(())(((('&&&%%$$##"""!""!!!!!!!!!!"#$$%'*3C[noov{{wruwvwutg]UJIis|z|}bIGP`acfjffhcUNVYXcgQ]YWQVD>co]dWg]hfYVI9/3>JVZ~}zvuvrpoliecba^_agecby}qlnmlu}sRS[Q>:Jk
rc{pLQkvynifbejioy¦zvqqkM=:<Kcu
pD/,/?cg^Y[hsf]fmj_F*-0-+*('&&&%$%%"#%))**/4568>FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{
}ytprqommjhc^WY_diqw}
++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn
xkxxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJpa~tpnaTKHA;>JPVy}ytqqqssohe_[ZWYZaaWHIqupoifkwu|[OVN?<Lvo_sNceezzwmgb]bfej£ xleh^HB=;GZgv
{`<,,3GivnYXf
qlmeU>)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~
}xsqpqpmljga]Z[`djqx}
++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""! !"!!!"#$%,<VqphntytmmprswxvnbVM=:hrrwrwykNCHUgilvdWGDGAJPKKJJDGA>;;@F?O]:k{
~tcTMIEA@FJQrzvsronprnie_[YONT`^RF2Gjmnjhhnv|c@KNNEHo¢j\o
y51Lwwkd^[^c\f|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~
|vsqqqpmljga]Z]afks{
|y{~,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;jtyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:<w}rbUMFDCCBFLgwusqnlnongb[X[SNNCLOI9,FjjfjhjowzG<BIIGm¢££¡sa]C+={wh_\YXZSu¡p[PY\M;:BIRW[adfjkaM7,7I\inv~vjy
z}k\J,%(***('&%$%%$###"""!&/7:<?>==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~
|vrqpppoljga]Z\`hnu{
~|yyxy|
,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@nzkYNKECBBADG_
xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_£¦¤¦u`eyK1@zsbZXVVNTyjONYP;19BKQWY\_cfe\J60=Qcikku|~z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~
{urqpqonkhe`\[^cimv}
|yxxwwx{}**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81Uvnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDWrgUJGCBAB@>@\ysopokhhhea]YTPH91155556<iligcehm}X02As¢¥§¤
m\vq`7>w}shVRUTTMq
yn^OZaT54;GNTX\^abc`YF9?KZdgfglphysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:<JZZJSOC=@IHLPLZmwnidEWmuunic[XSRQPPSTSSRQOJEB>=>@BFIMNRUVWXXWWX]bhnuz|
{urqppoljfc_\]_ciqx}
}yvvuvvwy{{**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-JtrrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA
}n_QICA>;=<9;T
}qopnhfgec_\VUPD533113356Mplhfecltt>,O~ £¢}cZw`v|r:6oskXNORRQWwmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw}
ytrqppoljfc_\]`fkry~
}xwtuvwwwxz{{
****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""! !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>ivmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|nNR^CavsG7rxlaGGGMNNdwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777<IFFKPGCFNMUdbZTVVOEIezqlf^XRPPQOQQQQRQLHC@>=>@DGJMOSUVVVUTUVY]cjpv|
~xrpqqpnljeb^]]ahntz
}zwwvuwwwxyyz{|****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""! !! ""'-<Vpunnswpifb\TPP^krxvog]PD56Mwtmps^LTEDLHEA?DEGFIIIIPVVOEEF?>=;9D??>=A@JtlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{zmRFFCBW~upU:vtk\BEGLKRkvui^TSTVXC<PZY[Z]`bcdfebVP^giglxl]w|nga_]VUR?+(('('&&%%$##$#"!!!)5<>>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw|
}wrpqqpnljda^]^biqu{
|wwvvuvvwwwxxz{}********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiwxlT>FBAKX}xjbBztfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv|
{trppppnljc`]]_eipu|
zyywwwwvwwwwvxyz{))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""! !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__MuseSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.<R`Z[_alfaPIFSJfrne\W_^^YOPPPPNKHDA>=>@CFILNQQQQSTSQQTX\ckqx~
ztqppppnkic`]]`flsv|
}yxyxxwwvwwwxxxzz{(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLixvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy}
~ysppppomjhca]^bgnsy}
{{xxxxyyxvwwwxxwwz|(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡|wx{hWg{T/`ole[K=62231/./12<dc_[YXVSPLKKKKOMJB61-+-31+/Zf^XU]]_fowjaYC=<Facb`]ZPTdd{dUGHMPQRTUUUQOH?411;N^mwtrwxurqqqprstu]GVODQZ^_[XUURLHE?2&$'''''&%%%$##"#(3<=>?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy}
~ztppppomigb`]^bhnty~
ywyzyxxyxwwwwvwwwxz|))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^¥¥qZb}w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH</),/-&'4_b_[X[^deqn^R=;@^_UZZ\WPGPQk`PCJJMNRX\`]ZNE;313EYfpvuqprqmjloonnlpKOUGFQ[]\ZVUTNJFB:-##$%&''%%%%$##$'29;<>??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~
~ysoooonlhda`_adjpv|
|{yzxyxxwwxxxxxxxwx{~(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81<apefnZ@D@96<CDAACCBDEHEKd ¤¬§}vtj
e3pje\RC2.,,***+,/0/;V\WSPPONMLMKMKJIHE@5.1.)$):Z^abca]^hhaH:BUZW[`aYRLIDGNXMFLQOORU[a_VL@9307J_kotvsnmlgeijgg_\ZGZNIHR]^\ZVURJFC@5*$$$$''&'&&&%##&08;;<?@?@AAAA@@?:/##%!!""""##%'(***++**++++++./38@TbhmoideUMWije^\XUONMNNNLJFDA>==>ABGJMNPPRRPPOONSZ`hpu{~
}uqoooonlhda``bflrx~
~y{yyxxxxwwwwwwwwwwy|(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71EulgjqVAF>99?CDCBBCACEDIa£¦¨ª¬ª¡vyz|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx}
ztpoooomkgecbbchnuz~yyxywxxxxwxxwwwwwwxz}(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{lfiqT?E>99BEEDCCCBCEHYp¤¬«ª®£¡riQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=<?DKQ_qmIGXY_de[SVQNNNNNMLHE?==>?BFHKMMPPPPOONOQV\biqz~
|uqnooooljgebcefjqw{
wy|yyzxxwwxxxwwvvwwxz~
''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk§ªª¬¢££¤¢}zRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689<CFEHUTFG\R]ef[W\SMNMNNMKGD?==?@DGHKMMNNNOPOOPQV_ckrw}
~ytpoppnnkigeecghlsx}
zszzxyyyyxxwwwwwxzyww{''(((((())))))++++,,,,,,,,------,,------....////....................----..----..------,,,,,,++,,+***))))**))))**********++++++++,,++****))((''''%%%%%%$$$$$$$$$$%$####"!""!!!!!!"#'-7Povjknprrqg`VL?2,.2<GR[fmooi`XK?21Iq}lgjlL@CC:8@DEDEGGC?C^}£®°®°®©«tv£¡£`QUSRQQQQQRRQQPPQSTSVQMKGECB@A@?CCBB@>>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz}
~
ytpooonnljgedehiov{sx{{yyyxxxxwwwwwwwxxy|''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No
¡¯¹º»¸ª§|¢sMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~~|wronmmnmjhgdefjlpw}zux|}zyxxxxwwwwwvuuxxy~''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw¤±¹»¼º²¯©ª¬® ¥\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2<EOSV^bbegihkid_QQTOOWZ[XWTOJD@?;95-%""$#"##$$####")6>A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~~ytpnnmmnmjhfefgjmty~utz|{{zxxxxwwwwwvvvxxy~
(())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`t£±µ¶º»¶¹»º»º®¦¢jZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}{wronmmmlkjgffgilqw{
ywx{}}zyxxwwwwwwwwwwwxz(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_v ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}{ysommmmmlkifgggimrx|
yy{|||zyxxwwwwwwwwwwwx{))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu¥¦¬¹¾¼³³²³±¬¤¨¦^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~~ytommmlmmkjihghhjnty~yyy|}{{zyxxvvwwwwwwvvvy}))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pachtu@=:5<@ACLf ¤²¶·´±¯¬nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz~~~
{wrnmmmlllkjhhghhjouz}zxz{}{zzxwwvvvvwwwwvvvy}))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}sbchte5968?CB[|¤£©«¯³´©£tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..<KHEFCBBGRA6872111=LTWWSQPOKKKKMQRSNTSE<=?@CHORUTONNLPPPPRRPNKID>6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|~|
ysomkkklkkkigfghimqv{
|yy{|{{yxxwvvuuvvvvwwxz~))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp~H776=AHj£¤¢¦¬®±¦kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<???@???@><:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_py^XNKLIA@BBCFGIKMNNNMNOU^gr{~
{wrnlkkklkkkigfghimqv{|zyz|||{yxwvvvuuvvvvwwxz~))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5<GQ[dieaVJA96?]~zv{{hbbl~t195<@Ov¢¡¤ ¤¥¤¡ {txu{ikklmjkigghffda[V[\ZXZXTQPPOMMMMNQSRGA<;9:89886551/0/2FGEEBDEIRP633216GQTTUURROMKKKKKMMPONOLD62:?AEFGHFBCCCB?CFIJHFA<0+(')'''&&%%$%$%''&&&$$%%%&*07;>?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}~}}~
}wtqlkkkkkkkjhfffgimqv{|yy||}{zxwwuuuuuuvuvuuxz
)))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3<GR\bfd`UKDCL`{zwvz|ia`j|Z+26>Uz¥¦¥§¢ ¢uiba\nv_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|}
|wsnmlkkkkkkjhffffhlqv{{vxyzz{ywvwuuuuuuuuvuuy{****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}xxv|}k`^dzC+2<Tz§©ª©¥¡
~wlghiaq}]bfhfdb_dbb`^][YVVVTQOKIGGEFHHIJJJKMOOHC<97765665333/0CKGFGGCFIM?112:GRTUSSRSQPNMLLLKKJIGFD:,#$$%)-6<=><>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~
{wsnkkkkjjkjhggggghlqv|
~zyxy{{zxvvutusuuvvvuwvx}
****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?qtryufYF1$#"""#"#%'*19DOZ`cdaZ]gq~y~j`\^vz6/5Ow §©©¨§¢|yuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}z
zvpliklljjjigfffgijnrw{
}yxxy{{{yvvutstuuvvvuuwy~**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outyp_Q=+$###"#"#%(,08CNX^ac__ku}
|
|m_[\t
{nv~b21Dp
¥¨¨¨¨£wuugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;<?@A@CEFFECCA<756446555662;KICDCHHHIN=16FPTUTTUSPONNMMLKKIIGE?5%##$$%&''(*-2;BFDA;2--,---,-,*+*((((((&&%'%%%%%%%$####""""""""##""#"#""###""####%*18AGIHD<3*$#%%&&&&'')*++)+,0;ELJOPV]c^UNJEB@?@ACFHHJKKMMKJIJOT]hry~}
}wsnjijjjjjjhgffggilptx|
{xwyz{zyxvvuttstuvvuuvwz~**+++,--./..//0000001111111100110011001111111100111100//////.../..////////....0000//..//.....----..-,,,,,,,,------..........----,,+***)((('''&&&%%%%%%$$%$$$####$$$$$$$$%%',/>juszzeWI3'$#####"#$'*-6ALV]`ba_n|
xx{}}o]Z[gf_i{|H)>d}¤¦¨¦¢u|vprpoleahhdgpeN^Ic
YMQSTUSUUSRRPNHIKJJGEB@=;98999;=<?BCCEEBB@<764456666643>IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;<?HKP]llg_YQNGC??@ACFHHJKKKKJIHILR]gpx~
zvpkjjhjjjjjhgffghjlptx|
}zwwyz{zyxvvuttstuvvtvwx}
**+++,....-.//11000000001111001100111111111000//00000000////////....//////............//..--....,---,,,,,,,,------..--......----,,++**)(((((('&&&&&&%$%%$$%$######$$%%$$%%'-3>eusv|paP=+$#%$######&)-6@KV\adebn
uwx{~o\YZfcgpzx|=-Vs ¥§¥£z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grz
~xrnjjiijjjjihhgfhjjmrvz~
}yvvyz{zxwuutsrrtuvvvvvw~**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz}kpwvxzr`]^dguz{y}x/>hz£¢¡ zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4<HY[Puwsvvutqmf]TLFB?AACEGIIJJJKKIIHHJP[grz
{vpkiiiiiijiihhggjkkosxz
}yvvxzzyxwutssrrtuvvvvwz~,*+--,--..-.11002233222222221111001111112200122211110/00////////////////////.........---..-,--,,....,,,,--------------......------**)))(((''((''&&&&%%%%%%%$$$$$##$$$$$%&'+1:B\|uqqqlV?-&$#%$%$###$&).2;EQZbhmvz_Omjippsda`fs~xvy
a3[rpgaaehgb^fowpovxxovo8DGBAA?AEEDAC>;=<;<=:853211/./11159<=>>>>===;8556776888:<DHFGEFFFHMTNHQUSQPOONMMMMLKKJKFA5&%$%%%&'&'))(&%$(+-//...00//01/'""#%',*(((''''''%%%%$$##"""""""###&-*$"!!!!!""%/=KZiv~zrg\M>1)'),04<JTMDOO_~|vvuutqmf^VMFB?DABDGIIIJKIIHHGGHOYgr|
~xtnjhhiihhjjihggfikmpty|
~{xwvxzyxwvtssssstuuuuvy|
,*+-..--../011112233222222221111001111112211333311110/00////////////////////.........---..-,--,,----,,,,--------------......----,,**)))(((''''''&&&&%%%%%%%$$$$$##$$$$$%%(.4;FWwuqqonS7'%%#%%%$###$&)-39CPZcmt{|ub<Gkppsqwhdcfs|wvPCetp\`aghc`]cly}ukl{||L9BC<;;<@><<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{zqf\MA8436ASalk[]bgy}wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~
|vqljhhiihhjjihgggiknquz}
}yxvwyzyxwvsssssstuuuvwy|---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<IXmxkhkmY2$%%$%%%%%%%&&(+28AM\hu|~|{wp\;(?fyrqtzjdcbmt{
}{z|uIRmym_\`a^adcgt}pktxtwj-9><867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899<DKA>:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it
~xrnjihhhhhhhhhggghjkprv{~
}zwwyzyzvvutsttttttttsvx|
,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagirywwzzdK]pxyvlhddgaY\binv
|rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.<K^n}yupkf`WWX]fg_[SO]dRJkqmrvuuqmd\RJCBCDEGIIIIHHHHGGFGLS]jv
ztqmkihhhhjjjjhgggijmqty{~
|ywvxzywwvutsssssstttvvz
........000111222233332223333322333333333333333322110000/////////////..............-,,,,,,,,,,--,,----------------....--.///..--,,+)*****)(())('((('&%&&%%%%%%%%%$%%&%%%*/16=MZfonhdcfV+!%$$$$%%%&'(().6FXo}xofUE?69Tupl||kcbeiv
usuvyzbL`pprv|{rjgihhmlkmryxz~ymaVKKGMK.03111112310..//-//---++,,'$))'(,,03588::9:;:::;:=??BCCBADFILIFHGDFIJH@>FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mx}ytoljhhhiiiihhhgggiknrtx{~~zxvuwxxwvuutsssssssuuvy{........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl
{tj_VPIDFRabf~zncahk~{ssuvw|yOQgljkoustkgjklot|}yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj|
{un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nx
|wsmkihhhhhiihhhgggiknruy{
}zxvuwxxwvuutsssssssuuvy|--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx
wld`YWRNOXadn{wngfhpyknrprw{WAWbfacegwnalos{~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/1345533333788<BFIJIJJHILLPRQPOQNKHIHCABEFBBCGGHIHF@?9,%%%'(+-..-*(%##)-..1:@EIEBB=1$#%+2:=?=:8642+*)%$%$###%%#"$$%%%%$%&%&''())('),2EVgy
|pdkoji[]h[5Adv|wrbcpjaXNFEEGIIIJJJIIGGDDFGKUamx
|xrljhhhhggiihhhggijlosvz}
~{yxwvxxwvtttsrrrssssuuuy~
--..//0/111111333333333334444433333333343333333333222221100000//////..--........,,++,,,,,,,,,,,,,,++----------------............++++++++++++))))(()()'&&&&&&'''(-FZL00+')-28ANZenrsliccfQ)##%%$#((.9K`tyjcaa`\XVUYhuvsxrhfiounnnopwyY8KW\^]Z[nyjwyr}
oW>9448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.00<CEIIEB:+! "%07;;;<;:741.,+'#%%###$$#"""!!!##$$$%&'())+,.18FYjz
yrh^OG>40031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw
|vpkihhhhggiihhhggijlosvz~{wvuvxxwvtttsrrrrsssuuw{~
--////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|xcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~z}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx
{voljihhhgghhhhhghjknotw{
}|xwtvxwvvtssrrrrrrsstuy}
..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih
|oXJNPX^bb``\Zc}~{ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-# )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWany
zumjhhggggghhhhhghjmorux~
}zxwuvxwvutssrrrrrrsssux}
////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~
w]B5<ELRX\_adbbds{nkjijlrtpkklouu_2,.9HKHNKJLBB?4;dxzpbN;4579;;:968:;;6(%$$%%%$$##""""""""""""""!!!#$%&&(+---,--/.;A=BEILORSTUUUX\^]^^]]\XUSSTRQPIFD@=;>=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/36<Qhnjp{ytlf\RE732249?GEDGIKWZY]aTIHKMIKLIJIIHFEDEFLW`nx
zsligggggggghhhhhijmosxx
}zyvvwxwuttssrqssssrstw|
////002222112233333333333333444455555333444444443344431121000000..........----,,,,,,+++++,,,++,,++,,,,,,,,,,,,------......//..--++,,,,,*+++*))**))(())**+*-.////111/GA1128?HP[cije[QZmgbagqT$#),2HlyiI.)24;DJRXZ]adfhuqigfhlmkiikloqv_,)+.4FHEGF>65IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;<CHNORRRSSTUW\]]^bb`^YVXYXXWWUMIC=978730,+)))(((*++*****(*,/5AFFEC=/# !%-6<?;71.+)&%##"#$&#"$))%%#"###"#%')*,022/1=\u~zy|~|wpic\PFA:768:??ADFINKNHJ^eSUZHFJJJJJGFEDDHOXboz
}vrmigffffggghhhhhjlprtw|~|ywuvwxwuttssttrrrrrstw|
00112233332222223343234444444444555555334444445555443322322200000.......--,...--,,,,+++++,+,+++,*,,,,,------..------..........----..--,+,,,+****))*))++-..00121100119B=57>ENT]fjjbUKHakecdnq>!(*6Qy}~sW9&(+,15<AHPTZ_dlr~z|shdbgmqmkijkoop^+)+**7FIEC>3,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;<BGLORQQQSVVZZ\[`aca^\]^]Y\^WSNID<75640..-,*)())**++++,+,-17@DEDA7+!""#(.4;>:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{y|
|xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy
{vqmhgfeeeggghghhimmpqtv
}{yxyyyutssrrrrrrqqssuv{001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx
yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!! "#$%&)))*,,,/3689<AEJORRPPTVX[\\_^`ab`___\[[[ZWINH<74220/.-,+)))**+++++-,-0;@DED?5& !"$).39;950+'&$#""""##$%(***$%###$&('*++++*+3APWB=9<:Zlr~|us{~xpkg_UNDB@@CFKKMLLQNH?KKLOXglbIIKIHFDCEIP\epy
zuqmgffeffgghfgiiilnqruv
~{yxyywttssrrqqqpqqssux~
001222332222333333433444444455555555555555444444556644444222100000/.....------,,,,,,++++++,-++++,,+,,,--,,,,,,------....--------------,++++***++**,,//1233456656567788DLDHRY`fmmi[P>05VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}}ukhqx~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqy
~xsokggedffggghghikmprtvx
~|xxzxwtsssrrqqrqqqstsy222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|zvh^\[^chggegmprZ+'*-5<Cc[B793,,(%(()*,.5<;557887876577:2++-,++,-./,*)(&&$"! "##$&'('((()-110/05:>AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/19<ED>3+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~|qjeiqx}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx
}xsokffedffggghiijlnpruv}
~|yxzuwtrssrrqqqqqqrst{333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryoszvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$ ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p|pc\`hry}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz}wrnjgfedfffghhiijmorsty
~}|zyzvvsqrrrrrqppqqrsu{333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98<FLRSTPTXUTQOQOQSUVW^ab_bfc]UTRQM:5;9400....--/025684489;60'##%%+6F[s
zm`TB.%$,9AFGFEACB<4442221000/-*'$$$$$&(4H_u{qaWY`jsy|wqjghddda`afkllc[WXW`efaa[LUk\JEFKOXbisz|
}{wrnjfeedffffffiijmorsu|
~|zyxvvsqrrrrrqppqqrsv}2222223344465544334444445555554466666666556666666666766544442111110/..----,,,,----,,++,,+,,,-,+,----,,+,..--------....------....//.--,*+,,--/0125789<>?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^pzkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7Ndw}ncUSYckt{
zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~
}yupnjggfffeggfgijkmprvv
~|zyxvvsqqqqqrrqqppqrx2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<azwpc=%'&'()*+-0A`xvklx~xsmlovd]\[aceffikl]/&*,-3Sg[Y[XN9*'&&')(-00/.++-012311699Ie6&.00/11..-,++('&%$$%%"!" ! !! !#""')-/7?DFINQLNPRPMJLNQRRSW]effhgdZUVKIZN67=:62111111278864230(%%',Cax
wO0:BMB><@LJID?967:;<<9998-$ !$$$*?Si}tdSKPZeov|
{zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz}
~|xtomifeeefeggfgijlnqqtw
~|zzyvtsqqqqqqqooppqry3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxw+13234321/-+((%#"####" !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')AilB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmyeNBHR_ipx
~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz|
}zvsnlieeeefffffgijnosst{
~|yxyusrpqqqpppoopppu{
333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix[-4566321/-+*)(&#"""! !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b{R<FIJJKNSVXX\aa^UUQONMGA4&##%&((.AWmudI9=JW`kv}}~~tjada^`h]aYUWafaWZa
ACW_[`diosvxy|
~|yurmkheeeeffeefgijnosst|
~|yywusrpppqqppooppqv|
33444444554444445544444455444455555566666677666666667664554433331100..----,,---,----,,,,,,,,,,,,,,--..,,,-,,----......------..//..014577667799>@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]qysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z
^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}|pbE/1>JXelw
xjaZY^\WWW\^^_jeXi}LZa^ZYforsvz|
~~~~}zxtqkkgedeeedeeghilnpsss
~|zxvuqqpppoooooonopw}
44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxxa3(*(**+,.<Zxsmls~vjiiiijpma]\^cabfhfY0+.34?fd^]^ZMJPTSC,'&$$&*.*-317AKWhp|x(/244310/,--++)%#! "!#&(*,,-279<@BGMPQIBHKNSY__ejglni\TVMRdXSOPPMHA80/13666666<8=YzlFFIILMOQSVY\^__\\[\[XQB,$"##$$&)7McxynX=,)/@LYet~yvtdefeZZ^jjg_LHNk
tSPLQYgnsuuw|
~||}}{xtqkieedddddfffgilnpqtu
~zywtsqqpoooooooonnrx
44444444554444444434444444554455555566666677777777778AC;6644443231000/....,,---,------------,,----..--------......------....--//13558;=AABB@==@CFHKNPRTUXY[[[\]\\Z[[\\^_bhnsvtqg]TLQUPB5211/>enhfhqy~yi>%'*())*0Bdyolkqukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx
U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345OspsrHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw~uiR3(56>DO^o|
ztneZWVX^djdedRL[{}of[Valonw}
{|{{{yxtokheddccddeeeghloqrty
|yvvusqpqppoonnnnlot{
44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp}
zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy ;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//DiwU=AYx
uPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r}sfR/&7;?AMany
yskcYSQPKOVX^_^_v}WU[TU[`cp~~|{zz{|ywsnjgdddccddeeegimpstu~|yxvtrqqpoooonnnnnpu|
44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~
mK*()=cvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]r{obM-)+/6?Malu|
yskfYUQPR]_]\bgo{ ¢¥¤WLNSQJG@;ALVl~~zyyz{zyvsmhedddddddeeegjmqstu}xwuspoppppnnnnmnopu~
44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<gidfegOLOMNNMNNK8((.6:K\ZLQY]dmw~
h(//./-+)(())('$!!""#%%),/112FWdlqrvxzvrttvtrtsstrmaUXeaTONNNLIFDBCBDFIA0/Aoz{~
}a7%()%2W}
sIDIJKKKMNNMMIFKLNNNOKIB-!!""$%%(3I_t~wn^F(+3/*3M_iqx|}xpieb[U[\^\]agq
iSHVaain\MJHGGDBJf||zyz{zyxurmhddddddddeeegjmqsuw|zxwuspopooonmnnmnopv
44444444444444445555554454554455456666677766666655665563455543333222110./.---.............//..........////..--------..--------00368;?CFKNRVXWSOPQTWZ[^bcfehijkkkkkjkllmnrsssutpkb]TNKDC@><:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|C*,,***''&&&&&%# !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{{||~m8&(%'/NwhCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w
~{tl\G.&1)(:P[elqw{~
{smijkhaZ^cgkpxB.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{~|yvtrqpoooomlllmmosy
4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9<KV^fnr|,!&(''&&&%%$$#"! !#"'?Zfgdcdegrxxutvtsromkkllnpprv{}t\PLNNLHFECBCDHSamrz~{}}}~y],$%&$$/NtZ9DHLMMLMNMLKFHKLMMKJIG>.#!"""%(8Pfz~~{tiX>+,0,.EQZ^gkquz|}~|yutspngefhkqx\2DL\ba]nsm£©¦ogmvwxzywxsojedbbdddddefgimooru~}{yvtrqpoooollllmmosz5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213<Wjcfeorghgfjkjifgiffed`fxsomiifeghh}rhfdaga\N,*)<gkhilmSBKOPQQONLIGCB@B>?9;54BQZbglus $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptzwZLONLKGECCCCJT`kt}y{{xuoJ)&&%&&.Nt
zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,<Tk~}yrgS8"'18=LQZ\`fknqrwy|~~~|wxwuqqqojnszA'7EQ[]QUb}³¼»º¹±¢~pwt|zzzyvsnkgcbbdddccdegilnqss
}zxvtrpooonmllllnlot|5544443333444444444555444444445555665556666666666666665555454444332111000//..///..////0/////////....../////.....--..--..----..00146:>AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~X&&$$$$%%""!! 4Zkje``bcclvyuvuxxxxusqpnnlljjjklnszy\ONLKGECBBEJRant}ywwsp`9,,(&'(/OsvI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@XmzrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~71<ES_VQMn ¶¾º¶·£«´fXp{yvrmgccbccccccdehjmoqsu
}zwutrpooonmllllmmpu}
554444444433334444554444444444446666555677666666666666555344444444321100000////.//////////////////////.////.//.//.....---------/1358;@EJOT[aejiea]_bdfjljmnnpnnnnnqrtwyzxwvtrnid`\WTROLHFB@<97312:^ia`bkX04;:62/7Nhd\ZWS]xrigbdhjhilr}mfcbjo\G'(@hkijpp]?HNSTUSQPNIEA?=<>EB=8@ARdQYafku}=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu~p=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[q
}ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy|
33<DT[LEX´½½«¨¤¬¹²
w_nxwvokgecbbbcccccegjmoqsw
{xwusrpoooomkklllnqv
554444444433334444554444444444446666555677666666666666555344444444322210000///////////////////////////.////./////.....--------/01357:=BFMS[aejmjfcbdehkkjmnnpnnnopqsvx{{xwtsqmgd`\XVSPMJGDB>:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkntiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{- ()'''%%%""!! /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw
~~i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_w
}wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~
y77;AMRJDo¦·¾¶| ´º¸¶~uvutokgecbaabbbbcegjmost|
}zvtrrqpnnkkkklllnpw
445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679<AELRZ`fknnjgfgiiijiklnpooorswx{|yxxwvsnige_\ZXSQOKIFB?:8622/?cc^afjG&(*)+/;Te[WRMNTtwfghiiifhlp~mkjhuzpgihhlpnphCGMQUWURRNIFEA>=;859FI?:9BIPPV^fmu~w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy~~d0#$*;FHJH?53<CIKLKKJHHE8%""""$$&3Md{
}vm]C)"*:LUWZ]\\[ZYTUZdhlqttuwx~~}}~~~rG9@CKICM©¶½¶{Y©²ª¡£uqrokfbbaaabbccdfhkoqqr~~zwtsqoooonmlkklljmpx
555543455544444444333344444455444455555665555566667777664444444444222211111100////////0000//./00////00.///0000/..-....----..//0135789<AEKQX^djppmjgfhhijlknoqpqswy|}~{yzywrmjfc_\[XTRPMIFB?;962304[g`_bhW(%)),0<VfZUQOPUrxheeebekhimzskjio|zxoloppq\AHNPSUTROLGC@><:95328CG>15=CFQ[^enu{k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{
xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}|mVCFEIE@a¨²·¸ª|¦ronhfdbbbbaacddfglnqrs~zwusqooonmlkkklmknry
65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689<AEIQU[biptrnjggfhikloqvyy|}~{zwqmjfb_\[XUROLJGC?<973213Li^]aca0$()+0>XdWSQRTXirfabcaailrpqujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37<?DT\acnu|
X"$!! "#$&# -Yokedhhinrusvwustuvyzyz{yrjiihquqprvwrolhfilpswrSIFFBEIP]krwvonmnpmprqojeiooko|~}wS$!#$#"%#+*(/:?EGIIJEFF?1%#$""$%-?VmylX:&*;JU[ZZ]__]ZWUSV[`eikljkowz|||}~mVOLGIBAl¥««¨¡ ¡skidbbbabbbcceggjnqss
}zwsqonnmllkkjjjkkns{
655555665555554444554433334455545555666666555555666766666555444433333311111100000/////0000//0000////00//////00/..-....----..00013568:=?CHNTZ`gnrusnjighhinqv|
~{zwqnkfb_\[XUROLJGC?<973200@c`X]be@%((*/A[bVRQTVVdsg`ccchmmokgqsh^_cimrx{xttm]GIKMNOOOKKJEA>;9653321/2<=437AGRYYaemuy
M "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq
whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}n[ZZTQHDq¦¥£jedabbabbbcceghkppsv~}zvsqnnmlllkkjjkjlnt~
55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{
}zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}|}}rC#$$"##"%%',.**4=BGHGHE</%$$$$&)2G^u
tgS1&6DKOV[\_^]_\YVTTX\`caceikppmuw|pdgcZVQOu¦¥£¡ |fbca`abaaccegilqquy
~{xuqnmmllkjkkjhkkmow
9999886676555533445544333333444455555555666666666666667666664444333333221122111100///0//////000000/////////////..-....----..//013579:=?CGLPW\ciorutpmifhltx|
{vspnlhdb^[ZWTQNKIHC?;86220/1Cb[XZ`]2%+.0@Z]VSUWXY`smdhpqqnnqmnk_]bidfgklngaUQSSTQQOOMKIHGEB?>;762100//,-7FE?CHNidU_glqx}~8 """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx||rnkge`\\|¤¢z¢}kcbbbabaaccegilqrt{
~{xupnmllljjkkjkjjmqx
9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz}
~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|yn_A)(<EJNRVY[]]\\[[ZZ[]_`a``_beedhlpx{}|wsspkgfe
~sVoyydaccabaabcegjmpru}~
{wtpnmkkkkjkkhijkmsz
;::::::877665554444444333333444444445555556666666666666665554444443321221111110011//////--//0000..//////00/////..-....----.../013579;>@BDHLRV\agmrvvspljnuy{~|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?VoynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}{uttrsqopv{|o`ababaabcegjmqst
}zuspmmkkkkjkkijilns|
<;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\syjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv
¬¬¢z{¡xdb``aabacehknrtt
{wvuqnmlkjjjkkkkjkow
=<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqwwvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~+('&%&&$#%$!(Onxi`c^K<ATntdY[]^bbbfe_`hdO=?ICGHNUVUWZ`hoty|{xuqlt|tlllkjhhhXKR[mwvqpqrqpeW[fosplkkjmt}||wY)'(())*))('''(*-4.(()****))*)))3Kat
xfL-,<GJKLNQRVXYZZ\\\\^^\Z^]Z]__\YZ\^biostwywqnpuuuuvw ³¶®£{ |rabaa```bfhknrtv~
{wtqpmmlkjjjkkkkkmrw
==>=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut|
ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{
&!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=<BHEHFMQOQT_hosstvyywups}xolkkjhif[RTapvtprssrm^VYbnrnjjjimt~}~uT'()(()*)(('()'),21)'(*+*+,,0--/;Si|}scC+/>IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz®³«
¡~f`_``a`adhmprtw
}{wsqnmlkkkkkkkllkmqx
@@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}|v|unf`UK=:9<?AFHMRSTSTSRPMJGEB@>=;:;:7544456;>=<;;3/+*<Yfgjrthcdktz~,"))'''#@rvnjjkijfZA8IMEOUVY\acdeg^C4E?AQKEHHILNS]flrttstuxyxvx}}umjlhffcaWZiuwsprstphVTXalrnjkijnu~|}rK$')))()((***)*')03-''(****-0339G\s
|r`A+3BJKKMPPPPRTVYYYZ\___]\\[Y_^[TUY_`ZbjjmodZ`gnswy|
¨©~o_`__`_cfkmppuv
~yurplllkkkkkkklllory
A@ACB@?><<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|pmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}(!'+('"3suokkmmloo[C8<ODJUXZ[\^^[L94586EMIBIJJLT\cgimprqppuxzzz}}unhjgcaab\`pzxqqqrslbPPW`jpkjljjqy|~qC&''(''()*))**)(*-150'(())*,234>Rfz
}yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{¡ yda`aaadgiloswz
|ywtqpomkjkkjjjjjjkns}
BAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqsmtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||m<%'()))*+*+**++*++/471&'()),18<I\r
wjV4,:DIMOOPRQONOPRUYZ]^_a`]YWUU\_]YSONaggfgfZYagrv|~ ¢tfeddegilmpsw~~|vsqmllmlkijjjjjjlou
FFEDDCA???<:998877777755333344443344555566666655555566555544333322211101111000////....--,,,,--..//------........--...........//112356;;>>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bqxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|}m@2.*++++****+,--,--08</&(),..9ASgy
~udH-+:CGKNOPRSRONNORSX[]___^ZVTQZ`c_ZWTU_giie[_hnuz~¤¤¢~}lhkiikmnrsuw~~{wurolllkjikiiiijjmpw
HHFECCBBA?>><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr
sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~
w $"B{qhjovy|w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}rRIE<0,,+*++*+..,+--.2:92)-8>7=Mbt
ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|¢£ vpnmooqrswxy
~~{wurpmlljiihiiiijjmpx
MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`|
{uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvzj7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv
ylc[SD4-+++++++,++++,.4;=033:>DZn{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{
}wustvwwxwz|~~~|ywtqpnlljjjiiihhkklqy
NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{
ypd[I6,+++*++,++++*,/5>735=BQiz}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}{wyzzz||
~~~{ywrqpnlkiiiiiihhjklq{
ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-148<ACDDEGGC@AEFGGGFEA;40000155766655633.0Ibgs{|xX $+kvlnqoryscW\\[^\UA556530-/4DIFFXdTQVZZY^^bded```dcdc\WTM?:CUbggkg_ZY[]]n~wpprqqsXFEKNQW]ejlmrxsk^L4-,,,-,,-+++++-16:;;BLZr|sh`]bfcbbYGOMG>9=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|}
~~~zxvrppnlkiihfhiiiikms}
PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuyvk[D0-.----,,++++,/4;;=@Nbv{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~
~~~|zwurppnlkiihfffhhijov
TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\l~uqqrsp_DBFHLOV_cgr|
tgV=//......++,+-.07<@ERj|pdcP=^gbeehhhhlolbODKQQYafgknrsx{~~~~|}~ ¡¢£¡£¢
~~~~~~}~{yvtqonllkihgfeeggijpw
UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]aw}vtutsqR?BFGKOV_hou}sbM5---....,,,++-,27K\dtylcbcZ^eccefiiggjooppokdgmrsuuyz~|{zxy{|}
£¦ª««««¬®°ªª©£
~~|~||}}{yvtqomllkihffefggijqz
XWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[fzwvtstvkK=BEJQU\ky~ym]C2.,//00...++-..4Caw
ug`_aabacddggeehnrrvy~}||~~~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥~zzy{{{|}
~~}}~~zxurqnmkjiihffffggijs|YXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655<FOUSR^g^Z\mxvustsqvtSAGLMP_s}~sfW>149;;<862-*+022C_y
|ob_baabbdeeeggjkprtx}
zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡
~|ywvuvvvwy{~
}}~|yvsrnmlkkjihffhhhhimt}
[[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v}yvtprrjjwxXEIMVcw|qcSEGLPPPMHC@81214Ed|xk`_cbcaabcfghjossu{
~yvspohfimpmn²³¸¼¼½½¾¾¾½¼»»»»¶°§{yxvsrorpprrvw{~
~~{xwsplllljiiihfiihhilu[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@<AIRfppk`[^]]T+!!"%*6QdbceZF0,)%$"##$$&(*/9;844447;==?BBE@;:86766750Ku}tkimqvzua[_Z_gK((+.-1038:7<FE>;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i|wususj`bkuxXFKXo}~zzxsid_bfffb]VQJA846Ml|rdb`bcbb`abdglptw{|
~}}}|xsnjff_hmpko¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦zz}~zxvtromkhhjjljoy}v{
~~zwupnkjiijiiihfhhhhjpw[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^s
}qotvy}zxy{}|zwrkf\VKFK[q
|xkaadbaccehiknquy}}
~zvvwvvusmhb\_dacgm¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª
styyyz{zxurpkgffijhdemnw{ww|{wtrpllkkjjiighhhiikq|
¡¡^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;<DXv}sjgbnt]/ !$'1Hhmml]7*-)'%#"!#%#$&*-46666679=@DFHHGC@<985441:suonqstyyb^`]agI%%*/9:78>>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{xkhmtrx
{tlf\\_m{
}tf]^_bdcfgklquw{
wnjjkljd[WTQRUSW`¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuv
}wurqllkkjiiighiiiikq{
£¤]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt
yecholm{
|wolqx{o`Z_`^bgjnqsv|}~wz}|{sfZZXTPMWPGQ^jl£¨µ¹»²¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emz
~xurolkjlhhiiihhhhjou}£¢^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|wdbdljbq}
{m^[^^^einrwz|
{ytquy|vvld[T^aFDl§²·¸¸¹¾¦w»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t
}xtpmlkihhhiiihhhkpw ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgstbccii^j{
whZTX^bhkos{~}zzzug\armnge^_qsIZ¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWo
~zwrnllihhhhhhhhimqz
^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkx~qbacfg]h{~vh^WUajghov}}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp
|yupnlkhhhhhhhhimr{
^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0<MROKGKa]QJKMYnc.%1Rd`V?,&""""!!#%%##""$(),.2449:<>?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<<?CFHJLMMQSVYYYWX\[ZYXXTPNIECDMSW[ennpruvsuvng\X]_hs
oaaabeal{~vlb^_gossy}}{wutskqo_YpsmklsjZX[Wuª¸º¼¾¾¼¸±ªµº¼¾¾½¾¿»´rQKTTQMNNNSV\`dilooppsyvwwssmiprpoaBKp
~{xusoljhhhggghjms~[[[[[ZYWVSQOMJHEDB?=;97764331111//.-------------,,------,,,,,,+++++***))))))))))))'&&&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334445555777787666667777666777765664333333322222211000000//..----,,,,)***0QfUPV\b[-! &+9HQTMIQe\NNPSetK!$/NaYUK;'""""!!#%%$##"$')*,/14668;;;<;7Icrsmfgjopu{wmaZY\^`d@-,/02589736AR\UKHPRUUWZUL@=;::=?DGLLMMORVYYYXYZZXUUUSOLIDABHOVXbjjjlpsqsrke_YZ`o{oaaaekiq
yrnjhny~
}zwuvrrronpqrnoqmkhfYS_c|©·º½½»º¶¯¥°¸»½¾¿»¶©~UMRRQOLMNOOOQV[_cgkpqqrt||wqsxqj`mvxwn\ay}}
}{wtqmlhgggghhimu~ZZZZZYYWTSQOMJHEDB?=;:9764322000//.-----------,,,,,,,,,,,,,,+**(++++))((((('''(((('&&&%%$$$$$$$$$$#$&&&&&&&&''(())))**++,,,,../000112222233323444466666677666667777666667766554333333333221110////////.-----,,++,,+()5_bSPU\fa2 "(*7CRTOJ[i[PKQZpk5 ,Jc]TMG*""""!"$%%&%#$$',+**.//1269841Owpmgdfimppwpmc\YZ]^`Q1//2258<;:8<=KE=;EQWVXZ\ZQF>;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\Zdssb`bhru}
{yxsrx||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬¸»¼¹±eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}py}y||}~
}{wrpnjihfgggkox\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hwub_dkv{~~
~}~xuqmmrsppqonmlklonnncY`ed`ZU`´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx
zy
zx{{{|}}
}{zwsomjhggghkpz}YYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ</%!! !!"%&'&$(&(+/,('&(&$$!*Lrnmhhlmnmpr[Khf`\ZYQYL*.1699788:75786328DKMKKMMHEB>:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp}
wd_fpz
}{voga\_chklpqroonllnmlU@MX_[H@Y°¶¹¶¯µ·«¯±¶¶ªNOQOQQSRRSQPNNLOONNSUX\bfimoqt|
vvyzzzz{~
~zuqolkjihjls|
}|YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:<?DGLOSXYVVXZ[\XVPJKKHGFFFGGGFCIPX]`dgijjcX[cinw
yd_ft}|}uhXLIORT\`bcfmpoonnjeN=R_^V>3Np¥´¶¯°¦²°± oMOT]WSTXWURQQPOOOOOPQUX[`hq}¡¢
wuyzzzz{|~
~~}xvtpnkihilt
w}WWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt}h_jv
|zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^¯²R¤}³·ª
v\MNRXQTYa]TUVVTQPRRPNOPR]n~¢£¢ £~xvxxzz{{z|~
~}{wutrollkny
}rxWWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<?DIMOSSQQSUVWTRNJKKKKIGJMMLIFACIOW\acffcYZepz}h_ly
~|xy~
~wod[mm[RWVY`\XXY^dffc\UF51GQORZ]z¢ª_s²µ¨vVIILOOOSVVROTUTUTRQQRQNMa~¥«°¯ª§¡ywxxzz{{{{|}~~|zywtromnqzvoxVVVVTTTRQONLJHGECAA><:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv
~jbp{|wqosy|
xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤
£¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTf¦²¹¼º¸°©¥ ¡{v{zzz{{{zy|~}}~~}}||ywrqppu}
{rnw
UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC"" !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#<sjjgikpt_<;ZegjfggaMV_F29E?DCEC<;756>FEA955564332599;;<<;:99<AEJPRTRMLMOPQQMLKKKLLNNOOOOMGDA@BGKRYaec[^o|
|jcq~~ypggmsv{~
~vrpppqstqoc]aSEEPWTNHINPMKQQPTSfa`|¥¨©¬ªmLHHMNMMNPPSTTSRSTRQQPUnª·»½¿¼ºµ¨ ¤£¡ |u{{zz{{yyzy}{{|}~~}}}}{zwtrrxxnmx
TTRRSQPOONLIHFDCA?><;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]<HgfaabcccWRVI65=;=C??=97532:BDB<543230312589;=>=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fx|y{
{jes~vl`_dkosw|
}|zwtqqqpqqn\9489<DINMKKKMIJFMRRPllj §¨©¨UFGHJJILLOKKPOMMOPPQOXw¸»½¾¾¾¾¼´¤ ¤ ¦¥vz|{zzzzyxy|~wwy}}|}}}}}||yxtrz}uonz
RRQQPOONMMKHFECBA?=<;976653210/.-,,,++*)****))))((((((((((((((((''''''&&%%%%&&%%$$$$##########$$$$%%$%%&''&&'())))**+++,,-.../0101223345555555557766666656665566666655555555554433221111000/.---..-,++*)))))''*)))*+-,)),,-013Egj]TRSY^dbF$!!!"'fxb[]`nJ""! !&(*++***--.120,,/cpjedhkn_M`jhdaacb[_[J?47899;<:97565108ABB=6310//012237:;=>>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|}vqu
{jiv
~}xma]`eimqv}~|{zwvtpqqpqnM)5688<CDJPSOKHKMVZZV^_l¨ª§¤wDHIIKJILLMJJLKJLMLMMTs¯¸¼¼¾¾¾¾¾¼¹³¤ ¥¥¡|z}{z}|zzxx{}vsuwz{|}}||||{ywtzyqon{
QQPPOOMMMKHFECBAA?;;986654320///-,-+,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%$%'''''()))***++,,-,-./01123223455555555445577666666665555555555555544333333111100////...-,,,+,,++****))))())-/0.+*+/3477Bcn^SQSU]cj[1 $$3lrb^[am0 "!""$(*,*,+++./012/,.`oghgijphdjkhe``acb]VH8205768>@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTarznkvyjjw
}zvkd_^_ehmsy
|}~~~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm§©¦ mAFJILKJIJIJJJJJJIKJSl®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡££z~}{{{{yzxxz~sptuww|{|~~~}}{yv{
uoon{
OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN% %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[p
~zqhltymlx
|{|vkb_\[_`fmu~
{vqpqou|~
~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{§§¥cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡¢¢{{|{{{zxxy{}nlnqttxy{|}|||{zy{}uoon}LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:<BEA>Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz
~}|ujcivyomy
~||zufefed^^ait
zvmbaees{|}|}~~yphdehiidW[`a``bcfgjkohbl§©¤SCFKKIJIHFGGGHJGGHI]£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³¥
zyzz{zyxxyy~pjllqrtvy|~|z|||z}|tnon|KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|~}|~|wnbbkw{omy}z||yulhjhe`^^iv
}woeWNQXmvy{}~}wqssqnjdW_a`__`eeghlogcg{¥¥£JEKJIHFHGGGGGGGIHFLn¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡
{yzyyzzyyz{qjggnqpsvxz|z{{}{~
ytnno}IIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?<PbeZRRUX\_inR1)))))SsbZ`j[ "$$&***.024545775=ikhhkklqgdeghhcdeih^G@B332227FD@732121111466:@C<;6..//1234569;<=ACDEFFGEGHILNNMMKIHHGGILNPROLJIGEDC@@@BDDDDEIS`ktxz{{|vqh^`jw}on{|{{{|{skjjhebcn~
ztf^MCDVmswz|~}~tppmmcX\___`abdfgeckgdiu ¤ G?GIKIFFFFFFFHEGFFZ¨·¼¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§¤
zyyxxxxzzz{qgehhmoou{|}}{{{z~}yrnmnGGGGEEEDCCA@@?=<:987755443000//----,,+*)))**))(((((((())))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,+,,-....//011122334444566666666666666644445554334544443334222222100//..---,++*))))((''&&&&''&&%%&'(*/7<=;8<BHJLLJIED?=GXje[XXXY[bhfR*"()(Ztd]blL&$&**+-0378:;987fjgikjophcfggghfgilWA?B7353337CKE731///0124699799951.././025789;<>ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky
{oo{|{{{{}}ypoljiffq
}ztgZTKHPctvvy{}~wponj[_`a``cacefgc^efhnqF@FGHGHEEEEEFEEDFOt²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£
{yyyyyyyywy}rgeggjnnsvyz|{{{z
|xpmlsEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{|qp{~|{yxyy}}tonnkilv
~~ysha[WQQZhsuwxz}wpljb]`abbbacdfgd^Ydfemrzx@BFEDHGGECDFDDCEJd¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢
}{{zzyxxwvw}sddgefkloswx{{{{z{yutw~BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en|
|pqz}~}yyzzz|wtqonlny~~~{xriea[WW]isuwwz}
xpgc``abccccdfffa]]hhhkruu<BEFIGFGDCDECCBEW¥´»½½¾¾¾¾¾¾¾¾¾¾¾¾¾º¶²®©¢~}{~
~{{zz{zxwvw}wgefgejlopsuy{{}|
@@AA@@??=>><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<?=;;:;=><;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ympw{}{zyyyz|~wqqoor}
z{z~
~|}}}|uqmida`cbisvww{|ypebbcdccccccfdcc]^ijjmrstr>AEDECCEDDDEDBDKm®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®«
~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}|
@@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShu
tmqxxzzzyyyz{|xurqqw
{zy{~~ywxzyzwxpnlmmlhmtwwx{}
unjfbfeddcccccdb`_floljpxyoCBGDEDACDDDEDBCW
¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²«
~vuyyyzzyxwwuvyzgeedefiklnrvy}}|????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7<ET]djosjjlmmppllmmgP<BMQOHILIA:5432.-055540/,+253389989@DCB<51---,,.//134468898777;::98989::9;=<=@ABDABBB@?@@>>????@@AAAAABBBCCCBBBETiv{oiovvwxxxyzuuvyvtrx{xxx|
xpptwuwvusrsutqlktwxx{
}uspkfcefffdccbc`[]cgkqnoru{pFCCEEEBABCDEFCJj¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢ ¡vuwxyzzywwwvxz}heecfdghknrvy}{}
<<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrglzxvsy|yyy|
xoimmopsvvuuwvutpmrwz{
ytpnijgffecccca]Z]cgmuuuomppLDDDEDACCCDDEFV£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£zuwxyzzywwwwwz}hedcdeghimqtz|{}
<<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867<BFCA<71/-,,-/1324435555666668777799887788;=>?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn}yx{}zz{|
|sb_dghlnpsuxxvtstttuz~
{wxusoigfedcbb_^Z^fmtwvuplivpQGFFFDCBCDEEAJdª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦zuwyzyyywwwvwz}lccdddfhjmrs{z|~
::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+<Ye_UZUVWZaefjjjgefb`jspihkntwo\8'-6/Ifg`]WO76JC851///..2:9421.0111567;857=BEEC=82.+,,-/024543444445554566779988777788;<==??>===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m}{~}zz|xiYY]`dfglrvwwvvuutwz|
~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~
{99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<?@@@AA@@><==AES_isvrrqldUB>FPX\bgmstthYVLf~~}q_QSUZ]`fjrttuuwvvyyz}~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥scdeinsxyzzyxwvwwy~ncbbdfegjlou{|}
yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<=<?@?@??C@>==?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr
zmVMOPTW]dkpqsttvwxyz|}}~}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU
£°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©hY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~
~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]t
ylVJHJLR[gnnoprtwyxx{}~~~~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»·
\PW]`dghijrwxxxvvwx|yqprssssttw}~wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi
~o[HDEHQcikkmprxywwy{{{|}}~~}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDX¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±\EPY\^bfgihltxyyzz|
~~~~ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA><?B@@=;4/----/1122222222222244321122332121156775677777888899::<=?@=@HWanqqrog\P>0.-,--//011248<BHIKPXj~|y|~s\HBCJ[cffhmryywwyzzzzz{|~~~{zwusole_ahd_dmoonlhfbaa_[NNHFCBBBB>DY¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹FEOV\^`ddgiiow{{|
}xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz}
vcG?HWcfdekpzyvwyz{yxxxz{|}~}||zvsrooplknkkoooljhfbab`\KLOGCBBBBABV£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{
uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !! "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz
yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«´¸·®o_cdddgjjkmnnry}
vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!! !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±¦¦«¯®¤xzwwvvwwwvyy}}
}xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!! !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026<CIOU\bjnrvx{}~qeabfnvwxvuuutrmhggjmswyz|~~}~~|ywuvstrppomifdda___a[KIJLIECD@AGm©²¹ºº¼¼ºº¹¸µ³±°®«¨¦£¡ ¤£
{vqiaZMC=<===<:989<???CDDDDDD77777766666554333222310/000///..--..-------/00//0000000000110000000000//00////..------,,,,,,,,--....../.......////////////0000//0000112221021122111110000000////....----+++**))))))(''''&&$$##""!!""!! !&)/6>DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><JNIW_c__`d`TTZ]aOGB;48879114<CCBB?:99778;FLME=74435567:<><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx
sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£°±±±±¬ª©§¤¤¡
}ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!! #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£ ~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!! !#%)/4<BGHIIIPUVWZYZWF-''''((((((((((++,,,)&&=md[[afhS>@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{||||}}{yyvvttqmjhecaa```TLNQX_ks|
}wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:<?BB?:62011001234452200//..../......--..////////......../3>IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}||}||}|{yxwwsqomjgebcacb\_kq{
wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!! "#&),04<ADFGJLNPRRSOG7-'''('))(((())))++*,+(,ekhhfcgW7AHG8-/7:=>D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1<FSVRNI@6-,,,-..--,,,,,-.../10////0011100/.-..--.//.0///000////./0../10468=>?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|~
zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!! !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,<FRQJ=337;55:Rfqnme^abc_]af[ROKKJHJIHB80/125@GD:1-----16<;;755544568==>?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~}|}~}|{{{zvvtsqonotz
}~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!! !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<?ENYcnw}~~}|zy{{zywuvwrrz~
|xsoh`QB=<><;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! "" #%(),15<CIJIHIJKNMLLE?.'&%&'((('')()(((()*(Qmcedac]./7=FPWXYM729:?<\trrpcYa^[`fhe]WSQQRQPNLJC933/18=CA81-**+*,.135445678879<>@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx}~zyxz{{{{||uolhffkmpqs{thnz
|z{{}
}wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !! !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1<ENMHE@94*())))**))+++,++--..////00001111110/////////00000000000/....--..///000//00011223443333:GVblv}}~~{vsvz~
wpic[UPMHHMQ`]_dnsqlgs
£¢¡¡¡¡}ytrssx{~
~~~~yqj]L@===<;;:<<<;<>>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!! %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==><KlpstUV`cabeeegc]YXWSPPQQMLF>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksy}|wqotx}
~woh`XSNLFCDDDEM]XROTZ_gn |toiddeimsw|~}~~~}~~~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!! "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;<ACTmqrne[Ycaekmkhe^][YVRPSROMJE=30/24993,+*))))+.01489:;<97779=@BBA=::6520013345655555432110/////--,+++,,,,,,++**)),2:@HHC@<6/('''''''())))()**++,--.../100001111110/////////00//00//..---------.112311111123555522247CQ^ipz}|zvpoquy}}}}~{ri\TLIIKLONLJHHIKQQOKIKT_o{sme`\YVVX[beipsv{~~~|{{|}}||~~|wodWF===@AA?@@ACFGFFGGGGFFFFFFEEEFCC6666665544554432210000////000010//////.---............//////////..--------,,,+++++++**,,++,,,,,,--....////////....-,-...----........---/...///////////../...-,,,,,++)))(''''''&&%%%$$####""!! !! "$%),.27;@CHHGFA>>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<Zqqsomi]eghlppida^^\WSPQPONKGC:3005473.+*))((*,-045789:86777:=ABB><;:752111224567797553442213330/.,,,----++**+)))*-6<DJA?:51*'&'''%%''&%&''(****+++./..001111221110//....////00..00-----------04455322012456766333347?NZeov|~{zxunkloux{||zzyxtsqnmlg^VNJJIIKMLNNJGGHJLKHFGKNZm{}|{}yzvqjb[UMGDBEGOUY^_deilqtyxy}~}|{{{zz{{|}||{|}|tkbUI@AABCCDEEFFFGGGGFGHHGGFFFFFFGG6666665544555422210000////000010//////.---............/////////...------,,,,+++*****++,,++,,,,,,--..//////////...-----------........---....//////////////...-,,,,,++)))(''''''&&%%$$$##"#""!! !#%(+/38<BEGIHFCA>>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!! #$(+-18<DGJLLJGB?ABCEFB=2&('&&&''''(')))Gog`[^abjU!.:<==;EGXOLJHB=EOWWXdjhgiiabiqrojchhd_]ZVTQLIKIFDA:614675/,,++))))*,-015988855569:;<>===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:<BHMSX[^`^]_^behlpux}~|yyyxxzz{y{{|{||~|ytldZMFFDFFEECCCCCEEEEDEEFGFHHGFHF4433332233323311211100///.......//......-.--..--,,,,,---/-----,,,,,,----++,,+*))))***)*+,,,,,,,,+,--..////..........--,-----------------......//....//....------+*++)))(''&&&&&&%%%%$#$$""!! "%*,.29AFKPOLJGDBABEFIF<,('&&&&((')%(')Vlc_\bhglS2JLLNSUJGJNKIA<D@BGQ^aeghji_gjnplbejgbac^WTPLHGFEDB=834675/++++****))++0269995534457:=>>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344<FR[eov{}}ywtrkdgjlquy{{||{yxwtrokgc`\WVTQNJFA<:88887677::::9;;;<=>><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#"" "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?<FT[dWVcinonlopklpeW_jjhica_XRRMIDA>>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779<BGLQW[]__][WQMMTY`gptwzzwwxyyzzy{|||{zy{}}xribYNEDCCCABAACCDEFFJKJJJIGFED1132222211111/.//........-------,.,,++*,+++,+++*****++**++++**,,*)****++****(******+*)****++,,,,,,++------......--..--------------,,------.......-..-,--------,,++++))((''&&&&&&&&%$$$###""" #$'*,-.26;@BFIJIGGDAAA@?8-*,('%''&(''<pkddegjlh=+/3?ZLCFG<PRNKIGFLQ_f`X_gmoonpqopof_ejhgiffa]XRNGB=;:;:74110-,++**)*,+--,*,./01345420037589::<>A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999<??>=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#"" "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<<????>><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#"" "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<<B=53/)((((()+,//./.-----/2466321135689;;=??@@@@??=<;:998888755410....,.-..-,+*)))(''&'(-147:;97630,($####""""###$%'&'(*,,,-....--,,,-----////....--,,,,--,,,,,,,,----..///00000//..00....../128DNX`hpuzywoljgeb`agnqssuqomiedb`[\^`cfhihd_YPG>988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!! $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6<HbcZYhqmrrqmnmiijkgda_b`_^]\USUSQMIC@@C?8:9561(&''((())*./00.,+++,,-03101/025788:;=???@@AAB>@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669<AGMSWZ\_]\YSNHFEHMT]ekpuwxxvspmnopqsx{zyyy{zwtqjc[UMIHIHIJIGIGHGFC@=;9....-----,,--,,,,+**))((('''((((''''''''(('(''''''(((())**))(((()(((&&&&&&&&&&&&''&'(())))**(())))+++++++,+**+,-,,+,**+,,+**,,,,****++++***+++********+**,++++++******))))('''((''%%&&%%$$"" "%),/147;=BHNRQOMLKKMLH7*&&('&'&&($Ntiejjle2#120.306EIOWYVVVWVMD;08F^RU[akjruqmprnjiggdb`]^^YUTNMPRNLMF??A<763551+'''(((()*-.//.-,,++*+,-.00/0035778::;<>@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!! $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1<HZZTW^hjmqqswvqiaafd_ZY^_VPNIHJNMMMIDB@<854774-(%&'()))),/..-...+)(('))*--.014566899;<=>??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!! $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467<DLT_eltvsmhc_YUTX^cgkmmmje]XRNHE?<:<>>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~
~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;<?BGMSUVXYYVRMLJLNRTX\afkqsvwzywtrqqtvxx}
wocVGC@<;:964332/.**))****))(())(('''&&&&&%$##$$$$$$%%%%%%%%%%%%%%&&&&&%&&(&'(('&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((*((((((((())))((((((((''''''(((((((((((((((())(((((())****))))))*)()))((''(('%&&%$##""! "%(,/4;?AEHIGFEFLNONMNG7'')**)((]{mceiph@!.20/.,*)+-7AFJRRTX^c^OEA=M_ffjnwq_YS`edkrxyumjd[VSPOJDEFHFDDDHHGFFFC>:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz|
zqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;<?ADA@AEKLMMLHC4(('))*%UwolhnrpW&+/3/..,+**+8@KORRUVVY^QCCO^gkjgmjVYa^ZWWcortplni_YVRNHC@CEGFEFGEFHIIFD?;94.**-**.11.+(')+,,,,-+,+,))()-164345688;:988764222333332222210//-----....,--****(&%%%$$$$%(*.100210.+(&%&&&&&'(((()*++++,,,,......--//..,---++++****++,,--/...//////00112343233334455666778999888998;AIQ[bnrsole`[[YXY_cgikjjhe^[XSPJGEDEFECCCBBAAAABBBBA@?=<;::9999:>EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~
~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###! $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz}
~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##! "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666<CNXaiqtvtqjgb`][Z]dgkjhhfc^YVTPKHGFEDCBAAABBBBBBA@?=<;:977889<AEJOQUWZ[[[Y[[Z[\_acceffhknqtx{|zwxx{|{{yuqg^M=200.-,,+++++(((((())))(''''''&&%%%$$$$$$##%%%%$%&%%%&&%%$$%%%%&&'''''%((''&&''''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&''%%%%%%&&&&&&&&&&&&%&%%%%$$$$$$$$$$$$$$%%%%&&&%&&&&&&'''''''())))))***)(())('''(('&&&('$%#""" #')./12237:=@BCFHJMLGA4)(*'6uvkkjpqj8%-.----++))*0FCBIQSQPOSRMXY[`k]Knovnigd^YYVRYabe`\][[\SKFA?>?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::<?CHNSWZ\^^____^^_bdffffcbehkorvz||zz{}~{yvtsni^QA2..-+,,-++**(((((())))('''&&''&%%%$$$$$$##%%%%%&&%%%&&&&''%%%%&&''''&'((''&&&&''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&&&%%%%%%%%%%%%%%%%%%&$$$$$##############$$##$$$$%%%%&&&&&'&''()))))))))(((((('))(('&&&'&'%$##"" ""'),.01358<BCDDFJLOLGA2((,,l|kkqrnlA#*------++*))-/9FLQPPPPKLPW^`[b^W_eproiiec][X]`aa]\ZUXXSOLE?<=>?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!! !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((~~~~~~}}||}}~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}
|{zzzzz|}
~~~~~~~~~~~~}{||{}~~~~~~~}}}
~}}}}||{{{{||{}||}~~||{|~~~}{xwyz~~}|}~~~~}|~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}
~~~|{zzzzz{}~
~
~~~~~}{||{}}}~~~~~}}}
~}}}}||{{{{|||}}}}~}{{{|~~~~~}zxwyz~~}|}~~~~~}{~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}~~~|{zzzzz{}~
{}~~~~~}{||{}}}~~~~~}}}~
~}}}}||{{{{||}~~~}~~|yy{|~~~~~~}{zyz{~~}|}~~~~}{y~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}
~}~|{zzzz{{}~{tv}~~~~}{||{}||~~~~~}}}}~
~}}}}||{{{{||}~~~}~~~}|yy{|~~~}}}zzyz|~~}|}~~~~}|z~~~~}}{{}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{}
~~}}~{{z{{zz{~
yssv~~~~~}|}}|}|||}~~~}}}}~
~~}}}|{zz{|}}~~}|{yz{|~~~}}~}}zzyz|}~~|~~~~~~~zy~~}}}}||}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||zyyzzyyyyyyyyz}
~||}|{z{{{{|~yttu}
~~~~~}|}}}}|||}~~~~}|}~
~~}}}|{{{{|~~~|zyyz{|~~~}}~|{zz{|}~~|~~~~~~|yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy|
}|{}}{z{{{{{}~yvuvz~~}|}}}}|||}~~~~}|}}
~~}}}|{{{|}|{xwxy{|~~}|}~|{zy{|}}}|~~~~~~{yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy{}
|{{}}{z{{{{z}~yxwwz|~~~}|}}}}|||}~~~~}}}}
~~}}}|{{{|~~~{zyxwy{|~~}{}}|zy{|~|||~~~~~~{yy~~}}|{}}}~}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~~~}}|{{{yyyyyyxxxxyz|
~|{{|~|{zzzz{}~}|{z{~|{}}}}}}|}~~}}}}|~
}~~}||{}~~|yxwwxz|}}}|{|
~|{{{|}~{z}~~~~~{yx~}}|{z||}~~~~~~~}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zyyyyyxxxxyz|
}|{{||{zzzz{}~~~|{|yz}~~|{}}}}}}|}~~~}}||}
}~~}|}}~~~~{yxvvxz|}}}|{|
~|{{{}~|zz|~~~~~~|yx}}||{z||}~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~
|{{{|}{zzzzz|~}|}~}}|~}|~~~~|{}}}}}}}~~~~}}||}
}~~~}~~~|}}zywvvxz|}}}|{|
~}{||}~{yz|~~~~~~}{y}}||zz||}~~~~}}}|||}}}}}}}}}}~~~~~~}}~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~
~|{{{|}{yyzzz{~~|{|~~~}}}~~}}~~|{}}}}}}}~~~}}|{}
}~~~~~|}}zxwuvxz|}}}|{|
~}|||}|yxz|~~}{z~~{|{{{{|~}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~
}{z{{}~~{zzzz{{}~|}}~~~}}}~~}||~}|||}}}}}}~~~|{{}~
~~|}~~}zyvuuw{|}}||z|
~}}~~~}zxxz}~~|z~}{{{{{{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~~~}}||||{{zyyyxxxxy{|~
|{{{{|~|{{zz{{|~~||~~~~~~~{
~|{|~}|||}}}}}}~~~~}{{|~
~}~~~}zxvuuw{}~}||z|
~~~|xvv{~~}{~}}{{{zz{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{}
|{|{{{}|{{zz{{|}~}}~~~}{|
~}{~~}|||}}}}}}~~~~}{{|
~}~~~}zxuttw{~}||z|
~|wvw{~}{~}}z{zzz{}~}}}|||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}}{{zyyyxxxxy{~
~|{{{{{|}{zzz{{|}~}}}}~~}zz~}~}|||}}}}}}~~~~}{z|
~~~~~}~~~}zxuttw{~}||z|
}|xvx{~}~~}{{zzz{}~~~~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy|
||||||||~~|zzz{{|}}~~~~~~~}|{y~}xx{}}|~|{{{|||}|~}}~~}}{|~
~~~~~~}~~~~{xusstw{|}}|{{}zwvx|~~~~~z~}|{{{{z{~~~~}~}|{{{{|||||||}}}}}}}}}}~~}}}}||}~}}}}}}}}}}}}}}}}|||zzyxxxxxxz~
~||||||||~~|{zz{{|}}~~~~~}}~~~}|z{y{}|spqtxz|~|{||||}}|~}}}}||{|~
~}}~~~}~~~~zwtrstw{}~}|{{}
~zwvy}~~~~z~}|{{{{{|~~~~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxy{
~||||||||}~~}zzz{{{|}}}~}|}~~}|zxyzx|~xspprtw|~}|}}||}}|~}}}}||{|~~}||}}~}~~}}yvsrrsw{}~}|{{}
}yvwy~~~~
{~~}|{{||{}~~~~~~}|{{{{|||||||}}}}}}}}}}||}}}}}|||}}}}}}}}}}}}}}}}|||zzzxxzxy{|
|||||||||{}~}zzz{{z{~~~~}~||}~~}}|zxxyvyzvssrtx|~~}}}||}}|~}}}}||z{~}|||||~}~~||xurqqrw{~}|{{}
}xvwy}~~~
|~}||{{|}|}~~~~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{{zzyxxwy|}
~|||||||||}~~{{{||{}~
~}}~||}~|{{zyywvv}}wtuwz}~~}|||||}}}}}}}}|{{{}}|{z{|}~~}}}zxusqqrvz}~}|z|~
{xvxy}~~~
|~}|{{{|}}}~~~~}||{zz{{{{||||||}}}}}}}}}}~~||||||||||}}}}}}}}}}||{|{|zzyxxwy}
~|||||||||}}~~|{{||z|}~
~}~~~~~~~|zz{zzwwvy}}xwxz{}~~}||||}}}}}}}}|{{z{}~{{zy{}~~~}}}zxurqprvz}~}|{|~
{xvxz~~~
|}}|{|||}}}~~~~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~}{||||||||}}~|{{||{|}~~~}~~|zz{{zxxvw{~zyz{|||}~}||||}}}}}}}}|{{yz}~|yyxy{~~~}}|ywtqppsvz}~|{{}~
{xvx{~~~
|~~}||{}}|}}}~~~~||zyzz{{zz||||||}}}}}}}}}}~~||||||||}}}}}}}}}}||{{{{zzyxzx{
}{||||||||}}~|{{||z{|~}~}~~}}{z||zxxvwz}}x{|}|z{}~}||||}}}}}}}}|{{yz}~}||yxwy{~~~}}{ywtqoprvz}~{z|}~
{xvx|~~~
|~~}}|}|~~}~}~~~~}|{z{{{{{{{{{{||||}}~~~~}~~~||||||||}}}}}}}}}}{{{{{{zzyyyz}}||||||}}|}|}}~}y{||z||}}~~~}z{~}}~~}|||{{ywwzyuy~{z{~~~}}}}}}}}}||zyzyyz}}|{zwvwx|}~}~~}|xuspnpruy}|{|~
}zwvy}~~
~~~}}|}}~~~~~~~}|{{{{{{{{{{{||||||}}}}}}|~~~~||||||||}}}}}}}}||{{{{{{zzyyy{~}||||||}}|||}}~}{||||}}}}~~||wuw}|{|}~~~}}zuvvuy{z{~~~}}}}}}}}}||zzyxxy|~|{zxwvwx|~~~~}{xuspnoquy~|{|~
}zwwz~
~~~}}|}}~~~~~~~~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|}||||||}}|||}}~~}}||}~}~~|ztsw}~}||}~~|tsvvy~{z{~~~}~~}}}}}}{{yyxxwx{}~{zywvvwz}~~|zwurpnnpuy}|{|~
|zwx{~~~}}|}}~~~~~~~}|{{{{{{{{{{{}}||||}}||||{|}}}{||||||||}}}}}}}}{{{{{{yyzzyy{}}||||||}}|||}}~}{||~}~~}ztuw~~~}}}tswxz|~{z{~~~}~~}}}}}}zzzywwvwz}}{zywvvwz}{zwtrpnnpuy{}|{|~
{zwx{~~~}~}~~~~~}|{{{zz{{{{{{||}}}}}}}}}}|}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}}||||||||}}}}}~~||}~}~~|{xx{}}~}vsuwz}}|z|}|}}}}~~}|zyyxwutvy}~~|zxwvuuvy~|zusqpnnpuz|}|{|~}zwvy|~~~}~~~~~~}|z{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~~|{||||||||}}}}}~~}|}~~~~||{{}~~ysrvy|}{z|}|}}}}}}}{zxxxvusux}}|{zxvvtuvy~}yusqpnnpuz}}|{|~|yvvy|~~~}~~~~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~}{z|||||||{|}}}}~~}~~~~}}{}}
}yspsw{|z{|~}|}}}}}}|zyxwwutrtx}}|zywvusuvz~|yutrpnnpuz}}|{|~~zwuuy|~~}}}~~~~}|{z{{zz{{{{{{||{{}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~~}|{||||||||}}}}}~~}~~}}||}|xsopv{|z||~}|}}}}||{zxwwvusrsw|}|zxvutsuv{~|yuurpnnpuz}}|{|~~}yvuty|~~~~~~}|{{{{{{{{{{z|||||}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy{}~~~}||{{||||||}}}}}~~~|||~}~{xtomu|}{{|}|||||||{yxwwvtsprv{~}}}}~~}|zxvvusstw|~|yvtsqompty}}||}~~~~}{xusuy}}~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy}~~}}~|||{{||||||}}}}}~~|z{{|~~|zwtnlt|}{{|~|{|||||{zyxvvusropuz~~}{{|}}}{yxvutsstw|~|yutsrompty}}||}~~~~|yvssvz~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}~}}~}{||{{||||||}}}}}~~{xy||~}{ywuoks|}{{|}{z||||{{zywvtrppnouz}~~|zz{|}|zxxvtsrrtw}~|xttrqonpty}}||}~~}}{xtrsv{~~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz~~}}~}{||{{||||||}}}}}~~{xz||~
|yzwwqjq{|{{|}{z||||{{zywvsronlnsy}~~}}{yyz|||zwxvtsrrtw}~|wtsrpnnqty}}||}~~}}ywspsv{~~}||{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{}}~|{{{{{||||||}}}}~~~|z{|}
}{zyzxslqz|z{}~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}~|wtsrqonpty}}||~}|zxtpqrw}}}~}~}||{{||||{{||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxxz|}}|{{{{{||||||}}}}~~~}|~}~
|{zyxxunpy|z{}~{z{{{{{zyxvtrplkjlqx}~~}zyxwwy{|{zywtrqpqtx~~|vtsrqonpuz}}||~~}{yvsoprw}~}}~~}
~}}}}~~~~}}||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~|{{{||||||||}}}}~~~~}}}~
~|zyyxxvpnw{z{}~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpqux~~|vtsrrpnpv{}}||~}|zxtqnorw}}||}~}
~~}~~~~||}}}}}}}}}}}}~~}}~~~~~~~~}}}}||{{zzzzyyyxy|~}{{{}}||||||}}}}~~~~~~~~~~{zzyxvtqov{z{}~{z{{{{zzyxtspmjiginu|~|zyvutuxz}|yywtrqpquz~|vtsrrpnpv{}}||~}|yxsomnrw}}||}~~}~
}}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyxz}}|{{{||||||||}}}}}}}~~~~{{zzyxwvpnuyy|}}{z{{yyzxxvtqomkifiov|~{xwtsstxz{zzyvtrqpqtyzvtssrqpqw||||}~~|zxvrokntx~}z|}~
~}||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyz|
}|{{{||||||||}}}}}}~}|}~~}{{zzyxwwrouyz{}}{z{{yyzxxvtqoljhfiov|~{xvtstuxz{zyxvtrqpqtzzwuttsqrsx||||}~~|zxurokouz~}y{}~
~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{~
|{{{{||||||||}}}}}}}~}|}~}~|{{zzyxwwvruyy}~}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{zwutusrstx||||}~~|zwtqnkou{{y{}~
~~~|}}}}}}~~}}~~~~~~~~~~~|||||{{yyxxzyy{
||{{{||||||||}}}}}}}~}~~~~|{{zzyxwwvsuxy|~}{z{{{{{zxwuroliffiov|~{ywuvuvxz{zyxwurqpqu{{xvuutrtvx||||}~~|zwtpnkov}yy|~~
~}}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxyyz|
}{{{{{||||||||}}}}}}~}|~~|{zzyxxwwwwtsxz|}{z{{{{zzywurolifeflu|~|yxuvvwxz{zyxwurqrsw|zxvvvstuvy}{{{|}yvqolkov}}zx{~~}
~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxzy|
~|zz{{{||||||||}}}}}}~~||~~|zzzyxxwwwwssx{}}{z{{{{zzywurolifeflu|~|yyuwwxz{}}|{ywutsty~{xwwvtvuvz|{{{|}yvqnkkqw}}yw{~~}
~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz{~
~{z{|||||||||||}}}}}}~~{{~|{zzzyxxwwwwssy{|~}{z{{{{zzywurolifeflu|~~}{zwxyz|}~}{zxwwx{~}zxxwvutvy{{{{|}yvqnkkrx~}xwz~~}
~}}}}}}~~~~~~~~~~~~}}||{{zzyyxy|
~|{yz{}}||||||||}}}}}}~~{{~~{{zzzyxxwwwwtsw|}~}{z{{{{zzywurolhfeflu|~~~|zxxz{~}|{yyz}{yxxvusuy{{{{|}yvqmjjrz}ww{~~}~}}}
}}
~~}}}}}}~~~~~~~~~~}}|{{{zzyyz|
~||{z{{{{{{{{{{||}}||}}|}}||}|{zzzyyxxvxwtrvz~|{yz{||{zyxusoligfglu|~~~}{{{}}|}}}zxywutvz||||}~}yupljksz~{wuz~~~|}}~}zxx{{}~~~~~~~~~~~~~~~~}}|{{{zzy{|~
}{{{{{{zz{{{{{{||}}|||||}}||}
|{zzzyyxwwxwurtz}|{yz{||{zyxutpmjgghlu|~~~}}}}~~~|zywutvz|||}~~}yupkjmt{~~zwvz~~|}}~}ywxxyy|
~~~~~~~~}}|{{{zzz|}
}zzz{{{{zz{{{{{{||}}|||||}~}{{|
}{zzzyyxwxxxtqry||{yz{||{zyxutqnjhgimu|~~~~}~~}zxvuwz|||}~~~}yupllou|}yvvz~~~~|}}}~|xvxxyy{~
~~~~~~~~}}|{{{zz{|~
~{yzz{{{{zzzzzzzz||}}||{{|}~||{|
}{zzzyyxwwyxsnrx||{yz{||{zyxutqnlihimu{~}}}~
|xvvxz|||}~~~~~}yupklpv}}yuwz~~~~~~~~~}}||}}~{xxxyy{{|~
~~~~~~}}|{{{zzz}
|zxxxzz{{zz{{{{{{||}}||{{{|}~}||{|
}|{zzyyxxxyvsnpw||{zzz}}|{zyvtrokhhinv|~}}~~~~~}ywuwy|||}}}~{wtojkpw}}yux{~~~~~~~~~}}||}|}}yxxzzz{{|}}
~~~~~~}}|{{{zz{}
~}zyxxxzz{{zz{{{{{{{{{{{{{{{||~}|{{|
}|{zzyywxxzzuoqw|}|z{{}}}|{yxtspliijnv{~~~}~~~}|~}~~~~zwuwy|||}}}~}{wsnjkqy}}yux{~~~~~~~~~}}||||{{xxy{{{||}}~
~~~~~~~~}}|{{{zz{~
~~|}|zxwyzzzzzzzz{{{{zz{{zzzz{{{|}}|{{z{~
}|{zzyywwx{}xsrw|}}{||}}|~}|yvspnkjjnv{}~}~}{zyzz{|~}}|~{wuwy|||}}~~|~~~~~}{wrmkmtz~}yux{~~~~~~~~~}}||}|{zyyy{{{||}|~
}}~~~~~~}}|{{{zz|
}}|zzzywyzzzzyyzz{{{{zzzz{{zz{{z{}}|{{zz}~
}|{zzyywxxz|zusw|~}|||}}|~zxtqoljlpw|~~~~}~~~}ywvuvwx{~||{}}wuwy|||}}~~|}~~~~~}{wrmkmu{~}yux{~~~~~}}}}}}}|~~}}|{z{z{{{}}}}~~}~~~~~~}}||{{{{{{}
~|zzyywwwyzzzzzz{{{{{zyzzyzzzz{{z{~~}}{{{{|~
~~{zyyxxwwxx{{utx|}~}|||}~{yurommmry}~~~|{ywvtrrsuy~~}|||}}wvxz|||~~~~~}~}{urmjmv|~~|xux|~~~~~}}}}}}}|}}||||||||||}}}}~~~~
~~~~~~~}}||{{{{{|}
}{zywwwwwwwyzzzzzz{{{{zzyzzxyyzz{{z{}~}|{{{{|~
|}}{yyyxxwwwwyzvux{}~}|||}~{wsqpootz~}zywutsqpqty~~{zz|zwxz|||~~~~~~~}zuqmknw~~|wux|~~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~
~}}~~~~}}||{{{{{|~|zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~~}|{{{{{}
}{}}{yyyxxwwvvwwwvwz}~}|||}}xusqrsw{~~zyxwvusqpqty~~{vxz~|xyz|||~~~~~~~~~~}yupnlpx~~|wux|~~~~~~~~~}}}}}}}|||{{||||||}}}}}}|||}|}~
}}~~~~}}||{{{{{|~{zyxwvwwwwwwwyzzzzzzzz{{xxxy{zyyzz{{z{|}~||{{{{z|
~~zy}~{zyyxxwwvuvvwvvz}~}|||}~xvvuuvy|~yxxwuusqpquz~~zvvx}|xyz|||~~~~}}~~~~}xtponqy~~|wux|~~~~~}}||||||||||||||||||||||||||}|{{}~
}}~~~}}}||{{zz{}~{xwvwwvvxxwxxyzzzzzzzy{{yyyzzzyyyyyz{{|}~}{||zz{|~~~~}v{~|zyyxxxwvuuvxwvy}~}||}~
{yxxzz{~~yxxwuutrqruz~}yutw{}zyz{{{|}}}~~~~~|yuqoosz~|wux|}~~~~}}||||||||||||||||||||||||||}|{{{|~}}~~~}}}||{{zz{}~zwvvwwvvwwwxyyzzzzzzzyzzyyzyyyxxyyyz{{|}~|{||{{||~}}}zw||zyyxxxwwvvvyvvy}~}||}~~
|{zz|||~~yxxwuutsstv{~~yusvz~{yz{{{|}}}~}~}yuqnot||vux|}~~~~}}||||||||||||||||||||||||||}}|{||}||~~~}}}||{{zz{}|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~~|{||{{||||}{xx}{yyxxxwxyxvvtvy}~}|{||}~}}|~~~~zyxwuutsstw{{usty~|zz{{{|}}}~}|}~yuqnou||vux|}~~~~}}||||||||||||||||||||||||||}}|||}}~~~{{~~~}}}||{{zz{}{xvvwwwxxxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~}|{||||||{{}{yz}{yyxxxwxzyvttvy}~}|{||}~}~~~|zxxvvusttw|}wttx|}{z{{{|}}}~}{|~~|ytomou}zuuw|}}}}}||||||||||}}}}||||||||||}}}}~~}}{}~}|{}
~yz}~}}}}{zzz{z|~zxwwwwwwwxxxxyyzzzzzzzzzzyyxxyyxxyyyyzz|}~|{|||}|}}|z|{x{}{yxxxwxz~zussvz}}|zzz||~~~~{yxxwvvsuuy~}yutw|~z{{{{z{}}~~}{y{~~|xtpnpv}~~xttz}~|}}}}||||||||||}}}}||||||||||~~~~~~}}|~~}|{||yz}~}}}}|{zz{|~}zwwwwwwwwxxyxyyzzzzzzzzzzyyxxyyxxyyyyzz}~~|{|||~}~}|z|{x{}{yxxxwx|zuttv{}}|zzz||}~~~~{yxxxvwuvv{~}yutw{~{{{{{z{}}}~}{xy|~|xtpoqw}~}xstz}~|}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~|yz}~}}}}|{zz{}|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~|{|}}~~}|z|{x{}{yxxxwy}zuttv{}}|zz{|||~~|yxxxvwwxw{~}zvtw{~|{{{{z{}}}~~{wvy||wtporx~~~|wrtz}~|||||||||||||||{{{|}}}}{{||~~~~~~~~~~~}}yz}~}}}}|{z{{~
|ywvwwwwwwxxyxyyzzzzzzzzzyyyxxyyxxyyyy||~}{|}}~}|z|{x{}{yxxxwy~yutrv{}}|zz{|||}~~~|zyyywwxwx|}zvuwz~~||{{{z{}}}~}wvy}|wtppsy~~~|vrtz}~|~}}||{{|||||||||||}~~~||||{||}}}}~~~~~~
~z{}~}}}||{||}
|wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~}|}|}~|z{zv}}{yxxxxz}ytrrtz}~|zzz{{|}~|zyyyxxwxy}}{wuvz}}|{{{z{}}}}xvx{}}~~~{vsopty~{uruz}~{~~}||||{{||||||||||}}}}}}{||}}}}~~~~~~
~{{}~}}}|||}}
{wwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy{|~}|~}|{zyw~}{yxxxy{~}xtsrtz~|zzz{{|}~|{yyzyxxx{~}{xvwx{~~}|{{{{|}}}}yvx{}}~~~{wsppuz~~{uruz}}z~~~||||{{{||||||||||~~}}~|||}}}}~~~~~~~~
{{}~}}}||}~
~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~~|~||yxx~}{yxxxy|~|wuurtz~}{{{{{|}~}|{{{zyxy{}{xwwx{|{}||{{{|}}}}~zwx{}}~~~{wsppuz~~~~zuruz}{x}|{{{{{{||||||||||}~~}||}}}}~~~~~~~~
{{}~}}}|}}~
}yywwwwwwwwxxyxzz{{{{{{zzzzyyxxxxxxyyyy|}~~}
||yxx~}{yxxxy|}vutqtz~~{{|{{|}~}|{{{zxyy{}{xwwy{{{||{||{z{}}}{xy{}}~~~{vrqqw|~~~}zuruz}{w~~~~~~}}{{{{{{||}}}}}}}}||~}|||~~~~~~~~~~~~~
|z|}~}|}}~
|yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~~|{xvv}}{zyyyyz~~xutqtz~~}}}|z{|~~~~~{z{{{zyyz|~|{yxxyyz{{{{{||{|{}~~{wy{}~~~{vsqrx|}~}}~~|yssu{~}zu~~~~~~~~~~}}||||||}}}}}}}}}}||~~}|||~~~~~~~~~~~~~~|}~}|}~|zxwxxwwwwxxxxyyzz{{{{{{zzzzyyyyxxxxyxyz|~
||yvv}}{zzyyxx||wvrtz~~~|{{|~}|~~~|{{{{zyyz}~}{zzzyy{z{{{{||||{}~~|wy{~~~~{vsqsx|}~}}~~|yssv|}zu~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~~~}}~
{ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyy{|}|yvv|}{zzyyxxx|~zxsuz~}|{|~~z|~~~}{{{{zyyz}}{{{{{{{yy{{{||||{}~~|yz|~~~{wsqsx|}~~~~~}ytsw|}yv~~~~~~}}}}}}}}}}}}||}}}}}}}}|||}~~~~~}|||~~~~~~~~~~~~~
~~}}
~zxwwvvwwwwxxxyyyzz{{{{{{zzzzyyyyxxxxyyz{~
~}}yvv|}{zzyyxwvy~|xtv|~}|{|~{z|~~~}{{{{zyyz}}{||{|||zz{{{{{||{}~~|{{|~~~{wsqsx|}~~~}ytsx~~|yw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~
}~~~
{xwwvvvwwxxxxyzy{zz{{{{{{zzzzyyxxyyyyyy{}
~~}}vu{}{zzzzzyvw}}xtu{~~}||}}|{{~}{{{{zyy{}}|{|}}~}{zz{{{zz{{z|~||||}}~~~{wsruy}}~~~}xttw|{xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~
~~~~~
|xxwwvvvwwxxxxzz{{{{{{{{{{zzzzyyxxyyyyyz|~
~~wuz}{zzzz||~~xsu{~}||~}{{{{~}{{{{zyy{}}|{|}~~}{yz{{{zz{{z|~||||}~~~~{wsrvy}}~~~}xttw||xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~
}}}}~
~zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyy{|~
~}zvy}{zzzz|~ysu{~
~||~~|z{|~~}{{{{zyy|}}|{|}~~}{xzz{{zz{{z|~||||}~~~{wsrwz~}~~~}xttx||yx~~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}~~}}}}}}~~~~~~~~}}}}~~{xwwwuvvvwwxxyyz{{{||{{{{{{zzzzyyyyyyyy{{}
~}}yu{}{zzzz{}xru{~
||~~|z{|~}{{{{zyy|}}z{|}~~}{xyz{{zz{{z|~||||}~~~{wssw{~}~~~}xutx||zx~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~}~~~~
}~~~|yxwwwvwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyyy|~
~~|{yz~}|{zzzxyyx|{vrv{
}}}{{|~|}}{yyz{~~{z|}~~~}{xyz{{zz{{z|~{{|}}|~~|wstx{}~||||wuty}~}}~~~~~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~~~}~~~~~
{xwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyz|}
~~}||}|{zzzxxwyyxsrv{~}}}{{|}{||{yyz{~~{|}}~~~}{xyz{{zz{{z|~}{{|}~|~~}xttx|}~||||wuty~~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~
|zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzy{{~~
~}~}|{zzz{{|}zxssv{}~}}}{{|}{{{{zyz{~~}}~~~~~}{xyz{{zz{{z|~}{{|}~|~~}wttx|}~||||wuuy~~~~~}}~~}|||}}}}~~~~}}~~~~}}}}}}}}~~}}~~}}}}~~~~}~~~~~
~|ywvvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz|}~
~}}|{zzz{{|}{xuvv{}~}}}{{}~|z{{{zyz{~~}}~~~}{xyz{{zz{{z|~~{{|}~}~~}wtty|}~||||wuuz~~~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~~
|yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz{{}~~~}|{{{zy|}|xvuw{~~}||{{}~}{{{zzyz|~~~~~}yxxy{{{{zz{|~{{|~~}}|wtvz|~}}}}~|wtv{~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}~~}}}}~~~~~~~~
}yxvvvvvwwwwwwxxyyzz{{||{{{{||{{{{zzz}}~~~~~|{{{yxyz{vtuy|~}||~|{{}~}|{{zzy{}~~}|yxxyzz{{{{||~}{{|~~}}|wtuy|~}}}}~|wtv{~
}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~
~zwwvvvvwwwwwwxxyyzz{{||||||~~}}}}}}|~~~~~~|{{{yyzz{usu{~~}{{}}{{{}~}||{zzy{}~~~|{yxxyzz{{||||~}{{|~~}}|vtuz|~}}}}~|xuv{~
}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~
|yvvvvvvwwwwwwxxyyzz{{||}}}}}}~~}}}~~~{{{{{yyz{vuxz}~|zz}}z{|~~~}|||zzy{}~}}|{yxxyyy{{||||~~|{{|~~}}|vtuz|~}}}}~}xuxz~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~}zwuuuuuvvvwwxyyyyy{{{{}}}}~~}~~}{}~~~~~~~{z{zzyy{vvz{}~}zzy}~~}{{}~}|{{zz{|~~||{zyyyyz{|||{|}|||~~}}{wuuz}}}}~}~yvx{~~~~}
~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~{xuuuuuuvvwy{zzzyyy{}{}
}|}~~~~~~}z{{{yyzww{{}~}zyy}~}{z}~}|{{zz{|~~|{z{yyyyz{|||{|}|||~~}}{vuv{}}}}~~~ywy|~~~~}
~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~yvtttttuvvy}~}|zzz|~~
~|}~~~~~~~}{{{{yzxy||}~~{zy~~|zz}~}|{{zzz|~~{{z{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~|}}~~~~~~~~}}}}~~~~}}||}}{}|~~~~~~~~~~~}}~~~ytsttssuvx|~|{|}
}~}~~~~~~z{||{y{{}}~~
|z|~~{y{}~}|{{zzz|~}{zy{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~|
~~}}~~~}~~~~~~}}}}}}|||||}}}~~~~~~~~~~~~
}xtstsrrsv{
~}~~}~~~}z{{{zz{{}}~
~~{zz}~}||z{y{}~{z{zyyxzxz||||{}~~||}}~}}zuuy{}}}}}~zy{|}~
|
~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~
}xustsssuy
~~~z{|}yyy{}}~{zz}~}||z{z{}~~|{{zyyxzxz||||{|}~||}}~}}zsuz|}}}}}~{y{|}~|
}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~
|yvtsttw{
~|~zxz}}~~{zz}~}||z{z{}~}{z{zyyxzxz||||z{|~||}}~}}ztvz|}}}}}~{y{|}~|}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~
|xsstty~
}~~|y}}~{zz~~}||z{z{}~}{z{zyyxzxz||||z{|~~||}}~}}
zuwz|}}}}}~{y{|}~{
~~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~
~xuuvz
~~~~~~~~}{|}}{z{~~~}|zz{{|~}|zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}}|z|}~~~}}~{~~}|}}|~~~|||||||||}}}~~~~~~~~~~~~~~
~yxy}
~~~~~~}~~~~}|}~~~{z|~~~}|zz{{}~}zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}~}{}}~~~}}~|~~
}}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~~}}
~}}
~~~~~~~~}|~~}}}}}~|}~{z|~~~}|zyz{}~}zzzyyyyz{{{{zz|}~~}{||{|{~ytvz|}}}}~~{}}~~~}}}~}~~}~}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~||}}~
~~~~~~~~|||}}|~}}~~||~{z|~~~}|zy{|}~~~}zzzyyyyz{{{{zz|}}}}{||{|{~ytvz|}}}}~{|}~~~}}}~}~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}}~
~
|~~~}~~~}}|{}}||~~}zz}~~~~~}}}||}~~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ztvz|||||~~|}~~~}{{|~~~~~~
}}}}}}}}||||||||||}}~~~~~~~~~~~~||}}
~~|}~zz~~~~~~~|~~~~}{{||||~~}z{~~~~}~}|}~}}|~~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z|}~~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~~}
|||||||~}}}|z}~}}}}z{~||||}~}~
}|z{~~~~~~~||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z{|~~~~~}}
}|}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~}~
~}{{zz||}~~{{{~~~~z|
~}}}}y{~}|}~}}}}~~|{z{~~~~~~||{|~~~}zzzzyyzz{{{{{{z{||||||{||}ztvz|||||~~~~~}{yz|}}}~~~~}}
~{|||||{{{{{zz||||||}}~~~~~}}~~~~
|{zyyyyz|}}{{{{}}~}~}~}|~}~
~~}~}zz|}~~~}{{|~}{{||~~|{zyzzyyy{{{{{{{yy{{||||{||}zuw{|||{}~~~~~~|zxz{}~~~~~}}
}{||||{{{{{{{{{{{{||}}~~~~~}}~~{yyxxyyz|}
~|||{|}~~
|z{~~~}~~~}}z{{}}~~{{|~~}}~~}{{}}~}|{zyyyyyy{{{{{zzyyzz{{{{{||}zuw{|||{}~~~~~|zxy{}~~~~~}}}~|z{{||{{{zzz{{{{{{|||}~~~~
~~~{zyxx{{|~~
~}}||}~
~~{xz}~}~~~~~~~~~}}{{|}|~|{|~}~}~~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}zvx{|||{}~~~~}zxxz|}~~~~~~}}|}}|zzz||{{{zzz{{{{{{{||}~~~~~~
}}|{{{||}~~~~}~
~}~~~}|||~}~~~~~~}}}|}|}|~~}{|}~~~{~~~}{{~~}}|{zyxxyyy{{{yyyyyyyy{{{{{||}zwy{|||{}~~~}zxxy{}~~~~~~~~~~~}}}}
zyzz||{{{zzzzz{{{{{|||}}~~
}}||~~~~~~
}~~~}}}~~~}~~~}}~~~|}~~~}z{|}~}|~}||~}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~~~||}}}}
zyzz||{{{zzzzz{{{{{{||}}~~
~}}~~~~~
~}~~}}z}}{}~~}|}|}|{||}}~~||}|}}~~~~~}z{}~~~~~}}}}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~}}||}}}}
{yzz||{{{zzzzzzzzz{{||}}~~
~|{{~
~~~~~~~}~~~~}wz}{~~||}~||{z{{{}~}}}|{{z{}}}~~~~~~}z{~~~~~}~~~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zxz{}||{}~~}{xwy|}~~~~~~~||||}}}}
{yzz||{{zzzzzzzzzzz{{|}}~~
~}yxx|
~~~}}~~~}vx}~~}{}}}{|{{z{|~}~}|{yyz|||}~~}z{~~~}}~~}~~~|{zxwwwyzz{{zzzzyyzz|||||{{|~~zy{{}||{}~~}{xxy|~~~}~~~~~~~~}}||||}}{yz{{{{{{{{{yyzzyyz{|}||~~
~}{xvuz
~~}{xz}~}}|~~~}wtw|~}{}}|z{zzz{{{}~~}~{{{yyyyxz|}}}~~~~z{|~~}}~}||~}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~}zwxz}~~}~~~~~~~~}}||||}}
{yz{{{{{zzzzzzyyzzz{{|||~~
{xvtsuz
|wuvz{{z{}~{|}{uty}}~~{||}|{|{zz{|||~~|||{yzy{yzxwz|}}}}~~~~zz}~|}}~}||~}{yxxxxxz{{{zzzxyyyy{{{|}}||~~|yx{||{{{}}zwx{~~~~~~~~~~~~}}||||}}{yz{{{{{zzzzyyzzyyz{z{||~~
{xusrruz
{trtxxwxyzzvyzxy||||z{z|{||||zzzyz|||~}{z|{yyy{zzxx{|}~~}~~~~zz}~~{{|~}||~}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~~~~}}||||}}{yzz{{{{yyyyzzzzzzz{z{||~~
}wtrqqquz
~ytssvvvwxz~|utx~~{zyyx{}z{{zzzyyyyzz|}||{{{yyz|{{zz}~~}~~~~zz}~
}zz|}}||~}{yxxxxxz{zzzzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~}}||||}}
~{{{{zzzyyyyzzzzzzzyz{{||}~
xtppqqquz~
|wtstvutuvy~zwx{xxwyz|{{zxyzxwyyxxz|||}{{z{|~}}|}~~}~~~zz}}|{{}~~|{}~}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{yz{|z{{{}~~}zwx{~~~~~~~~~~~}}||||}}~
}||{zzzyyyyzzzzzzzyz{{||}~
|urqqrrrvy}}
{vutuvussuy}~}yy}}xwvx{|{{yxzz{z{zxxy||{||{{{}~}}~~~}~}|{{}~}|}~~~}{yyyyyyyzzzyyyyyyxz{||}}{z{}}{zz{|{{{{}~~}zwx{~~~~~~~~~~}}||||}}}
~}||{zzyyyyzzzzzzyxyz{||}~
}wsrqqrrsvx}{}
~ywvtuvvtstw|}||z}~}{ywvxz|{zxwz{}}}|zzy|||}||{|}~~~~~}}~~~~~~~~}||{|}~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}~~~~}zwxz~~~~~~~~}}}}||||}}}~}}{zzyyyyzzzzzzxxyy{||}~
ztrrrrsssux~}{{}
}zwxtuuvutuwz||||~~}z{xvxy{{yvvy|~|~~{{{}{z{{|||}~~~~}~~}}~~}}~~|y}{z||}~~}}~~~}{yyyyyyyzzzyyxxxyxz{||}}|{{{|{z{||{{{{}~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~
~|{yyyyyyyyyyyyyyzzz|}}~~
|wsssssrstux~{z|~
~zyyvtuvussuyz|}~{{zyvvz{zxvtv|~~zz|zyzz{{}}~~~~}}|{}~~~}~|yvy{zz{~~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~~}}~~yxxz~~~~~~~~~~~}}}}}}}|||||~}yyyyyyyyyyyyyyzzz|}}~~
|xurssssrstux}|z|~~
~{{zwtuvsrsuwx|{yzwuvxyxwussuy{}}}{zzzyy{||}}~}}|{{{|~~}}|usx{zz|~~~~~}}}}|zyyxxxxzzzzyyxxwxyzz{{~~}{z||{{z{|z|{{~}|~~}}~~yxy{~~~~~~~~}}}}}}}}}}|||||}~
{zyyyyyyyyyyyyzzz|}}~~
~{xvtsttttstuvx|~{|}}~
~{{ywtturqruvy||yxwvtwywusqpqtvy{|}|{zyy|||}}}|{|{|}~~~}~{sry{z{}~~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~||}}}||||||||||}}
~{{{yyyyyyyyyyzzz|}}~~~~
~zxxvuuuuttstuvx|}}}~~}
|{zywtturrstvy}|yxwuuvvuttqqprtwz{||}}||}}}~~~}{|}}~~~~~}|~~~}~}|try{z|}~~~~~{{|||zzzzzzz||zzyyxxwxyzz{{~~~zy{{{z|}|z|{}|y{|~}}}~~{z{|~~~~~~~~}}}}}||||||||||}}~
}{zyyyyyyyyyyyz{{|~}~~~
~zxvvwwvvvuutuvvw|}~~
|{zzwttvtttvwy~{ywvtttsrrsrrtttuvz{}}~~}}~~||}~~}}}|~||~~~~~~~}|ss{{z|}~~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~{z|~~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}}~
|zyyyyyyyyyyyz{{|}}~~~
}|xwvwxyxvvuuuuvvw{
}~
~~}{{{xvvwutuwz{|~}zyuuttsppoquuuvvutx{}~~~}|}~}}}{}}}~~~~~~~|{su||{|}~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|zy{z{{{|{{{}~zz}~~~~~}}}}{z|}~~~~~~~~~~}}}}|||||||||||}}|}
}{yyyyyyyyyyyz{{{|}~
~|zywuvwyyywwvvvvwwx{~~~||
~~}}|{||zxwwtutv{|yy|}|zxtsuusnlmqwwvvwvsvz~~~}||~~~~~~}~~~~}~|z|}zuw~}||}~~}}}|yyyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}~xy|}~~}}}}zy}~~~~~~~~~}}}}}|||||||||||}}|}}~~
~{yyyyyyyyyyyz{{{|}~}zyvvvwwyyyxxwwwwxxy{
~~|yyy{}~
}}}}|{~{yvutuuvy{wvy{zxwtstttpklqvxwvxwtvz~}}||~~~|||}~~{xz}zxz{z|}~}|zzyxyy{{zyyyyxwxyzz{{~~|yxzzzz{|{{{}|wx|}~~}}||zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}
|yyxxxxwwyyyyzz{|~
~}{yxwwwxxxxxwwwwwwwxy{~}wuwxxx{}~
~}|wuuvtwxxsqvxyywustwwpiipvyxxyxwvy|~~{|~}~~~|zz|}}||{|}|{|}{z{}|}~
~|{yyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|zvy|~~}|xxy}~~~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}}}~zyxxxxwwyyyyzz{}
|{yzxxyyyyxxyyxxxxxxxyy{}|ustuwvxz{}~
~}xvvvtvvupnruxzwuttvwoffmtxxyyyxwwz||~}}z|~~~~~~}||{z{|}|}}{{z{||~~~|{
~}~~{yyz{{yxxxxwwxyzzz|~{yyyyy{{{{{{|zvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}|zzzzzyyyyyyzz|~
}|{zxyxxyyyyyyyyxxxxxxxyy{}}{wtsrtuvwx{||}~~}
}xuvvtrrrnknsw{yvtttuoecjswxzzzyxvx{{{}{zz}}}}~~}{zz{{|~~{zz{|||||}}
~~~~{zz{{yxxyxwwxyzzz|~{yxxyyz{{{{{|~yvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}~~~
~zzzzzyyyyyyzz}{{zyyyyyzzxxzzzzyyyyyyxzy{|
~|{vrpqtutvy{|{{{}
}yvwvupopmknrw{|zutssoeckrwxz||{ywvxyw{~|y{}~{||~|zxxz{{}}{xyz||{|}~~~|{{{yxxyxwwxxyzz|~{xxwwyz{{{{{|~xvy|~~~zwwy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}~}{zzzzzyyyz{|
}|zz{yzzzzyyyyzzzzyyyyyyz{z{{}wpmotuqtyy{{z|~~ywwuupkmnkmqw}}yuttsohgkswy{||{zxwwwtx{z{~~{||}{xuvxz{}|xwx{|~|~~~}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}~zvx|~~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~}}~}
~||zzzyyyz}~
~~{{z{|{{{zzyyzzzzzzyyyyzz{{z{|~||zrnkoutoqwy|{{}~~|xwvuokkollmu{ysrrtspkkmtwy{||{{xxwvtw~}z{~~|{{}|xwwxz||yyzz{}~
~}|{yyywwyxwxyzyz||xwwwyz{{zz|}{wy|~~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~~||{{{|~
}||||{{||||{{{{{zz{{zzyyyy{{{{{|~~xrlinsrnpux|{{}~}ywwunmipomnrwtnnpturnlotwy{||{{yxwvsuy~|}|{{}}{|yyz{{x{}~}
}|~~~~|zyyyyxwwxyzyz||xwvwyz{{zz|}|xz|~~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~
|||}
~|{z{{|||}|||{{{{{{{||zzyyyyyy{{{}~{qjhkpropuy}}}}~zxwtmlhnpnnqsplkptvsomquwyz||{{yxwvsqu|~~|{{}}}|zz{{y{~
~}|~}}~~|{yyyywwwxyzyz||xwvvyz{{zz|}|y||~~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~
~}}
}}|{{{|{}}|}||||{{{{{{{{{{yyyyzz{{|~|rljlmrttwz}}}}|~zxvrmiglqssromllquvspnruwxz{}{zyyxvtppuz~}}|}}}~~}yx{{{|~}}}|{|}~~~{yyxxvvwyyyz{}|xwvvxzzzzz{}~z|}~~~|yvwz~}}~~~~~~~~}}||||||{{{{||}}}}~~~~~~
|{{z{||}}}}|}||||{{||||||||{{{{|||||~~}zqlhkmsxzz|~}~}|{xvqkhflqvvrpmlnqtvurpruwxy{|{zyxxvvrrrvz{zz}~~~~~~}yx|}~~~~
~}|z{|}~|yxwwvvwyyy{{}|xvvvxzzzzz{}|}}~~~|yvw|~~}}~~~~~~}}||||||{{{{||}}}}~~~~~~
|zz{{|}}}}}}|}||||||||||||||{{{{|||||~}{yslhikr{}~~~~}||
|yvoheelrvvtqnnnqruusrtuwwwz{}|zwvwxvttvwxww|~}}}zz}~~~~}|zy{{|~zvwwvvvxxyz|}|xuvvxzzzzz{}|}}~~~|yvx}}}}}~~~~~~~~}}||||||{{{{||}}}}~~~~~
}zz{}}}~~~~}}|}||||}}||||||||{{{{||||}~
zumhhjr|~~}{|
|yuogcelsvvvspoprqsvtuuuvvvz{}yvvxxxusuxxxw{~}|}|{~~}~~}|zyyx{|{vvvvvvxxyz{}}xtvvxzzzzz{}|~}~~~|yvy}}}}}~~~~~~}}}}||||{{z|||}}}}~~
|{zz{|~~~~~}}}}|||||||||||||||||||}}||~
~{slklu~~}}~}}z|
|ytnf`emrtwwtqqrsrtuusstuttyz}}xttuwxwstwwwx|~~}||zz~~~~~}||
~~~|{zzyz||xwvvvvxxy{z}}xttwyzzzzz{~~}~~~~~~~}{ywy|}~~~~~~~~~}}}}||||{{z|||}}}}~~
|z{z{||~~~~~}}}}|||||||||||||||||||}}||
}zuopw}}||~}|~}z{}ztnf`fnstwwutstsstuvttutqswx||wrruvywstvvwx{}~|||zyyxwy|~}|}
}}~}{zzxwz}|zxvvvvxxyzz}}xttwyzzzzz{}}~~~~~~~}{ywy|~~~~~~~}}}}||||{{z|||}}}}~~~~~
~|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}}
}ztsw}~}{{{|~~|z||y{ztnfbfmsvxxwwuuttuvwvvutqruvzzwrqsuxvttvvwwz|}~~}}}zy{yvvuy|~}|~
{{|}zyyxwy{~~{yvvvvxxyz{~}xttwyzzzzz{}{|~~~~~~~}{ywy|~~~~~~}}}}||||{{z|||}}}}~~~~~~~
~{|{{|||}}}}~~~}}}}|||||||||||||||||||}}~~
~yux}~|zyy{}~|z|{xz
ztnedemsuwwwwwvuutvvwxwvqruwxxuppruwttttvwxy{{}~~~}~~{{}zyvrrw|}|~
|zzz{{yyxxxz}}yvvvvxxyz{~|wstwyzzzzz{|y{|~~~~~~}{ywy|~~~~~~}}||||||||||{|||~~~~~~~~~~~}~~|{{{|}}}~~~~~~}}|||{||||||||||||||{|}}~~|
~yy{~~~~zyyzz}~~}{xz
ztniefnruvvxxxutttwxyzzxtruwwvrporttrrrtuvxzyz|}}}}~}~~}{vrot}}~|yyyyzxxxvvz}xvttwxxxy{~zustwyzyyxz}~{xz}}|||zxwy|
~~~~~~}}||||||||||||||~~~~~~~~~~~}}}}|{{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~}
~zy}}zzyyy{~}zwyztojfgnruvvxyxvutuwxy{|zvtvxwuponpqqportuwyzyy{}~}}~~|wstx~~{xxxxyxxxvvy{~|xuuwxxxy{~zurtwyzyyxz}}ywz}}|||zvx{
~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||~~{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~
|y{|zyyyxz}~|wuxztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}~~~|yuty~zwwvwxwxxuvy{}}xuuwxxxx{~ytqtwyzyyxz}}xwz|~}}|yvy
~~~~~~}}||||||||||||||~~~~~~~~}}~~}||{|}~~}|||{}}}}}~~~~~~~~}}|||{||||||||||||||{|}}||~|~{{{zzyywxz~}|vsxztokggnruvvwxwtssuxxy||ywvxyvspnnnmmlnrtuxy{yz{|~~~~}vrt~}{vvvvxwxxvvxz|~zwuvwwwx{~}xtqtwyzyyxz}}vvz}~}||yx|
~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~}}||}|||||||}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}z}
|{~{|{yyvwy~~~|zurw|unkhhnsstuxywsrsuxyy||zxxyxvsomnmkhkmqtuxz{{{z{|}~~}~~yutz|yuuuvvvvuvwwyz|~|ywwxxxx{}xsrtwxyyyx{~}uu{}~~}|{x{~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{|||||||||||||||||||}}|{|yx~~zz}|{|zywwx||~{ytrw
}umkginstuvxxvpmoqqtvyzzwwxxuromlhfgimqtvy{||{{{{|~}{||{xw~|wuttuvvvuuvwxy|}yvvwwwx{~}xsqtwxyyyx{|tu{}~~}}|{~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||}
~{}xx{}zssy|yuuz~~}~~|~}zxwvyz{zwtqw
}umkhjnstuxxwrkghkknrtwxvvwwtqokhddggkruwz{|}|{{z{
~}zxz|}~}}}zwz{wtsstvvvuuvwyy{~{wwwwwx{~|wrqtwyyyyx{ysu|~~}||~~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||{|}}z{wvxzxsswzvqqu~~}{z|}{zxwxz{yvspw~vnkiinsuvwxumecceilpruuuuvwtqnjeeeigksvy{|}}|{z{|
~|wttwxy{{|~}xz~yvsrrsvvvuuvvxyz~}wvvvwx{~|urqtwz{yyx{~}wrv|~~~|{|~~}}}}}|}}||||||}}}}}}~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}|z}zsttuvttvtqooov~|zyx{~~|zwvvxyzwrqx
}wokihlruxxwsh`^^^cjoqsqrrttrqmhdddiimrx|~~~}{z{}}{}}ywvvxvvtvz}}yx}|yvssrsuuvutuwwxz}~ywwwwx|~~zuqptwz{yxx{}vsx}~~}{|~~~~}}}}}}}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{zz{{{{||||{|}}xy{xrtvvwvxurnoonqw~|xvv{~~}zxvvxyywrry}vpmiimrvxxwobYWYTYfoqsqrrrqonkgeffjkmpuy|~~}zzz}}zz|~~zyywy{zxvz{|ywz|xussrsuuvutuwwxy|~~{xwwwx|~~zuqqtwz{yxx{}uty}~~~|{}~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruy{{}~yrnoonnt}}xvvz}~}{xwwxyyxuuz
}wqnjimsvxvvobUPQMVdorsqppommjkihggjlnosvz}~|zyz}~{z|}~{ywx}~{{}}|wvy}|wtrrrsuuuttuwwxx|}|ywvww{~~zuqqtwz{yxx{~{vty}~~}{|~~~~~~}}}}~~}|||||||}}}}~~~~~~~~~~}}~~~~~~~~}}}}||||||||~~}}~~~~~~~~~~}}}|||||||{{{{{{{{zzzzz{}zwwzxru{}}{sooonns}~xuv{}~}{yxxxyxywwz
}yrmjimsvwuvqeRKKMXenstqomliigjjihfimopsuz{}}{yyz}}{|}||ywv{~~}~wuvx{~~|wtqqrsuuustuwwxx{}}}ywuvwz~~ztqqtwz{yxx{~{vty}~~|z}}}}}~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvorxy{}tqoopos|~vuv{}~}{zxwxxwwwxz}tmjkosuuuvqhSKLS]hqttrpnifeegiigefmqqsuwz{{yxxz|~}|}|{|
~{yvz}}ztrsx}}~~|vtqqrstttstvvvwwz||}~ywuvx{~yrpquxyyyyx{{uty}}~}{~~}}}}~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{{{{{{{{{{{{|}ytswumotsvz}|tqpponr{~wuv{}~}{zxwxxwwvxz~wplmqsuvuuqkYSSZckqtsrpojgeefffefgmrrsuwyzzxwvy|}}}~}|~|wx|~~zvsrvvy}|vsqqrstttstvvvuvy{|}ywvwx{~xrpquxyyyyx{ytty}}}||~}}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xqorqlmrssw{ztqqqooq{yvw{|}}~}|{yxxxvvuwy|~{}ysopstuvttpk`ZZagmqsqonnmjhfecccdhnsttuxyzywvtw{}}}~~
zx{|~~{xvspsw|~{urqqrstttstvvvvvyz|}ywwwx{~xrpquxyyyyx{~ysuz}}}|~}}}}}}~~~~~~}}}}}}||||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||wokmmlmqrqu{ztqqpqoq{zww||}}~}}{zxxxuuuvwz~~z}{urrvvuvtrojb^]dinqpnlkmomkiecab`fouuuvxyywusrtx}}}~~
}yz{~~}|yopsy}~zurqqrstttstvvvvvxz||zwvvx{~wrpquxyyyyx{ztuz}}||~||}}}}~~~~~~~~~~~~}}}}}}}}}}~~}~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|{||||||{{{{|}|wohhklosqosz{uprssqt{|wx||||~~||{ywvuttvww||{}yvuvututrnieaagnpomkhgjnqolgd_[[dpvwwxxxwtonprw|~~~~}}z{|}~~ztpqw|~~~
}yurpqssssstuvvuuuvx{}|wvvx|}vqqrvxxxwxx{zttz~~|~}}}||}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|||||||||||||}}woihjknsoorx{vqruusv}|xy|||||}}|{zxwsrtuvwz{{}{xvvvvwurnjgdfkppnjgddglqpnif`[\epvxxxxvsqlkorw{~~~}
||}}~~{wtnsu{~~
|xtrpqssssstvvvuuuvx{}|wvvx}|upprwxxxxxy{~~ytuz}}~}|}}}||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xojjjkmpmnpv|xssvvtv|~|yz||||{|}|zywvsstuvvx||}~~|ywwvwwwtoligjnpolhcaadiopnjgb^_gqvxxxxurnihnsvz~~~~~~~
}|~~~yrqoux{|}~
|xtqpqssssstvvutttuwz||wvvx}~{uposwxxxwwy{~~xtvz|}~{y{||||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}~xqlkjkmnlknu{zusvvtx}~}yz||||||||zxwvutstuvw{|}~}}zxxwuuwtomkjlppokf`^]`fmomjgca_hruxxxxwrkegmtvz}~}}~~~~}|~~~ysprtwwwz{
|xtqpqssssstvvusttuwz}|wvvx}}{uqoswxxxvwy{wsux|~}yx{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}~zrljkkmlhhks{zwuuuvy}~}z{|}|z|{{{zywvvsrruux|~~}}}{zxxuuwtpnlmnqrpkd^YY]cjomidb^_fptvvxxuoidfovwxyzz{~~~~~~}~{yqoqrrty}|wtrqrrrrrstvvutsstwy|{wuvy|~ztppswyxxxwy{~|urvx{{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrlkkmnjefiqyzywuvwy}~}{{|}|z|z{zzyxvvtsrttvz}}}|{xxyyxuollnpqqokd\VW]djmkid`]`fortuwwtmgbemtvxzyzz}}}}~
~}~~ztqqsv{{wsqprrrrrstvvutsstwz}{vtvy|~ztppswyxxwwy{~{vsvx|yuy{{{{{zz~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy|zxwxxz}}|||{||z{zzz{yywvussstty}}}|{yyyyyvnjmoqqqokcZVW^figecba`bgnqrtuurlfadkruwzz{|{yy{}
~}~yvux{~{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux~|wuy{{{{{zz~~~~}}}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}{{{{{||||||||||||||||}zskhklkfdfjqy~zzxyyz}}|||{||zzy{{|{zywwsrssty~}}|{yyyyyunjmppqqokbZVX_ed`^^_abdhmpqsttqjebdjquwzz{}|yuw{
~}{yxz|~~{vqpoprrrrstvvutsstw{~zusuy|}ytppswyxxwwy{}zvsuxzsty{{{{{{{}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||}}}}}~~{rkhkmkedgmr{}{yzzz|}|{{|||{{zzyzzzzywtsrrsw~~}|||yxyyxunklopppmibZWYbc_XYZ\^cfjmpqrstqkgcciouwz{|}|yvuy~~}~|}}~~zupoopqqssstvvusssrv{~{vsuz}|wrnpsxyyyvvy{~ystv|~wrty{{{zzz|}}~~}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||}}}}}}}}}~~~}}||||{{{{|||}}}}}}}~~}}~~~|qlijkiedips||{zzz|}|{{||||{zzy{{zyywtrrrsv}~}|{wwxxxuplloooonic]Z_ie[QTTV\ehknpqrrrpjfbdiotvyz|~}}|{}~
~~~~~~~~~yupnopqqssstvvusssrw|zusuy||wroqtxxxxvvy{}yssw~{sruz|{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}rkijkieeksu|}|||z|}|{{||||{{zz{{zyywsqqrsv{~~{vuvwxupmlkklmmke^^gnjZLOOPXbgjlmpqrqoiebcimrvz{|}}~~}~~~~~~}ytpoopqqssstuuussstx|ytsuy||wqpqtwwwwvvy{|wstxyrrv{}{{zzz~}}||}}}}||}}}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}{{{{}}||}}}}}}||||||{{|||}}}}}}}~~}}~~}tmhikjefltw|}}}z|}|{{||||{{zz{z|{ywtppqsu{|vttuxuqmjfejmolg`akok[IJJMS_gighnpqqnhcachkpw{||{|~
~~~~}}~~~~~~~}ytonopqqssstttussstx}ytsux{|wppruwwwwvvy{{vsu{~xrrx|}{{zzz}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}||||||}}}}}}}}}}||||||||||}}}}}}}}~~~~~}tkhhijfhotx|~~}~|||{{{{|}}{{{zz{||zxrpoorsx~}wrstvwsoiecfjlkhcfjlh\PJGHP[egcbiopolgc`adhmv{|{{}
~}~~}}}~~~~~~~~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsv}{vrrw{{zyz{{}}}}~~}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~wliiijilpu{~~}}}~~}|{{{|~~|||{{{{{xvqomorrv}}xrrruvtohccfhhfhfikmi_YNKJPYec]]emnokfb_^aelw||{{|
~~}||~~~~~~~~~~|wrpmmnpqssstuuussstw}~xttvy{zuopruwwvvvvy{{vtx}xusrv{{zyz{|}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~{oijkjnptx}~}||}~|{{{}~||{{{yzzwtpnmnqqtz~xrqqtvsohccghg_adjlmhaXPNLOXfaWXajmnkfb_]_blu|}{{}
||}~~~~~~~~~~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wt{{wurrw{{zyz{~}}}}||}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~~rmkkkpswz}~~|||}}|{{{}~}}{zzyyzvspnlmoqrx~}xrpqsusogcfhie[Z_immhbVRNNQZgbUT^gmnjeb^]`bmu}}|{}
}}~~~~~~~~~~}{xronmnpqssstuuussstw}~vstvy{ytopsvwwvvvvy{{wv}}zwtqsx{{zyz{||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~uomlnuwy{~}{|}}~~|{{|}}}}|{{zywtrqmjkoqqv|}{vqnorsroheegkg\UYdljg`VSTV[ah`VPZekkhb`^\`dmv||{|
}}}~~~~~~~~~}{wsollmprrrssttssrrux~|vssuz{xsopruwvuuuvx{ywzzwvrosyzzzyz|||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}||}}}}~~~~~~~~}}vrpnpwy{}~|}~~}z{}}}}}|{{yxvsqoljknppsx|}}zxspopqqqogceini]UW^gfe^WV[^`eh`XOVcig^ZY[\_dnu{~|{{~
~}}~~~}}~~~~}{wsolkloqrrssttssrrvz~|vssuy{wrnoruvvuuuvx{xx|}xuusqw{{{{{{~||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~
}xuustx{|~}|}~|}}}}}}|{zyxvsqnmkkmopquxxywvqpnnppqogbelpl_VWYbcd^XY_`_ae^ZPS`hcVTV\\_dnty~}|{}~
}}}~~~}}}}~~}{wsolkloqrrssttssrrvz{vssuyzvqnoruvvutuvxzwz{xwywv{~~~~~}||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}{{}}}}~~~~~~~~~~}}}}zwwuvx{~}{|~~
~~|}}}}|{zyyvsqnmllmopqruuutsqpmnppqofbelombVUX_bb^\\``\]`\YPS^hcTOU\^`dntx~|{|}~
|}}~~~||||~~~}{wsoljknprrssttssrrvz{vssuxyvqmnruvvutuvxxw|zxwz{|
||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}||
~||yyy|~~}{|~|}|~}||zyxvspnnnmklorsrrqpqsqmknopogbeljidXQV\bca_ab_[X[WSPT^fcSJOZ\`emsx~~}||}|~}{}~~}||||}~~{yvsojklopqrtttrssqrvzzsrsuyyupnoruuttstvxxy}~{{~||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||||||}}}}}}}}~~~~~~~~~}}}{z|~~}{}
~}}~}||zyxvromnnnjknqrrrpqoqtollnnmhdfhe``ZRU[ab`_ac`[WUSOQV]cbTKNY]ahoty}~~~}~}~~~|}~~}||||}~~{yvsojjkopqrtttrssqrvz~zsrsuxxtonpruuttsuvwy{
||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~~z}~}|~
~~}||zyxvromlmmkjnprrssqnorqlklnmieghc][[TWZa`^]`ee_ZWTOSW]baVLOX_djquz}}~~}~
~~~~}~}||||}~}zxvsojjknoqrtttrssrsw{~ysrsuwwtonpruuttsuvvz~
||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||{{||||||||}}}}}}}}~~~~~~~~~~
}z}~}|~~}||zywvrnljllkimprstuqnppqjjkpniegic[YZVW]b`\W\gkfaYTQWZ^baWMOX_fmtvy}}~~}~
}~}||||}~}zwvsojiknoqrtttrsssuy}~ysrsuxwsonpruutsstuwz
||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~{z~~}~
~~|{zxwtrnjklljjlorrturmonkhmnonkghicYY[Y\`a^VPZinjbZVX]]_cdZOR[cjpvy{}}~~~~
~}~~}|||||~|{xurniijnpqrtttrrrruy}~yrqsuwvqnnpsutsstsvy~
||}}||||}}}}~~~~~~}}~~}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||||{{{{{{||||}}}}}}}}~~~~~~~~~~~
zz~~~~
}}zxwtqmjjklkjkorrttrmonikoopoliijd[Y\]_b`[QP\ioiaZZ`gdcef]UY^gntwz|}}~~~~
~~~}|||||~~{zwurniijnpqrtttrrrruy}~xrqsuwtpnnpsutssssuz
}}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~
zz~}}~~
}|zxvsqmjhjjkjkoqrtsqnmkkoqpppljjkf]Z\`ac`WNT_koja\]dmlghg`Y\bjqwyz|}}}}~~~}|||||~~{zwurniijnpqrtttrrrsvz~}wrqsuvtpmnpsutsrstu{
~}~~}}||||}}}}~~~~~~}}||}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}}}||{{{{||||||}}}}}}}}~~~~~~~~~~
z{~||~~~~
~|zxvspljgiillknqrttqmmknrrrppljkle^\^abb]QLWdmojd__eoojkha[_clrwyz|}}||
~~~}|||||~}zyvurniijnpqrtttrrrtwz}vrqsuvtpmmpsussrsuw|
~|~|{{}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~
}yy||{|zzz}~~~~{zwqnmighjmnmnqrttrnlmqrppopljkke]\`deaYNQ\fnnjfcafooiji`Y\dmtxzz|}}}}~
}~}||{{{~~|zywvsokjknoqrtttrrrsw{|uqqsuvromnqsutsssvy{y{||y{}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~
}yz}}{{zyyyz{}~}}|{wqnmjhikopporstttqnorqpoopnlnkbZ]bfe_VPV`hmligdbgpmffhaXZcnvxzz|}}}}~~
~}~}||{{{~~{zxwvspljknoqrtttrrrsx|{uqqsuvqomnrsttsssv{}yuvxz{y|}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~
{{}|zyxwwwwy{||~}}{xqnmkiikoqsqrssstsqrsqqqqppnol_Y]dhe]UR\fknligebhpkcbfb[\dpvy{z|}}}}~~~~~~}||{{{~~}{yxvvspljknoqrtttrrrty|zuqqsuupmlnrsttsrrx~~|wrrtwz{y|~~}}}}|||||}}}~~~~~~}}}}}}}}}}||||||~~~~~~~~}}~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~|{~}|zxxwvwwy{|}
~}}{xqnmkijkoqssuttsusstspqrsrqpol`Z_fhe]TWahnpmigedioibaec^`gpvy{z|}}}}~}}{||~
~}||{{{~~|zxwvvspljknoqrtttrrruy|zuqqsuupllnrstsrrr{{vsnnswz{y}~||}}}}||}~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}}~~~~~~~~~~~~~
~~~|~|xxxxvvuw{||
}|||yrnmkkklpruuvtttttuvtqrtsrrqpi`[ajmd[U[dimnkgddglmicdfcadjquy||}~~~~~}||{|~
~~}{zzz{}~}{zxxwvtqmklmoqsssrrrruz}yspqrttplloqtssrqt|~xtqpmnrxyz{}|}}}}}}}|~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~}|}zxxxxvvuwz{|
}|||zrnmmmlmqswwvtuuttvusrruusrqpi_\dmne[X_fkmmjgdehllidfgecglqv{|}}~~~~~|||z|~
~}|{zz{}~~}{zxxwvtqnllmoqsssrrrrvz}~wrpqrttpllortssrrw~~xroommnryyz|~|z}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~
}{|zxxxxvvuvz|}~
}|}|zrnmoonortxxwvvvttvtsrsuusrqpj_^fnog^]dilnmjfefikkhhhhfeimquz|}}~~~~~||{z|~
~~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~~wrpqrtsommpstssrsy~xtponmlmsyz{}~{z~~}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~
~|{{yxxxxvvtvy{|~~}|{{{snloqqrruyyxwvvttusrrtvusrqqj``gnqiccfkmnlifegiiigjjhfgjmqvy{}}~~~}}|{{z{~}~}{zz{}~~~|zxxwwuqnlmnpqsssrrrrvz~~uppqrtronnpstsrqu{ztqqponlmtzz{~}zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~
~|z{yxxxxvvuvxz|}}}z{|ytomprstuw{{zxwuttvtrruuttrqrmedjoolghknonigfeghggimkigjloqvz||}~}~~}}{{z{}~~}~}{{{{|~}|{zxxwvupnmmoqqrssrrrtx|~vqpqstqnlmpsssrrxytrponnmlnswy{~{zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~
}{yzxwwxxvvvvvutu{
}z{{zvompsuwx{{{|zwvuuvuttttsrrqrnhgmponjkmpokeedcdfccjmljiknpswz||}}}~}{{{zy{}~~~~~
~~~|{{{|~}|{zxxwvuqnmmoqqrssrrruz}|tqpqstqmlmpsssrsz}ztqqponlklotyy{}zyy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~|ywxxwwxxvwwvtomny
}zyzzwqnqsuwz}}}}|xwvvvuvvssqppoqojjoppmlmpqohbacbabadjmlkkmoqux{||}|}}}zzzzyz}~~~}}~}{{{|}|{{zxxwvuqnmnpqqrssrrruz}{tqpqstpllmpsssru{zsppooonkjmpvz{}}zxx~~~~~~}~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}||||~~~~~~~~}}}}}}}}}}}}||}}}}}}}}~~~~~~~~~~
{xvwxvvxxwxxxslijt
}zyzzytqptuw{}~}zywwvvuussqponpolmpqqmnoqqpf_`aaba_djnnkloqrux{|}}}}}|zzzzyz}~~~~~}{{{||{{{zxxwvuqnmnpqqrssrrruz}~ztqpqstollmpssssw~xqooooonlkmqxz|~|yww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~
|ywwvvvwyxyxxqmjjr}yzz{yvsqtvy|~yxwvvutsssponloqoorrommprqmd__aaa`_flonmnpprvx|}}}}}|{{{zzz{}~~~~}~
~}{zz{||{zzzxxwvuromnqqrssrqqruz}~ysqpqqqnkkmqsstu{tpoooomlkjntwy|~yxww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~
~}
|ywwvvvwyzyxwqmjjq
|||{yxvsux{|~zyxwvvttrronmkorqqsromnpsqnd__acb`djlommnpqsvz|}}}}}|{{{zz{{}~~~~}~
~~}{zz{||{zzzxxwvuromnqqrssrqqruz}}xtppqqqnkknqssuw}|ronnnnmlkjnuwy|~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~{ywwvvvwxyxwuqmllp}
}{zwuvwz|~|{zxvvutrqnmmmpssttspnorsrnfbbcdccglnplloqrtwz|~}}}}|{{{zz{{}~~~~}~
}~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkknqssvzxponnnnmlkjnuxz}~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~
~}
{ywwvvvwxyvuuromnoz~}{zwwwz{~|{zywwvttpmmnppsuvvupoprtsmhddcddegkopmloqrtx{}}}}}}|{{{zz{{}~~~~}~~~~~~|yzz{||{zzzxxwvuromnqqrssrqqswz}~~|wsppqqqnklorrrx}unmoommmlkjnuy{~~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~
}}{ywwvvuwxxuttsqpmn{
~|zxwxy{}~~{yyyxvuutqnnprstvvwvsqprutpidddeeehmpomnprsuz|}}}}|||{zzzzz{}~~}~~~~~~~~{yxx{||{zzyyxwwtqonnpqssrqpqtx{}|||vrppppomllorsuz~rnlnommlkkkntxz{yxvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~
~|~|ywwvvvwwvtrsvvsop{
~}|zxyyyyy|~~|zzyxwvvurpprtutvvwwusrsuvrkfeeeefioqpnpqstv{|}}}}|||{zzzzz{}~~}~~~~~|{yy{||{zzyyxwvtqonnpqssrqpqty|}||zuqppppomllortw|{ollmnmmlkkkntx{{xwvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~
}|~}ywwvvwvvurrrxzuss}
~{zzzzyzz|}~}|{zyxwvwvsrrsuvvwwyyxvstvvsnjhgfegipqppprtuw{|}}}}|||{zzzzz{}~~}~~~}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpstw|wlllmnmmlkkkovz|~zxvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~}{~~}ywwvvwvvtqqrz}ywx~
~}{zzy{{{{}~}|}|{yxxwwwuttuvxxxy{zzwstwwtpkjiffgipqpqqruuw|||}}}|||{zzzzz{}~~}~~}~~}|{zz{||{zzyyxwutronnpqssrqpqty|}||xspoppommlnpsvy~~tlkmmnmmlkkjov{~~zwvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~}~~~}}~~~~
}|}~}xwwvwwvuropsy}{|}
~}|zzyzz|||}~}~~|{zyxxxvuvvvwwyy{|}{wvvwwtomjhgikqqqqsvvwz||}}}}}|{z{zzzz{|~~~~}}{zzz|{{z{{zxvtttsqoopqrrrrorv{}}|zuroooonmlmnprv{{pllmmmlkkjilqx{}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~~
~{|~
~~ywxwxwutrpoqwz|~}{z{{{{|||}~~~~|{zzyxxwwwwwxxzz|}}|xwvwwvspmkijmrrsruwwy{||}}}}}|{z{zzzz{|~~~~~{{|{{{}|{z{{zxvtttsrpopqrrrrprw{|||xtqoooonmlmnprx}~vnllmmmlkkjilsx||xuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~
|{~
~}|xxxxxvsspmotw}~{z{{|||||}~~~~|{zzyxyxxxwxxxz{|}}|yxvwwwurpnlmnsttuvyyy|||}}}}}|{z{zz{z{|~~~~~}|xz{}}{|||{{{zxvtsssrpopqrrrrrtx{}|zwrpoooonmlmnpty|smllmmmlkkjjmsx|~{vuutv~~~~~~~~~~~~}}~~~~~~}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~
~{{~
~}~{xxyyyvtqomnrx|}{{{|||||}~~~~|{zzyxyyyxwxxxzz|}}|{zvwwxvsrqppqtuuwx{{z}||}}}}}|{{{z{{z{|~~~~~~|zuvz~~||}|{zzzxvtrrsrpopqrrrrrux}{{zuqooooonmlmnpu|yqmklmmmlkkjjmsy|}zuuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~~|||xxzzyxuqommrw|}||~~~~~~~~~|}{zyzyzyyxxwwxy{}~~}|{wwvvxxvtsqsuuvwz||{}~~}}}}}|||{zzz{{}~}}}|{xuuy~~}{{zzzzxvtrrssqpprrrqqruy|{{ytqoooooomkmnqu{yqmkllmlkkjiinty}|xvvvuv~~~~~~~~~~}}~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~
}||{zzzzyvspnlqv{~}zw~~~||{zyzyzyzxxwwxz{}~~~}{zxwwzzxwutuvvyy{}}{}}}}}}}}|||{z{{{|~~~~~|||yvvz~}{zzzzxvtrrssqpprrrqqrw{|{zwspooooonmklorx}~wolkllmlkkjiinty}|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~
}|}}~|y{z|zwupomqv{}~{w~~|{zzyyxyyzyxwwxz{}~~~}{|zzyz{zywwwxy{|}}}z|||}}}||||||{||{|~~~||}|yxz}}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns||tnkkllmlkkjiintx|{wuuuuu~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~~~~
|}~~~{y{|}{yvrporwz|}|{}~|{zzyyxyyzyxwwyz|}~~~}{{z{{z|{yzyyz|~~||z{||}}}|||||}|||{}~~~||||{yz}}{zzzzxvtrrssqpprrrqqty{|yxvromoooomlkjms}{rmlkllmlkkjiintx|{wutttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~
||}}~}zz}}{zwtqorvz{|{|~|{zzywwyy{zyxwx|}~~~}|||||||||{z{~|z{yyz|}}||}}}}}}}}}~~~~~~}}{{|z{~zyyyxxwusqrtrqqsrrrrvz||zxtrpnnnommkijnu}yrmklmmmkkkjjjouz~}yuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~
{||~~}}yy}~|{yvroruyyy{}~|{zzywxyy{{yxwy{|~~~~~~~~}}|}|{z|~~{yzyyz|}}||}}}}}}~~~~~~~~}}{{{||}zyyyxxwusqrtrqqsrrssvz||zxsqpnnnommkjlpxxqlklmmmkkkjjlpv{}xtttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~||}~~}~zwx|}|zwsqrtwxx{}~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}|||}|yxyyzz|}}||}}}}}}}}~~~~~~}}{{|}}}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkklrz}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}~~~~~~~~~~~~~||~~~~}|~{wvw|}{xtrqtuwy|~
~|{zzyyyyy{{zyxxx{}~~~~~~~~}}~~|yxxzyzz|}}||}}}}}}}}~~~~}}}}||}~~}{zyyyxxwusqrtrqqsrrrtw|||ywropnnnommkknt||upkklmmmkkkjjmqwz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~
~|}}~
|{~{vtty~{yusrruxz}}}{{{zzzxxxz{zxwwy}~~~~}}}}~{xxy{|}{}}|||}}}}}~}}~~~~~}}{|{{}~}|yzzxxywusqstrqqsrrsuz{||zvroooooomlkjnw~ztnljkllljjjiimrv|{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~
}|~}~||~{vtux~|zwtrquy{~~~}}||{{zzxxxz{{xvvx|~~~}}}}}}}yxy{|~}~}|||}}}}}}}}~~~~~}}|{{{}}|yzzxxywusqrsrqqsrssv{{{{yvqoooooomlkkqyyrnljkllljjjiilrw}{usrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~
~||~}~
~~}~|wuvy~}{xurquy{}~~~~}}||{{zzzzz{{{xwvw{~~~~}}}}}||yxy{}~~}|||}}}}}}}}~~~~}}|{{~~|yzzxxywurpqrrqqsrstw{{{{xuqoooooomlkmt{~xpmkjkllljjjjjlry~~yurrrrrr~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~
}{|~}~
~~}~}xuvy~~|yvsruz|}~}|}}}}}}{{zzzzz{{{xwvwz}~~}}}}}||yyy|~~}|||}}}}}|~~~~~~
}}|{}~|yzzxxywuqpqrrqqsrsuy{{zzxtqoooooomlknt{}wpmkjkllljjjjjmsz~}xurqqqqq~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~|~~{||}~~
~~~|~}xvwy}~}zwtux{z{}{y{~~~~~~~~~~~~~~~~~~|{{z{zx{|{yxwxxz}}}~~}}~{yz{~~~}}|~}}}}}}}~~~~~~~}~|zzzyxxwuqooprrrrrsv{||zyvspooooonlkknv~|uplkkkllmkkkijotz~{xusrqqqr~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~}{||~~~~~~|~}wuvy}~}|xuwz{yz{ywx~~~~~~~~~~~~~~~~|||z{zx{||{yxwvx{}~~}||}}~|{zz}~~}|}}}}}}}}~~~~~~~~}~|{zzyxxwuqooprrrrstx{||zxuspooooonlkkpy{tollkkllmkkkijpu{{wtrqqqqt~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~~~||~}~~
~|~}usuz|~|yxy{{xyyywx~~~~~~~~~~~~~~~~~~}}}||{y|}}|yyxwxz|~~}||}}~|{zz~~}}}}}}}}}~~~~~~~~}|~~|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlkmqz~yspllkkllmkkkjkpv|zvsrqqqrt~~~~~~~~~~~~~~~~~~~~}}}{||||}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~
}|}}~
~|~|tru{|~}xy{}{xyxwwx}~~~~~~~~~~~~~~~~~~~}}}|z}~|{{zywxz{}}}||}}}{zz~}}}}}}}~~~~~~}}}}|{{zzxxwuqooprrrrtvz|}{wvtqpooooonlkot|}wrollmlnnmkkkjkpv}zusrqqqsv~~~~~~~~~~~~~~~~~~~~~~~~~~||}}}}~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~
|||~}
~z}|usw{|}~||||yxxxwx}~~~~~~~~~~~~~~~~~~~~~}{|}}||zzyyyy{}~~}}~~~~|zz
~~}~~~~~~~~~}~~|~~|zyzzyxvtonoprrrstv{||zvvsqpooooonmlnu~|uqomllmmmmkjjjmqw|ytsrrpqux~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~
}{|~~
~z}}vtx|}}~~~}zwxxxy}~~~~~~~~~~~~~~~~~~~~~|}~~}}z{{zyy{|}~~}}~~~~|{{
~~}~~~~~~~~~}}}|~
}{yzzyxvtonoprrrsux|}{yvusqpooooomllox{tqomllmmmmkjjjnrx}~xsrrrprwz~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~
{{~
y|~yux|~|}zxxyy{|~~~~~~~~~~~~~~~~}}|}~~}~||{z{{z{{}~}}}}}~~}{{
~~}~~~~~~~~~}}}~
}|zzzyxvtonoprrrswz}}{yussqpooooomknqzxsqomllmmmmkjjkosz~|wsrrrqtx{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~}}~
|{
y|~{vx|~|}yxyy{||~~~~~~~~~~~~~~~~}||}~~~~}|{{{{z{z|~~~}}}}}~~}||
~~}~~~~~~~~~}||~
}}{{zyxvtonoprrrsx{~}zxussqpooooolkns|wspomllmmmmkjjkosz|vsrrqsuy|~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~}
||
xy}xx||z|~~ywxyz{}}}}}}~~~~~~~~~~~~~||||~}}~~|||||{z{||}|||{}~}}||}
~~~~}}~~~~}||~{{zyxwtpopqqrrux|~|zxurrqonnnmmklnu~}urpnllmmmmmliilpw|{vsrrsuw{~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}~~~~~~~~~~~~}~
|~
ww~~zz}}z{~~{xxyz{}}}}}}}}~~~~~~~~~~~}}}}~}}~~}}}}||zz{{|{{|z||}}||}
~}~~~~}}~~~~}}
~}yyxwtpopqqrswz}~|zxtrqponnnmmklpx|uqpnllmmmmmmiimry~zvsrstwy}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~}
|
}|
uv}zz}}zy~~~|zxyz{}}~~||}}}}}}}}~~~~~}}}}~}}~~~~}}||{{{{{{{{z|||}||}}|}~~~}}~~~~}~
{yywtpopqrrtx{}~{ywtrqppnnnmmklqz~ztromllmmmmmmiimry~yutssuy|}}~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||}}||||||||}}}}||~~~~~~~~~~}~~|}{{~~tu|{{~~zx|~~{yxyz{}}~~{{||||}}}}~~~~~~~~~~}}~~~~}}||{{{{{{z{z{{||||}}|}~~}}
}yywtpopqrrty|}~{yvurqpponnmmjlqz}ysqnlllmmmmmmiimrz~yuutsvz~~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
~~zz}~
~usy|{~|zy{{yyxz{}}~~|z||||||}}~~~~~~}}}~~~~~}}}~}|||||z|{{zz{{{{}}}|}~~}~
}zxwspooqqsvy}}|ywusrpooooonmklr{~xsqolllllmmmmkjns{~yvtrsv|~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
zz{}~~usy}|~|{zz~{zzxz{}}}~}{||||||||~~~~~~~~}}|}~~~~}}}~}|||||{{z{zzz{{{}~}~|~}}||
~ywspooqrswz}}|xvtsqpoonnnmllnt~~xrpmlllllmmmmjint|}wttrtx}~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
~
|z{}}}~usy~}~{z{~~|{{zz{}}}~~}||||||||~~}}~~~~}}|}~~~~~~}~}}}|}}||{{zzzz{{}~~|}~~}||{wtqpqrsty{~}{xvsrpqonnmmmklov~~xqomlllllmmmmjjou|}wttsvz~~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~
~~
~{{|}}~usy~{z{~~}}}{{zz{}}}~~}{|||}}||~~}}~~~~}}}~~~~~~~}~}}||}}}|{zzzzz{}~}|}~~|}}
}vsrqqssuz}~|zwusqpqpnnmmlklpw}xqomlllllmmmmijov}}wtttw|~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~}}~~~~~~}}}}}}~~~~{{{|~}svy~|z|~~}}~~||{z{{|}~|zz||}}~~~~~~~~~~~~~~~~~}}}}}~~}|||~}{z{yzz{{|~~~~~}}}
xsrrrqtx|~}{zvsrqqppnnmkkjkqy}wqonlmmmmlllkjkou~{trtux|~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~
{{{|~ssw}~|z|~}||~~~}|{{z{~
|zz||}}}}~~~~~~}~~~~~~~~}}}~~~~||||~~~}{{zzz{{|~~~}~~}}~
|urrrquz~|zxurqqqppnnmkkjlr{}wponlmmmmlllkkkpvztruvy}~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~
|z{|tqu|~|z{}~~|{z|}~|{{{|{zz||||}}~~~~~~}~~~~~~~~}~~~~~|||||}~~}{{{{{|~}}}~}}}~
xtsqqv{~|ywsrpqqppnnmkkjmt|}wponlmmmmlllkkkrxzutvx{}~~}~~~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~
~z{|tsv|~|z{|~~{{yz|}|||~|{{||{{||~~~~~~}~~~~~~~~~}}}||}~~|{|{{|~||~}|}}~
|wsrsw}~{xvsqpqqppnnmkkjmu}}wponlmmmmlllkkkryxttwz{}}~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~~~~~}}}}}|}}}}~
~z{}
wwx}}{|}}{zxxx|~}}}}~{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|}}~~}|||}}|}~}{z
~|}~
~wtrry}{yvqppqqpponnllknw~woommmmmmmmljilsz~ytuwz}~~{~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~
}xy~
{zz~}|}}|{ywvv{~~}~~{{zz{{{{|~~~~~}}}}~}}~~~}}}}~}~}~}}}~}||||}~~{z}
}{}~
zyttt|~zxupppqqpponnlkjnv|upnlmmmmmmmljimt{~xuvy{~}z~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~}xy}}}~~}}}{zxvtuy|}~{{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}~||||}~|z||{}~
}{yyy~zwtpppqqpponnljinv{tonlmmmmmmmljinuz~}xux|~{x~~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~
~yy{~~~~}}}{zwusty{|}{zz{{{{{|~~~~~~~~~~~~~|{||}~~~~~}}|}|~||{{|~|z|}
~|{}~
}~}|~~~zwtpppqqpponnljinx|unnlmmmmmmmljjnuz~|vtx|{w~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~~}}}}}||}}~~}
}{|
~~}~~}|}|{ywtttwy~
{zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~~}|~~|{{{|~~|}|~~~{|~~
}
}|}{yvrooqqqooonmkhjoy|unnlmmmnnnmkiknv|{vuy}~xv~~~~~~}}}}}}}}}||||||||||||||||||~~~~}}}}}}}}}~~~
~~}}~~}|}|zxvsssuw|
|zyz{{||}~~~~~~~~~~~~~~}{{y||}~~~~~~~~~|{{z|~}||{}~~~~{|~
~}zxyzxvrooqqqooonmkijr{|tnnlmmmnnnmkikox~{wwz}|vv~~~~~~}}}}}}}}||||||||||||||}}|||~~~~}||}}}}}}~~
}}}~~~|{{{yvursrtw|~zz{||||}~~~~~~~~~~~~~}|{{}~~~~~~~|{zz|~~~}|||}}}~}~{|~
{yxxxyxvspoqqqooonmkjkt}{rmmlmmmnnnmjhkqx}{xy|~ytu~~~~~~}}}}||}|||||||||||||||}}|||~~~~}{{}}~~}}~~~
||~~~{zzzxvurrqsv{
}||||||}~~~~~~~~~~~~~~}|}}~~~~|zyy|~~|}}}||}||}~}~{|~
|vv|zywyxsqoqqqooonmkkmt~{qlllmmmnnnmjhksx}{xz~vrt~~~~~~}}}}}}|}||||||||||||||||||}~}}
}|||}~~~
}|~~~~}{zzzwvtsqqqtx}
}{|}|}}~~~~~~~~~~~~~}}}}~~~~~~}}{xz{|}~~|{|}{{|}||~
~{wrz{tw|ysppqpomnmmkjmszqmmlmmmnnmliilty}~zxz~}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}}
}|||~~
~|}~~~~|{zzywvtrqqqtw|~||}|}}~~~~~~~~~~~~~}~~~~~~}}~~}}{xy{~{|}}{|~||}~~}||~
zwrv}wv||urpqpomnmmkjmuyqmmlmmmnnmliimtz~~zz|{trv~~~~~~}}}}}}|}||||||||||||||||||~~}}
~||}~~
~|}~}~}|zyxxuutrrrqsv{|{||}}~~~~~~~~~~~~~~~~~}}}}||}~~}}{yy}~}{|}~|}}|~~~}||~
~wrq{{wz|vsqqpomnmmkinwxpmmlmmmnnmliinuz~~{|~~xssw~~~~~~}}}}}}|}||||||||||||||||||~~}}
||~~~~~
~|}}}~|{yxwvsstsrrqrvz~
~|{|}}~~~~~~~~~~~~~~~~~}}}|zz{}~~}}{yy}~|{{{}~}}}~~}||~
|tor}
|vx{yrrqpomnmmkhnwxommlmmmnnmljiov|~||}wrsx~~}}~~}}}{||||||||||||||||}}}}~~~~~
}|~
}}
~|||}}|zwvxvttusssrsvx||{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz{~{||{|~~~~}{|
|tos}~}vv|xsrpponmkkioy~wpmmllkmmmkjhinv|~}{rru{~~}}~~}}}{||||||||||||||||}}}}~~~~~
~}
~{}
|{}}}}|zwwvussutssstvx{~||}}}~~~~~~~~}|~~~|||}|{z{{yyy|~}{zz|~{||{}~~~}|}
~ups|~{~~xv|{trpppnmkkjpy}vommllkmmmkjginw|~~~wqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~
~
~z}
}{}}}}|zyxvtstvutttuvxz}}}}~}~~~~~~~}}}~}||{|{{{{|zyyz}~}{zz|~{|||~~~~~}~
vqrz~|z~{z||xspppnmkkkqz{uommllkmmmmlgiox}~~~}vqsx}~~}}~~}}}{||||||||||||||||}}}}~~~~~
{}
}{~~}}|z{zvttuvuutuvvyz{~}||~}~~~~~~~~|}}||{|{|{z{{}|y{z|}~{zz|~{||}~~~~~}}~
xqqx|}|~~}|zspppnmkkkq{{tnmmllkmmmmlgipx|}~{upty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~
~|}~}~}{{zzxtuvwwvwuvxy{{}
}}~~~~~~~~}}~|{zz{z{{|}~~~|{{|}~{xz}|z{|}~~~~}|~
|stx|~}upppnmljkr||rlmmllllmmljhjsx}~~wssw{}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~
~}}~}{{{{yvwwvvvxvxzz||}~}}~~}|{zzz{zz|~~~~~}|}~~{xz}|zz{|~}~~~}|~
ww{~
yqppnmljkr}|rlmmllllmmlihktz~|vstx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~
~~~|}|{{||{xxvuuvxyz{{||}}~~~}}~{zyyzz{|~}}~~{xz}|yy{|~
~~~~~}|}
{{}}tppnmljkr}|tnmmllllmmlihltz~zutuy{}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~}
~~~~~|
}{{{||}zxvuuvx{{{|}}}~~~~~~~|{yyyzz{}}~~{xz}|xy{{~
~~~}||}
}~~}vppnmljkr}|tnmmllllmmmihluz~~~zutvz{|~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~}
~~~~}|{zz|}}zxuttuyz||}}~
~~~~~~~|{yyxy{}}~zw{}~zwyy{~}}~~||}~
}|}
yqoonkkls~}uollllkmmmmjimw|~}~~wtuxy{}~~~~~~~~~~~~}}}}||||||||||||}}}}}}}}~~~~~~~~}
~~
|{yy{}|zxussuxz||~}~~~~~~}|{zzxy{}}~{x{}~zwyy{~|}~~~||}
}|}
{roonkkmt~}tnllllkmmmmjjnw}~}~}utuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~}
~
~zyxxz||{xusrsvz||~~~
~~~~~}|{zzxz|}}~~
~~|z|~~zwyy{~}}}}~~}||}
~
}tponkkmu~}tnllllkmmmmjiow}~}~zsuvxy{~~~~~~~~~~~~~~~~}~}}}||||||||}}}}}}}}||~~~~~~}
~~
|ywwwz{{|xurqstz||~~~~~}|{zzz{|}}~~~~|}~~zwyy{
~~~||~~|||}
~wqomkkmw~}smllllkmmmljipw}~}~xruwxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~~~~~
|xwuuvy{{{wusrsuy{}~~}~~}}~}||{{{|}~~~~~||}}xvwy|~}|||~~}}{{}
yromjjmx|tmllkklmnljijqx|~~}~}xtwxyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~
~~~~~
ytsstvy{{{xusssuy{}~~}|~}}}||{{{|}}}~~~}}~}ywwy|~~~|||~~}}||}
{vromlox|tmllkkmmmljijrz|~}}~|xvxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~
~~~~}~{xrooruy{{{yvsssuy{}}}~|{|~~~}}|||{{||}}
~~|}~yxyy|~~~}}~~}}||}
zvsporz|slllkkmmlkjhks{}~}}~zwvyzyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~
~~~~}}
~zysooquy{{{yvtssvy{}}}~|z|}~~~~}|||{{|||~}~~}}~}zxyy|~~~~~~~}}}}}
~ywsrs{|slllkkmmlkjhls{~~}}~yvwy{yy{~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~
~~~~~}|||{wpnoty|||yyuvuvyz||}|{|}
~}}}}||{{{{|~
~}}}|}|zwxx{~~~~~~~|}}
|ywx}~{rmkkklmmlkjimu|~~~~|xuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~
~~~~~~~|~}~zronsy|~~{{yxyyzz||~~|{{}~
~}}}}}||{|}~}}}|~|zwxx{~~~|~
~|{~{rnkkklmmlkjhmv|~~~{wvxyyxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~~~~~~~~|}wqorwz~~}}{{{{{|}}}|{z|}~
~~}}}||}}}}~~||}}|zwxz}~~~}
~
~~{rnkkklmmlkihmw}~|}{wwyyywwz~~~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~}}~~~~~{
ysoquy}}}}}}~~}|{z{|}~
~~}}}{{{|}~~||}}|zwxz}~~~~~~~
~~
~{rnkkklmmlkiimw}~{|zwwzxxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~~~~~~~~}}}}}~~|}|uoqvy|}~~~~}~}|{{{|{|~}||{{{||}
~||}~}|zxy{~~~~|}
~
yrnkkklnnlkgiox~|}}zwxyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~}}}}}~}|xrrux{|}}~~~}{{{||{|~
~}|||{{{||~
}||}}|zxyz}~~~}~
yrmkkklmmlkhjpx~}}|yxyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~~{~|tsux{|~~~~~}|z{{||{|}~}|{|{{{{{~}{z|}~||zxyz}}~~~~~
~
yqlkkkllllkikqy}~}}|yxyyxwvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||~~~~~~~~~~~~~~}}||~~~~~~~~~~}}~~~~~~~~}}~}}}}}~~z|}uptwz{~~~~~|{z{{||{||~~~}|{|{{{{{
|{yz|}}{|zxyy||}~~~|}
}|~~~~
yplkkkllllkikqy}~}~|xxyyxvvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~|z}yrrvyz}~~}|{{{{{zzz|~}~}|z{{{z{{|
~|zyz|~~|z{xyzz{}~~|}
{vy|}~
ypmkkllllkjiksz~}}}|yyyywvvvy}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}}z{{truyz|~~~}}~}|{{{{{zzz|~~}~~}|{zy{{||}
}zyz|~~|{zz{zzz}}~
{trw|}~~~~~
{rnllllllkjimu|}~~|yy{}zwuuy}~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~zz}~wsuxy{}||}}~}||{{{zzz|~}~~}|{zzz{|}~}zyz|~}|{|{zyy}~
uopx}~~~}~~
}snllllllkjimu|}|yy|~xtty}~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}|||y{yuvwy{|z{|}~}}{{{zzz|~}~}{{zyz|}~~{yz|~~~{}{zyy~~
zsmow}~}~~
vollllllkjinv}}|yy|ytty}~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~~xy}vvzyy}zy||}~~}}}||{{zyz||~}|{zzzz~
|zy}~~~~}}|zxz~|snmry~~}}~}
xrnmmlmmkihnw~~}{yy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~}|}|}}}}}}}}~~}}}}}}|}}}}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~||}}||||}}}~~xx}zy|yy|~zy|}~~~}}}||{{zyz||~}||{{{{~
|zy}~}}~~~}||}
~vpnqw{~|{~~}}vonmnmmkihow~~}~zyy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~{xz~}}zz|~|z|}~~~}}}||{{zyz|}~}|||{}~
}zy|}||~~~~~~ursyz}}yz~~
yroonmmkiipx~~}~zzz~ytty~~~~~~}}}~~~~~~~~~}}~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}||}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~}wz}
{y{~|z|~~~}}}||{{zyz|~~}||||~yy{|||~~~~tuz}}~zz||~|}{tpoommkiipy~~}}yzz~wtty~~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~}~~~~}}}}~~~~}}~~|}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~zxz
~}{}~|}~~}}|||||{zy{}~~~}}}|}|z{|}}~~~~~
}sw}~~~{{~}{z{~~~}~~zuqommjhhqz~}~zwxy{wttx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}||||}}}}}}}}}}~~}}}}||}}~}yy|
~|~~}~~}|||||{zy|~~~}}}}|~
~{||}}~}~
}sv{~|z}|yyy|~
}}~xtqomjgjs{~~~zwy|zvstx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}||~~~~}}||}}~~}}}}}}}}}}{xx~|~}~~}||||{{z{~~~}~~}}}|~~
|||}}~~}}~
tw|~~yy~{yyyyz{~}~}wspnkglu{~}{xz~~xustx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}~~}}}}}}}}}}~~}}}}~~}}}}xy|}}}~~}||||{{{}~~}~~}}}~~
|||}~~~~||~
vx{}{yx
|yzyyzzz}~~|||wrokglv|~|zx|{ttssx{~~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~}}}~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}{xz}
~~~~}}{zyz~~~~~}|}}}}}
~}|}}~~}~||xw{{{xz
|wvwyyyyy|~|}~
}|}ysoljlu}}zx|yrqssw|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}|~~zx{}}~~~~~}}{zz|~~}}}}|}|}|}||
~|}}|}}~~||
zvzzyv{wvvwwwwwwyxz{|
}{|wqljnv}}yx~vsrsqv{~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}|}|yy{~|{}~~~~}}{z|}}|{|||{}}}||
~~}}~~|}
|vzzwu{}vuvwvvvvurrsz~
}{~ztliox~|yz}vttspty~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}~~}~zxy|~|{|}~~~}}{z}~~~|{|{{z|{}}~||
~||~~|}}wyyvszzvuvvuuuusons{
|~
}volqy~~{y|zurtroty}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}}yxz}}{|}~~~~~~}|{z~~~~}{zy{{|}~~}}~
}}~|}~~~ywurrz~wtttutttupkpy~
~~~ypmty}||y}ysrrqoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}||~{xx|}||}~~~~~~~~}|||~~~~}{zyz{}~~~~~
~~~|}~}~~yurqrz|vtttututrmmw|
~{soty}|||}xssspoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||~zvy|}|}~~~~}}~~~||}}~~~~}{|{{{{}~~~~~~
~}~
~z|}~~zuqqryzutttuuvtnlr{~~
|vruz}~||~{vsssnnuz~~~~~~}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}~~}}}}~~}}}}~~~~~~~~~~~~~~~~~~}}~~~~}|}}}}}}}}}}}}}}}}{{{~}wwz}~|}~~~~}}~~~|~~}}~~}}{|}|{z|~~~~}}~~
|wyz}}}}wqrry}yttttuuurmnx~~
|}}vsu{}}||~~yusttlnv{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||{yx{}}||~}}~~~|}~}}~~}}|z{{z{{|~~~~~}~~
{wy{}}yposz{xuttuuttpjp{~~~~
|z~ytv{~~}}{vssurlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}|}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||~~{xy|~}{|}~~~~~}~~~}}~}}}{{{zy|}}~~~~~}~~
{xz|~}qor{|wuttuutslmu|~~~~~
~zx}{vx{~~~~xusstrlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~}zyz|}{{|~~~~~}~~}}~}}|{{{zz|~~~~~}~~
~
}sprz{wtttuuuqkqz}~~~~}
yuz}|xx|~~|vssssqkmuz~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~}}||}}}}}}}}}}}}~~||||zz|}}~|zyy}~{{|~~}}~~~}}}}||{|y{z}~~~~~~~
tps|{vtttuutpku~}~~~~~
yrw}{wy}~~zurssspkmv{~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}}~~~~}}zzzxyz|~~yvz~{z{}}~}~~|}}~{z{}~{|{}~|}~~~~~~
wrs{{wuuttusmkx~~~~~~~
ypty~|yz|~}yusqrrmjlv|~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}zzzxyz{~}yx{|z{|}~~}}~~~|||{yz|}}||}~~~|}~~~~~
~
yutz{vuuttupjlz~~~~~~~
zpqw}}z{}~~yvsrqrqmloy~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~|yz}{z|}}}||~~~~{{{zz{|||||~~}}|}~~~~~~
~
|wuyyvtttttojq|~~~~~~~
{qpu{|{{}~}xtqqqqqpqv|~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~}~}zyywvxy{{z{{z|}||z{~~}{zzz{}||||||||}~~~~~~
~
wuxxwtttttnjs~~~~~~~~
{qorx}{||~~}zxtrqqsstv{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy}~zx}|y{{z{y|~}}zzxyz{{{||}}}|}~~}|||}~~~~~
~
ywyyustutrjkw}~~~~~~~|sprx}||}~}yxwvtrsxwx}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvuuwxz}}zy}|yyyyyz~~|{zzzyyxy{||}}~}|}~}|||}~~~~~~~
~
zwyxtssssqimy}~~~~~~~{qpqv{~~}|}~zxxzzxvx|~~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxussuvx{||{|~}zxxy{}~~|{|{zyxxz{||}~~}}}~~}||||}~~~~~}~~~~
~
ywyxtsssrniqz}~~~~~~~{qpquz~~}|}~}xwy|||
~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}}~}yxxussrtwy||~}}|}{xxz}}|{||zyxwxz{||}~~}}}~}||||}}}~}}}}~~~~~
ywwwtstspljsz}~~~~~~~~~
{rpquy}}}|}~{vwy}
~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{yyvtqrrvwy}~~||{zz|~~}||{xxxxz{{{|}}~}}~~~~}|||}|||}~~||}}}}}}~~~~~~~~}~~}~xvxwusssqkjt|~~~~~~~~~}~~
uoquw|}}}~|yuw{~
~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{zwtqrrtvy|}~}|{{{}~}|}|yxxwx{{{{|}~~}}~~}~}|||}|||}}}||}}~~}}~~~~~~~~~}~~|}}vtwvusssqjlw}~~~~~~~~~~~
~topuw|}}}{xvw|~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||~|{z|~}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~~~~}~~|}ztrxwvtssqjmz~~~~~~~~~~~
~uqrvx|}}}~zxxy~
~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~zxywtqpprvz||~~~zzz|~}||{yzywwy{{{{||~~}}}~}}~}||z{zzz{||||||||||}}~~~~~~~
}~}~~}~
~wtrwvvtstqio{~~~~~~~~~~
~wsuwy|}}}}zyy{
~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~{xvusrqqsv{}~~|~~{z|}}zzvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~
~~}}|xutuwwuusojoz~~~~~~~~~
}wxz{~~~~~{y{~~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xuusrqqtw{}}}{|}|{}~{yxvwxyxyyz{{{||}~~~~|||~~~~}{{{{{{zz{{{{||||{{||}}~~}}~~~~}
~~~
~xwuuuvwuutpkpz}~~~~~~~}}~~{z}~
~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~~~}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}}
~{vvuutvvvutpkpz}~~~~~~~~
~{{~
~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xwuusrrtx{|{{{}{}}}~}}{xxwwwwyyyy{{|{|||~~|}|}~~}}{{{{{{zz{{{{{{{{zz||}}||||}}}}}
}~
~ywuuuuuwvutqkqz}~~~~~~~~~}
~{{
~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~}}~~~~~~~~}~~~~~~~~~~~~~~~~~|zwwvtssty|||z{{||}|{}~{xvwxxyyzzyz{|}||}}~}}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~
~~~~
}|}~~~}zxuutuuwwvsqnqz}~}}~~
~}||~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwussuz|||z{{|||}|}}zwuwxyzzyyyz{}}}}}}~~}}}||||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~
~~~~
~{|}~~~|xvutuuvwwsploy~~~
~|~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~}{xuwxyzzyxyz{{|}}}}~}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~
~~~~~~~}~}}}}zxutttuvvsolnx~
~
~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zxyxvtuwy|||z{{||{|}}||zxwwxzzyyzz{z{||}}~}}|zz{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~~~}}~~~~
}~~}}}|zutssvvuqolo{
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyywuvx{|||{||||{|~}||zwxyyyyxyyz{{}}}}}~}~}}{{{{{{{zzzzzzzz{{{{{{||||||||||||}~~}}}~~~~~~
}}~}yvtsttppnnt~
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{xyxxwxz|||||||||{}|{{{yz{{yyyzyz{|}}|||||}~}}{{{||||zzzzzzzz{{{{{{||||||||||||}~}}}~~~~~~
{xustrqqsv{
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|}zyyyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}~}|}}~~~~~~
~{yvtruuy}
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxyz{{||||||}}zwvyyyz{{{|}{{{{|}{{{{{{||}}{{{{{||||zzzzzz{{{{{{||||||||||||}}}~}|}}~~~~~~
~{xwvy{|
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}}yttvwz{{||~}|zzzz|{|zz{||}~}|{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~|||
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~|{yxxxzz{{||{||{|}}zusuw{||}}~}{zzzz{z{zz{|||~}}{{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~~
}~
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}~{vstw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~
~|z~~
~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~~~~~}}~~}|{yyyxx{{||{{{|~~{xutwz||~}|}{zzzzzyzz{z||{|}}|{z{{{||{{{{{{{{||||}}}}}~~~}}~~}}}~~}}}}}}~~~~~~~~~~~~~~~
~}|z{|}~
~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyxzz|||{{|~}zwuuvz|}|}}|{xxxyyzxy{{||||||zzz{{{||{{{{{|||}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~
{zzyy|~
~~~~}}}}}~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz|||{{}}zwuuuy|{z||{zxxxyyzxyzz{{{{{{zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~
}}|zyy}
~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~~|zwuuux{{z{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~
|z{
~~~~}}}}}~~}}}}~~~}~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~}~~}}}~}{yyzyyzzzzz{|~~}|zwutsw{}|{|{yxxxyyz{{yyzz||||zzz{{{||{{{{{||}}}}}}~}}}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~
}
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzzzzzzz{{zz||zz|zwuvuvy|||}|yyvvwxzz{yzzz{{{{{z{{zz||||{{{|}~}}~~~~}}}}}}~~~~~~~~~~}}}}~~~~}}}}}}~~~~~~
~
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|z{{zzzzz{{z{~~|{zzyxvttvz}|}||yyvvwxzz|{zzz{{{{{{zzzz||||{{{||}}}~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~
~
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~|z{{{zzzz{||}||{{yxvqrv{}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~
~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~|z{{}|{zz{|}}}{zzxxvqru|}|{zyyyxwvwyz|{zzz{{{{{{zyzzzz||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~}}}}}}~~~~}~
~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~}{{{~}|{z{{}|zyzzxwvtqv|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}~~~~~
~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}|{{|{|{{|~~|{zyyzzxvsqw||zxwwywwuuvxzzzzy{{||{{{zz{zz{{|||}|{||||}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~
~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{||z{zyyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~||}~~~~}|{{{{||z}|{||zywxytppu{|yyvvwyyxvuvwyyzzyzzyy{{{{z{||{{{{{||{||||{{}}~~~~~~~~~~~~~~}}}}}}}}}}~~}~~~~~}}~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}|~~|||{{{{{z}
~|}~~{{yxwspotx{zyvvvwwxyxxwxxyzzy{zz{{{{{|}}||||}}}}}}||||}}~~~~~~~~~~~~~~~|}}|}}}~~}}}}}}||~~~~~~}}
~~~
~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~|}}{{{{{{~}||~}|zxvroosxzyyvvvuwxzzywxxyzzy{{{{{{{||}}}|||||||}}}}~~}}~~~~~~~~~~~~~~~|}}|}}}}}}}}}}}||~~~~~~~~
~~~~
~}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~}{{{{|}~||~|ywuqnnrvyyyvvvuwy{{zxwwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~
}~~~
}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{{{{|}{{}ywurooquyyyvvvvxy||{ywwyzzy{{{{{{{||}}}|||{{{{{{||}}}}~~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~
~~~~~~}~~~
}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~}|{z{z}}{||~}yxvsqpquyyxwuuwyz}}|{yxyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~~~~}}}}}}}}|}}}}}}}~~~~~~~
~~~~~}}~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}|{z{{~~|{|}~}yxxuroptwxxwvuwz|}}|{zzyyyxyzz{{{|}}}}{{{|{{{{zz{{|||||}}}}}}}}}~~~~}}}}}}}}|}}}}}}}~~~~~~~
~~~~~}}~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~~|{z{}~}|{||~~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~~~}}}}}}}}|}}}}}}}}}~~~~~
~}~~}}}}~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~}{z{~}|{{|}~zyxuspprtwxwwwwxz{}|{z{zyxwxxxzz{|}}}}z{{{||||{{zzzzzz{|||}}}}}}~~~~}}}}}}}}|}}}}}}}||~~~~~
~|~~||}}~~~~~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~}~~~~~~~~~~~~~~~~~~~~~~~~~|{{}~|{{{|}~|ywvsooqsuwxvwwxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~~}}}}}}}}}}}}}}}||}}~~~~
~}}}}}}|}}~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{{~|{{{|}~zxxtpopqsvwvxyyyy{{{||zyyxwwxyyyz{{}}{{{|{{{{zzzzzzzzzz{{{|||}}}}~~~~~}}}}}}}}}}}}}}}||}}~~~~
}}}}}}|}}~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{|{{{|}~~zyxtpopoqtuvyzzyy{{{||zyyxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~~}}}}}}}}}}}}}}~~~~~~~~~~~
}}}}|||}}~
}}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}~|{|~{{{}~~{ywtqppmnqtuxyyyyzz{||z{{yxxxyyyz{{{{}}|z{{{{zzzyyyzzzz{{{|||}}||}}~~~}}}}}}}}}}}}}}~~~~~~~~~
}}||{{|}}~
~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|}~~|{||}|~}zxurqpnnoqsvwzxyyz}}}|{{zyxyyyzzyz|||||{zz{zyyzyxxzzzzzz||||||||}}~~~~~}}}}}||||||}}}}}}~~~~~~~~
~}{z{{|||~~~~
~~
~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|~~}|{||}|~~zwtrnnonnpstwzz{yy|{||{{z{{yyyyyyz||||||{{{zzzzyyyzzzz{{||||||||||||}~~}}}}}||||||}}}}}}~~~~~~~~
}|z{{|||~~~~
}}
~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~}~|{{||}|~~{vtrnnonnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~
~|{{{|||~~~~
}|~~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~|z{||}|~~{wsqqponnprsvxyz{xyy{{{{zyyyyyxxyz||||||||{zzyxxyyyyzz{|||||||||||{||~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~~
}||||||~~~~
}{~klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~}~~}}}}}}}}{{||||||||||||}}~~~
|sjd^[]_ada_][^cehhe``bglsz
{qf]YX]bjtz~{urppmifaadgknprvyz|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~
kmnopppopppsuvwwwxzzzywurpqqppppqqrstvwyzzyyzz||}}}}~~}}}}}}}}||||||}|||||||}}~~~
yohc_\^_ac`]\Z]aehgda`beiqx
zrh]YX[cisz~~zvspomhfaacfkmnknsy~~}ztnkiihhhhiklnpqtuuuuuvvuwvwyxupf\WTW\^chpx~~|xusnliggcdfhhigijjmprtttttsqqqpoqsrqswy~~zuplgbbcba[Y\ckottsrrsuwz~lnopppoqrrstuvwwxzzzzxvtrpqqppppqqrsuvvxzzyyzz{{||{}~~}~~}}}}}}}}}}||}|~|||||||}}~~~
}wogc_]^_bb]ZYX[_dggeb_acfmu~
{si^[Y[bhpx}~|xtrpmnifbbbejmonorw{|zvqkihhhhhhiklnortuuuuuvvuwvwyxupg\VUW\^cgpw~~{wtpljihgdegiiiijkjmprttttsrppqpoqsrprvz||ytqmhcbcb`\Z]ckruusrrsuwz~
npoppposttvuvwxxyzzzzxvtrpqqppppqqrssuvwzz{{zz{{||{|~~~~}~~}}}}}}}}}}||}}~~||||||}}~~~
|vngc__^`cb]ZXWZ_dhjhc`aaent}
{ria][\bgnu||zurqommifdbbdinsvwwv{~~~{ytojhhhhhhhhilnortuuuuuvvuwvwyxupg]VUW[^dfow~~{wsnliiihffhiihjjkimprttttsqppqpnqsrptx{{{xtqlheddc_\Z\dmtvvsrrsuwz~
moqqrrrstutuvwxxy{{{ywusqpppppqqrrrstuuwyyy{}}|||||}}~}~~~~~||~~~~}}||}}~~~}|}}}}~~~~
}vmga]^_cda]ZXVY]elnida``emt|
uleb``chot{yvtqomnjihgdcdhow}yx{}{xrnjhhhiiiiijlnosuwvvvvwwvwwxxyupg]VUVZ^dhow~|zurojghhgghhjjikjkmnpstsuuropppoprrqqtxz||wsqkgecca_[\`elsutssstvxy}nooqssqrtuuvwyyyy{{{yvvrqpppppqqsstttvvwyyy{||||||}}}~}~~~~}}~~~~~~}}||~~}|}}~~~~~~
~vnd]\^acca_ZVUX`jqrld``aflrz
zrhc``dhmrxwsqnllmjjjjfdeit~
{prz~~|xsplhhiijjiiijmoprtvvvvvwwwwwxyzvqg]WUWZ^dgow|~{wromighhffgijjjklmmmpsutttroooomprqprtxz{zwsniecaba_^_bflsusrrtuuxz~nonprrqrtuvwxyzz{|{{yvvrqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}}}~}|}}~~~~
wnb]^^cccb`YTTZdotunea`bfkqw
{slfaaeilottpnmlllllljhfgkw
ynqxzvqlihjiikkjjjkmoqrtvwwwwwwwwwxyzxri_XTW[_cgnu|~}zvsoljhhhhffgjlllmnnllpsvtssroooomoqportxz{yvrlhca`ac``bcgnstrqqtuuwz
nnmnqqrsstuvwx{z{|{{ywvtqppppprrssssuwwxyyy{{{||||}}}~}~~~~~~~~~~~~~~}|}}~~~~
xnea`_dee`^XUT\hqwwngaaadkqw~
{sliccfjlortpnmlklkmmlkhhnv~wpq{~xuoljhhjjkkjjjknpqruwwwxxwwwwwxyyxskaYTV[`cgmt{~~~{xsnljgghhhgfgkmmmonlllpstsssronnpnpqoortyz{ytpmjdb`aeccdfjnrsrqqtuvxynnmnpqsttuuvwx{|~~|{ywvurqqqppqrrtsuwxxxxxzz{{z{}}}}}~~~~~~~~}}~~~~~~~~~~~
~ynhebdefc^\WUV_lu{zqjba`ciou|
|uqjeefikoppomllkkmnnnjhinv}xqqx}wsnkhhiijjkkiiknoqruvxxxxxxxvxxyyytkbZVVZ`chms|~~}ytojihghfefghimoooonljkpsssuuroomnppommptxxzxspmida_aebadimrturpqtuwwy}nnmnpqstuvuvwx|}~~|{xvutrqqqpprssuuwxxxxyyzz{{y{}}}}}~~~~~~~~}}~~~~~
yoigcdeec^\WUYdpx}|skda`bflt{
}wskecgijloonmllllopnolklow~~xrpt|~yuqlkiijlnmmkiilopqsuwxxxxxxxwxxzzyukc[XW[_bgms{~~~}xuqmihihgdefhjknooooljjkpsssttronmnppommquxxxvromhb`_cebdhkqtvsqpqtvwww|nnnnpqtuuvvwyz}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~~~
zrkhdcdec`^YW]ht{}vohc``dkry
xrkdbfhjknonlllllppoonnqrx{wtoow}|vrpkhjklooonkijlppstwxyyxxxxyxyyz{ytlcZWY]_bglsy~}zxtpoliggfedefijkmoooolijmpsssssromlnppommquxxuspmkfbaaeeehlotwvsqpqtvvuuy
nnoppqtuuvxy||~~~{ywutsrqqqrrqrtuvwxxxxzyzzyyy{}}}}}}~~~~~~~~~~}}
ztmhdbceb`^ZW`lx~~xqjd`_cjrx}
yskfaceimmmmkllllqpppmotuy{ussojs{{vqojhjmnooqokiiknouvxyyyxxxxyxzz{{ytlc[XX\^afksy|~{vtpmjjhgedccefjlnnooooljjlpsssssromlnppommrvxxsqnkifa`cefhloqvywrppquvuqtx
klnopqsuvwxzz}~~}{zxvutsrqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~}
ztnheccb`^][\dpzztjda^bhnt{
{tmkfdeikjijkkkmnrssrstvxz}ysnnqphluvtokjjklnoqrpkhikmoswxyxxxxyyyyzzz{zume^ZZ\_aglrwz{}}{xspligffdbbcdfhmnppponmljhlortsrsronlnpqnmnqtvurqnmkgc`bffjoruxzwrpqruvtrtxjlnopqsuvyyz{~~~~}{zxvutssqqqqrrsuvwxxyyyz{{zzz|}}|||}~~~~~~~
ztnhdbba`^]\^ju|zumgb^aflsz
|vomhehjkiiijllmpsuustvwx{{uoijoplhpspkijnpoprstpkhikmoswxyxxxxyyyyzzz{zvnf_ZZ\_bgkqwz{|zxupligeddcbbdehjnoprponmljhlpstsrsronlnpromnqstroolljfcacfhmrvy{zurprsuvtsrvjlnopqsuvyy{}~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~
~xslfb``ba`^^cox}{vqkc``fkqy
}wpnigikjhijkllnqtvuttwxyyzunlnqolikoniikoqonrttpkhikmoswyyzzyyyyyyzzz{zvnfa][]_bgjowz{zwtrniggecbcddefiknoqsponmjhhkpstsrsspnlnpromnoqqolkkkhdccegjqvz}}{usrstuwtsrvjlnopqsuwyz|~~~~}|{xvutssqqqqrruvwxxxyyyz{{yyz|}}|||}}~~~~~
}wpieb``a``_`eqz~{vrlea`eiow~
yrniehkjiilmlloquwvuuxzyxxwrpttmkjhllgjmppmmsttokhikmoswz{zzzzyyyyzzz{zvnfa^[^_bhinwz{yuqnkgeecbacffegimnppqponmjhgkqttsrstqnlnpqnmnnnnmjjiifdddfjlsz}~zurstuvwtsrtlmooopqvxz|}~}}{yvutsqqqqppqrtuvxyyzyz{zzyy{{}}yz|}|}~~}}~~{unie`]]``^^biu|{tlfa`bgpv|}{vpkigklihknmnpruvvvv{}zwwvvwyvkhiimqmnptvrmoqrpkiijmprwyzyyyxyyyyzzzzywohc`]^`cfkotxyusnligddb_abdffhlmpqrrpmmnjhhkrvutstspnlmopmklllkjjhffcbbegknt{~~xtrrtvwwtqpu}mmnpoprwy{~~~}}{ywutrrrrrpprttuvxyzzzzz{{{{{{{{y{|}|}~~~~zrlgb^\]___afnv~}unhbabhpv|~}wrljghjjijllmpsvvxxw|zuuyz{yslgfglqrqt|snqrpljijmprwz{yyyyzyyyzzzzzxoid`^^acfjnrvwspmjedccb`bceghjmmppqqomklkiilrvuuttspnlmopmihiigfgeedcbadgkrx|}wtqqtvxvsqqt{
mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~}xqje_]\^__`chqz~wpjdbagnuz~xrmjgghkjjklmqtvxzzz~ysuz}}vqnheeiostz|tpqrpkiijlorw{|{{zzzzzz{{zzzxqjd`^^acfhlpttqljfdcbbdddeghjlmmnnppnlklljjnrvuuttspnlnppkhgfffeeedccbaehlrx}}{wspqtvwurqprz
mmmnnprxz}~}}{ywvusssqqrrqstuvxy{{{||{{zz{{yy{||}~~~~|xphc^]]_```clu{xrkfb`flrx~~ysmjgghjimlnoruxz{{{zsuz~|toojfeglru{~xtrrrpigijkorw{|}}{{zz||{{z{{xslc`^^acfhlorqnjhedcbbdddgiklnoommppnllmmllnrvuuttspnloqpjfeeedcbddccacfilrx{~|{wsqttvvuqqpqyllnooqtw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~~ysngc`__```aemw{umg``chou|ysnjfehjjklprtwyz|z|~wqw~~yurokjihjoqx|xrquvtojghklprw|}||{{{z||{{{{{xrkda__bcegjnpojhefdbabbcgihjmoonmmoommnomkkosuuuuuspmlopoicbeea`adcdddffhmrx||{zuqqssuurqqppxlllnorux{}~~~~||zxvvtsrrqqpqrsuvwxxzzzzzzz{{zz{{z{}~}~~{vnjfc^]\_``bhnx}woi`_bgns{~ysnieehjjklptxz{zywyzxus{{yupmlkjlmryyuqrxyvqkhhklorw|}|||{{{{{{{{{ywqkea``bdegjmnniecedcacccfhijnoonmnppnmmnmlkpsuvvvvtqnnpqnhbbdda``bcddddghmry}|{ytqqssutsrrrrw~lllnqsvx{{}}~~{{zwvutrrrqqprstuvwxxzzzzzzz{{zz{zz{|}}~}
|wsmhdb^\Z``adjqz~xqkb_bflrz~xtnieehjjklqty{|zywvvvvw}{wrponnmot{xtsv||xtkhhklorw|}||||{{zz{{yyxvplfb``adghikkkhbacddbdfefhjknoonmoqqpmmnnllptvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqu|llmoruvy{{}}~~zzywuusrrrqqqsstuvxyxzzz{{{{zzzzzzz{{|}~~~ytplgcc_[Z_`cfkt{xrmc_afjpy~ytnkhehjjklqtx|}|}zuvvtv||ytttqpopu|ysu{~}xsmihkmprw|}|||||{{{zzwwwvplgd``aehhhhghfcaddddghhikklnoonnpqqoooonlnrtwuuwwsponoplfaaaa_^aaabccefhmry}|zxurqssuurqqqqszklmoqswxyz{|~~~|zyxwvutrqqrrrtuvuvxxwxxx{{z|zz{{{{{||}~~|uqmgec`^\]__bgpyzune`bekqw}xvpjfdfhjkmsw||}}yuutu|}zyxzvtursv{wqt|~}xrlhhjloqv|}}}}{|||zywwwvtrnie```cijjgggecacddhhijkkklnonmmmpnmpqqonnruvttvvspnopplf`aa^^__aabcdggimsx|{xvrprstvtqrrqqsy~klmoqtwxyz{|~~{zzyxvvttqqqqqrsttuvxxxxxyzzz{zz{{||||}}~~~ysnjeec`^]]abeirz{vofaaejpv|~yupjgeghkknuyz}}zzyz}zwyyvrpqrtx{xsu|~}yskghjmprw|}}}}||||zywwvusroiea``chhieeeebbdeeiijjkkjlnonmlmnllorronosuwuuvvtqnoppkeaba^]]^addcfiijnty|{xvrprstvtqqqpprx~|kmmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~|vpjfeec`___cdhmu|~|voga`dhnu{}zuqmhfghkkpvzy~}~~}}xvttroopruxzxuu}~}xrkghjnpty|}}}~}}}|zywvutspmidbaadffedffebceggjjjjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^]\_adeehjjkosy{zxvrprstvtqooppqw{znomosuwxyz{|}}|zxwwvutsqqqppqrrsvxxxyyzzyyyzzz||}}||}}~~|ytmieeec`^_bdejqx|~|vohb`cgmu{~zupohdfhkkow{|~}}}|xutqqnmpqsuwzxux}~}wqmihjlosx|}}}~}~~|zywuusrplidcabedddeggebcfhikkkkiiijnonmkkllmqrrqpotwxttvvurnoppjd`aa]_\_accgikklotyzyxvrprstvtqooooqwxwmmmotuvxxz{{||}{xwwvvusrppqqrsstwxxxyyzzzzyzzz{{{{||~~}}wqlieeeca`adegksz|vohb`aglsz|wtpmieegklpvz{
zvtvxxurqnlnnortuxzxvw~~wqokiklnqv{}~~~~~|{ywussttqmhebbbeeeffggggfgjkjkmjijjjnmmllmmnopoqroqtvutruvtqmnqokc``bbb``bcdfijllqtxzyxuqqrtuvsqpppppuutmmnptuuvxy{{||{zwvvuvusqqqqqrrstxxxxyyyzzz{{zz{{{{}}~~}|wqljgfdcbacfgilt|~xpicaaglry~~{vspmjgehknquy{zupnqrtrqolkmoptxvx{zux~~xqnjikloqv{}~~~~}|{ywtsssspmhebbbefggggghhhhjkklmjijkkmmlklnmnnppsspruvutruvtqnoqokc`_cccaacdfgjjlmruxzytqqqruvuqopppppstsmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}|wplifeedddeghkpw~zslgbcfkpw}|ytrpnkhehlpptx{~wspmnpqppnmmmotxzyy{xuy~~xqmiiknprw{}~~~~|{zxvstssrolhfcaadgghhhhiijjlmmnnkjijjkkkjlmmmlprssrsvwuttvwtqopqokeaaddcbbeegijijnrvyyxurqqruwtpnppppoqrqmmprrtuvwxyyyyyxvvvvsrrqrrqqqrstvvxxxyyzzz{{zz{{{{~~~~}|vpkjfdedfeggimsy~~zrniacfjov}~{xsppokiefknotx{|vqqmmmnnnmmnmqvz|}yxuuz~xqmiiknpsx{}~~~~|{zwvssrrqnkggc`adgghhhijjkkmnmonljiiijjkjklomkpqrttuwwvtuvwtqopqokfccdddccegjkjjkosvyxwurqqruwtpoppppopponoqqrqrswxyyyxxxvvutsrqppppqrsttuvwwwxyz{{{{||}}~~~~~~~~~}wpjhgfgghhhjkov~~{tojeceiot{~|zvpppomifgjnoswz~zuppnmmnnnnnmotwzzxuu|~xpkiiknsuy|~~~~}|zxvvtttsrmjhieabdgjklkikkmnpqqpnljfggijkkmmmnmprtttwyywtuvvtpopqolfcceddcbdhjlkkkosxxxvrqppsvxvrpnponnnoonoqqrqstvxyyzyxwuuttrqqppppqssttuvwwxyyz{{{{||~~~~~~~~~{unjhhgiiijjlmpw{upjgceiosy|{xuppppnifeinorvz~{vqommoqomnonqvx}~ytty~xpkijknsvz|~~~~}|zxvvtttsqnkiifbbdimpqommnnpqrspmkjehhijjjlklmnprstuxyyvuvvvtpopqolfeedcccbdhjlllkptwxwvrqppsvxwsqnolkkknnnoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~~ysnjiijkkklllnsy|vpkfceimswzywtqppomjgdgloquz~}xsomnqpnmmooqvz{vuz~xpljkkorwz|~~~}|{yxvvtutspoljhgccglostspopqrqstqmkjghhijiihiklnprsuvxyxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnnoqqrrstuvwwwwwwvvssqpqppppqssttuvvwyzyz{{{{{{||}}~~~~~~~wrljjjklmmnnmouz}wpkfdcgmrvyxutqppnlkhdgkoquz~}wrpnpqnljloory|
zxwz~xplkklprw{|~~~||{yywwuttrqmkkhgcfknrtvurqrsttutqnljiiiijiigijknpqsuuz{xursvuroopqolfddcbbbbdhjmmnmnrvwvtrqppsvxwtrpnmkjjmmopqqrsstvwwvwwwwvuttrqrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}|uojijkmnonponqv{~xqlgddglpuwwvsqoomlkiehknosw}|vrplmlggjlnpuz{vuz~wpkklmoqv{}~}}{yzxwutrqonkjhgfhnrvyywusrsuvwurnlijjjhiiiggjknqrsvvxyyustvuqooqrpledbcbabcehknnmmnsvwutrqopsvwwtqomkjjjlmppnnqrrtvwwvvvuuuusrsrrqqqqrrsttuuwxxyyzzzz{{yzzzz{|}}}|}|~~yrliijknpqpponrv|ysmiecfkotuvurqoollljegimosx~~}{wrpljgfgkloru{
{vsw}~wpkklmoqv{}~~~~}{{zyxvtrqonljiggjrx{||zwvstwxxvsoljihhhiiihhkmnqssuwyzyustvvroorrpjddbbbabcehknnnmosvvutqqoqsvvvtqomkjiilmppnnqrrtvwwvvvssttsrsrrqqqqrrsttuuvwxyyzyyzyyvvvttvy||{z{z{{}~~wpjihiloprqqpnrw|zuniecekorstsqpoommljeeimpqw}~}|{xspljghikmosx~
|vrv}~wpkklmoqw|~~~~}}|zyxvtspooljihhmt{~~|yvtuxyyvspmlhgghiiikjjmnqsstvz{yustvwrporrpgcdb```acehknonmpswutsppqstuvvtqomkjhhklppoopqqtvwwvuussttttsrrqqqqrrsttuuuvwxyzyzzxvrqqpprwzzyyywyyyz||voghghmpqqpqposw|{vpiebfjnprsqqpooonljfejopqu{||{zxsplijijloot{
vsx}~wpkklmoqw|~~}}|{zxvusqnoljihinw}}zwuwy{{vspmkighhiiijjikmqsstvz{yustwwsqorrphedb__`acehkoonmptwusrppstuuwwtqomkjhhjknoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{{ywusolkijmqxxvttrstsvy}~~{tmhghjkoooooopt{|vqjdaeimopqqppppnonjhginpptz}}}{xtqmjiiimnqv}
yuwwpkkjkoqv}~}|zyxvuqpnolihhjszzwvw{|zwtpmkjihiigiiiklnoqsuwy|yutuwvrrqrsnfabb``_`cfhknnnnquvtrqqqstuvxwtpmljihhjknooooprsuvvuttuuvvssrrrrqqrrssttuvvwxyy{|ywspmjifgjntsqokjikmquz}~~}wokhffhjnoooopqu|}wqjebdgknopppqppopokhginpqsy}}~{wuqlkiiinosx~
}wx~~vokkjkoqv}~}|{yxvuqpnpmjiiku||xwx||yxurpmkihggfgiiklopqsswz|zvuvwvrrrssmd`ba```adfiknnlmquvtqoppstuvvvrnmljihhjknoppprstuvutttuuuussqqrrrqsssttuvvwwxyy{{xuqljihfegkonjgdcdfimptx{|~~~~zrkhfdceimpppopqv|~xrjecceilmpppqppprqmhfilpqsx|}}{wtqlljjjnqv{
}uv}|vpkklmoqv}~~}{zxwvrqopmkjjmv~}yxz||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqopqsttuvvqnomljhhjknoqqqrstuvusttuussrrqqrrssttsttuvvwwxyy{zxtpljigfedhjhgcb`bcfjlpty{|~~~~~~{vpidba`chmqpppptx|~xskfccfiklooprsssrsnheiloprw|||zusqnlkkjosx}
ztt}|vqmmlmoqv}~}{zywvrqoqnkjjow~}zz||{ywurpljihggghhhjloprqtwz|wsvwwvrrrssme```__aaehjmnnlmquvtrqqrstsuwwqnonlkhhjkkmoopqqqstssttssrrrrrrrrssssststuvvvxyz{yxuqmkjhgeccddcbba`adffipv{}~}}~~}ytnfc````fnqqqppv{~xrmfacfkjjlmoprssssoighknpqtz|{ytqqlkkllpvz~
ztu}~wpllklnrx}~~~}||zxvusqoqomkkpw}}{z{zyxwtrpmjigffiihgijnpqrsw{|yuuxwurrrrqja^_]^`bcfhkmnmlmrsuttrprtttuwwqnmmkiffhkklmnpqqqssssssssrrrrrrrrsssstttuuvvwxyz{yxvrnljhfdbaaaaa``accddekpv|}|}~~~~{uqkfa]]`ciorrqqrx}ysngbadijjkmoprtuutpkhhknoqtyzzvqnnjkklmrw|
|tsz~|unkkklnrw|~~}|{xwvtrpqomlkqw|{yxzyxvusrpmihgggiiihikmpqrswy{xuuwwurrrrpi`]^]^`cefiknnmlmrssrsqprvutvwxsnllkifdhjklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmjigecbabca``bdcdccfmrx{}}~~~~{vqlie_]\`ekpsrqrty~{uoidacehjkmopstwwurkihjnoqtxyysnlljkkmpuy~
vrvz{unkkklnsw|~|{yxvtrpqomlkqwz}}yvvvtutsppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqprvwuwyxtollkiedfhklmopqqqssssrrssrrrrrrrrqrssttvvvvwwxyz{yxvqnmmkigedcddb``ceedccfiouy{{}~~~xrmheb^\]`fkqsrqrtz|vokebdegjkmpquxyyvslihjmoptwxvrnkmkkknrv{
xtsyzupjjklosx}~~}{{ywurpqomllqvy{}{xtsrqrrqnmmkhgffiiihhgimopqsw{ywssvwuqprrog`]^]^_bdgjlonmlmprsrqqrtuvvwyxtollkidceglmpqppqqrsttttssrrsrrrrrrsttuuuuwwxxxyz{zzwtqqqpmifffeceddeecdbaadktxz||}~~~~xrmfc`^[\]akmqsrsvy~}wqkeceggijmpqsxzzxunjhhoppsuvsnlkjkllnqw~
{srxxupkjklnrw|~}}||zxvsrrpnmmqtwy{}{xrqrnllnlmmkjfddeiiihgggkoppswz{zvtuutqqsrne^\\[_abegjnpnkilpqsrpppsuttuxxqmnnlhfdefmnppppqqrsssssrrrrrqrrrrssttuuuuxxxxxyz{{{yvttvvqmihhgfilmllhhdb`bfpvyzz{}}}ztnhda^\\]afmoqrsvy|~xrkdadfhjjmprtx||zwpjhhmppqsrokiijlllnqx
yrqtwupljjknrx}}}}}|zywussrpnooruxy{wrmkkhgfhikmkhddceijjihhhhlmotxz|yutuutrrsrne^\\[_adfjmoqnkilopqpppqststuyyrmmmlhfdcemnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zywwxxtqlkhilortrrolgbaacltwwxz|}}~~|wpjdb_^\_`dhmpqrux{~xrleacdhjjmqsvz}}zxrkhhloppqmjghhjlllnqx
yrqsvtoljjknrx}~}}}}|zzxuusrpnpoqtvwwrnigfdcadgjnkhddceijjhjkkijjotxz|wstuutrrsrne^\\[^aefloopnkjmopppppqstsstzysnmmkgeccemnooppqqrsrrrrqqrrqprrrrsttuuuuuwwxxxyz{{{z{yyzzwtnmjlosuxwwsqjbbbcjpsuvyz||}}|unica^_^abfinqstvy}~xrlfabcghjmqux|~~{xtlghjnppmkgefejmllnqx
{tstvtnljijnry~~~}|}}|z{xwvtqpoonortvtmiddc`_`cgimkhedceikjhkmmljknswz|wstuutrrsrne^\\[`begknopnkkmooqqqqrtvvrsxxqmlljgebdflmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||{{wunnmptwxzzzxsldbbcfimqsvyz{||~~ypkgeb```bdfkorsuy~}tmhb`dghikrvy~~}zvnhgknqmifebdehkklnrx
~xvwwuplkijosx}~~}}}||}|{{yxvtqnpnnnpsusnf```^]^cfilkkgdcfjklloonnlknqw{{xtuvvussrqog]Z[^bfhhknpomjjmppssqqsuvvtsxxqnmlihebefklmmmnnpqqqqppqqppqqqqqrttuuvvvvxxxxyz{|{{||{{{{xvrqqtwyy{{{zupifcbcdfilprrstw|~}~}vnifdcaaabeknpruw{}voicbcgiikquy}~{vpigjmnjgcdcdehlkmnrv}
zxyyupkiijotx}~~~~}|{||||zxvtroonnmnqsrme`_`^^^bgjmnlhdcfiklnqqoomkmqv{{xuuvvusssrnf\Z\`cgjjloqpmijlnprsqqsuvvrtxwqnmkjiebedklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zwuuuwyz|{|||xsnjgcb`bdgjkjkmrw|}~~zsnigdbaaefjmpptx{~
~vqlfcchjkmrvy}{xqjfhlnjebbceehllnoru|
}xvzzupkiijnsx}~~~~|yyz|||{ywusqnnnmnpqqld`_```aejmopmhdcfiknortqonlkou{{wtuvvusstsme\Y[aegkkloqplhijnprsqqsuvvquyvqnljjiebdblmmmlmnpqqqqppqqrrqqqqqstuuvvvxxxxxyyz{|{{}}{{yy{yxxvyz{}{{{{{uqkifc`abffffgimu{}~}wqlhecaabghlnotxz}
}vslebehjlnsx{{ysjefjnjd`acefhlmnqsv}
|vtyyupljjkmrx}~~||xwxx||~|{xusqnnnnopqpkdbddbcdjmpppkgdcehkpqswrpoljnt{{vtuvvussrqme[YZ`fhkllpqqkhjimorsqqsuvvuuyvqnkjihebcckjjklmoqrrqqppqqrrrrrrstuuttvvwwxxxxyz{{||{|||{{|{zz{{{{||||{zvsplid`acaa_`cehox{~{vqljgda`dikortv{}
~zslfcegjlpuz~}yskfehlkc`acehlnnnotx~|trwzupllmnmqx}}yurrwz{|}|zvspnonnnqrnkeffhjlorsutpkfbbeilorvzwrnjint{{vtuvwtrssrlcZYV_gkmmnpqojggjmpsspqtuxwstxupoljjhecdfkijklmnpqqqqppqqrrrrsttuuuuuvvwwxxxxyz{{||{|||||||{{{{{{||||{zwtrnjfceb^\Z[^`bluz~ztolihfdehmptvy{
|tmfcdgjmqv{~}xtlgfhlkf`adhjnpomou{}wqqwzuplllmorx}{vplmtx{{}{xurponnnnopokijlqstvwwxwqkfbcegjnrx{ysnhgmtzzvtuvwtrstslc[YW_imnmopqmiggjnruspruvwusuxupnlkjhedegjijklmnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zxxtqljggc`][[\^`hqx}~xrmkhgggjnsvxy|~
}umfcdgjosw}~xtnhgilkhbbfjmqrpmou|
yroovyupllklnqx}xsjffnuy||zwspoommlmoppnnotwxxyyyzwrlfbcegjmqx{|unfejszzvtuvvsrstsne^\[ahlnooppmighjostrrsvwwutvxuomlkjhfefhihjknomoppqqppqqrrrrsttuvvuuvvyyxxxxyz{|||}~}}}}{{||{{||||||{zz{urnljgda_]]\^`enw}|wqlkhggjnsw{||
}ungcegjnrx~~ytpjgjljhdcfkottqmot{
xqppvxupllkllpx}}|vofbbkry|}{vrqnmnmjmqqpqquyzzz{{zzwrlfccegilqw{~xpfchsyyvtuvvsrstsmf_]\bfjooopplhehjnrsqtuvwwutvxuomlkjhgeghjijkmnopqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz|{zywtrnlhfd`____`enw}|wqlkjhhlrx{}~
|vogefhknszzupkhjkmiechnsuwrlmrx}
~vooqwyvokkjkmry~|sia]]fou{{xtqpomlmmnpqppqv{}{{}}}{yskfcdehjlrx}|vnfaeoxxvtvwvttttrnha`^agjoprrqlifikprssstwxxuuxyvolllkideghjjjkmnppqqqqqqrrrrrrstuuvvvvvvxxyyyyzz|{||{{||{{{{zzzzzz{{zz||{yywuqnkhfdccb``dmv}zuomjjijpx|~
|xpifehkosw}{wrljiknjgekrwyxtnmnu|}uooqvyvokkjknsy~{qg`[]enuzzvqonnnlmoppqqqsx}}}}}zrkgcegikmrw}|vme`bmuwvuvwwtuuuspjed_bhlorssrnihjmrttsstwxxusvxuolkkkiegijjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{ywvroljiged`^bkt}~ytnmjjjouz~
}yrkffgjosx~}zunjhjmkiipvz|{uommr||urqquxvokkjknsy~zph`\^entyxsommonlmoppqrruz}~~~~~~|xqjgdehikmqv{{vld`bltwvuvwwtuuvtpifeadjmpsssqnigkmrutrstwxxuruxuoljjkigikmjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxzzxxz{||||{{||{{{{zzzzzzzzzz|||{zyyyxsqnlkhea^ajt}~xsmlkkkrw{
~yskgfgjosy~|wpkhjlmlksy{|zuokmtz{vtrquxvokkjknsy~zqia^`gotwtrnlmnmlnnopqppuz}~~~}{vpifdegiknpvz{uld_bmuyvuvwwtuuvtpigecejoqsssplihklrwurstwxxtqtwtoljjkifhkmijjjlmoqppppppppqrsstuvwxxxxwwxxzzwxxy{}||{{zz{{zy|zyyyy{{{{||{{zy||{zvtrplga^agr|~xrkjklpv{~
{uoheehmrx~|wrliimnlmu|~}ytolnrx{utvtuvvnlklkotz{ricbchorssolllkklmnoppqquz}~}}vngcdefgjmqvyyulc_bkuxvuvwvutvvsnighggloprtrnjgfjmquustuxxwsquwrnkkjhffijmijkjlmopppppppppqrssuvvwxxxxwwxxzzyyxy{}||{{zz{{zyzzyyzzzz{{||{{{{{{{{xwtsnga^bis{~|vpljklqw|
~zupheehlqw}~zwsmjimomov~~ytommrxyuvzvvvwnkjkmoty~{ricdfjoqrrolmmlllmopqqqqsx}~{tnfccefhimrvyytmd_bjtwttwxvutvvsoihiihmoprtsnjhgjosvuttuxxwsquwrnkkjhfgjmnijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||||{{zyxyxxyyzz{{||||||{{{{zywupha^ajt{
|xupmkklqw}
~zupgedgjov|~zvvtmjimoopxztollrx}wty}yvvwpkjknnsx}zsjffjlnpppnnmmllmmpqqqqqsw}~zsmebcehijmswyytmf_ajsvtuvxvutvurnjijjjnprsutnjiilqtussuvxxvrrwxsnkkjhfgjoqijlllmnoppppppqqqrssuuvwxxyywwxxzzyyz{{}||}}~~{{zyyyxxzz{|{{||}}||||{{{{xwsja^`jt~zutolkmmsx~{uogedfinu{}yuvumkimopqy{toklrxyvty}zvvvqmlllmrx}{sjggklmpppnnmnlmooqrqqrrtx}}~}zrlebcegiknsxyyunha`iruuvxyvutvuqnkjjlknprsutnjjjnruursuwxxuqtxxsnkkjhfgjnpijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{z{{{{||||{{{{{{{zuka^ajt|}xqolllnptz~
|vpifdeims{|vttuokjmopry}zuqkjovzvvz}yuttqnlmmnsy~|tollmmmoppmmomnmnoqqqqppsx}~~~~}ysmgbcegjkosy{ysmfabjrttuwxwvuuuqnkkllmoqsttqmkklpvwvrsvvxxtptyysnlkkiegilqijjjklmooqpqqqrqqrttvvwxyyyyxxyyxxzz{{{{||||||{{yyvuuuvwzyzz{{||||||{{{{{ytkc^ait|~{uomlllmqu{
zvqjfdejnry~~zqortokkkmpsx}zvqlkow~zxyyyvstsrnlmmosy}uqnnpnmoppmmomnmooqrqqppty}~~~~|xrkgcdfhkmqrx|ytnfbckprtuwxwvvvuqmjlmnoqrsttqmlkorvxvtuvvxxtptyysnlkkieijlqijkkkklmnppqqqqqrsuuwxwxyyyyyyyyxxzz{{{{||||zzzzyxvvttuwxxzz{{||||||||{{|yumd`ags{~{uqmkkllmqw}
}xurkfdeimrw}~{vnlorokjjlorw}|vqnmqw~}z{|xusrrsrnlllnry|vrrrsnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghknstz|ytohddmprtuwxwvvvvqlijmpqrstuusommotwxvtuvvwwsrtyxrnlkkigjlnsijllljklnopqqrqqrtwwwwvwyyyyzzyyxzzz{{{{||||zzyyxxwvvvuvvwyy{{||||{{||{{}zxpd`afsz~~ysnjjkklmpw}
~ytrlfdegkqv|}yslkmqokijmosw}|vqmlqx~}z|}xtqqqtqnllknqy~yvrttqnmopponommooppqrrqquy}~~~~~|vqjfccfhkotw}|ysnjffossuvwxwvwwwqljinprtutvvsomnptwxvstvvwwrrtywqnlkkiijlpuhijllllmmoqqrrrrsuwwvvwxyyxy{{zzz{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~~ysolhhiijnsy~
}ysrmgddfiptz~~zunfgmqnjhllnsv{}vrnlrx~~z{{vsopqtpmmmlmqw{xssuusommppoppnmnoopprrrruz|~~}}zvoiecdghlosy}|wrliggnrttvwwwvwwwqmkloqsututvspoorvxwsstuuwwssuxvqomljjjjmqwhijllmmnopqqrrsssuwwwwxxyyxy{{{{{{{{|||{|||z{{yyyyxxvvuuuvwyz|||||||||{{{{wpe__cnx|zunkihghijpu{
zuqpmheegins{|{vpjedinokimmosu{}wrolpv~zzyvtppqsqnklkmpv~yvtuwwupnnqpprqomnoppqrrssuz|~}~}zvoidcdfhlpsxz{wqlihjpstuxxwwvwwwrnlmoqsuuutvspopswzwstuuuxxttwywpnlljjjloqvhijllnooqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{xri`_clu{~wskgggfgikqw|
zuqomiffhhnsyyvpjecegnnmmonpstz}xsolou}
}{ywurqrsqmjlklnu{}zvsuwyyvppprqqsqomnpppqrrssty|~}|zvojeddfhkptxzzvpmkimruuuwxxwvwwvsnlmoqstuvuvsqpruxzvutuwwxxttxzwolkljjjmprshijlmnppqrrrssssuwwwxxyyzzyz||||{{}}~~}z{{{{{{yyzzyyxxuuuwwyyz{{}}||||zz{{zulc`djsz~}voieefefilsx~
~xtrpniffhhlruvrmfbbdflmnoqssrsz}xsolou|
|}{urrrtplkkjknsxywtqtw{{wpporsstqomnppqqrrssty|}|yvpkdcdehkpt{|xupmkjotwusvxxwvwwutnlmpqstvvuvsrpsuxzvuuvyyyyttvxvnllljjjkorqijjllmopqqqrrsttuuxxyzzyzzz{||{{|||||||{{{y{yyzzzzzzzzxxvwxxxy{{||}}||{zz{ztmc`cjtz~xtngdccdgipt|
{wtpnnjhhiilprrmhdba`cinpquyywvz~|xrmmqw~
{urqstpljkjkmqtvtqptx{zurpssuvupnmnnoqqsqqruy}~{xuojeccehlquzzxsonmoquxwuwyzxxxxwrljmprtvwuvwtrrvx|{wuuvxyzyuuwytnkkkkkkllonijkllmooqqqrrtuuvvxxy{{{{{||||||||||||||zzy{yyyyyyyyyyxxxxyyxy{{|||||||zz{ztnf``hqx~}upjeaccdgksw~
~zvspnnlhgiilopoiec`abdjprs{}yz~~zvqmnqw
ytsrstpljkjjknrsrnnsx{zusquuvvspnmoopqrsrqsvy}~{xsniedcfjmqv{zwponmoswzxvy{zyyxxxrljmprtvwvvxvstxz}{wuvwxyzxuuxysmkkkkkkllmlijlllmopppqssuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zvpja`enw}|smgb`acehnv{
}xsqpnolhgiijmlkgda_bdhlqux}}{~zvronrw
||}xrqtvvpljkjiilpqpmlqvz{vsuvywvrpnmprrrrrsrtvy|~~{wrnieeehknrxzyvponmouxzwxz{{yyyyxrklnprtuvvwwutvy{}{wuwxxyywuwzyrmkjjjkkkljjijlmlmnopprsuvvvwwxxy{{{}}||{{||||||||zzyyyzyyxxxxzzzzyyyyzzz{{{||{{zz|zz{zwrldafnw|zslf_^acejpw}
{vqqpnomihiiijihec`_cejorv|{{}|~
|ytpnry
~|}|wqqvwwpljkjhikopnjkpuz{wsxyzxvrpnooqrrrqsstvz|~{vqnifefilpsxzytqonoquy{wy{|{yyzzxrjlmprtvvvwussuz|}zwuwxxyxwux{yqmkjiikkklihjklmmooppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{zsngdekuzysje`__ciou|
}urpponmkhghhiiebdccbikmpsx}yx|wposy
}xrqwzyqkjjkjkmnnlikqu{{xvx{{xwspopprrrqrttuwy|~~{ywqlhddfgjnrvwuqqqopruzzxwy{zyyyzysmmpqsuuvuvvtsvy{}zwwxyxzyxuvzzsmjiiijjjjhijklmnopppqrtvvvvwxyyz||||||||||||||||{zyyywwxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zuoieejqx}}}|wqhc```fltx~
~ytqppnnnmhhhhiieacccdjloqtw{wty{~ysrsz
|vtw{xqkjjjhjmmkjilrvyyywx{|yvsoopqrrrprttuwy|~~|zvqkgdefhjnsvtspppppruz{xwy{zyyy{yrnmqstuvvvwvtsvz|}zwwyyyzzxvw{{qnkjiijjjjijjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zwqlfdgnvz{{zwqhc``bhowz~
~{vtqppnnppkhgghhfbcbbdjmqtvvzvqtuuz|xvw|
}xvw{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosw||yyy{zyyz|xrmnrtuvvvwwvttx{}}zwwyyz{{yvy|zrmkhiijjikjkjklmnopppqstvuttuvwwz|||||}}||||||||{{zyxxxxxyyyyyzzzz{{yyzzz{{{{|{{{{{{{{{zxsngbdjqwyyyvojd_bfksz|
~|yttqppoopqmiffggfbcbabiorsvuvsoprptz~{x}
}{ww{yqkjhiikjjjijmswxyywx{|ywrnnpqttrprttuwy|~}|{xrpjfeeghlprtrpppooosx}|zyz{zyy{|xqmnstuvvvxxvtvy{}~zwwyyz{{zwy|zrmliiijjhkjkjkmnnoppqrtuvuustuvx{{{}{|}~}}||||{{{zzyxxxxyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{xspidbfmruvurniebchnu{~
~{xvsqqppnnpqogeefgfcccadhmpswwqjjlmprv|||
~{xy{wqlkijkkljljkntxzxxwyz{xvtooqrssrqrttuxz}~}|zxsnkgggjllopppppppootz~}{zz{{zz{{xplotuuvwwwwvuvy|~zwxxy{||zv{|xqljjjjggikikijlmnoppqstutssqqsuxzzz||}~~~}}||{{{zzyyyzzyyyyxxwwxyzzzzzzz{{{{{{{{{{{{{{{zwqkd`djosrpmifccdkpx|
}zwtsqqqppppqrogeefgfdccbafkosvvtmjkmpsw{}
|{}wqmkklkkkkmkkouxzxxwyz{xvsppqrssrrrttuvy}~~|zxrnjhghkmnpoopppponou{~{zz{{zz{{xqlpuvuvwxxxuuvy}~zwxxy|||yw||xqlkjkigfhklnijjknopprstutspmlnqtwwwxz|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|ytle`cglnmliebbbflr{}}yvtssqppppqrsspiffghgeeea_agmsuwvpmkmprv|
}|{~~wqmkklkkkkmnopux{yyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|~{zzzzzz||wolqvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgijnphijknopprstusrmjhjmpssuvx{||}}~~||{{{zyyzzyyyyyyyyxxvxzzzzzzz{{{{{{{{{{{{{{{|zvnha`cgiihfbbbbhnt}}~}zxussqnooppqqtupjgghiheffb^]emsuwvplknqtw}
}|xy
wqmkjkmmmmnpqrtx{zzxyzyvsrppqruurrrttuvy}~~{vrlifghkmonmmoooonmqv|~{zzyyzz||vomsutuvwxxwtsvy~}ywxy{||{uv}|wpljkihfgiloqijijloqqrstusqmhgfhkooprswwz||{{||{{zzzzyyxxyyyyyyxxwxxxzzzz{{{{{{{{zzy{|||||{wqha_bcdeedaabelpw~~~}{xxutqrpnnooprtuuqkhhiiihiic^\bluwxwqmmpux|
~{y|xrmkikmmlprvzzwxzzxxz{yvsqpppsutqqrttuvy~~~zuqmjghillkikkmnpomnqv|}|zzzzz{}{tontvstwwxwvqsw{~|xwyy{||ytu}}xqmjjjggghkpujjjjloqrrsturpkgfedgkkkmnqsvz{||||{{zzzzyyxxyyyyyyxxyyyyzzzz{{{{{{{{zzy{||||}}wqhb_baaaabbabfmsy~~~}}{{{{{wvuuuuurqqonnoommnoqtwwvrkggijjjlkg`W[fquvwrmnsx}
yqmkjlmmlpt|yxyzxxz{yvtrqqqsutqqrttuwz}zuplihhjjjhghiklppmpsx{~}|{{zz{|~{tontvsuwxxwuqsw||yxyy{|{ytu|}yqmjjjhgghlsxjjkkloqrrsturpkfdcdeggghkmnqwyz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrjdba__``aaabfltz~}{zy{~~}zxvussrrpooooonjkkihhijkkmorvxywrkgghikjonj`UXeouvwrnot{
~
xpmkmoqposzyyyyxxz{zwurrrrstsqqrttuwz}zuolighjigecehjlmnoquxz}}|||{{|}}zsnnuwuvxxxwtqsw||yyyy{{{xrsz}yqmlkkigghltyjjljloqrrstutrkfdcfefffgijlosvyz~~{{zzzzyyyyyyyywwxxzzzzzzzz{{{{{{{{zzy{||||||yskdca__```aabfksy~~yuqrrz|zwtrponnmmkjiiiihfggdccdefhjosxyywrkffghjkoqmbVVdpvwvrnpt{
z}
xpmkoqttsw}}{wzyxwxz|zxsrsssrtrqqrttux{~zuokhfhiifbadhjllmqswyz}}||||||}|zsmovxuvxwxwsqsw||yyzy{zzunrz}yqmlkligghkuyhjjkmprrrsuwvtokgdgfffddfghimqwy{|{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z|||||{zyrmifb_\_^^_abglu{~ztmihjry~|zwqoomlihhggfeddedcddec```aacglswyywrlgfhjhjqtpeYXdouwtqonrw~|z
}vpmlnswyz{~|yvvwvwxx{|zwsrrrssrqppprruy|~}xsmifgiigd``cgjmnnqvxz{}}|}}||}~}zsoquvuwxxxwtqrv}{xzzz{{yrmqz}womllkjgfglv|hjjlnpqqrstwvuqmifhgggdddddehlruxyzyyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjhd`]^\\\_`ejtz{vld``dlvxwsojhgffcaaaa`_^^_^^_`aa^_____ciosuvuqlhfghginrsk][eouvurpnorx}|~
zvpmlnsz~}wqmloruww{{yurqqqrrrqppprrvy{~|wrmiffhhec^^bekmoortxz|}}|}}||}~}{uqquvuwxxywtqrv}{xyy{{|yrmqz|vomlmlkighmv}hjjlmoppqrtvvurnkiihggfdddddehmqswxxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zvrnjfa_^][[]^chpx~~}xod]\]bhpspmieba`^^^^]]\[XXYXZ[\^^^]]___bgknoqrpkjhffefhnsob_hquwurpnnpsy~
}}
zvpmlot||tohfinuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnquuxz~}|}}||}~~{xtrvwuwxxyvspqv}{xyy{{{xqlqz{uomlmmkjiglv}hjjllopppqsvvusomjjiffgdeedbefjmqtvwyyxxxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{y{||||}{zxtplhda`^[\]_aemv~{vk`[[\aglnlgdb^^\ZY[[YYYXTTVTVVYZ[\\\]]abfgjlmnpkigfeccdiqogdlrwwurponnqx~~~~z}
zvomlpt}|umfeinvvwz{zuqrrrssqonnprswx{~~zupkgffhhfd]]cflnostuwz}|}}||}~~|xsswyuwxxyvsopv}{xxx{{zwqlqz{uomlmkjihhlu|ikjlmoppqqstuvuqooolkjihgfdbdegimqsuwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}|{yvromifdb^\]\^ciow|~yqf_YY[_cfigc_][YXYXWWVVUSSSRRUVWXYVXWY[_adggghjnkigec_`dhnqnkqvvvtqpooorx|
{}~|~ztmljov~}slghiluuyz{xsqpprsqponnnqsux{}zuoighikkgc``bfknqqtvwy~}|~|{|~~ytsvwvwyyztolnu|{xyy{{yvokpyxronmnkifehlu{ikjlnoppqqsttutrrrrppnmlihgedddgjnqtuvvvvvvvvvvvvvxxyyz{{y{{{{zzzzzzzzzzz{}}}}{zwtqpnkgc_\]\\_bipvzyvlb]YY\^bcda_[YWVVXWXXWWWVWWUWYZ[\\WWVXZ\_behhfhjiheb`aadhlrtqswwwurpooopv{~|y||}|wploosy
siehkmqswz{wtqpprsrommnnqsvx{~~|xsoigghjjhdbachmpqqtvwy~}|~|{|~{utvvvxyyztnlnu|{xyz{{xtkipxwronnnlkhgjnuzikklnoppqqrssttsttuttrqpmljgedcdfjoqstvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{xwusplheb_^]\\^ciorrof^[ZZ]^```][ZXWXZ[\^^__]\[[[]_`ab`][ZYZZ\`cfhhjhjgb`bdffeiqvsvxwwurponnory}~||y{|yy|z{~}}~tkefjnmpsy|xurrrstsokknortwx{~~{vqligghjjieddhlpqrruvxz~}|~|{|~{wuvwwyyzxsmlnu|{xyz{{xrjipwvqonnnnonklptyiklnnoppqqqsstuuvvwwwutsppljgedceiloqrttuuuuuuuuvvwwxxxzzz{{{{zzzzzzzzzz{}||}}|{zwwurojgc`^][\\_djnnib\[\\]`_^_][[\Z[^^accddcbaa_`cdded`^][[Y\]abehjjieaaeggfacpvsxxxxvrponmprx|}zz
||{vx
wmeejonpsy}yusrrstspkjmortwx{~~ztplighikkidfgkotsssuwy{~}|~|{|~|yvvxxyzzwrllnu|{xyz{{yqiipwuqonnnnppllpsyhjkmmnnoqqqrrtuuuuwwxxvvtqojgecabdgilqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}~~}|zwxwurkifca_`^\\_ejie_^\]\\]]\\]^_``bcejjiiihfdcacffhihdb`]\\\Z\_aehjhd`bglmh]]muuyyxwvtpnmnosy|~|zz|x|
{rgcfklpt{}ytrsrsvsplkmoqtvyyzytrplggiklljgikntvvutuwx{~}}~|{|~~{yxxxyzyxsmlmt|zyzzzzxogipttonmnnoqqnlosvjklnmnnoqqqsrtttuuvvwxxxwurmhecacddefnprssstttttuvxxxxwyzz{{zz{{zzzzzz{{|||}~|{xyxvsnlligdc^][]`baa^`^^\\\[\^`defgjkkpponnkifddehhjlmkhfca`_]__`begfb`ekoqk[Xiuxxxwusqqonnosy}{{}}|{
}y~
xledflqt{|xtrsrsuromkloqtvxxxvrpokhhjkmmljlpswwwtrtvx|~}}~|{}~~|zyxxyzyxsmmnt|zyzzzzxngiqtvqonopqsusnosuikkmmnnoqqrtsuuuttwwxyxxxxupkhdbbb``binqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnmkhe_\[]]]\^___^^^^]]cfklnooqrttsqpligfefggilommlhgdcaa__`abbcdjnooh[Vetwxxwutrqqommrz}yw||}
}~{w{
~tmiiosuz{wsrsrsspmlklprsuvvutqnmkkkklmmmmnsvzzxtrtvy~~}}~||~~}{yxxyzzxsmmnt|~zy{{zzwngirutqrpprsuxvrprthjikmnnpqqstsuttuuvvwxyyxywqojgdb`^\_ekpqqrsttttuvwwwwwyzz||||{{zzzzzz{{zz|}~|ywvssomnnljda][]][Z[^_`_^^]``hlpqsuuvwxwvtqmjigfhggimooopmmihec_^__^adinnllg]Wdrvwwxwtrsrnjip{}xu|~z|
~
uos|
zvporsxz{vsrsrsqnlllnprruvurqpmmkjjklmmooqwx{}xusuwy~~}}~|}~~}|zxxyz{ysmlmt|~zy{{zzvpgjrwtrqpoqrtxxuqqtikkmlnopqrttuvttttwwwwxxxxwvrnifb`^\_dkopprrrrssstuuuvwxxy{||zz{zzzzzyyxxwx|}}{yvtssonnnmjb^\[]^\[Z\]^_^_`celruw{{|||zzyurnlihgghhjosttuspmligecec`cgmqnklmf`gpsuxwurnpohffoz{wt{~~{}
vqu}|yuruz{wsqrssrommopvvtrprpnlkkhigjlnoqru{~}|wuuvxz~|}}}~~}yzyz{yyrmlmr{zy{{zzvqgjruvtrqppsuyzxsstjllnmoopqrttttttttwwwwxxxxyyvrkhda^\^binppqqrrsssstttvwwxy{{{zz{zzzz{{vrrquz|{xvtrqromlkjhb^]\^\\[[[\]`_`bfkry|~}zxtqnkihhhhjlrvwyyxuqoljfdeecgmrojiktvsspqtwvrkhklhhjt{|wt{~~}~
~usyzvwz{wrrrtrpnoqw}xpoqpnmkjhigjlopqsw|~}zvssvy{|~}}~}yyz{|yyrmlmr|zy{{zzvpgjsvussrqpsu{|yuttklmooppqqrttttttttwwwwxxxxxxwsplie`]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzxqkjlqvz}}zutqqpqqmhedca_]\][[\]\]\`aadgnu{|yuromjihiiilntx{|{{yuqmifdcfglrrkghmwz}ztprusomnllkmqx~}z{~
|vx|}{}
yy{zvspoooonszxooqpnljihiikmpprux{|{xsorvy|}}}~}yyz{|{ysnkls}zy{{{{umejswtrsrqpsu}~zvuulmmoqopqqrttttttttwwwwxxxxxxxsrolga][`fnpprrqqrrrrrrsuvvwxyzzxz{zzzzyrkffhmrx{yvrpopopqmd`__`^\[[\[\^__^`aadgmsz~}ytqmmjihiijnovy|}}~|wqniebbgjpuqjggqy{}xpqqnorsqnnsw}~~
|wz{vx{z{zwsnkjlnqxuooqpnkjhiijlnpqsvy{{zwqorvy|~}}~}yyy||}ytojks}|{{{||uldirutrsrqpsu|}{wuumomopoqrrsuuuuuuuuwwwwxxxxxxwusroib[Y^fnpprrqqppqqssrtuvvxz{zyzzzzzyuneacejoswvtrpppnmlfa\Y[ZXYWYZY]acccbbdeglrx{~{vqmkkkjhhiimqx|}yunhdbglpssligjz~|umigmtrropu|
~{xsy{}ywtpkilnwzqnopqokjhghilnppsu{~}{uqprvy|}}}}~zy{}||xroklt~~{{{{{{ticjrusrrroorw}}|yvumonpqqssttuuuuuuvvwwwwxxxxxxwvtsqkc[Y^fnppqqpppprrrrrsuuuwxyyyzz{{zyrjc__aejmqsroopnmkid]XUWWVUTVWY^fhhhfdfeeglqtx}}xtpnmkkjihjjnsz~~|wsngdelqrrofbgp|lhjrxurosx
~pow}~yutqnlnt}
wpomopqpkjhghjlnpqsvz}}ztppsvy|~~}}~~zz|}||xrolmt~~{{{{{{shdjrtsrrrpoqx||{yvunppqssttttuuuuuuvvwwwwxxxxxxwvustmd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_aejnomllmlljhaZUTVVVUTTW\cjnookigdbdfjmqwz}}zvrolkkkkjjkmqu{~~~|zwqmgfkqtsrrjfnw~}plqy|zusx~
~
zonv~~ysrqqqt{
~vpmnnopqpmljhiklnpqsvzzzxspptwz~~~~}{{}~||xrolnt~~{{{{{yqgdjqssrrrqqqx{{zxwvnpqsttuuuuuuuuuuvvwwwwxxxxxxwvutsnd\X]dmppppppqqpprrqrtttvvwxxzzzzzvngd`^^`bfjljikkjkif`YUTUWXWUVZ]entuuqmhdaaceimpuzzxtpomkkkkjlnnsw|~~|xvpkijouvuuuqnr{}rrx}yy~
|{ypov}~~yqqpqtzvommmmopqpmlkjjklnpqsv{{xvsoptw{~~}||~~}|xronot~~{{{{{xogdjqssrrrqqqx{{zwxvpprrttstttvvvvttuuuuwwxxxvwwutttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^__`befhghghhfe`\UQPSVWZXY_cjqwyzvmhe_^^afjknrsurpnmmkkkkmppsy|~}zusonlrw}}|yyzuw
|ru|
{yyrpv~~x|}{rppqz
ynnmnnppqrqommjjlmnoqsvz{wtqprtx|~}|{{}}{vsonot}|yz{{yunebjsurrssrrqvzyyxvvqprrttttuuwwvvuuuvvvwwxxxvvvusttqme]WYbkpponppppppqqqqrrstuvwwxxyxwsmgc^``bceeeeedccba]YRONQTVXY^ciqv|ypie^[[]`fijmoononmmllklmpsvz}~|wsomopw|rv
}|ytrx
zwsy~ztqoq|~rmmmmopqsssqnnkjlmnoqsvzzvsqqrtx|~~|{{}}zvsonot}|yz{{yulcbjttrrrrrrrvyxyyvvprttuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtnhc^aabdddcca```^][WONMPRTW\bipw{|sjd`\]\^bfghjklnmmmlllnpsvx}}}|zxuqnmpt|
}uz
}vtsz
yqoqy}zuolq~wonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|~~|}}}}zvsonot}~|{{{{yukbclssrrqqrrtxyxyyvvpsuuuuuuvvwwvvvvvvvvwwxxxvuuusrrpkc[UYaimoopppooooqqqqpprstuwwxx{zyvohca`aabccbb_]^^]\YUNJINQRW^enw}~ulda_`_^`cdeghinmmmllnoruwy||zywutommpv
yw~
~
~zvuu{wnlqy}ysjjs|rnmmmlmnrtutsqmkjlmnpsuxzxtpqqrtx|~~|}}}}zvsonot}~{{{{{yulbensrrrqqrrvzyyyyvvpsvvuvuuwwxxwwwwvwvwxwwwxwvvvuttqmg^WX^ekmmnnnoonnooppppqrtuuwxxxxxuqmhfcbaabdb_^\^]ZYWUPLIINT\blu}vleccecb^^`cdfhllmnnnopsvxz{{xwtrqpnnpysw
|xwuu{wnlry}yohm|}unmmnmnnoruutrpolklnoqrtw{yurppsux}~}~{}}||yvsomnu}}{z{}|ztjcfourqqopprvy{zyxxwrtvvvwxxxxxxxxxxwwwxywxxxxwwvvuuroia\Z]agjnonnoonnoopppppqsttwxxxxxvrnjhhgca`bbaa`^]YWUTPMKIMW_ir{
wmhfhjkid`]_cefhklnpqsuuuuvvvttqommnnt}}sz
}yxww||njrxxpigt
ztnmmnnoopsvxvspomllopqrvx{yvsqqsux}~~{||||yvtomnu}}{{||zysjehpusqqoppruy{zyxvvuwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspkd^Z]_dilnoooonnoopppppqrrtvxxxxywspmkjifc`bcdb`_[XVUSQONMP[eoy~
wmhfjoqniea_adddiknsttuutssssqqppnmmpxy
}|{x}~phmnjbbgyyspnmnnonptwyxtqnmnnopqrux{yvsqqsux}~{||||yvupmnu}}|{||zyrjfktusqqoppruxyzyxvwvvvvwwyzzzxxxxvvwwyzzyyyxxwwvvuuspje][]_chjlnnoonnooppppppqrsvxxxxzwupomkjhdcddba_]YVTTRQPQQU`kt
}ulfgkpsrnje`_bdfgjnrusttsrpppoononmmqz
{}
skhb\Y]ky{ytonnnmnnptwyxurnnnnopqrvy|yvrqqsux}~{{{||yvvrmnu}~}}}}{xrjgmvusqqopprtwxzzxwxwwwwwwyyzzywwwwwyyzzzzyyyxxxwvvvsplg`]]^`bfilnoommnnpppppprstvwyxyxxvsqnnmkigghc`^\VTTSSSUVX\grz~wnffjrvtqmkc^^`cgimqssrrpononlonoonlq{~yre\X[er{~zyvroqqnmlmquxywtqnnnnopprx|}{wvtttvy~}{{{||ywtrmpv}~}|~}|xskhqxvqppppqqsxxyzyyywwxxxxyyzzywwwxyzzzzzzyyyxxxwvvvtrnic_^^_bbejmmnmmnnoopppprstvwxwxyywusqpomkiihc^]\VSTUUX[]`fpzyqgeiqvxuqoje`acghloponmjkklklnnopoos{
}xlfenu{{zuwuqruuqmlmrvyywtqnnnnopqrw{}{xvssuwy~}{||}|ywurmrx}~}|}}}xtlirxwqppppqqrvwyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutqjfbaaaa`bgklnmmnnnnooooqrsuvwwxzzxvvtrpnmihdb_][XVXYZ]`cgnv~ysjfhov{ytrnjefghhhkmjiighjmlllnpqpqw{}~
}|qms|~ywtwussuvsmlmrwz{ytqnnnnpqrtw{|zxwttvxz}{}}}|zwurosy}~}|{}}ytljuyvpppppqqqsuyzzzzwwxxyyyyzzywwwyzzzzzzzyyyxxxwvwwvusliedc`_^_dgklmmnnnnnnnnpqstvwyyzzywusqpnlgeba]\\XYZ^_bcinuz~ztlfhnt{|wtqlklljkiijhghhglonmlnpstvy}}
{ztnxvsuxyurstrmlmrwz{ytqnnnnprtuvzyxxwwwvx{~}{}}}|zwurptz|~}|z~}zsmmxzupppppqqpqtyzzzzxxxxyyyyzzyxxxyyxxxxzyyyyyyxxwwuwvtplhhd`___bdgjmnnnnnnnnnqqrtuwxz{{zxxtqnmifc_][Y\]^`adgkqwz||tnffkrx|zwplorojljiiijjnnponjipsuxy{
~{{vq|vtv}|wrpsqmlnsx{{wtqnmmoqqstvxxwvvuwuw||{|}~|zvurpu{}~}||~}{ror{|tpppoopppqvyzzzzxxyyyy{{zzyzyyyyxxxxzyzzz{zyywwuxxwsqnjfba__`aeimnnnnnoooopprtuwxz{|zxtqljgcb`\[YZ\_begknvz~~~|ungehms{zxrptvtnkjhhjihlmoqpkhlrwy{~
}
~}{~yu{}usw{qorqmlnrvyzwtqnmmnpppprsqqppsuvx|{z|~~|yvvsqv|~~}|~~~{sou}|tpppooppprvzzz{{yyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxxwspmifc`_]_cglmnnoopppppprtuwwyzzurokhfb^\\YZZ]`bfjlqsz~~}woheejpxvxxy}~|uqpnkjihgginnjhhpw{}
~xw~~~}}|xy}}zvtz
tprqmnnquxyvtqnmlmnnllopnlmmoquw{{z~~~|ywvtsx}~}}{tow}{soppqqrrqsvzzz||yyzz{{||||{{yyyyzzzzzy{{{|{zywwuxxxxvspmifb^\]`flmnnpppppppprtuwvxywqnjfda^ZZZXZ]_bdknquy}}xpidejouux}|xwurmkhdcdhjiiimu{} }tqw|
~~~~yvyzyzu|vrpqnnoqvxyutqnmlmlljjlmmkllnotvz}|~}ywwuuz~}~{usy|snppqqrrqsvzzz||zzyyzz{{{zzzyyyzzyx{{z{{zz{{yxwwwwxxwvuqmiea_^_chkmonoppoprstuvvttsplhea]ZXXZ\]^acfjosvz}xrjegjlrsy{wpjbcc__cjknptx}}
unmqx
~}ywuz~zxsqqoopqvzytqommmjkkjmlmomnnortuy|{}~}zyxvv{~~~~|wu}zropppqsssty{{{{{zzyy{{{{{yzz{{yzzzy{{{{{zzzzyyyyxxxxxxvrpliea`_aeimnnoooopqrrtvvrplieb^\XVVX\^aceimruy{}ytmfejkms|
|tkiga^bhknkmlqy}}wyytmls~}|}~{xvz~xtsrqqqrvzytpmkkkjkllnnmnmnnnqqrvxxz}~zzyww|~~{vw~{sqppprttwy||{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzyyzyzzyyyywtspmic`^_cglmnoooopqrstttmjeb_[YWWVXZ]bfiknsyz|~{uohekmqtz
yvphdfijihiiksz|uu~wty}|||{~~
|z|~}||yussrrrtxzyurlkjjjkllnmllllmmnmosuwx{{{zyy~~~yux|tqpppruuy}}zz{{zz{{||{{{yzz{{yz{zy{{{{{||{{{{{yyyzzyywvvrplf`]^aelmmoppoprtttrnieb_\VWWXX[]`ekmqtx|}}uqkgkswwy{znjhllefikjqy{rt
}zx}}}||{~
}}|zyzywstrstwx{{upkjjjjkknnmmlkjkjjjlprtwx{{{|y~~xtxzrpppqsuv{~|yy{{yy{{{{{{zz{{{{yz|{z{zzzz||{{|{{{zzzzzzzzwuspke`__djlmoppoqssutpke_[YWVVXZ\^`flpswz}|xrnor{~{y}ohnlb^cikqyxqn|
zxz}~~~
~~}zyyyzzvtsstw|}ysolmmkkjknoqponlkjhijjkpsx}~z{{{z~xsw{qppqrstw||yz{{zz{{{{{{zzzzzz{{||||{{{{||{{{{||zzzz{{{{zxvsmgb_]aegijklorsssqmhaZXXXXX[_behlruy}}zsqx}
~pppbY\emrz{slw}xx{{|~
||yxzz{yyywtttuy}}wrnmmlkkloqsuwutrponmmijlosx~
|z{|{|~wrxzqpqrttux|}y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxupjc`^^adfghimpqqpmic][Z[]\\aeimpsxz}{vv}
vssh^\`js|}umu
yy{{z}
|yyyx{}|zwxvvuuux|{tpoljjkknqtvwyxxxusrrrnjjlou{~
|{|~}~}tpv~ysrrstuux}~|{|||{{{{{{{{zz{{{{||}}||}}}}||{{||||||||{{{{|{xvrmfc_^^acdefkmoolid`\\\]_abginstx{|~zyy
yuund_`fr{}vns~yz{{{}}xxyzz{}|zxxwwvvvxyxqpoljkmmnqtvyz|{ywwusspljjlqw}
~||~|snt|zutrtuuvy~{{|}}{{{{||||{{{{zz{{||||{}}}||||||||{{||||zz|{zytnjfc_^^_`aaehjifd_]Z[\_aeglptxy}~~z|
{vvxohdft~xpt
}{yzyyz}zyy{z{|~}{xyxwvsttutqoklknqnoquy|}~}|zxxxywrlhhlr{}{|~
zqnt}~yvustvvvy~~|{||}{{{{{{||{{{{zz{{||||{}}}||}}||||{{||||{{{{|{wrmieb`]]]^^adecb`^][]_bhmorvz}~~z}yx~~xomuxps{xzywvy{zyyzz{}~}{yyyxvtqprqonmopssppty|~|}}{~|toiglw~
}|}
zplt}|xusttvvvz~~|{||~{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ysokgd`]]\\\^ab`__^^^adhnswz{}}}{{{tyxoo
}y{yusvy~}zy}|zy{z{|~|zyxxxusonpponqrtttrsw{~~~|ulghq{
}~zokr||wusuuvvvz~~|{|}{{}}||||{{{{zz{{||||{}}}~~}}||||{{||||||}}|zztrniea^_\\\\_a___^`behmswz~|~
{}|zxll{
~z|ztsrw|~|xuxzyyzy{}~{yywuurpnnpooprtttttuy}{skgnw
zpkr{|wvtvvvvvz~~|{|~{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzywvrmid`\\\[\\^`^^_aejqux{}}~
|}~~whgt
~yzzvtsvy|}zww
~wz{yz{||yzxwtqnllnopqruuuuvwz}zpiiq}
zqmv}|xuuvvwvwy~~|}~~~{{||||||{{{{{{|||||||{}}~~}}||||||}}}}||||{{zxxtpmifb`]\\\^aaacflqvx{~}
{|}~
ylhr
|}|ywvvy|}zxv}}xxyyy{}{xtsrqnkijmprqruuuxxy{~
vlelx
zrow~}xvvwwxvwy~}{|{|{{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}||||||{zyvuqoljfa`]]_adeilqwy{~
|{||}sls
|yyyxy|~{zz
|xwwxz}~zuqqonkjiloqrqsvwwyyz{
zofis
{ttz~}xwwwwywwy~|yxvus{{||zzzz{{{{{{||||}}}}}}||}}||||||}}}}||||||}|ywwtqolidc__`cfgkpuz|}~~
|{zz~
tnt}zyzzz|}
{wvxy}}xtpqnkkjlnqsrstwxxxyz{~
~sgen{
|uv~}ywwxxyxwy~|wtrpm{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||zz{zwuqnkgcbdfilpu|}
~|{{}
}tot
{{zyy{{uuwx|{vsqpmlmnpstttvwy{{||||~
xjciw
~yz~zwvuxxvwz{tljji{{{{zzzzyyz{{{{{||}}}}~~}|}}}}}}||}}{{}}||||||{{||zxvtrnkjjlorwy~~}zz}
}rmr
}yxxy|
zvuvwvuqqpnoqruvvvvxyz}}}}}}~
oefp~
}~
zxwvyxww{|ulghii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{ywywurppruwz|~~~
~zz~}pko}
~wtsv{
}xxssqqrqprtuwwwzyzz{}}}}}}~
tgeky
}zxwzzyxz|ulgikii{{{{zzzzyyz{||}}}}}}}}~~}|}}}}}}||}}{{{{||||||{{|||{{{zxuttvxz|~~~~
yx
}pko}
upoqy
zqppstrsvvwxw{||z{|}}}}}}~
ykdhv
|{yx{{zyxxnhflkll||{{zzyyyyzz||}}||~~~~~~}}}}||||||||zz{{zz||{|||}}||z|}}{yy{|~~
zz
|olq~
uljlv}{}
~rpqrtttwxxzy}~}||}~~~~
}oeer
}{{|||zxwqkkkkkln{{{{{{zzzzzz{{{{{{~~~~}}}}||||||{{yyxyzz{{{{}}~~||{}~~~|}~
|pnq|
zohflu~}yusvwuuvwxxzzz{{}~}{|}~~shem{
}||}}|zwqjjopnoprzzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyxxzz{{zz}}~~}}|~~~
|rpry
sgbejqyzyyuu|
|ywxyz{||{{{}~}|}~~~~~ylehs
~|}}~~}zumimqrpqqtzzyyzzyyz{zzzz{{||~~~~}}}}||||||zzyyzyzzzzyz}}}}~~}}~~~
|tru{
vgbeehq}{{|zw|{wz{}~}}||}}}}~~~~~
}ngen{
~}~~ypijpstqrtvyyyyyyyyzzzzyyz{|}~~~~}}}}}}||}}{{{{zzzzzzyz||}}}~~~~~~~~
|vx{~
xhflignw~zyyxx{
|zy}|}|{{||}~}||~ogdju
~ulkmprttvwzyyyyyyyyzzzzzzz{|}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~~~}}~~~~~tw~wjjqoklqz~yvwxxxxz||~|{}}|}||||}~}|}tgcgq
xokmoruxy{|~xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~}}}|}}~~tqxxoptsnmqz}{xuuwwuttstw}}||}|}||||}~}|}~xicen{
|qkloruxz{~xxxxyyyyzzzz{{z{|}}}~~~~}}}}}}||{{{{zzzzzzzzyz||}}}~}}||}}~~}umr{
¡
ztvvtonr{|{{wuututrposz~}|}||}}||}~}|~~~
{mddky
~wlkmptwy{}xxxxxxyyyyzz{{{|}}~~}~~~}}||||||{{zz{yzzzzz{{{|||}~}{{|{{{||~}yqms} ¡
y{zvrps{|xwsoqvuspmqx}|}}|}}|{}}}}
~ofcjx
xnikoruy|}xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{{{{yzzzz{||||||}~||{{{{{||~
~{utx ~zxvvy|zwrlnuwupmqv}~{{|||||{}}}}~~
sieix
}qjkmqtw{}xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~~~|||{z{{||~
ztt¡¢
zzz|~|{xsmknsvurpqt{zxyz|||{}}}}}}}~
xmhjx
xmjmorvz}~xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{zz{yzzzz{|}}|||}~~~||{zz{{||~
|vt} ||}|{{yohjnrtvutqsx
|xwy{{|{}}||~}}|}~
|pllw
tjknptx{~wwwwwwxxyyzzzz{|~~~~~~~}}}}}||}{zyzzzz{{zzzz{{||}~~~}{{{{{{|}}~~
|yx~
~|qecnqsvxxvuv}ywyz{|z{{~|{||womv
yokmoquywwwwwwxxyyzzzz{|}}~~~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{{{{|}}~~
|{}
zqf[inquwxusuz|xz{}|y{{~~~}}zz{|~
{rou
~skmprtx}wwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{zz{|}}~~
}~~yqcQ[jotwvusqw
~}{z}{~~}}{{yxy{}upt
znkortx{wwwwwwxxyyzzzzy{||~~~}}}}}||}{zyzzzzyyzzzzz{||}~~}{{{yy{|}}~~~~~xo^HGflsvvusqu~
}z{{{{yzxxvvwz}vpt
tklqsvzxxwwxxyxyyzzzzz{||}}~}~~~~}}}}}}||{zzyywwwxyzzz{||~~}}{zzzzz{|}}
}|ymX:6Zhpuvwvst|{zz{zwvttutuw|
wpt~
|okortx}
xxxxxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{|zzxwwxyzzz{||~~}{zz{{z{|}}
}|xjM/.Sinrtxxtrz
|y{|xuutttuw}xqu
wlnqtx|
xxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~~}{zz{{z{|}}
|udD+.Uknorwyupvz}
}~|yxuutux~wrv
slptuzxxyyxxyxyyzzzz{{zz}}~}~~~~}}}}}}||{{{y{yyyxyzzz{{{~~~}}{zz{{z{|}}
~s`C,1Ymonpwyvqt~ytx}}
~{{xttux~urv
{qnruv{}xxzzyyxxxyyz{{}}||}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~~}}|{{{z{{|}~~
¡s`H8A_pqmosvurtz}ysvy{~~}{vtsvz
usw
tnpsvx~uxxyyxxxxyyzz{{||}}}}~~~~~~}}~~}}}}|zzzzyxxwwyyxy{{}~~}|}|{{{z{{|}~~
wjWIKbrupopttsuy}|xww{~~{vttvzusw
~qnsuw|
|qxxxxxxxxyyzzzz||}}}}~~~~~~}}~~}}}}|zzzyxxxyyyyxy{{|}}~~}}|{{{zz{|}~~
~vm[JNfvxtpotuutw}~zx}~}}ywuuv{utx
yppuvx
ymxxxxwwxxyzz{yy{{}}}}~~~~~~}}~~}}}}|zzzyxyyyyzzzz{{{|}~~~~}}|{{{zy{|}~~
~}{|}
zri[LPjx{wrqswusx}||~~}|xwvww{utxtorwwz
wmxxxxwwyyzzz{{{{|||}}}}}}~~~~~~}}|||{{zyyyyyyyyzz{{|}}~~~}|}||{zzz{|}}}~
~yvxx}|ulbWMYt~~zupquvsx~}}~~|ywvvy}vuz~pqtvw}
sixxxxxxzzzzz|||{|||}}}}~~~~~~~~}}||||{zyyxxyyyyzz{{|}}~~~}|}|}{{{{{|}}}~
zxwx| }ztkbXUbz|xonvxvx}~|yxwvz~vu|
wosuwz~qixxxxyyzzz{z|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~~}|}|}{{{{{||}}~{z
}yxxy {wrme_^j{}{wpjsywx}~{ywvvz~vu|
qpuvw}zojxxxxyyzz{{{|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~}|}|}{||{|}}}}~}wt~{xyz ~ywrmiedny|zxphoxwy}~~}{ywvvzwv}
|nrvwx
xmkxxxxyzzz{{{{||||||~~~~~~~~}}}|{|}}}}|{z{{{{zzz{{{{~~}}~~~~{{}}||{{||}}~~yqn|
~~ yuqppmmqw{vvuogjtxy|~~~{xxx|}vv~tnrvy}
~qjkxxxxyzzz{{{{||||||||~~~~~~~~~|{}~}}}||{||||zzz{{{{}}||}}~~||}}||||||}}~|sjiy
¡
zuttuspqtupqsphgovy}~~~yyyz~
}vv
}porvz
{mhkxxxxyzzz{{{{||||||{{~~~~~~~|}~~~~}||{|}}|zzz{{{{||||||~~~~~~~~}}||}}||}}~yoegy{
¡ ¢¡¡ }zyzywqmnnlnsslgkqx~~{yzz{
}wxyoqtv|
|uhgjxxxxyzzz{{{{||||||||~~~~~~}}}|}~~~~~||{|~~|zzz{{{{||}}||}}}}~~}}}}||}}||}}~|tjbhyxz}¡ ¡ ¢¢¡
}}}|ypiijjnssnhhnv
~{zzz{
|wz}upsuv}zzzyyuoggjyyxyyzzz{{{{||||}}}}~~~~~~}}~~~~~~||{{}}{{zz||}}}}}}}}|}}|}}~~}}}}||||||}}~|re_fx~}{y
¡¡¢¥¤
}xnbbgknrroifku}|{{}{xzwrstvx
~ytpqqqongefkyyxyy{{{||||||||}}}}}}~~~~}}~~~~}}||||||{{{{{{}}}}}}|}}|{}~~}}}}||||}}~~~~~~wma_gqust}
}z¢¥¡¡¡ ¡¡¢¤¤
|wnfbdhkmpqlghs}|{}{y||rquvwz~xuroniikiigeecfkzzyz{|||}}}}||||}}}}}}~~~~~~}}||}}||||{{||||||{||{z|}}}}}}||||}}~~~~~{shb`fiklozqu}¨¥¤¥ ¡¡¡ ¡ ¡£¢
~zuplgbchlornheo}~}}~
zx}wqsvuuwvspkjhhfghhhfeeceizzy{{|||{{}}||||}}}}~~~~~~}}}}}}||||{{{{{{{{{||{z{||}}}}||||~~~~~~~}yrgb_ceginz
mms}¦§¡ª©£¡¡¡£¢
~~{xssrnc^djospielx~|}}~
zx}vpuvuqrnkigffgfhihhiheddgzzyz{|}}||}}||||}}}}}}~~}}~~}}||||{{{{zz{{{{{|}}}}}}}}|}~~}~~~|{wnfaacehinzrjlx¢¥«®ª¥ £¡
~{vtwzwj_^ckpojfkuyz{~~
~yy~
~tsvtqnjfdcdeghjlmnmmkggefzzyz{|}}||}}||||}}}}}}~}}~~}}}}||||{{{{{{||||}}}}}}}}|~~~}~~~}{siedceegfl{vlmw~£¦©¥£¢~|yww|~sf__glnjfly|||~
~y{
yutsokhedceggkostvvqnjjiiizzyz{|}}}}}}||||}}}}}}~}}~~}}}}}}}}{{{{{{||||}}}}}}}}|~~~~}~~~|vmfegeeefgn|{rqv|¡«©£¡¡~|zvwzxlb^aimjfmy
~y{tssmjgddddgjmsw{|~~vpkmkmmzzyz{|}}~~}}||||}}}}}}~}}~~}}~~}}}}{{||{{}}||}}}}}}}}|~~~~}~~~zrgcfhfdeego}vtu} ««¤¤|zxvy}yqd^^gmkglw~y|~qspjfeehefintz}zrmmmpq{{z{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~~~~}{||||{{||||}}}}}}}}}}~~~~~~~xpebehddedgp{zttz¦¬¨¢¦¤
|yxx{|via]clmkmt||z{
wnmigdfgjilqw
zrmnptv{{{{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~}{||||{{||{{||}}}}}}}}~~~~~~~}ukebegddddhq~~vuz§¨¢¤¦¤¡
~||}~~{pg^_imnoqx|
|{{rmkhgggjnpw~
vnlqtz}{{{{{}}}~~}}}}~~~~|~~~~~~~~~~~~~~~~~}{||||{{{{{{||}}}}}}}}~~~~~~|yqgeeegffccirvtz¢£¤¤¢
~~~|woc^elqqorv|
|{{yokijjlkoty~rmmsz{{{{{}}}~~}}}}~~|~~~~~~~~~~~~~~~~}{||||{{zzzz{{}}}}}}}}~~~~~~~~{wndegggghccirvuz ££
|}~{ui`dkqqont{
|{z
umjgklmosy}qopw|||{||}}~}}}}~~~~}~}~~~}~~~~~~~}}}}|z||||||{{z{||||}}|}~~}~}}}}}ytidfgijjhbbjs~{x|¢¢¢
~}|}~yoaejoojgny~zz~oifhlppt{zqnrx~
{{{||}}~~~}}}}~~~~~~~}~~}}|z{{{{{{{{z{{{|||||}~~~~}}}}}xofcfgjkkhbbjs
~y}¡
}|{|}xnlmnnibix~
~{z~
xiegjotv|
xqnsy
zzz{|}}~~~}}}}~~~~~}}|z{{{{zzzz{||||||||}~~~~~~~~~}}}|{tidcfiklkgbbit
z~
}{z{}~xsqpqmeiv
}|{qfehksx|upps{
zzy{|}}~}}}}}}~~~~~~}}}}|z||zzzzyy{{{{zz{{|}~~~~~~~~~}}}{xodacfilnkgbbhu~
|~
|{z{||xtuusklu
||||mfgilv{|ropt|
{{{{}~}~~~~~~~~~~~}~~~}|}}{zzzyyyyzz{{{{||}}~~~~}}}|||}}}}{vjbadglpomibcku~
~|{y{{}}|{zzyspv~
|{|vhejmqyzqprx|||||}}~~~~~~~}}~~~~~~}~}}}}|zzz{{yy{{{{{{{{{|~~~~~~~~~|||}}}|wpgdaeimrpnibcjs}
~|zzz{|~{wyz{y||z|rghmovxpprz
||}}{|}~~~~~~~}}~~~~~~}~}}}}|zzz{{{{{{||{{{{z|~~~~}}}}~~~}}}}}zskedcejosqngbcir|
~{
¡
~|{z{{||x
ywx
|{||mfinqz
vpqs{}}}}{|~~~~~~~~||}}}}~~~}}}}|zzz||{{{{||{{||{~~~~~}}||~~~~~}}|zqhddddkpsrnfbcir|
{w}}
~
¡¦~|{y{{|~w{xvy
|{|uhdios~
~spsu}}}}}|}~~~~~~}}||}}||~~~~}}{{{{yzzzz{{||||{{{|~~~~~~}}|}}|zulededhnrsqlfadkw~
xow|~{|¦«¥
~||z{|}x{zz|
}|ndeknt
|rnqw~~~~}}~~~~~~}}||}}}}~~~~}|{{{{yzzzz{{||||{{{|}}}}~}}{}}}}{yqhcdffipttqlgdfmy
yjqy}{|¤ª¥
~}}|||}{~|}
}}ykfgknyxpory~~~~~~~~~~~~}}||||||}}~~~}|{{{{yzzzz{{||||{{{|}}}}}}}}|}~~}}}{vldceghlqttpkhgho{
{gjv}}|{|¥ª¬ª£
~}}}}}~
{
~~
sjhikq~uoquz~~~~~~~~~~}}}}||}}}}~~}}|{{{yzzzz{{||||{{{|||||||}}~~}}~~~~}zshcdffjnqutolhgjr}|feszxyz|~
|{|
©¬«¦¢
~~~}}}~
}z~
~mijknv}upqv{
~~~~~~~~||}||||||~~~}|}|{{{z{{{{zz{{{{{{||zz||}}~~~~~~}|vmdaehjmpuwsnjghks~jamwvxwx|
}~©«¦£~~}}~
zy|~
zkjjlqz|spsx
~~~~~~~~||}||||||~~~}}|||{z{{{{zz{{{{{{||{{||}}~~~}zqhbafimoswxrlighmvn`isuvwy{~
¢¦£¡~~~
vuy}
sjkkmtzrqty~~~~~~~~~~}|||||~~}}|||{z{{{{zz{{{{{{||}}||}}}~~~}xne`cgjmruxwqlhgjrz
p`dqvwwx|
}tsw||mkklowxqruz~~~~~~~~~~}|||||~~}|}}{z{{{{zz{{{{{{||||||}}}~~}~}vkb`dhkntwywqlhimu|
sb`nuvwx{~
{srw}vjjlmq{vrsv|~~~}|||{}}||}}|||}~~~~}|}}{{z||{zzzz{{{|}}}}}}}}|}~~}}~}{sibagjmrxy|wqlihnt{
ve`ltvwy}
~~
ztpu{
rjnmnv
}ursv|~~~}}}|{}}||}}|||}~~~~~~~~}|||{{z||{zz{{{{{|}}}}}}}}|}~~~~zxpf`chkmtyzztolhhnu{
}wh^mwz|}
~~{sklt|
{ojnnpxzsrtx~~~~}}}}}||||}}|||}~~~~}}~~}|||{{z|{zzz{{{{{|||}}}}}}|}~{tlc`eijpvyzytmihkow{
}xj_m}~
|tkdjv
wlknor{xqrvy~~~}}}}}{{||}}|||}~~~~}}~~}|{{{{z|{yzz{{{{{|{{}}}}}}|}~zricafjltyyzxslhglrt{
{mal~
}ukacr
tklnpu}wqsv{~~~~~~||{{{{||||||||}~~~~||~~|}||||{|zy{{{{{{||{{|||}}}}}~~~~zqhbdilov{{|ytmhgmux{
}p`j
ytla\j|
pjkosyvqsw}~~~~}}}|{{{{||||||||}~~~~~~~~~~~~~||}}|}}}}}||{{{{||{|||{{{{{|}}}}~~~~}wmdbfjmqx{||yuojjnrru
saf}
|vpjbVZr
{okkot{vrsw~~~~~||}|{{{{{{|||||||}~~~~~~~~~~~~~~}}|}}}}}||{{{{||||||{{{{{|}}}}~~~}wlbbgjnsx{}{yuqllnnkn~
vde{
|vpkbRQi|
vnmmpt}ursx~~~~~||}|{{{{zz|||||||}}}}}}}~~~~~~~~|}||||||||{{{|||||{{|||}}}}}~~~~vk`chiotz}}zyurnnojgk
yhfz
ztneQL`w~
snmnrw~~tqsx
~~|~~~~{{{{{{{|||||||}}}~~~~~~~~~~}~||}}||}}||||||{{{{||||||}}~~~|vkdfijou{}}|zvsppmdai
zjew
}umbMJYp~
plmosz|srtz~~|}}}}{{{{{{{|||||||}}}~~~~~~~~~~||}}}}}}||||||{{{{||||}}}}~~~{vlfhjlov|~{{zvsqpk`]jzkdu
~uk`[]ly
xnlopu{
zssu{~~~~}|}|{|{{{{{{|||||||}}}~~~~~~}}||}}}}}}||||||{{{{|||||}}}~~}{vmgiklrw}~{{ywtrph^^l{lbr
{yx|ypjkqy~
smmpqv}
xssv|~~}}~~~|{{{}{{{{{{|||||||}}}~~~~~~||||~~}}}}||||||{{{{||||}}}}}}~~}zvnijlmty}~||{zvtpe\_nxoco
~
|tprx~zxuux~
pmppsyxssv|
~~~~}|||{{{{{{{{||{{{{|}}}}}}}}}~~}}}}}}}}}}||||||{{||{{}}|}||}}~~|ztojmlov{~~~}|zwsme]_n
}upfi
vleis{~|||}z|
|nmopszuqsv|~~~~}||{z{{{{{{{||{{{{|}}}}}}}}}~~~~~~}}}}}}}}}}||||||{{||{{||{}||}}~~|ytplnnrx|~~}zxsi_Zap
yqmcd
}rf_enx~~zz|~{|~
wnmnpt{sqtw~~~~~}|{{z{{{{{{{||{{{{|}}}}}}}}}~~~~}}}}}}}}}}}}||||||{{||{{{{z|||}}~~{ytpmnory|~}zwqgZXet
zql`d|
|qc^^gq{}yx|~{|~
smlnpv||ssuy~~~~}|{{zz{{{{{{||{{{{|}}}}}}}}}}}}}||}}}}}}}}}}||||||{{||{{{{z{||||}}zxtpnnnqy|~}}zuneXXht
|toaex
|rc^Zbkw~}~{xw|~}}}
plmnpwyrsu{}}}}||||||{{{{zz{{{{{{zz{{}}||}}|}}}}~~~~~~~~}}~~}}}}}}}}}}||{{zz{{zzzyyz||z||||yqmmlnry~~ztl_Y\js~
~yskap
~
|qe^Y]fov|~zyyy{~}|~
{nmlmqxwsru}}}}}||||}}||{{zz{{{{{{{{|||||||||}}}}~~~~~~~~~~~~}}}}}}}}}}||{{{{{{{{zyyz||zz{{zwokkjlry~~xpeZY^ir{
{sjr
}z}
|qd]Y[clqy}{zywwy~~|z}
ulmlnqxusru~
}}}}{{||}}||{{zz{{{{{{|||||||||||}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}||||{{||zyyz{{yyz{yunkkjjox|~|ulbZ[`fny
}y|~zwz~
}rd][\aipwzzzyvvx||{z}
rmnlpt{tstw
}}}}{{||}}||{{zz{{{{{{||}}}}||{{|}}}}~}}~~~~~~~~~~~}}}}}}}}}}~~}}||{{||zyyzzzyyyzxtmjhhjmw|~~wohb\_aelv
|wwz~{{|
~sf_^]_houxxzyvwwz|}{}
{nmnmqu|~vtuy}{{{{{{y{{{{{{zzzzzz||{{}}}}}}zz}}}}}}}}~~~~~~~~~~~~}}}}}}}}}~~~~~}}||||}}{zy{zywvxywslggghmu{~zqjfcadcbhs
yvvz}yyy}
|uha`_^fnruy|zyxx{{|{~
ynmnnty~uuvy
}}{{{{{yz{{{{{{zzzzzz{{{{||||{{zz}}}}}}}}~~~~~~~~~~}}}}}}}}}}}~~~~~}}~~}}||{zy{zywvwxvqkgeghktz}umfddfdbbfq
yuw|{xxy|
}ypic``^cehns|||xwy{{z|~
vmooov||tuvzu}{{{{{y{{{{{{{zzzzzzzz{{{{||{{||}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}||z{{zzyvuvvuqjfcfgipxyqiddhjfb`cl}
zvw}zwx{}
}|vpjfeb`__`aiq{{uposvyz~
smopqw}{tuuz
yo}{{{{{zy{{{{{{zzzzzzzz{{{{{{||}}}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}{{z{{yzyvuvvtpiecdfgku}}vjecgmnib^`i}
{vw~zwy}
|xmdbceba````fq{}ypiehmsx|
pnpprx~zsutzum{{yyzzzzzzyyzzyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||}}}}}}||~}}}}}}||{zzzyyyyvvttrngbbcgehrzzricflqpof_^f|
|y{~|z{~
{mWKQ]eca`a`_enwysngb^^grw
}pnpptx~
yvvx~
{pk{{yyzzzzyyzzzyyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}||~}}}}}}||{zzzyyxxvvtsqlfabdighowxqiglrwvrkc^e}
|{}~|{{
xfMBL[eda`a``bhnqolf`\VY_jrw~zqnpquzvvvywnl{{{{zzyyyyyyzyyyyyyyyyzz{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}~~~~~}}~~}}||{zzzyyxxwwtspkebcfiihktwqjkpw{zupgag
~|~}|}~ueUR[bfdbaaddbdgllhe^ZUPU_aiuxqrqtx||vwwz}smk{{{{zzxxzzyyyyyxyyxxxxyy{{{{{{{{}}}}}}}}}}}}}}}}}}||{{{{}}~~~~~}}}}||{zzzyywwxxtsojebcgijiiquskkrz~}ztkfm
~}|~ugdikjhebbbffdadjkgb\XTQZXX^m}
tqtsvz|uxxzzpmj{{zzyyyyyyzzxxwwxxyyxxzz{|||||{{}}}}}}||}}}}}}}}}}|||||||~}}}}}}~~~~~|}|{yxxyyxxxwurohcacilkgelsroqu{~yplp
~~}}vopzytlhccejidacegfaYTPOVTPTfztsttx|~|wxx{vmmmzzyyyyyyyyyyxxwwxxyyyyzz{|||||||}}}}}}||~~~~}}~~}}|||||||~}}}}~~~~~~~|}|{yxxyyyyxwvsoic`ejnnjgkppopt|}vqt
|~|zw{}~zw{yphddhlkeabbdb]TPKKMRNM]q}
ussty}~{wxw||qmopzzyyxxyyyyyyxwyyyyyyyyzzz{||||}}}}}}}}~~~~~~}}~~}}|||||||~}}~~~~~~~~~|}|{yzyyyzzxwvtojebgmqqnikqqomqyysu
{ttz~}uqqtx|||}}~~xogcdhonicb``\UMIGHBLKLXgt
|tssty}yvyx}
ypmpryyxxwwyyyyxxwwyyzzyyzzzzy{||||}}}}}}}}~~}}}}}}}}}}|||||||~}}~~~~~~|}|{yzz{{yyxwurokgeinssplkpqoknv}{uw~
}tonrw}}zrmkotxyyyy}~umgccgpokea^[VNGE@>EJHGS^l{
{srrtywvyy~~uooqtyyyywwwwxxwyxxyyzzzzxyzzz{||||}}}}}}~~}}}}||||{{}}||||||}}~~}|}|{{{z{{||zxxwvsnjgfjpsuqnknoniis}|wx
}
|rlhhilsvvqmjjortsrty}}sjcbaeoplea\YTLA<78?FC@IVgwwqrsuz~~wvyywpnqtuxxxxwwwwxxwyxxyyzzyyxyzz{{{{||||}}}}~~}}}}||||{{}}||||||}~~~}|}}|{{{{||||zxxwurnjffipturnjkkkgep|{ww
}
sy
}
rihgffgkstqnkhjlonnpv|}tjcbadlpngb\ZULB635=GECIUcnz
ursvw|~~xwyztmnqvwxxwwwwwwxxwyxxyyyyyyxyzzzzzz||||}}}}~~}}}}||||{{}}||||||}}~~}|||{{{{{||||zxxwurnjeehotusojjjjdcmzzvv}|
tfp~
|w{
wlhhfefgiottqlhdehihjs|ujfcabjqpib\ZUNG>9;DLLNTQSfr~~ustvx}~~}wxyz|qmnsx{wwwwwwwwxxwyxxyyxxxxxyzzzzzz||}}}}}}~~}}}}||||{{}}||||||||~~}|{|z{{{{||||zxxwvsnjedhotuuplkjjddlyzvu}}
l^f{
|uu|slkkiggfhnuvsmhbbcdbgq|ukhdaajssle^YUQLKGILPRX]L>\i{
}sssux~~~}vxy|yqmoty~wwvvwwwwwwwwwwyyxxyyyzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|{zzyy||||{{{{zyxvsokdcfnswwtokkjfcjwytu}
xgY\u
srw~zqnookiiiimtvumhcb`_`er|wlhebblyyqjc^[WTWYWVXSX_H.Ebv
zppruz|}~
}wxy|~uonmt|vvvvwwwwwwwwwwxxxxyyzzzzzyzy{{||}}}}}}}}||}|||||{{||||}}}}~~zxxxxy|{{|{{{{{zyvsolebclquwvplljgdhw~ytv
~~
~rdVSl|
ursv~
xsssromkkjmswwsnhdaa`gt~{pjfcdpzysleb^[X[^]\WX^^H+4Ri|
yqqruy{}~zvyz|{soorx}uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}~zwvuux{{{|{{{{{{zvsolfbbhnrwvsokjgcfu~ywsv}~}~{vkaTO^z
xqssy
wustuutomjlswwvrmheedjv}rlfbfszxsked_]\]bdc\]_Z?+0CXl~
xqqruz|}~yvyz~yqpqt{uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}{{{{{}}}{{zz{{}}}}}~~~}yvuttxz|{|{{{{{zyvsokfdbhnrvwuqkjgccpz{vsqu}
~zvxwrg_TMUs
xpprw|
~xttsw{ysnjkrvvutpmiiinu~}skeaeqzwskfea^^^dihca^T918EPby
wpoqt{}}~
zwyy}wpqrw}ttvvwwwwvvxxwwvvwwyyzzzzzyyyz{||||{|}{||||}}}}||{{||}}}}}~~~~~}zwsqsvz|}||||{z{yxuqlgbcinsuvtrnkidajstpprv|yqlprmf`TKNf|
vnnruy||xwtt|~upmnquvutspommpu~{rkd_cpwvsnhcbaabgjljd\N97DLUksooouz~}zwxy~
{upqrxuuvvvvwwwwxxwwwwxxyyzzzzzzzzz{{{||{|}{}|}}}|||}}||||}}}}}~~|zwsqruy|}}}}}{zzyyvsnfbckptvwusolje_enmlmsv}
zlehkje`ULGWw
xonruyz{~~zyxvyxqnprvwwwurqonrw~{rme`dqwxupieeccdgijf`XI::HZo~ponov{~}zxxyxsqrs{
vvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||}~}|}|}}|{||}}||||}}}}}~~|zwsqqsw{|~~~~|{zyxvrmebempvxxvuplkf_bjiimrw
|mfgjid^VNFNo}
zspruyzyz{zwyxz
zqoruxxwwwurppty}smfagsz{uqjggfededdb^UE9=Tq~qpoov|}yyyz~urstv~vvvvuuwwxxxxxxyyyyyyzzzzzz{{{}||||~~}{{{}}|{{{||||||}}}}}~~|{wsqpsv{|~~|{zyvtpkecgoqwxxxwpmjeacghhnsy
}rigmje[VPHHbz
|vqruyzzzzyxyx|zrpswzxwwvvtrru{}tldbiu|ztqjjjfcb``_`]RD=B_~{|~~uqoov|~~zyz{
|rqruyuuvvuuuvwwxxxxxxyyyyyyyz{{||{|||}}~~|||{|||{{{{{{{|||||||}~}zvrqquy|~}|xwuuroieeiptvxxxvspniefikjosztkhiic[SNHFSw
yuruxzyz{yyzx|
zspsy|zxwwwusrv||tkecmx|ytqmllgbbba^`_SIEMg
yvxyy}{tpopw}~
~{zy{wrqtvzuuvvuuuvwwxxxxxxxxyyyyyz{{{{{|||~}}}|||{||||{{{{{{{{|||||~~{wrqptx|}~{wsrppnnhegkpvwwyywurolllnqnnqz
ypliec[ROHBHl}
}wsuxyyz{yyzy{
zsqt{}{zwwywuux}
{skeenwzwrpnmmhbbcc`a`VOLSm|wtvurtysqoqx~|
}zzy{|sqrtv|uuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~~{wrqprx{||xuqmkiijifehmrvwvxxyxuqoqrtvpmqyunhdb]VPJBD\z
|vtwyy{{{{{z|
ysqu}}|{yyyxuvz}yqkefnwytollmmidbcfbb`YROWpzyvuromxxqppry~||zzy{ytqsvzuuvvuuuvwwxxxxxxwwyyyyyzzzzz{|||}|||||}|{{{z{{{{{{||||||~~|wrqpqw{{zrpjfecdggddinuvywxwywusqtwwxsprwwniea^XQMDAOt
~wtvyyz{||{y}xsqv~~|zzxxwuy~ypkefoxzrmkjlljfbcecca[SRYq¡z{xsqmioxpoorz~}|zzy|}vtttx|
uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z{||{{{{||{}}}}~~~~|xsnnov{zxqjea`_]_acgmruwxxxxzxvspty{{sosx}slhd`ZSLF@Fg|
{uuxyzz}|{|~wqpv~{ywvy~vokegs|zsnihjjlgcdedab^WV]s yuqlfevxoopry}}}{yzx{~{trsuxuuuuuuuvwwwxxxwwwwxxxxyyzzyyyz{z{{{{{{|z{{z|||{{{{||{}~~}~~|ztnnouzywpha__^Z\`ciouuxxxxy{ywtqtz}|soswytpjd]UMHA@Uv
~xvxy{{~|{}}}|vpqw}zxwy~~tojdgs{ytmhgijlgdfhebc`[[at{toie`jwopqsy}}}zyzxz|wrrsvz~ttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~}{upnosxxskeabca^]_dkrvwxxxxy{ywtqsy|{rnrvzwpg^XPG@=Ik{
{xy{{{~|{}}zyspqx}yxy~~toicgs{yulhgilkhghhfdb_^^csyqiec^cx
wqrrt{}}{zzxxyvqsux~
ssuuuuuvwwwxxxwwvvxxxxyyzzyyxyzy{{{{{{|z{{z|||{{{{||{}~~}~~|wqnoqvvqhdcegea`agntwyxxxxy{yxuqtx|{qnrv}
zria[SG@<E^y
}zz{||~|{}}yvqoqyzxz~}sniciu~{vlhhinkhhjgeec^__dswnea`^_nvqstv|}}
{|{ywvtstvz~ssuuuuuvxxxyyywwwwxxyyyyzzzzyz{{{{{{{{{{{{{{{{{{||||||~~}~}yspppuuofdglnkecemruxyxxyyzzyxtrtx|zqnqu{
|siaZVLD>CTs}
}{{|||}|~|xuqoqz}{}|smgciu}|wokhjlklmmjfec`_bdq
wnd^]\[e|ursst|~~
}z}|xuussux{
vvvvuuuvwwxxxxwwxxxxyyyyzzzzzz{|{{{{{{{{{{{{{{{{}}||||}}~~zspnoqsogekqurkggntwyyxxyyyyyxuttw{ztqqtx
~vka[XQHAAKdz
{||||~|~}zuqqr{~
{rmedkw}|wqlijklnoolhfebacemx
zpf^]\[_stqqpt|~~
}{~}xtsrsux~
vvvvuuuvwwxxwwwwxxxxyyyyzzzz{{{|{{{{{{{{{{{{{{{{}}||||}}|tpmlornhhmtyxqjhntwyyxxyyyy{yvttwzyspqrw
xne]XUMGCFVt
|||||~}|~|uqps~
zrlcdlx}|xsokjknoooljjgdcdflr~
}tia__]\jspont|~~
~|~~xsqqrtx
ttuuuuuvvvwwwwwwxxxxyyyyzzzz||||{{{{{{{{{{{{{{{{||||||||}~~xrnlnpngimv{{smlpuxyyxxyyzzzyvttvzyspqqw
|sk_YVQLFEPg}
|||||~||~}zuqps~
yplbcmy}|xtpmmkmnnnnlljeddfloy
{ska^`^]f}spont|~~}|~~wrorrtzstuutuuvuuvwwwxxyyyyxxyyyyzz{{{{zz{{{{||{{{{{{{{||||||}}|}smjkmjggqy{yvsptyzyxyyyyzzzzxwuuxxuqqrw
yqdYWXQLGL^x
}||||}|}{tqrs}
wplfgq{~|xwtonllmommoomighkmmt~~xsi__aa`bu|roqqu}}
}{~}xqossu|
~stuuuvuuuuvwxxxxyyyyxxyyyyyz{{{{zz{{{{{z{{{{{{{{||||}}}}|~wojkkgefqy}zwvxzzyxyyyyzzzyyxuuxxtppqv~
}sfXWXSPOQ[q
~}}}}~}|{trrt|
uolghs|}zxxvqpnlmqonppokijmoou}}xoe_`cb`^n}rooov~}
}}|wqortu|~stuuvvvvvvwxyyxxyyyyxxyyyyyyzzzzzz{{zz{{{{{{{{{{||||~~}}~~~yokkhdbeow~}{yyz{{zyyyyyzzzyzyvvxytpnqv}
|o\UUVUSRZhz
~~}|{{urqt|
}rmifis}|yxxvsponnrrqqppomorsswy}~zzwoe__bda_l|rooow~}
}}~zuqoqux
~stuuvvwwwwwyyyxxyyyyxxyyyyyyzzzzzz{{yyz{{{{{{{{{||||}}~~~}~~~|pjjeb_dov{{{{{{||{yyy{{zzz{zzwwwztpnqx~
yfWRVXVS[cs
~~{zz{urpu~
{pmifjt}}zxxvuqpnntusqprqpruvvvvy}{wyvod__`deal{ropqx}}
}}}ytqoquz~rsttuuwwwwvxyywwxxxxwwxxxyyyzzzzzzzzyzzz||{{{{{{{{{}~~~~~~~~~~~{vokfb^`jsz}~}}|{{{yyy{{zz{{{ywvvwuqnr{~
vbUVZZZY_l|
~~}~|zz|vqpv
ynmjfkv|}{ywwwrpoostrpqrqpquxxtqtwvttpkc^^begejyzrpppx~|~~~vsoortz
pqttuuvwwwvxxxwwxxxxwwxxxyyyzzzzzzzz{zzz{{{{{{{{||{}~~~~~~~~}zsmhb]^fqz~~~}|{{{yyy{{zz{{{yxvvwvurv}
q`WW]_\^hw
~~}|zz}vqpx
wlligny~}zyxxxutppsuroqsqpptvvrnqrrqpnke^]`dhhiu
xrqppx~}~~}vrnort|
oqtttuvwwwvxwwwwxxxxwwxxwyyyzzzzzzzz{zzz{{{{{{{{}}{}~~~~~~~~}xqje`^dpz~~~}|zzz{yy{{zz{{{yywwxxxx|
{o`X[^_`fs}
}{z~wqpv
}rlljhpy|yxyyywvspsvtnnnmlkoqqolmmnnnolh`\]cghhpxqpppx~~~{tpnosvprtttuvwwwvxwwwwxxxxwwxxwxxyzzzzzzzzzzzzzz{{{{{{}}{}~~~~~~~~~~zrkfb_eoz~~~}|zzy{{{{{zz{{{yyxwyzz{
yj][]_afp{
}{{~vporz
yojkhiow}|ywyyzzxuruwulihgefjlllkjjklnnmja]]cghglywqoppx~~
~~zsonoswoqrssvwvwwxyxxxxxxxxxxyyxyyzzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~}wniebgoz}~}}{yzz{{{{|{{{{{zzxxw{{}
th\X]bjs{
~{yz~{smlnt}
~tmjljilptxyzz||||{wxxumfa_^_afihffgjmpmkgc__ahijmt{~
~qppoqx~}~~~~yroopt{opqrsuvvxxyyyyyyxxxxxxxxxxyyzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~~ytojhks{~~}|{z{{{{{{||{{{{{zxxw|}
~tf\Z]kx|}
~{yz~wnhfgjsz
yokjljgiklquwz}}~zywqja[XXX[aeffghilollhd`_agiilmqw|zonnnpy}~~~|wrppru|noprruwvxxyyyyyyxxxxxxxxxxyyzzzzyy{{yzzyyyyy{{||}}|||}~~~~~~~~~~zytonpw}~}|{z{{{{{{{{{{{{{zxxy|
}tg^^jy}}
~{yz~}rjdaceju~
vljkjiehkkkmqtxz~}ywsmf]VUUTV\dgiklnopmmiea`cfhiiginsxxtmllmoy~~}vqpqsv}nopqrtvvwwyyyyyyxxxxxxwwwwxxzzzz{{{{z{{zyyyy{{|||||||}~~}}}}~~~~~~||wusuz~}~}{|{{{{{{{{{{{{{{zxwz|||
|rhemx}~~{yz~znfbabbfp|
ukikihfhmlijjlruz~{wtpjbZTQQTVZdknpqssromieb`cfghhfehmrrnkkklnx~~|tpqrsv}
mmopqttuvwwyyyyyyywwxxxxwwxxyyzzyyyy{{{{zz{{||{{||}}}}}}}}||}}~~~~~~~|{z{|}|}|||{z||}}|{{|||||{{xz|}{z
zvwz|}
~zyy~uicbbbablw}sighiieillgfdcgkrxxsnjd^WROOSX]isxxxxwwrojdcacddefebbdhjiiiiknw~~{sppsuxllopqstuvwwyyyyyyywwxxxxxxxxwwxxyyzz{{{{{{{{||{{||}}}}}}}}||}}}}~~~~~~~~~~~|~|||{z{{||||||||||{{{{|
}yy~
}~
}yz~|qgedecbahr
|sighhhfjiihecaadhmnljf`[XSQRU\hu|~}zxsqnihfeefhigedddeffffhkt{|~~ztrrsuzllopprstvwwyyyyyyywwxxyyyyxxwwxxyy{{{{{{{{{{||||||}}}}}}}}}}}}}}~~~~~~~~~}||{zzz{{|}}|||||{z{||}
|yz~
}
|{~{piihhdb`dn}
{skiihhfhiiifddbcbddedb`\[WWX\ev|{vrponjhjkmnkigfcaccccdgqx{y~~||xsuusu{~oooporstvwwyyyyyyywwxxzzyyxxxxyy{{{{{{{{{{{{||}}||}}}}}}}}~~}}}}~~~~}}}}~~}~||{zzz{{||||||||{{{}}}
}{{}~
~}
|~{qmlljgc`akz
{qjkjgggijjjgffdfaababa`]][\]co
}~ywqsqnklorsromjfbdcccbgnuyx~|||xqttsu|
~mmnoprsuvwxyyyyyyyxxxy{{yyyyzz{{{{zz{{zzzzzz||||||||||||}}}}}}}}~~}}{{}{}}~~}|}}|{zz{{{{{{||||}}|||}{|
}|||}
{}~~
~yrqoomkf`adr~~~}yojljgfhjijihggecbababcbcdbaer~zxuuurnnsw|{xvqlfcdcddelpsu|z{wrsstw{mmnoqrsuvwyyyyyyyyxxxyzzyyyyzz{{{{{{{{{{zz{{||||||||{{{{||||}}}}}}}}{{}{||}~}|}}|{zz||{{||||||}}}|}}|}
}}||
|}|
~{vtturqokc`ajsrqsx}wnkmlhedfghhgfedbbbbaabdgkkns|zyzzyrrv|{ulfeeeeeimpu}|zzvrssuy{mmnopqrsvwyyyyyyyyxxxyyyyyyyzz{{{{||||{{{{||||||||||{{{{||||}}}}||}}}}}{||}~~~}|}}|{zz||{{||||||||||}}}~
~|z}
~|~{}
}ytuxwtssnd`_fmjijou||vomnmjffeefgedbcbddecaafmqty}|{~xvy~
|skjhfeefjmr||{ytrssuy}
oonoopqrvwyyyyyyyyxxxyyyyyyyzz{{{{||}}||||||||||||||{{{{||||}}}}||}}}}}{{{}~}}}|}}|{zz||{{||||||{{||}~
|z{
|~||
}}vrvyusuurg`_difeehpwxsnmlkkhhfefebaabbddgebafowz~~}~{{|xpmjgeefjkox
||ysrssuz~
||
nonopqrswxzzyyyyyyyyyzzzyyyyzz{{||||{z{{{{||||||{{{{{{{{{|||||||}}}}{{{{||||}}}}{{}}||{{||||||||{{||||~~}z{
}}
~{{rptvsqtxule`aeedachnsplmnmjjhhdcdcccbbddfgfcdny}
}}~|tqnjgefhjjtzzwssssvz
{zmonopqrsvwxyyyyyyyyyzzzzyyyyzz{{}}}}{z{{{{||||||{{{{{{{{{|||||||||||{{{{{{|||}||{{|||||||||||||}||}}||~~~
|}
}
{~wpostqpsxwph`acdcacchllklkkjlmmheddcbddddehlmgfjq|
|wtnjhgihhq~yyussssv{
{|lnnooqrsvwwxyyyyyyyyzzzzyyyyzz||~~~~}{{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}||{{{{||||||||||}}||}}||~~
~
{{tnoqsoptxztkaaddddeeegiklkklorrnhffccddeeegmtm`[ew
|xsmjijggnz|yxussrsw{|lmnooqrsuvxyyyyyyy{{zzzzyyyyzz||~~~~}|{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}{{{{{{|||||||||}}}||}}||~~
z}yqmoppootwzvlcaddfeffdeijkjikpuuokgeeeeeeedgmuo\R]v
}wpmkjgglv{xxtssrtx||mnnnnpqrvxyxzzyyzz{{{{{{zz{{||}}~~}||||||||{{{{{{{{zz{{z|}}|{|||||{{{zzz{{{}}{{}}{{{{||||||}}~~~~}}}}}~
}}vnkmoooquxyuledghiidcccfggefjnvupjfefeeccdcfkmeXJWv
|xtqmkgjs|
{xusttsuz~
~|nnnnnpqruwxxzzyyzz{{{{{{{{||}}}}~~}||||||||{{||||{{{{{{z||||{|||||{yyzzz{{{{{{{{{||||||{{{{}}||~~}}}}}~
z~|tmjlonorvyxslffhjkmhfddefedehmrroifeeecaaa_`dd^TJUn¡£
~{wuqniipw|zxtsttsuz|}nnnnnpqrtvwyyyyyzz{{{{{{{{||}}}}~~}||||||||||}}||{{{{{{z|{{z{{{{{{zyyzzz{{{zz{{{{||||||{{{{||||}}}}}}}~
{{yrljlmlnqvyysmhgijlonlhhggeedfikllgggfec`_]][^^ZSPWf
¢¢~zxupliotx~
wvtsttsv{znnnnnpqrtvwzxxyyzz{{{{{{{{||}}}}~~}||||||||}}}}}}{{||{{z|{{z{{{{{{zyyzzz{{{{{{{||||||{{{{{{{{||~}}}}}}~
zwuqllmlknqvyyslhgilnqrrmihhgfdefgiigghfeeb`^\\\\XTWZb|£¢¢|ywsnjmru|wutstttw|{
oonnnpqsuvwxyyyyzzzz{{|||||||}~~~~~~}}|{{{{{||}}||||{||{{{||{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~}||}}}~
wspmmoonlmntyzsmkhhlpptvtnnkhfccdcdghhihgggc\YXYYWX^_cv¢¡
zyvokortz~wutssssx}}
oonnnpqsvvwxyyyyzzzz{{{{{{|||~~~~~~~~~|{{{{{||}}||||{||{{{{{{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~~}}}}}~
ysnkoqpnkllrxzrmjiilprwvtqpmica``acfgkmmlmolb^][[Z[adhv
¡ }|zslptsw{}vssqsssy|
oonnnpqsvvwxyyyyzzzz{{{{{{||}~~~~~~~~|{{{{{||||||||{||{{{{{zzzzzzzzzzzzzzzzzzzzzz{{{{{{{yzz{{||~~}}}}}~
zsnnpqpmiijpwxrmihimptvutrqnjda``acdgloopqrqhcbb`^_fhkv¢
~}vmqvsuw}}ussqssuy
|oonnnpqsvvwxyyyyzzzz{{zzzz||~~~~~~~~~|{{{{{||{{||||{||{{{zzzzzzzzzzzzzzzzzzzzzzyyzzzzzz{yzzzz||}}|}}}}~
|rnnpqplihhnwwrmhfgnrtuuusrplgcbbbcdhlnprsttnihhebdhknw¤
}xptxussy|srtrssv{
~|ppoonpqtvvwxxyxxyyyy{{{{{{}}~~~}|||||{{{||||}}|}zzzyyyyyxxxxzzzzzzyyzzzzzzzzyyz{zz{z{{z{||}}}~~~}
|spqsrpmihgnxxrkgcgossstsqpnjedbbacdhlpqrvttspnnkjjmmpy ¤¡
zy~~~ysx{utrv}
zttttttx||
ppoonpqtvvwxxyxxyyyy{{{{|{}}~~~~~~~}|||}|{zz}}||}}}}z{zyyyyyxxxxxxxxyyyyzzzzzzzzzzz{{{{{{{z|||~~~~~~}
{qssttppmielwwqjfcgqttsttqnliecbbacfimqrsvuusqorqprrrrz¢¤£¡
xu{~~zv}yvruzzstuttux}}{
ppppnpqtvvwxxyyyyyyy{{{{{}~~~~~~~}}||}|{zz}}||}}}}||zyyyyyxxxxwwxxyyyyzzzzzzzzzzz{{{{{{{z|||~~}
zqstuurqnhejsuoieckruvutsomkgdcbcdfjloqstwvtrqrusrsuvux¤¦¤¢¡¡¡ vpt{~{yxuuv~
ystuuuv{
|
ppqqnpqtvvwxxyzzyy{{{{{{{~}}}}~~}||}|{{{}}||}}}}||zyyyyyxxxxwwxxxxyyzzzzzzzz{{{|{{{{{{z|||}}~~}
{rqtuutrofekstniddnsvwvurnliecbbefhlopqsuwvsrqtxwusuxuv|£¥¤£¡¡¢¡ ulpz{zzwtszxtuvvuv}~|
ppoonqruwwwwwwxzzzzzz{||}~~~~~~~|}}||||||||}}}}}{zyyyywyyxxxxxxxxyyzzyyyyz{{{{{{{||{{{{||}~~~}}~~
{srxwustpgfkrrlgcentwwvuqokjfdcdfhkpqqrruvrqoqw{zxuvvtqt¤¥¥£¢¢¢¡¡uijv~|{xvwzzuvvvuw|}}
ppoonrsuvvwwwwxyzzzzz{}}~~~~|}}||||||||}}}}|{zyyyywxxxxxxxxxxyyyyyyyyz{{{{{{{||{{{{||}~~~~~~~
~wuyxvuvqhfmrqljefnuxywvrpjjgfdefiosrrrrutpljou{|wuttqmpw ¥¥¥£¢¢¢¡¡
zlfp|||zvvx|xuvvvvx{}}pppppqsuuuwwwwwxyyzzz{||}~}}|}}||||||||}}}}{zzyyyywwwxxxxxxxxyyyyyyyyyz{{{{{{||{{{{||}~~~~~
zwyxxwwrkiotrmjdgpvwzzwrpiiifeehmuxvtttsrnhglsy{vsttpmmt¢¥¥¥£¢¢¢¡
~pfm{}{wsu{vtwuuvyy|z
ppqqqqsuuuwwwwwxxxzzz{||}~}}}}}||||||||}}}}{zzyyyywwwxxwwxxxxyyxxyyyyyz{{{{{{||{{{{||}~~~~~~
~zyxwvwrljpsrmhehqvwzyvsnjiigfeiox|xvvurplfehqwyvsrrpnlt ¥¥¥£¢¢¢¢
~qglz}{ysuz}ttwvvwyxyzppqqprqttuxwxxvwxx{{{||}}}}}}~~~|}}~~||{{||}}}}{{{zzzywxxwwvvwwwyyyxxyyzzz{{{||||{{||||}}}}~~~~~
~zvwxyunmpsqmhfkuwuxxtqmjiigfekq{|wxuromhccluxutrrqnkr¡¥¦¥¤££
shky~z~wwz|uuwuvvxwyzppqqprqttuvuuuvwxxyyz{|}}}}}}}}~|}}}}||||||}}}}{{{{zzywxxwwwwwwwyyyxxyyzz{{{{||||||||}}}}}}~~~~}
}yxzzwooprqmhflvzxyxtqmjjjiihmr|}yvuronjdbluwutssrnkq}¢¦¦¥¤¤¢¡
rgjw~z}x|zuuvuvvxx{ysppqqrsstttutvvvwxxyyz{|}}}}}}}}}~~~~~~~~}}||||}}}}}}}}{{{{zzywxxxxxxxxwyyyxxyyzzzz{{||||||||}}}}}}~~~~}
}|}|wqpqsplhfoyzxyxuojjllkkklpx{|yvurqpkedltustsssonr|£¦¦¥¤¥£¢}qgju~~z~z}xsuvuvwyz
{rorrqqrsutttvuuuvwxxyy{||}}}}}||}}~~~~}}~~}}||||}}~~}}}}{{{{zzywxxyyxxyywyyyxxyyzzyy{{||||}}||}}}}}}~~~~}~
~wsqrsokhfqzzwyyuniilkllkkoswyxxursqkfelssrttttpps|¤¦¦¥¤¥¤¢zphju~z{~xqvvuvxy|
}snkrrqrssttstuuvvwxyyzzz{||||}}||}~~~~~}}}}}}||{{}}}}~~}}{{{z{{yxxxyyyyzzxxyyxxxxyyyyzz{{{{{{{{{|}}}}}~~~~~
vqqrrolhgr~}yzzuqlllonnnmlnqstuwtsrnkilqsqtutrqrsz
¢¦¦¦¤¤¥¢
zqlmv~}z}|}xtvvwwx}
|xpkhjrrqrsstttuuvwwwxzzzzz{||||||{|||||||}}}}}}||||}}~~~~}}{{}{{{zzyyyyzzzzyxyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~}
wrqrrolhgs|||xsooorpppokklnpqvtsspnmlnpqtutssssw¢¦§§¥¥¥
yqmnv||z}}xuvvtty~~vokffimqqqrsstttuuvwwwxyyyyxyzzzzzz{{{{{{{{}}}}}}||}}~~~~~~}}||}|{{zzyyyyzzzzzyyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~}
xrqrqnkgfs}~~zurrrtrrpokjjjklpqqqqqplloqtutuussw¢¥§§¥¥¤
yqmntz~}z}}xuvvuuz~wvzyrkeccejnqqqrssttuuvvwwwxzzyywxyyyyzz{{zz{{{{}}}}}}||}}~~}}}}|z{{yyzzyyzzzz{yyyxxxxyy{{zz{{{{z{{{{|}}}}}~~~}
xstrpnjges~~}yuttttrrqpnkijijloppppnlloqtutttsrw¡¥¦¦¦¦¤{smnsy}}z}
}xuvuvv{|tpsslfcabdjnqqrsrrttttvvwwwwyyxxwwxxyzyyzzy{zzz{||}}~~}}}~~~~}~~}}{yzzzzxxxxzzzzzzzzyyyyyyz{{{||||{|||{|}}}}~~~~
yuutonjgdp}|zuttuutqsvrnlhgghknnmpnmlnrutusttv|¡¥§§¥¥¢
}vnlqvz}y{
{vuuuuw}
wokmjgedaadhmssssssttttuuvvvvxxwwwwwwxxyyyyxzzzz{||}}}}}}}~~~~}}}|zzzzzxxyyzzzzzzzzyyyyyyz{{{||||||||{|}}}}~~~~
~zxyzuqlheq
}|yuttrrsotyxsoigfdgkkjnmllmquuvtuwz¥§§¦¥¡~{rnquz|x{
yvuuuvy}tnkljjiigfhmquuttttttttttuuuuvvwwwwwwwwxxyyxzzz{|||}}|||~}}~~~~}}}|zzzzzzzyyzzzzyyzzyyyyyyz{{{||||||||{|}}}}~~~~
|{{~}vpifs}zwuuussronv}zunjgefhhilljjknquvvwx|£¥§§¦¢yrqsy{xz~wtuuuwzyqpnmnopqpoqvxttuuttttttttuuuuvvvvwwvvvvxxxxwyzy{{||}}||{~}}~~~~}}}}{zzzzzzzzzzzzxxzzyyyyyyz{{{||||||||{|}}}}~~~~~~
~zz
|vlgs}yvuvvttrnjt~{smideeegkkiijjottuvy~¡¥¦§¦¡uqrx{y{
}vsuuuvy
{uqprqstvvvy|~ttttttttssuuuuttvvwwwwwwwwvwwwwwyy{|||}}||||}}~~~~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~}}|
{}
ult{xuuuwutrmio|yqlhcaaeghikjjnqruwy
£¦¦¦xrqwzww~|vuvuuwyxqqsvvvxz|
ttttttttttuuuuttvvvvvvuuvvwwwwwwxxz{|||||||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~~~}}}
~zzzxtstuvusngkw~xsmd__bdehkjlnorty}
£¦¦¦}vruurpsxwvvvx{zuvvuuxztpsuxxy}
ttssttttttuuttttuuuuuuuuvvwxxxxxxxz{{{{{|||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~
~xtstuwvsmggp}|wodaaabcfhikmoqs{¢¥¦¦
zttqolkmjkkkptusvvuuy{{srtvyz}
ttssttttuuuussttuussssuuuuwwwwwwxxyzzz{{|||||}}}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~
zustvwvrlfdky~zpgcc`badfhilnpt|
¢¥¦¦ |sqqmiggceeekprrvvuuz{ztuvwz}
ttssrsttuuuuttuuuuttstuuuvwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyyy{{zzzzzyz{zz{{{{|z{{{{{|{{||||~~}}}}}}~}
zxyxwtqlgehq{zsigfb``afgghjnr{
¤§§¡
zrmjgecba``bhmqqstuvx~~vuvvw}
{ttssrsttuuuuuuuuttttstuuuwwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyzz{{{{zzz{{{{{{{|||{{{{{{|{{||||~~}}}}}}}}
~|wsmhefkuz{tpikjebbcdefghlpx~
¤¦¦
zumjgedca`]]`ejmqrtuvx}}zutwy~
{ttssrsttuuuuuuttttsssttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zy{{{{{{zz{{{|{{|||||{{{{{{|||||||~~~~}}}}}
zsliginuuokjmnjhggffffghlsy~£¥¤}}|zzyxvqpjfedccb`]\_adhnpsuuxz{wvy}
ttssrsttuuuuuuttssrrsttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zyyy{{{{zz{{||||}|}}}{{{{{{|}}||||~~}}}}~
ypmjikqqmjjnplmmlihffgfiouz¢¤¡
~||{yyxusrqplkfffdcdb`^\``bfkoruuwz{y{
ssttstttuuuuttsssssstuuvuvvvvvvvvvxxyyyz{{{{{{{|}}~~~~~}}{z{{zzyy{{zz{{z{{{||{{{|||{{{|||}}{{||~~~~}}|~~
~xqmopnlilprqorpmifeddhmsw}¢¡~~}{zzzyxvuttsqolhgeeddbcdeda``adglotvz|
ssttttttuuuuttttssssttuvvvvvvvwwwwyyyyyz{{{{{{{|}}~~~~~}}|{{{zz{{{{{{{{{{{|||||||||||{}||}}{{{{~~~~}}|~
~{yxvsqswxyz|ztpkgddfkotx|{z{yxyxyyxwuttsqolhgfdcbdceggcaaadfilqw
rrssttttuuuuttttssssttuvvvvvwwwwxxyyyyyz{{{{{{{|}}~~~~~}}}|||{{{{{{{{||{|||||{{||||}}{}||||{{{{~~~~}}|~
}{}zvpifddgjosx{~
}zyxzyxyyyyzzwxvusqokihdcacefhhfeccdefimv
qqrrttttuuuuttuussssttuvvvvvxxxxxxxxyyyz{{{{{{{|}}~~~~~}}}}}}||||{{||}}|||}||||||||}}{}||{{{{||~~~~}}|~
{umgdcdglotwz
~|yxyyyzzz{z{|}{{zxusqmiidcbaefiihgcdddegku
rrssssssttutssttssrsrsttvvwwwwwwxxyyzzz{{{||zzz|}}~~~~}}}}~~}|||||||{{}|||||||{{||}}~~|}}}||||||~~~~}|~~
|slgcdfjkmps{~~}{xxxxyxxz|}~~|xutrnkhda`cgghiihfecccgmyssssssssuuutttttssrsrsuuvvwwwwxxxxxxzzzz{{||{{{|}}~~~~}}}}}}|{{{{{{{||}|||||||{{||}}~~~~~~~~||||~~~~}|}
{qieefhjjmotvttvuxxyzzz}
zwvuqmjgcbcfhjlljigdddegny
ttttuuuuvvutttttssrstuvwwwwwxxxxxxyyyyyy{{|||||}}}~~~~}}}}}}|{{{||||}}}|||||||||||}}~~}}}}}}||||~~~~~}}
xmhfghihikmopqvvxxy{|~
zwwuromjhgeeimnnlkhfccedfo~ttuuuuuuvvutuuttssrsuvvwwwvvyyyywwxxxxyy{{|||||}}}~~~~}}}}}}|{{{}}}}}}}|||||||}}||}}~~{{{{{{||||~~~~}~~
{rjhggiggjjlmpuvwwy|{yxxurqqnlkecimppmkjgdddediyssttuuuuuuutttttssrtuvwwwwwwxyzzyyxxyyzz{{|||||}|~~~}}}|}}||||}}}}}}||||{{}}~~}}}}}}|{{{{{||||{{{|{}
~ynhihiikkhhjmptsv{yxwxxuwwtsnihkmqqnnjiecddberssttuuuuuuutttttssstuvwxxxxxxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|{}
}skjihikkfegknruy{zz|}}vplllnqqpomlgdddcel|uuuuuuuuuuutttttsstuvvwxxxyyxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|}}}
yplihillgeehimrv{soonpqqpppnjfddccgtuuuuuuuuuuutttttsstuvwwxxyyyxyzz||{{zzzz{{|||||}|~~~}|}}||||}}}}}}||||}}}~~~~~}}}}}{{{{{||||{{{|~~|~
ulihillifeffjnrz~uoopqssqqpnjgcddbenvvuuvvuuvvvutssstttvwwxyzyzzzz{{||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}||||}}~~~~~}||||{{{{|||||||||}|~
yojfgjmkifceghlpvywomruwwtqpomifeedcizuuuuvvuuvvvutssstttvxwzz{zyyzzzz||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}}}||}}}}~~~}||||{{{{|||||||||}
{z
yqhgjlmlifedcefjmqvz
xplow|}wsponkhgfecftuuuuuuuuvvvutssstttvwxzz{zyyzzzz||||{{zz{{{{||}}}}~~~}||||}}}}}}}}~~||}}}}}}}}|{||{{||}}|||||||}
|zz
xniijmpomecbcdehikot}
{qlmu}|xspnliigfegnzvvuuttuuvvvutssstttuwyzz{zzz{{{{||}}{{zz{{||}}}}}}~~}||||}}}}}}}}~~||}}||||}}|{||{{{{|||||||||}~y{}
ulhhmsrogdcccdfeeiltz~snos{{tqnmkjhfghjtttvuttttvvvussrrsuuvwyz{|{||}}}}}}}}||{{||}}}}~~~~~~~~~~}}}}{{||}}}}}}}}}}}|}}~~}|}|||||{|||||||{~
ywy}
{offhprojeecbdfcadfjqywonrzxplkmmkhgghoyuuvuuuuuvvvusssssuvvwyz{||||~~||}}}}||||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~~~}||~~~}||{{{{{{|||||||{~
~wx|
xlfflprokfc`aefddbdipy{snpw}ullnomkghgls~wwvuvvvvvvuvttsstvwwwyz{||}}~~}}}}}}}}||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~}|{}~~|||{{{zz{|||||||{~
zxz
~sgehmrspje``dhjhdceks~~vopw
wmklppojhhknwvvvuvvvvvvuvuutttvwwwyz{||~~}}}}}}}}~~||}}}}}}~~~~~~~~~~}}}}}}||}}}}}}~~}|{|~~||{zzzyyz{||||||{~~xx|
xkffkournhcafjmnjffir~
xqpv
ynklprrmiiijq~wwwwwwwwwwvvvvuutuvwwxz{}}~|}}}}||}}||}}}}}}~~~~~~~~~~}}||}}}}|}~~~~~~~~}}||}|}|{{{zyxyzz{}}|||{wz
~tgdglrspjedimrtroknu}{uqt|
}slkptxsliijmvwwxxwwwwwwvvwwuuuvwwyyz|}~~|}}}}||||||}}}}}}~~~~~~~~}}||}}}}|}~~~~~~~~}}}|}|}|{{zzyxyzz{||||}
~xw}
}oedfmsvqjggjqx}{xy|~uoqzvnjntyxokjijowxxxxwwwwwwvvwwvvvwyy{{{|}~~~~}}||||}}}}}}}}~~~~~~~~}}~~}}}}|}~~~~~~}}~~}||{zzzyxwxyz{||||}
zxy~
wlfdgpwvqlgjow
vppxyqklpvzuojhghnxyyxxwwwwwwvvxxxxvwz{|{|}}~~}}||~~}}}}}}~~~~~~~~}}~~}}|}~~~~~~}}~~}|{zzzyyxwxyz{{{||}
~xy}
}tkfcjtyvnkknt}xpqwyrmkmuzxskifeiq{
xxwwwuwwwwwwxxxxvwz{|}}~~~~~}}~~}|||}}~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}|{{{{yyyxxxwyzz{{||yvz}}
|ricepy{vqmjnwwppv~|vqoouyzwoiggeis
xxwwxvxxwwwwxxxxwxyz|~}~~~~~}}~~}|||}}~~}}~~~~~~~~~~~~~}~~~~~}}}}}}}}|{{yyxxxxxxwyzz{{||vuz~||
}qeagu{{wpikr{xppu}~xttpswzztmhfcem{xxxxyxxxwwwwxxyywxz{|~}~~~~~}}~~}|||}}}}}}~~~~~~~~}|}}~~~}}}}}}}}|{{xxwwxwwwvxzz{{||
|uv|~z|
ylaaly~{skjnu~
zrpt{|xwrruy{voieccivxxyyxwwwwwwwxxzzxyz{|~}~~~~~}}~~}|||}}||~~~~~~~~~~}|||~~~}}}}}}}}|{{xxxxwwwwvxzz{{||zvxz|~
rc_ft}}wmjkq{
{uosz|{vstwzwoiebbeq~yyzzyxxxxxwwxxyyyyy{{|~~}~~}}}}}}}}}}||}~~~~~}}}}}}}}~~~~}|||{zzyxxwuxxwwxyxz{|}ww{
|{|
yh]]hx|{smnnt~~wqryzwsvxwqiedbem}zzzzyxxxxxxxyyyyzyyz{|~~}}}}}}}}}}}}}}~~~~~}}}}}}}}~~~~}|||zzzyxxxvwwwwwwxzz{~wty}
}{|qd\akwyztpnqyxrsx}{tuwwtmgebel{zzzzyxxxxxxxyyyyzyy{{|~~}~~}}}}}}}}}}}~~~~~~~}}}}}}}}~~~~|}||zyyyxxwuvvvvvwxzz{~uuy
~}
|l^\`jr{{uont~zuru{
yvwxwqjifejxzzzzyxxxxxyyzz{{{{|{{|~~}}}}}}}}}~~}~}}~~~~}}}}}}}}}}}}|}||zyyxxxutvvuuuvwyz{}{sv|
~~
scZZ`ly}wrmqz{urtz}xvxwslkhehv{{yyxxxxyyyyyyzzz||||}}}}~~}}}}}}~~}}~~~~~~~~~~~~}}}|||||||{zywxxwuuvvuuvvxyy{}xtx~
{g\Y[ew~ytpnu~|vqsv|wvxvsnmiefr|
{{yyxxxxyyyyyyzz{{|}}}}}}~~~~~~~~~~}}~~~~~~~}}}||||||{zzzwxxwuuvvuuuvxxy{~wv{
p_WW^mx{wropx
~wspsy
~yxvtpmjfemw
{{yyxxxxyyzz{{zz{|}}}}}}}}}~~~~~~~~~}}}||||||{zzywxwvuuvvuuvwwxxz}~vx}
yf[VYdu}}xsos~ysmow|~{wvuqnkgfit{{yyxxxxyyzz{{zz{|||}}}}}~~}}~~~~~~~~~}}}|||||||{{zwwvuuuvvuuwwwwxz}
~vx
l^WV`s|{voq{
yrmptz|}|wvuqnkgehq}
\ No newline at end of file
diff --git a/src/media/test/data/bali_640x360_RGB24.rgb b/src/media/test/data/bali_640x360_RGB24.rgb
deleted file mode 100644
index 99ebd1e..0000000
--- a/src/media/test/data/bali_640x360_RGB24.rgb
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bali_640x360_YUY2.yuv b/src/media/test/data/bali_640x360_YUY2.yuv
deleted file mode 100644
index ecf1a5d..0000000
--- a/src/media/test/data/bali_640x360_YUY2.yuv
+++ /dev/null
@@ -1 +0,0 @@
-DEkHKlLLmMNnNPoPPoPQoSQnS~RoR~RoS~SoR~RrQ~StQ~QuP}OvN}MvL|JwK|JxG}GzF}CzA~Az>>z:;w:;u;8r88p88q89q99p89p::p98p8:q97q67r75s55s44u21x11y00z..z--y+~+y)~'y)~)y((|))|(~(}(~(}'~'~(~((~('~()}))})*}*+}+,},*}*+}+,},,},,},,~,+~+}+~+~*~)~)~)})~)})~)})~)}))}((}''}&&}&&{&&{&&|&&|&~&|&~&|&~&|&~&|'~'|&~&|&~&|&~&|&~&|&~&|'~'}'~'}'~'~'~(~(~)~+~,,}-/|02{24z55z77z78z8
9y9
9y9
9y9
9y9:y:9y9
9y9
:y:8y87z66}77:>E|J
QsYajhldqu^wx[xw]uq_mjahhdghajl_nn]oo[o
o^pocpoeqshtwhy|e~~`~~`|xbrlgdYlNAs4.z,|)+{++z+
,z*
+z++z.4z:B|O{Z}eqnxf~]
YX}
w]m
gbXHj;/t)%z$$~#~$%%'*{.1u78r>CpLSp^emmti{f|azuaoidaYgSOkRQnTTpQSqLDu=6y0'z%#$~$"~"#~""~"!~!#~!"~! ~#%~&(~+|07w=DpKPmW^jcgjlrjwxjyxjwujvvjuukuokk_nWKqF~Aq?}?t@{@u@|@u@|@u?{?u?}?u=~=v:~9v9~9u7~5w5~4v3~1w0~0y/}0x3}9u<}CpNVf]f[lqXtyT{|Vzz[v
r^mdc]OhE9o2,y+)(('~''}'&}((}()}))|+*|-0{12{36{7~7{8~=|>|?|CxF{FvJ}NtU|\ob|kmp}tjx~zg{}gcdfgghf~}h{yjzvjt~tmx|{p|r{t|t~t
t~~|tz~xsutrtyrq
poq|xupftXJu=4u/.z-,|0}38{=DxNzZwbvkyspxz|l~hbbdc~a}{\yvZww]zzd|~k}n|t}t~s~r~r~suwyzpd~XOHEA}>
<|;FÿIÿIÿLÿLÿLÿMÿNÿNÿPÿPÿPÿPÿQÿRÿQÿTÿSÿRÿRÿSÿSÿRÿRÿRÿRÿQÿQÿPÿOÿNÿMÿLÿJÿJÿHÿHÿHÿEÿAÿAÿ@ÿ=ÿ<ÿ;ÿ<ÿ<ÿ:ÿ8ÿ:ÿ:ÿ:ÿ:ÿ:ÿ8ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ*ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ+ÿ,ÿ-ÿ/ÿ0ÿ1ÿ1ÿ2ÿ4ÿ5ÿ5ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ8ÿ;ÿ=ÿDÿKÿQÿYÿ_ÿgÿoÿsÿyÿzÿ|ÿ|ÿ|ÿzÿtÿqÿlÿjÿkÿkÿlÿmÿoÿoÿpÿpÿoÿoÿmÿlÿkÿjÿjÿjÿkÿkÿoÿrÿwÿzÿ~ÿÿÿ~ÿ~ÿ~ÿwÿsÿkÿ`ÿUÿHÿ<ÿ2ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ1ÿ7ÿ?ÿJÿVÿaÿmÿwÿ}ÿÿ
ÿÿ
ÿÿÿ{ÿtÿkÿ^ÿPÿ?ÿ3ÿ*ÿ&ÿ$ÿ$ÿ$ÿ%ÿ&ÿ(ÿ,ÿ/ÿ2ÿ5ÿ8ÿ=ÿAÿEÿJÿRÿZÿcÿjÿrÿ|ÿÿÿÿÿzÿuÿnÿdÿZÿSÿOÿMÿTÿbÿpÿfÿVÿNÿEÿ=ÿ6ÿ)ÿ#ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ$ÿ&ÿ+ÿ-ÿ4ÿ=ÿCÿJÿPÿVÿ\ÿbÿeÿlÿqÿuÿyÿzÿ{ÿzÿwÿuÿvÿvÿuÿuÿuÿsÿkÿaÿVÿKÿEÿBÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ<ÿ:ÿ:ÿ:ÿ7ÿ5ÿ5ÿ4ÿ3ÿ1ÿ0ÿ0ÿ0ÿ1ÿ1ÿ6ÿ<ÿCÿMÿUÿ]ÿfÿlÿqÿtÿyÿ{ÿ|ÿzÿzÿwÿvÿoÿfÿ^ÿRÿGÿ;ÿ3ÿ.ÿ+ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ,ÿ.ÿ.ÿ0ÿ0ÿ3ÿ5ÿ6ÿ8ÿ9ÿ;ÿ=ÿAÿBÿDÿGÿKÿOÿXÿ^ÿdÿkÿpÿtÿyÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ|ÿ{ÿwÿsÿrÿrÿvÿyÿzÿ}ÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿ}ÿ{ÿyÿyÿxÿyÿ~ÿ
ÿÿÿ
ÿ
ÿÿÿÿ{ÿuÿmÿbÿRÿDÿ9ÿ2ÿ/ÿ.ÿ.ÿ.ÿ0ÿ3ÿ8ÿ=ÿBÿMÿXÿbÿkÿsÿxÿ|ÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿwÿwÿtÿsÿsÿvÿxÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿnÿbÿWÿNÿIÿFÿAÿ>ÿ<ÿ;ÿHHmIKoLLoNOpOOpPPpQQoQRrQ~SsR~RsR~RtR~RtQ~PuP~PvN}MwL}KwJ|JxH|GzE}DzA}@z@>z=<x;:v8:t98r99p89q9:q99p99p::p99p99q88q77r66s55u33v21v00x//z..z-,z,~+z+~*z(~(z(({(({(~(|(~(|(~({(~(})~)~)~)~)}))})*}*,},,},,},,},-}-,},,},,~,,~,},~,~+~*~+~+}*~*})~)})~)}))}((}''}''}''}''}&&|&&|(~'}'~(|'~%~'~&}'~'|&~&|&~&|&~&|'~'|&~&|'~'}'~'}(~(~(~(~)~)~*~,.}.0|13{34z55z58z88z8
9y9
9y9
8y8
9y99y99y99y99y:9y97z78};>C}GOwU^pbjgqvc{}_}~^{x^vp`nlblkblm]noZomYmkXig[ec_a^da~dgj~mhr~wf{c`a|vcqif^QmB6u.|*~+{**z*
+z+
+z++z+0z5={E{Q}_sj~ri{_
\Z}[xnbcThE6p-&w$$}%&}',{/1w48t=>rBDpHLmU^neoiwfcbz
sbh
`eV
PjN]me~upvxop`{fqyss@
$v!#{#####""~""~!"~""~##~$|$*z.3u9ApGMkS[i`ehjphsxh{}h|xhvvhuvivvksolj`nTMoD@r?}?t?{?u@|@u?|?u?{?u@}>u=}=v<};v:~:u7~5w5~4v2~1w/~0y.}.x2}8u;}BpM~Tg\h]nrVuxU|~W}|\{
v^qiccVgJ>p4.w+)))(~('}'(}((}()})*|+,|-0{11{34{5~8{9~=|>{@|AwE}JtM~SoZ~_lf~kjr}vi{~hge
egi
iij}|jzwkt~rjp|pmqyupvyyr}{t|t
~
t
t~~~s{~yrz~zp{~pqpo}qyrsj\rN>p51s-,w,~.{0}3|7{<|CzMyWy`tizrqx{|m~hdccb}{`xw\trZoo]rudx~l}s|u}u~s~r~r~suwwzk^~SNI}FA{>;y:
IÿJÿJÿKÿMÿLÿNÿOÿOÿOÿPÿPÿQÿQÿQÿRÿSÿRÿRÿRÿQÿQÿPÿPÿPÿPÿOÿOÿMÿLÿKÿKÿIÿIÿGÿFÿDÿCÿAÿ@ÿ?ÿ>ÿ<ÿ;ÿ;ÿ:ÿ9ÿ9ÿ7ÿ7ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ-ÿ/ÿ0ÿ1ÿ2ÿ2ÿ3ÿ5ÿ5ÿ5ÿ4ÿ6ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ;ÿ:ÿ9ÿ9ÿ7ÿ7ÿ:ÿ:ÿ?ÿDÿJÿRÿYÿ^ÿfÿlÿsÿxÿ|ÿ~ÿ}ÿ|ÿyÿvÿsÿnÿlÿjÿjÿiÿhÿhÿiÿfÿfÿeÿfÿdÿ`ÿ^ÿZÿXÿUÿUÿXÿ\ÿaÿeÿlÿuÿzÿÿÿÿÿÿ~ÿ|ÿvÿmÿbÿWÿIÿ;ÿ4ÿ,ÿ*ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ.ÿ2ÿ8ÿAÿNÿXÿcÿoÿyÿÿÿÿÿÿÿ~ÿ}ÿtÿjÿ[ÿMÿ=ÿ0ÿ)ÿ$ÿ$ÿ$ÿ%ÿ+ÿ.ÿ1ÿ6ÿ8ÿ;ÿ?ÿBÿDÿCÿFÿKÿOÿXÿdÿmÿuÿ~ÿÿÿÿÿ}ÿwÿnÿcÿWÿOÿWÿlÿ`ÿiÿlÿpÿkÿdÿwÿÿ^ÿIÿ+ÿ!ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ"ÿ"ÿ#ÿ"ÿ%ÿ(ÿ.ÿ3ÿ;ÿ@ÿFÿMÿRÿYÿ_ÿcÿgÿnÿqÿuÿyÿ}ÿ|ÿ{ÿyÿvÿvÿvÿwÿwÿwÿuÿpÿhÿ`ÿTÿKÿDÿBÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ>ÿ=ÿ=ÿ=ÿ<ÿ;ÿ:ÿ:ÿ7ÿ5ÿ5ÿ4ÿ1ÿ1ÿ.ÿ/ÿ.ÿ.ÿ2ÿ7ÿ;ÿBÿJÿTÿ\ÿfÿnÿrÿuÿxÿwÿzÿ~ÿ}ÿ|ÿwÿsÿlÿcÿZÿNÿ?ÿ8ÿ1ÿ+ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ-ÿ.ÿ0ÿ1ÿ3ÿ3ÿ3ÿ4ÿ6ÿ7ÿ9ÿ>ÿ>ÿDÿIÿNÿSÿYÿ_ÿaÿiÿmÿtÿxÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿ~ÿ|ÿyÿvÿtÿqÿoÿoÿoÿpÿpÿsÿuÿxÿÿÿÿÿÿÿÿ~ÿ~ÿ}ÿ{ÿ|ÿ|ÿ~ÿÿÿÿÿ
ÿÿÿÿ{ÿvÿoÿfÿXÿIÿ;ÿ3ÿ/ÿ/ÿ0ÿ.ÿ.ÿ0ÿ2ÿ5ÿ:ÿBÿMÿUÿ^ÿgÿpÿwÿ|ÿÿÿÿÿ~ÿ~ÿÿ~ÿ~ÿ|ÿ{ÿxÿvÿtÿqÿmÿlÿlÿnÿrÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿwÿjÿ]ÿRÿKÿGÿEÿBÿ?ÿ<ÿ;ÿHImJJoLLqMNqOOsOOsQQrRRsRQtRRuQ~QtP~PuP~OvP~OwL}JxJ}JxH{GyG{E{C}A{@~?{>=y<;w:9u::s88q88p89p::p::p::p::q99q88s88s65s54s43t22u21u00w00y//y--y--{,+}))})~(|(~(|((|((|(~(}(~(}(~)})~)~)}))})*}**}*,},,},,},,},},~,~,~,~,~,,~,,~,~,~+~*~*|+~+|*~(~)~)~*~(~)~)~)~)}(~(}'~'|'~'|''}''}'}'~'}'~'~'~'~''~'}'~'|&~&}'~'}'~'~'~'~'~'~(~(~(~(~(~(~*~**~+-}-.|11|12z45y56y78z8
8z8
9y9
9y9
9y9:y:9y89y99y98y76{9:};@E}LSvY
`mfmgsv`y{]yv^ws`pkcheddda__^^^[^^X_
]VZ
WYTR]O~NfO~SmX}_of}pjzea`}`xqel\mOBt8/{+|**{**z*
+{+
+{,
-z28z?I{Wva~mmwfcaa{vdm_hRBo4*t%%{%(y.2u49s<@qCDnEDmFInNUj_iiqziheydr
ie]Uh\ipjyqyYsIRsiqucRWzG!w !z$#~#~""~"#~""~""~"}&({-4w;?rEKnSWj]bhdihnthyzj||j{xiwwiwwixwjsolj_nTJoD?s?}?v>|>w?}?v?}?v?}?v>}>v=|=w<|:w9|9w7}5w4~3w2~1x1~/x/}/y0}6u:}@pK}Sg\~e]krWvwUy{V}
{Zz
z^wqdg\hOBp81w-+}))(~('~'(}('})(}**|+,{-/z02z32{47|9|:}?zD}HtNRrY`odgjmrgv{hh
gghhj
jj~}kx~vkt}pll|knj{jnkympozts{{t|s~u~u~~r}}}o}}~p
ppopzrslrbRqC8r2/t..x.}.z/}2|6z;|BzMwWyasizqqw|zk~}ge~}cc}{awu_sr[mi]iiamqev~|l~s|u~t~s~s~t~u~v
xvygZ}NHG~ECy@>y<JÿJÿKÿJÿLÿLÿNÿNÿOÿOÿPÿPÿOÿOÿPÿRÿRÿQÿPÿPÿQÿQÿPÿPÿPÿOÿOÿMÿLÿJÿJÿJÿHÿGÿEÿDÿCÿAÿ@ÿ?ÿ>ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ.ÿ0ÿ1ÿ0ÿ3ÿ3ÿ3ÿ3ÿ5ÿ6ÿ7ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ8ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ8ÿ:ÿ;ÿ@ÿEÿJÿPÿYÿaÿiÿoÿuÿvÿvÿvÿtÿqÿoÿlÿfÿcÿ_ÿZÿYÿYÿXÿXÿZÿZÿXÿXÿYÿYÿXÿUÿOÿMÿJÿGÿEÿHÿPÿYÿcÿjÿsÿ{ÿÿÿÿÿÿÿ}ÿvÿmÿcÿWÿIÿ<ÿ2ÿ.ÿ-ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ0ÿ7ÿ<ÿFÿSÿ`ÿkÿuÿ~ÿ
ÿÿÿÿÿÿÿyÿrÿdÿWÿGÿ6ÿ-ÿ)ÿ)ÿ)ÿ.ÿ1ÿ5ÿ8ÿ:ÿ?ÿBÿDÿFÿEÿFÿFÿLÿNÿRÿYÿdÿoÿwÿ~ÿÿ
ÿ
ÿÿ{ÿtÿkÿ`ÿ\ÿdÿjÿkÿeÿWÿLÿQÿiÿ[ÿOÿNÿVÿ\ÿYÿTÿ&ÿ"ÿ"ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ"ÿ"ÿ"ÿ$ÿ(ÿ-ÿ4ÿ:ÿ@ÿEÿKÿQÿWÿ]ÿ^ÿbÿhÿlÿqÿvÿyÿzÿ|ÿ|ÿzÿzÿwÿwÿwÿwÿvÿuÿtÿpÿjÿ`ÿSÿIÿCÿAÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ<ÿ;ÿ:ÿ9ÿ9ÿ7ÿ5ÿ4ÿ3ÿ2ÿ3ÿ1ÿ1ÿ.ÿ.ÿ/ÿ3ÿ8ÿ?ÿIÿRÿ\ÿfÿmÿpÿtÿwÿzÿ{ÿ{ÿ}ÿ|ÿzÿvÿqÿiÿ_ÿRÿGÿ:ÿ2ÿ.ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ*ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ1ÿ3ÿ3ÿ6ÿ9ÿ;ÿ@ÿFÿKÿPÿUÿZÿ_ÿdÿhÿmÿqÿvÿzÿ}ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿzÿxÿvÿtÿoÿkÿhÿgÿeÿeÿgÿjÿmÿsÿ{ÿÿÿÿÿÿÿ~ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿxÿrÿiÿ^ÿOÿ@ÿ7ÿ1ÿ.ÿ-ÿ.ÿ.ÿ/ÿ2ÿ2ÿ6ÿ;ÿCÿMÿXÿaÿiÿqÿuÿzÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ{ÿzÿyÿwÿsÿmÿmÿkÿgÿgÿgÿjÿnÿqÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿuÿhÿ[ÿNÿIÿGÿDÿCÿ@ÿ?ÿ@ÿKKnKMoMMnMOpOOrPPrPPqRSrQQtPPuP~PvO~OwP|OxM|MyK}KzI}IzH}F{E}D|B~A{?>{==y<;v;;v99s88q99p::p::p::p::p::r99r88s88s76u55u32u11w11w11x11y00y/.y--{,,{++{*~*|*~*|**|))|*~*}(~(})~)}*~*~*}**}*+}+,},-}--}--}--},},~,~-~-~,~,-~-,~,~,~,~-~-+~+,~,~*~*~+~+~)~)~)~)~(~(~(~(}(~(}(~(~(~(~((~'''~((~((~(}'~&|&~&}'~'}(~'~'~'~(~(~(~(~(~((~)*~*+~+,},,|-/|01z23{34{66z7
9z9
8y88y88y8:y:8y89y86y65y56y78|:>BGNwW_ngmcqo^om_kh_gbd^XdWTdSRbU
U`WWYXXTVVTUP[KFeB}>p<|AuG{Qv]}fnowea
ab{fsik^PqB6w0},,{*+z+
+{*
+{+
,{-4{9B{O{[}hst~|mgbb|etji]Ol>1o++s/4t79p<>nBCmEFlFGlGIlLPlX`mjrm{k
i|gulgb^l`Sw[yNUvTEuO
SvJFzDycjndZq%!y$$$~$$~$#$&)z03v8?qELkQVi[^h`ejimiqsitwkzzkzyjwvjvwjxxkuqmk`oRJqC?r<}<t=|=v>}>w>}>w>}>w=}=w;|;w<|:w7|7w8}6w4~3w2~2x1~0y.~/z.}3x7|>sH}Qj[}c`lqXtvTxzW}~[|
z`xtckagRGn=
4u0,|)((~((~()})+}++},,|,-{..{/~1{4|6|:y?}EuIOrRWn\alfiinshuxh~h
hffgk
mm}{mwsnp|nok{inhxdlbwclgxipnyts{{v|t~s~s}~r|o}o
oo
mo}wqqepYKo=6r1-t..x-~.z1|4{8{=yFzOvXy`ri{qlv||h}}}c}|a}|`}yawwdsnakjagfcccdejhq}wn~}s|t~r~q~q~t~u~uw}tzeZRJH{GDyB@y?JÿIÿLÿLÿMÿLÿPÿNÿOÿOÿPÿPÿPÿPÿOÿPÿQÿQÿPÿPÿPÿPÿOÿOÿOÿMÿMÿMÿKÿJÿJÿHÿFÿDÿCÿBÿ@ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ8ÿ8ÿ9ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ%ÿ&ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ5ÿ6ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ5ÿ5ÿ6ÿ6ÿ9ÿ=ÿAÿEÿKÿSÿ^ÿeÿhÿhÿfÿfÿeÿdÿbÿ]ÿZÿUÿRÿSÿRÿQÿRÿRÿRÿVÿVÿWÿWÿVÿSÿPÿMÿGÿAÿ=ÿ8ÿ7ÿ9ÿAÿJÿWÿaÿkÿsÿ}ÿÿÿ
ÿÿÿÿ|ÿvÿoÿeÿYÿGÿ:ÿ2ÿ.ÿ,ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ1ÿ7ÿ?ÿKÿXÿdÿqÿ|ÿÿÿÿÿÿÿÿ|ÿuÿmÿbÿRÿAÿ4ÿ/ÿ/ÿ2ÿ6ÿ9ÿ=ÿ?ÿBÿDÿGÿFÿGÿGÿHÿHÿHÿKÿPÿVÿ[ÿeÿmÿuÿ~ÿÿÿÿ|ÿxÿnÿcÿ`ÿUÿNÿGÿ:ÿCÿ@ÿ5ÿ9ÿ:ÿ>ÿ@ÿCÿKÿTÿ]ÿSÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ&ÿ&ÿ*ÿ.ÿ5ÿ;ÿ?ÿGÿLÿQÿXÿZÿ]ÿ`ÿcÿfÿgÿkÿoÿqÿsÿuÿyÿyÿzÿyÿvÿuÿvÿuÿvÿvÿuÿpÿhÿ^ÿQÿHÿAÿ=ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ.ÿ.ÿ2ÿ8ÿ=ÿFÿRÿZÿcÿkÿqÿvÿuÿwÿ{ÿ}ÿ}ÿ|ÿ|ÿ{ÿtÿlÿbÿWÿKÿ<ÿ4ÿ1ÿ-ÿ)ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ/ÿ/ÿ2ÿ5ÿ8ÿ=ÿBÿEÿKÿPÿTÿYÿ^ÿcÿhÿmÿpÿsÿvÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿuÿqÿoÿlÿjÿgÿdÿaÿ_ÿ`ÿcÿgÿjÿnÿtÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿÿ}ÿvÿnÿcÿWÿJÿ<ÿ7ÿ3ÿ.ÿ/ÿ/ÿ.ÿ/ÿ1ÿ5ÿ8ÿ>ÿFÿOÿ[ÿcÿjÿpÿwÿ{ÿ}ÿ~ÿ}ÿ|ÿ|ÿ{ÿzÿxÿuÿrÿoÿjÿgÿfÿcÿbÿ`ÿ`ÿdÿiÿpÿvÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿtÿgÿYÿQÿKÿHÿGÿDÿBÿAÿ@ÿKKnNNnMMmOOnOOpPPqPQsQQtQ~OuP~PuP}PuO}OvL|LwM{LxM}J{H}F|E}CBA??|?={>=y<<w<<v;;u;;r::q::q::q;;p;;p::q::r88r77t74t44v32x22x11x22x22x00x00z..z-,{,,{*}*y)}){*~*}*~*}*~*}(~*})~)})}*~*}*~+}+*|*,|,,}--}--}-~.}.~.}.~.}.~.}..}.-}-~-}-~,~,},~+}+~+~+~+~+}+)}))~)~)~(~))(((('')~)(~((~((~((~((~((~(~(~(~'~'~(~(~(~(~(~((~((~)*~*+~+,},+},-|-0|21{12{24y45y67y78y87y77y77x7
6x6
5x5
4x4
3y5
6z8:|>~CJyRYo_bhdbf``b^\eZWfTRfPPcQS^TT\UUWV
VUSPWMGaC};m5|1v2{4|;{E{P|\rhqky~ca`}cwphj^nPAu6/{+|**{**z(*z*
+z+/z7<|F}T}`vmxrjeefypieYkJ;o43p69o;?oDFmGGlHHlIIkIHkILnQVo_gnq~znj}iumjd`oTUvWD};~2C}Z/|-0zAxQ{EqMLq8"w%%~%%$$'*|04w;ArFLnRVk[^h_bhdeiijijlkqtkvxlxxkssiutittkrmni_oQHq@~<r;|:u;{;v;};x:};x;};x;};x:}9x8}8x7|6x5}3v4~2x2~2x1}0y0}.y/}1y9}=uD|NkZ~abiqZuvWxzV||Z||`ytcl
chZMm@8s1-|+)~)})*~(+~++},,|,-|/~/|/2}6|:>xDHsNSnX^jciikohsug|}heefgijno|wot~poj|hofycnax^l]w_jcwgkixmpozus{|s}s}u~}u|r{o
|omnpp|uol
amT
Fm;7p40t/0x.~0x2|6z={BxH{Rs\{dpi|pmx~|i}}d~}a|{_wuaqoelgbd_a\\e\\ja}ino{vs}z
u}ur~p~q~t~u~ww|syg\}QJI{FEyCAx@LÿLÿNÿNÿMÿMÿOÿOÿOÿOÿPÿPÿPÿQÿQÿQÿRÿQÿPÿPÿPÿPÿOÿMÿLÿLÿKÿLÿJÿHÿHÿFÿDÿBÿ@ÿ@ÿ?ÿ?ÿ>ÿ?ÿ>ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ4ÿ5ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ9ÿ=ÿBÿIÿNÿUÿWÿVÿWÿZÿ[ÿ[ÿ[ÿZÿZÿVÿUÿSÿSÿSÿSÿTÿUÿUÿWÿXÿVÿUÿRÿOÿIÿCÿ=ÿ6ÿ0ÿ-ÿ-ÿ2ÿ7ÿ@ÿKÿVÿcÿmÿvÿ}ÿÿÿÿÿÿÿzÿsÿmÿcÿVÿIÿ;ÿ2ÿ-ÿ*ÿ*ÿ*ÿ+ÿ*ÿ)ÿ*ÿ+ÿ+ÿ.ÿ2ÿ9ÿDÿQÿ^ÿgÿtÿ}ÿÿÿÿÿÿÿÿ|ÿsÿkÿ_ÿQÿCÿ9ÿ7ÿ:ÿ=ÿ?ÿCÿEÿEÿGÿGÿGÿGÿIÿIÿIÿIÿIÿIÿIÿNÿWÿ`ÿjÿsÿyÿÿÿvÿhÿkÿpÿdÿ]ÿ]ÿ[ÿMÿEÿBÿWÿYÿ(ÿ+ÿ4ÿ<ÿ;ÿ7ÿ=ÿDÿKÿ'ÿ%ÿ%ÿ%ÿ%ÿ&ÿ(ÿ*ÿ0ÿ5ÿ:ÿ@ÿGÿLÿRÿWÿ\ÿ]ÿ`ÿbÿdÿdÿgÿeÿeÿfÿiÿlÿpÿsÿvÿvÿvÿsÿsÿuÿtÿuÿuÿrÿmÿfÿ\ÿPÿGÿ@ÿ<ÿ9ÿ9ÿ9ÿ:ÿ;ÿ;ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ:ÿ:ÿ7ÿ6ÿ5ÿ3ÿ4ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ.ÿ/ÿ2ÿ7ÿ<ÿDÿNÿXÿbÿiÿqÿuÿvÿxÿzÿ|ÿ|ÿ|ÿ|ÿyÿsÿnÿfÿ[ÿNÿCÿ8ÿ2ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ0ÿ1ÿ3ÿ9ÿ=ÿCÿHÿMÿQÿWÿ\ÿ`ÿfÿjÿnÿqÿvÿzÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿwÿsÿkÿgÿeÿaÿ`ÿ\ÿ\ÿ\ÿ^ÿbÿeÿhÿkÿnÿpÿtÿzÿ~ÿÿ~ÿ~ÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿ|ÿuÿlÿaÿTÿFÿ=ÿ8ÿ6ÿ4ÿ2ÿ0ÿ0ÿ2ÿ4ÿ9ÿ>ÿFÿLÿUÿ]ÿeÿmÿsÿwÿ|ÿ}ÿ|ÿ}ÿ|ÿzÿwÿuÿrÿmÿjÿeÿbÿ_ÿ[ÿYÿYÿXÿZÿ`ÿjÿmÿvÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿsÿgÿ]ÿRÿLÿJÿFÿEÿCÿ@ÿ>ÿMMnNNnNNnOOoOOpPPqPQtQQuQ}QuQ}QvO|OwN|NxM{MzKzH{I|G}D|B~A}@}@@}??{??z>=x<;v<<u<<t<<r;;q;;q::q::r;;r::r98s99u77v65w43x22x33x22x11x10y.0y11z//z//z-,z,,y,,{+~+}*~*}*~*}*~*}+}+}+}+}*}*~*}*+|++|+,},-}--}-~/}/~-}.~/}/~.}-.}..}.~-}-~-~,~-~,~+~+~+~+~*~***)~((~())))(~((~()~))~((~((~((~((~((~((~((~((~((~(~)~))~))~))~**~*,},+}+,|,-|-/{//{33z45z45y5
6y6
6y66y64x4
5x5
4x4
3x2
1y1
2z57{;?~EzKOsUVlUYh[[d\
[cZ
WdW
UeV
VcVV`WW^XWZV
SXOK^F=i7|1v,{*|+{.3{;~F|Pw]gpqxic`~`{wdpik]PrA3y.}**{)*z*)z)
*z*
,z29z@K|Wyd~nrykdbe|thmejWJlB?n>@oCFnGGlIIlJJlJJlHHlFFpEGpM}Wpc|mpw}|n}sokeri`se~dx`}VA}<C}:1*z2~.w,}0t<~Aob2n%
%v&&|(-|06v:@rFLpSXkZ^h_bjcdlddmbaoc~doj~mprtnstktsissjsslpmpe[pPDt=~9u8|8x8{8y9}:y;}:y:}:x:}:x9}9x8}8x7}5y5~4x2~2y2~1y/~0z0}/{0}1y6|;tE|MlX}acipZuvWxyYz{]||_yxbq
hg_SlD:s3.y--~+}+,~,,~,-~..~/}1~3y9=wAFsLPoV[naeljnirvgy|gfdd
e
fi~k|l}n|{osjog}bo]z[oYyZl]w_icvejhvkmmxppszvsz||s}}}s}}}s~}s|r{o|mln
pp|uok
`mS
Fm>9r85u41x1~4x7}=uD{JsS|Ypa|fmn}ukz}f~|b|{a{xbspejgea_fZXiSRmS}Vp^{humywxzv|s~q~p~q~t~v~vt}suh\yRM
H}GD{CAy@MÿMÿNÿNÿPÿPÿOÿOÿOÿOÿPÿPÿQÿRÿQÿQÿQÿQÿNÿNÿMÿMÿMÿMÿLÿLÿJÿHÿGÿEÿBÿAÿ@ÿ?ÿ?ÿ?ÿ@ÿ@ÿ>ÿ=ÿ>ÿ=ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ9ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ:ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ4ÿ3ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ.ÿ0ÿ1ÿ1ÿ1ÿ/ÿ/ÿ/ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ0ÿ1ÿ0ÿ0ÿ1ÿ4ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ1ÿ4ÿ6ÿ:ÿ>ÿDÿIÿNÿQÿUÿVÿZÿ[ÿ[ÿ\ÿ\ÿ\ÿYÿYÿXÿXÿXÿXÿWÿWÿZÿXÿWÿTÿRÿMÿGÿAÿ9ÿ4ÿ.ÿ*ÿ)ÿ+ÿ-ÿ0ÿ7ÿ@ÿKÿWÿaÿlÿuÿ}ÿÿÿÿÿ~ÿ}ÿyÿtÿmÿdÿUÿGÿ:ÿ0ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ0ÿ7ÿ=ÿEÿSÿ`ÿjÿwÿ~ÿÿÿÿ
ÿÿÿ|ÿwÿoÿhÿ`ÿTÿJÿEÿBÿDÿEÿGÿHÿHÿIÿIÿJÿJÿKÿKÿHÿGÿBÿBÿBÿBÿEÿPÿ[ÿgÿtÿtÿoÿiÿfÿlÿgÿeÿgÿeÿXÿJÿCÿ@ÿGÿ9ÿ1ÿ2ÿ3ÿ,ÿ9ÿ<ÿEÿIÿcÿKÿ%ÿ%ÿ(ÿ*ÿ,ÿ1ÿ8ÿ;ÿAÿGÿLÿRÿVÿYÿ^ÿ_ÿaÿaÿcÿdÿeÿ`ÿ]ÿ\ÿ\ÿ^ÿdÿhÿnÿqÿtÿuÿtÿsÿsÿsÿsÿsÿoÿjÿdÿZÿLÿAÿ=ÿ;ÿ8ÿ8ÿ7ÿ7ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ1ÿ/ÿ0ÿ/ÿ.ÿ.ÿ2ÿ6ÿ;ÿDÿNÿYÿbÿiÿpÿuÿvÿxÿyÿzÿ{ÿ|ÿ|ÿ|ÿxÿrÿiÿ_ÿSÿIÿ<ÿ7ÿ1ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ3ÿ7ÿ;ÿAÿFÿKÿOÿUÿ[ÿ_ÿeÿjÿnÿrÿuÿwÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿ
ÿÿÿÿÿÿ~ÿzÿuÿmÿgÿ`ÿ[ÿXÿYÿZÿ\ÿ_ÿbÿeÿhÿkÿlÿoÿqÿtÿwÿ{ÿ{ÿ}ÿ}ÿ|ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿuÿjÿ_ÿRÿEÿ?ÿ:ÿ9ÿ6ÿ5ÿ5ÿ4ÿ7ÿ;ÿAÿHÿPÿVÿ\ÿcÿjÿsÿxÿ}ÿÿÿ|ÿ|ÿ|ÿzÿwÿsÿmÿhÿ`ÿ[ÿXÿUÿRÿPÿOÿOÿSÿ\ÿfÿmÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ{ÿsÿhÿ^ÿRÿMÿHÿFÿCÿBÿ@ÿ?ÿMMjOOlQQnQPoOOpOOqPPsO~OuP~PvO{NxL|LxK{IzJ{IzG{F}D{B~A|B~@~@??}??{@@z==x=<v==u==t<<s;;s::q::q::q88q88r98r77s66u65u44w33x33x22y20y22y02z22{00{/.y.-y,,z,,|,,}++}*}*|,},|+}*|*}*}*}*~+|+~+{+,},-}-.}..}.~.}.~.}.~.}/~/}/.}..}.-},-}--},,},+}+~+~+*~**~*)~))~))~))})(}()~))~))}))})(}((}((}((}((~((~((~()~))~')~))***+}++}++|,,|--|-.|//{12{31z45y56y6
5y5
4x2
4x3
3x12x1.y/
2{56|9>~DzHLtQUnX\h^^e]^c][cYYaYY`X
W^YX]YT\SO]IBf=}6r.{+{(z(){*.{4;}FzR~^uhqkyeb~^}{bvpgh\mN@s4-{*{)*{*){**z*
*z.6{:B{P|\}huq~|n
l
gd}{funjdZkQKjFFiFHiHIkIIkJJkJImGDo><r;=t@|JtV}crt~ith~iug~cvf~dx`}^{B}3|LSxX<q@RmSDm<:pC}HpX}fiU<j$,t08u=BsEKnQWj[]jdfkeele`m\\oW~UpV|Yr]|dsj|qptvktshstirpklimbWoJ@s;~6w6}6x6|7y8|8x8|8x7}7x7}7x6}6y6}6y5}4y3}3y1~/z/~1z0/z/~-{/|1z4{:uC{MnV}_fg~o_twZwyZ{{\{{_|ybs
lgaVlJ
=q74w0/z//{//}./|02z5:w@ErKQoW\k`eikmgrtey{e~~c~b~~bdegim~o}p~zqv~qph~_oY}VnVzYm\y_lcvfjiukhmunlowqot{wr{|{t{}{s{}}r|s|rzo|nlnpq{tnj
^mR
InA<q;:t88u8;t>~ErL}SqY~`nf~mntzk|~g}}d|{awtbolfc]fVSkO~MpI|HsLzQvZwexmxxzzw}rpqsuv~t~syqtj_xTNHEC|A?z>MÿMÿOÿOÿPÿPÿPÿOÿOÿOÿOÿOÿPÿPÿOÿOÿNÿNÿNÿMÿLÿKÿKÿJÿIÿGÿFÿDÿBÿAÿ@ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ>ÿ=ÿ<ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ:ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ.ÿ/ÿ0ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ.ÿ/ÿ2ÿ5ÿ6ÿ9ÿ?ÿEÿJÿNÿTÿXÿZÿ\ÿ_ÿ_ÿ`ÿ`ÿ^ÿ\ÿ[ÿ[ÿ[ÿ[ÿYÿXÿYÿ[ÿXÿVÿQÿLÿEÿ=ÿ6ÿ/ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ0ÿ7ÿAÿLÿXÿbÿnÿuÿ}ÿÿÿÿ~ÿ~ÿ}ÿyÿsÿmÿcÿUÿIÿ9ÿ0ÿ*ÿ)ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ0ÿ7ÿ>ÿIÿWÿdÿnÿwÿ~ÿÿÿÿÿÿÿ|ÿwÿqÿkÿaÿXÿPÿIÿGÿGÿHÿHÿIÿJÿJÿJÿJÿKÿJÿEÿ@ÿ<ÿ9ÿ6ÿ9ÿ;ÿEÿSÿ^ÿpÿoÿoÿkÿiÿeÿ`ÿ]ÿYÿSÿMÿIÿJÿLÿSÿ`ÿ`ÿ_ÿ_ÿXÿKÿAÿ8ÿ+ÿ;ÿRÿnÿnÿ5ÿ(ÿ7ÿ<ÿBÿFÿNÿYÿYÿ[ÿ`ÿlÿiÿTÿMÿVÿaÿ[ÿ[ÿaÿRÿPÿPÿUÿZÿaÿiÿpÿuÿuÿtÿsÿtÿrÿpÿoÿnÿhÿ`ÿUÿIÿ?ÿ8ÿ5ÿ6ÿ6ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ/ÿ0ÿ4ÿ:ÿCÿMÿVÿ_ÿgÿoÿtÿwÿyÿwÿyÿ{ÿ{ÿ{ÿ|ÿyÿsÿmÿcÿYÿLÿ@ÿ:ÿ6ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ4ÿ8ÿ=ÿDÿIÿPÿTÿ\ÿ`ÿfÿiÿmÿrÿuÿyÿ|ÿÿÿ~ÿ}ÿ}ÿ}ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿtÿlÿdÿ]ÿXÿWÿYÿ\ÿ^ÿbÿfÿiÿkÿmÿoÿoÿpÿrÿvÿyÿ|ÿzÿ{ÿ{ÿ|ÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿtÿjÿ^ÿPÿGÿCÿ?ÿ>ÿ=ÿ;ÿ;ÿ>ÿ@ÿCÿJÿQÿXÿ]ÿeÿjÿqÿvÿ{ÿ~ÿ~ÿ}ÿ}ÿ}ÿ{ÿvÿrÿmÿfÿ`ÿYÿRÿMÿJÿEÿCÿCÿHÿQÿ\ÿeÿqÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ~ÿwÿsÿiÿ_ÿTÿMÿGÿDÿBÿ@ÿ@ÿ>ÿNNjOOlOOnNNoOOpONqP~PsQ}PuN}NvL{LyJ{JzI{H|GzF~CzA~@{@~@}@~?~>}??|??{?>z??x>=v==u<<t<<s<<s::q;;q::q;;q:9r88r77u76v56w52x33x44x33y44y33y22z21{11{0/{/-{-,z..|--},,},},|,}+|*}*|*}*}*|*}+|+~+{+,},-}-.}..}.~.}.~.}.~0}0~/}/.}..}.,}--}-,},,},,},*~,+~*)~))~))~))~))}))}))~)(~()})(}((}((}((}((}()~)(~((~()~)*~*)~))})*})*}**}*+|++|++},,|-/{//{//z01y11y1
2y2
1x2
2x1
1x0/x.0y11{49}<~@GxLRrVZl^^f_`baa``^`]]b][aY
Y`[Z^X
S_NGdB:p3|+y({)})|)){)*{.3{;{F}Sv^irtzlda~`{wfogk[Nq>3y*|)){)*{**z*
*z*/{5<{D~S|`xl}vq|nig|ixukogj\RhKHiHHkIKlKKlKJlHEnA<q82t2~1v6}AuP}_tantiRxh~dyZ~TyRGyQQzJ~QvYaobana`p]Ur?}4o<{Ik[{rjr:j6
>nILncmih
cimwlJOoKTqZ[oYWnJ~IrK}NtV|`tiqpuwkuwhtsisqkkhm^SoF<s6~4w3}3y4|4z5|5z4|4z4}4y5}5y5}5y5}3y2}2y1}2y0~0z/~.z,~.z.~/{.}/z4{:vC{LnU|^fgnasw^wz[xy]z{_||bt
ogdZjQ
Co;6w63z22{23z27v:@sFMqUYm`eikogtwgz|ec~b~c}}ee~efiln~}o}}{qx~tsp~hpd~[oY}ZnZz]maxfjiulhotohqtrkrwtpv{ys|~}t||s|}{r|s
|szp|nlnpr{roj
\mP
InDBoAAp?ApAFnIQlV]kbhkoukyh}~d~}c{xdtpejch[SkLFrC~?w=|?{DwO~[vg~sw~{{usr
stuw~t}sysrkavUMEBA}?>{>NÿNÿOÿOÿOÿOÿNÿNÿOÿOÿOÿOÿPÿPÿOÿNÿLÿLÿKÿKÿJÿJÿHÿGÿDÿBÿ@ÿ@ÿ@ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ?ÿ?ÿ?ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ5ÿ5ÿ6ÿ5ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ0ÿ1ÿ2ÿ6ÿ:ÿ=ÿCÿGÿOÿTÿYÿ\ÿ^ÿ`ÿaÿbÿcÿcÿaÿ_ÿ_ÿ_ÿ\ÿ[ÿ[ÿZÿZÿZÿUÿNÿKÿCÿ=ÿ5ÿ.ÿ*ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ,ÿ0ÿ7ÿAÿMÿXÿdÿnÿvÿ}ÿÿÿÿ}ÿ~ÿ}ÿyÿsÿlÿaÿTÿEÿ7ÿ-ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ-ÿ3ÿ9ÿBÿMÿ[ÿfÿqÿ{ÿÿÿÿÿÿÿ}ÿzÿuÿpÿiÿ`ÿWÿPÿMÿJÿJÿIÿKÿKÿKÿJÿIÿFÿAÿ=ÿ8ÿ4ÿ/ÿ-ÿ,ÿ1ÿ;ÿQÿiÿrÿsÿmÿMÿ]ÿaÿWÿUÿOÿNÿPÿMÿGÿBÿNÿVÿbÿ`ÿXÿUÿXÿ]ÿTÿ=ÿ9ÿBÿTÿ^ÿqÿZÿLÿOÿ[ÿmÿ~ÿwÿÿjÿhÿ[ÿYÿbÿmÿpÿwÿtÿdÿ\ÿPÿ@ÿAÿJÿTÿ_ÿkÿsÿuÿwÿvÿvÿtÿsÿsÿqÿkÿhÿ^ÿSÿFÿ<ÿ6ÿ4ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ2ÿ2ÿ1ÿ0ÿ/ÿ-ÿ.ÿ.ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ3ÿ8ÿ@ÿHÿSÿ\ÿhÿoÿsÿwÿxÿxÿyÿyÿzÿ{ÿ{ÿ{ÿtÿnÿiÿ]ÿSÿGÿ>ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ6ÿ:ÿ?ÿDÿJÿOÿWÿ^ÿdÿiÿoÿtÿxÿzÿÿÿÿÿÿ~ÿ}ÿ|ÿ|ÿ}ÿ}ÿ~ÿ~ÿÿÿÿÿÿÿÿÿ}ÿ}ÿ~ÿzÿyÿwÿsÿqÿnÿgÿbÿ\ÿYÿYÿ]ÿaÿcÿgÿlÿmÿpÿrÿrÿrÿsÿuÿxÿ{ÿ}ÿ}ÿzÿzÿ{ÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿrÿjÿ\ÿRÿNÿHÿFÿFÿFÿEÿGÿIÿKÿOÿWÿ^ÿcÿfÿlÿrÿxÿ|ÿ~ÿ~ÿ~ÿ}ÿ{ÿ{ÿyÿtÿmÿfÿ^ÿXÿOÿGÿAÿ=ÿ9ÿ:ÿ<ÿFÿRÿ\ÿhÿwÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿ
ÿÿÿ{ÿvÿrÿkÿaÿUÿMÿEÿAÿ@ÿ>ÿ>ÿ>ÿOOmNNmOOoOOpPPoO~PpN~MqN}LwK{JxI{H{FzF}FzD}BzB@{@?~?>~>~=~=}>~>}??{@@y>=v>>u==t==s<<q<<q;:q<<q;;p;;p88q87s77t66u55v55x55y55z54z55z44{32{22z22z10z00z0.{/0{.~-}-},},}-y,},z,|,|,{,}+{*|*{*}+{+~,|,~-|--|--|-}/|/}0|0~0|1~.}..}.-},-}-.}.-}--},,},,},+}+*~**~*)})(}((}((}((}((}()~)*~*)})(}()})(}()})(}((}()}))}))}))}))})*}*+}++}+*}))|*-|--|..z..z//y01x11x10x0/x//w0.x/0y13}7=A{FLtPVnZ
^i`
bede`dd\ab]a
a`_
\`[
Z^YY^TOcF?k9|/v*|(}'|('|&&|''|*.|5<|H|R~]ugqmz}ga`}zbvpgfYpL=v3~,|*|**{*)z)*z*,{16{=}I|W|c}mww}~q~lj|gzvkullg^iUOhKMkMMnNMmJGnB>p:4r1+u*~*v.~9vPqw~vxe|`\~G~YzY~UwQRwNLwMHwQVxY~LzL}\v_|KkG|Ah*yFiL~]iTOn[nstoosomhqm{lvevpyzvtqzjm\|Fn=}KqU|arlspwwkxxivtisqjngm]PpD:r3~1w0|0y1{1z1{1y2{2y1|1y1|0x0|1y1}1y/|0y/~-y-}-z-},z-~,z+~,z,}0y2}8x>{GoR|\he~ndrv`ww^y{^{{azzcwrfk`kV
Hp@<s96x68y9;tBGrLSn[alhmhrug}ddb~_|a}}b|}d~~}f~g~i~m~~}m}~zqz}yqw~urr~prm~gpb~]m\{]m_xbneuijnsoiqsrirtsltwvry{|v||}uz}zu{}}t|t{s{p}nlmop|rmh]kTPkMKkLLkKLjNRiX^jbehknfsxe}~c|}b||bywerlge]kQHo@:u77{7z>~GwSbvn~yyx|t~srtv~x
~w~t{qwsplbuVL|FA?~>:z9OÿOÿNÿNÿOÿOÿOÿOÿOÿOÿMÿNÿMÿLÿKÿJÿJÿIÿIÿHÿFÿCÿCÿAÿ?ÿ?ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ?ÿ@ÿ@ÿ>ÿ=ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ9ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ)ÿ*ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ/ÿ0ÿ1ÿ4ÿ5ÿ9ÿ?ÿCÿIÿNÿTÿZÿ^ÿ`ÿcÿeÿfÿfÿfÿeÿdÿaÿ`ÿ`ÿ_ÿ[ÿZÿZÿZÿWÿQÿLÿBÿ<ÿ3ÿ-ÿ*ÿ(ÿ'ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ)ÿ-ÿ1ÿ8ÿCÿMÿYÿcÿmÿwÿ|ÿ~ÿÿÿ}ÿ|ÿ|ÿxÿqÿlÿ_ÿSÿDÿ6ÿ-ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ.ÿ4ÿ9ÿDÿRÿ_ÿjÿrÿ{ÿ~ÿÿÿÿÿ~ÿ{ÿyÿuÿpÿjÿaÿXÿQÿMÿLÿLÿLÿLÿKÿGÿCÿ>ÿ:ÿ5ÿ/ÿ+ÿ(ÿ'ÿ+ÿ/ÿ5ÿTÿsÿ|ÿ|ÿ{ÿoÿ_ÿVÿHÿFÿVÿVÿQÿUÿ[ÿRÿKÿTÿ\ÿ[ÿjÿRÿIÿZÿdÿeÿZÿGÿBÿFÿKÿOÿJÿXÿfÿ_ÿlÿmÿqÿoÿÿ{ÿlÿgÿlÿ_ÿ[ÿpÿfÿgÿKÿUÿ@ÿHÿWÿdÿnÿsÿwÿwÿxÿxÿxÿwÿvÿsÿnÿgÿ]ÿPÿDÿ:ÿ2ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ-ÿ.ÿ1ÿ7ÿ>ÿGÿQÿ[ÿeÿnÿtÿxÿxÿxÿxÿyÿzÿ|ÿzÿzÿwÿqÿkÿbÿXÿNÿEÿ?ÿ=ÿ9ÿ8ÿ<ÿ@ÿDÿHÿOÿWÿ^ÿeÿiÿpÿtÿyÿ~ÿÿÿ
ÿÿÿÿÿÿÿ|ÿ{ÿ{ÿzÿ{ÿ}ÿ|ÿ~ÿ~ÿ~ÿÿ~ÿ|ÿzÿyÿxÿvÿvÿuÿtÿsÿrÿpÿnÿhÿcÿ_ÿ_ÿ`ÿaÿdÿgÿjÿnÿpÿqÿrÿsÿvÿvÿxÿzÿ}ÿ|ÿ{ÿzÿzÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿjÿ_ÿXÿTÿSÿPÿPÿPÿQÿRÿVÿZÿ\ÿbÿfÿjÿoÿqÿvÿzÿ~ÿ~ÿ}ÿ|ÿ|ÿ|ÿyÿvÿnÿhÿ`ÿYÿNÿDÿ<ÿ8ÿ4ÿ4ÿ7ÿ>ÿIÿVÿdÿpÿzÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿxÿqÿlÿbÿVÿLÿEÿ@ÿ>ÿ=ÿ:ÿ9ÿONmONmOOmNNoMLoK~MpL}KrJ|HwI{HyE{D|C{B@{?;{;<|<:~<<~<~=~=}>>}>>{?@y>>v>>u==t==s??s==s<<r<<r<;r;:r:8s77t77t77u66v66x77y54z57z66{55z55z44{43{21|11|00{10{0~/y.}-y,~-z-}-|-|-|,{,},{+}+{+~+{+~+|+~,|,-|-.|//|//|//|..}..}--}-.}..}..}-,},-},+}+*}**}**}**}*(}*(}('}'(}((}()}))})(}((}('}((}()}))}))}))})(}((}((}()}))}))})*}*)}))|))|*+|+,z,.z//y/0x/0x0.y./w/.x..z/1{66=}AGxLRqX]j`cee
f_gi]jg]fc^a
__[
Z_Z
X`WVcPFi=~8r/|*{(|'(|('|&&|&'|'+|.5|=I}Tx^~iqq~yk|e~{b||a}xgpem[Jt;0z+})(z()z)*z*){+2{8B{L~X|dyo}wr}mj|{g{zgvshnek[TjNLkJLkJGmD@n;5r0)t(}$w"}'y+6zY~bzt}w{x|o`}SL~JyZ[sTUua_zS~T~Z}\}^|]vSzTq^xlodyTiJzMe^xNeQ|`iikoursf~kuox[{ItGTqV]qR{XrStQtRqMwKrV|drntpwwkwviwwivsjkhl\PoC9r2~.w-}-{-|-|.}-|.}.|/|/z/|/z.}.z.}.z.|-{.~.{,},{,},{,},z*}+z-|/z1|7x>{GrP|Zke~ndqu`xx^xx^y|a{{cxsfngh\OlGBp?<s=BsHMpRWkagjlrfw|dcbbde}}dzzfy~yhy}zi{|{k{|{m|}{nz}wnr~sns}snr~spr~ppn}inf}bl`y`kdvelisllorqkrrsjusvnwwzr|{}v|}{uz~zu{}}t|t{s{p}nlnqp{skl
bh\
XgXWfUUfVXf[^eagdloerudy|c~~c}|b{{bytenfh^
SlJ@r83x1}3}7y@LvX}gws{}yw~spr
tv~w~u~}r|xqurpkbrV
LzC?<:
9{9
OÿNÿNÿNÿMÿMÿMÿMÿLÿKÿKÿKÿJÿIÿGÿFÿFÿEÿEÿCÿAÿ>ÿ>ÿ=ÿ;ÿ;ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ?ÿ@ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ-ÿ/ÿ.ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ/ÿ.ÿ1ÿ3ÿ7ÿ;ÿBÿGÿLÿRÿVÿ]ÿ_ÿcÿfÿgÿiÿkÿkÿjÿgÿfÿcÿaÿ_ÿ]ÿ[ÿ[ÿYÿWÿRÿIÿCÿ;ÿ4ÿ-ÿ(ÿ'ÿ'ÿ(ÿ&ÿ'ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ,ÿ1ÿ9ÿBÿNÿXÿcÿmÿvÿzÿ|ÿ~ÿ{ÿ|ÿ|ÿ{ÿwÿrÿjÿ`ÿPÿBÿ5ÿ,ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ*ÿ/ÿ5ÿ<ÿGÿUÿcÿkÿsÿzÿ~ÿ}ÿ~ÿ}ÿ}ÿ{ÿ{ÿyÿuÿpÿhÿ_ÿVÿQÿMÿJÿIÿIÿEÿAÿ<ÿ7ÿ0ÿ*ÿ&ÿ#ÿ"ÿ ÿ$ÿ*ÿ;ÿ_ÿaÿfÿkÿqÿmÿeÿLÿFÿUÿ[ÿ\ÿVÿTÿXÿ[ÿYÿOÿXÿRÿQÿZÿXÿ^ÿ`ÿgÿiÿbÿRÿ@ÿVÿNÿDÿeÿ~ÿyÿnÿfÿ_ÿVÿ^ÿKÿ:ÿ8ÿ=ÿQÿVÿHÿPÿSÿGÿFÿGÿKÿVÿdÿoÿuÿwÿxÿxÿwÿyÿyÿwÿuÿlÿiÿ\ÿPÿCÿ9ÿ0ÿ-ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ,ÿ-ÿ1ÿ7ÿ>ÿGÿPÿZÿdÿmÿqÿuÿxÿxÿxÿxÿwÿzÿzÿzÿwÿtÿoÿhÿ^ÿRÿJÿEÿ@ÿCÿFÿKÿPÿWÿ\ÿaÿhÿmÿtÿxÿ}ÿÿÿ
ÿÿÿÿÿÿÿ~ÿ~ÿ}ÿ}ÿzÿzÿxÿxÿwÿyÿzÿzÿzÿzÿzÿxÿwÿuÿsÿqÿqÿqÿqÿrÿrÿpÿoÿkÿfÿbÿbÿcÿdÿfÿiÿlÿoÿqÿrÿsÿvÿvÿwÿzÿ}ÿ}ÿ|ÿ{ÿzÿzÿ{ÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿlÿbÿ^ÿZÿYÿXÿXÿXÿZÿ[ÿ]ÿ`ÿgÿkÿpÿrÿsÿxÿ}ÿ~ÿ~ÿ~ÿ}ÿ|ÿ{ÿ{ÿyÿtÿlÿeÿ[ÿQÿEÿ;ÿ5ÿ2ÿ2ÿ3ÿ9ÿBÿPÿ_ÿjÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿxÿxÿvÿsÿlÿcÿVÿLÿCÿ?ÿ;ÿ9ÿ9ÿ9ÿOOlONlMMnNLoK~KoJ}IrH|GuF|FwD{C{B{@~=|<~<};~7}79}98~:~<~<~>~=|>>|>>{??y>>v??v>>u>>s==r>>r<<q>>q=;p;;q98q87s77u77v66v66w56w78z66z66z46z55z55z54z32z21z02z22z0~/{.~/{/~,{-},|-|-}-{-~-z-}+z+~,{,~+{+-|-~-|-~.|./|0.|..|./|//|..}..}.-}/-},-}-,}+*}**}*)}))})*}*)}))|)*|*(|()|))|))}))|)(|*)|)(|()}))}))}))})(}((}((}()}))}))})+|++|++{+*|*+{++|,-z--z-.y..x..x./w00y02|27~;}?FxLQrU\m`agcgcj
j`mm`ig_fc`a
^`[
[`ZWaSOfG=n7~0x+|((|('|&&|'&|&(|))|*/|4<}G|T}^vh~rnx{h|{`{}`|yctmheWoH:u0~)|)|)){)){*)|).|28{BN}\yh~osw|n}|j}|f|{ezxhtmjb
YjRLkIIlFBp;6s3~+u&}#x!} z#~#z(;|MUzh}f|c|\}Y}2~@~Zv[WqR]xQPZ|V~Z{UyP{^v[zWt^z`ojyelWw4kOvNjNvoio|rjdbngXpZWxM~8{=xGwCtQqWvDp:x6v?zMw[}gup~vpxxkyygxxhvtknim_OpB8r0~*w+}+|,|+}+|+|+|+|*|*{+|-{,},{+}*z*}*|)}+|,},{*}*{+})|+}+{-|-{0{7x>{FrP{Zkc}ldqwawx`xx`xzb{{cxvepig`VjNGnFHpNRoY]idigpuezgd
babb}}c{ygv}viu|tht{ujuywnv{vov|wou~rnq~qmp~qnq}ros}qoo}kmhzdncxcndugoislmoqqkrqskvrvoxvzs|z}u}}{uz~zu{}~u|uzs|p~mmopo}vik
dca
^b\[e\\e_aabe`inaswdw{c}}d}}e|{e{zfwrflehY
NmB9r42y2{4}<xF|Tvc{nxzzyu~qq
ssu~u~r|~yqxxqvtpnepX
MxC?;99|:OÿOÿOÿMÿLÿLÿKÿJÿKÿKÿIÿHÿGÿFÿEÿEÿBÿ?ÿ>ÿ;ÿ:ÿ:ÿ:ÿ9ÿ7ÿ8ÿ9ÿ9ÿ9ÿ;ÿ;ÿ;ÿ;ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ<ÿ;ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ9ÿ6ÿ6ÿ6ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ1ÿ1ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ1ÿ0ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ2ÿ3ÿ7ÿ;ÿ@ÿEÿKÿQÿVÿZÿaÿdÿeÿiÿkÿkÿlÿlÿiÿjÿiÿfÿcÿ`ÿ^ÿ[ÿYÿXÿVÿPÿIÿ@ÿ9ÿ1ÿ-ÿ*ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ'ÿ&ÿ'ÿ&ÿ&ÿ(ÿ)ÿ-ÿ2ÿ7ÿAÿNÿWÿbÿnÿuÿzÿ{ÿ|ÿ{ÿ{ÿ}ÿ{ÿwÿqÿiÿ^ÿNÿ?ÿ4ÿ+ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ0ÿ5ÿ=ÿJÿXÿaÿlÿtÿ{ÿ|ÿ}ÿ~ÿ}ÿ{ÿ{ÿ|ÿzÿuÿoÿfÿ_ÿUÿNÿJÿFÿCÿ=ÿ7ÿ2ÿ,ÿ'ÿ#ÿ!ÿ$ÿ"ÿ"ÿ#ÿ>ÿ`ÿ^ÿRÿVÿ_ÿ^ÿ\ÿUÿRÿRÿUÿZÿ\ÿ]ÿTÿ`ÿ\ÿLÿKÿZÿ_ÿ^ÿgÿSÿJÿ[ÿcÿ`ÿ\ÿVÿFÿWÿKÿRÿgÿ_ÿdÿcÿgÿeÿeÿeÿdÿQÿ4ÿ:ÿHÿEÿCÿBÿ=ÿ/ÿ9ÿCÿPÿ[ÿhÿqÿwÿxÿxÿxÿxÿxÿxÿvÿtÿnÿjÿ_ÿPÿAÿ7ÿ0ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ.ÿ2ÿ7ÿ=ÿEÿQÿ[ÿcÿjÿrÿuÿwÿxÿxÿxÿxÿxÿ{ÿ{ÿyÿuÿpÿjÿbÿXÿQÿMÿLÿRÿVÿ\ÿ`ÿfÿlÿsÿwÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿyÿwÿtÿrÿpÿoÿpÿrÿtÿsÿrÿrÿtÿvÿuÿtÿpÿpÿoÿpÿqÿrÿrÿrÿmÿjÿeÿbÿbÿcÿdÿgÿiÿlÿoÿqÿrÿsÿwÿwÿxÿzÿ}ÿ|ÿ{ÿ{ÿzÿzÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿkÿdÿbÿ_ÿ]ÿ[ÿ^ÿ^ÿaÿcÿfÿjÿoÿrÿvÿyÿ|ÿ}ÿÿÿ}ÿ{ÿ{ÿ{ÿ{ÿyÿvÿpÿkÿaÿUÿJÿ=ÿ7ÿ3ÿ1ÿ2ÿ8ÿ>ÿKÿXÿdÿsÿ}ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿ~ÿzÿyÿxÿwÿvÿtÿnÿeÿXÿLÿDÿ=ÿ:ÿ8ÿ9ÿ:ÿOOlMMlMMlKInH}GqG}GtE|DvD{Ax>|={;|:{9|8}8}7}6}78}:;~;~;~;~;~<{=={==z>=w>>v>>u>>t>>r<>r==r==q=<q<<q;;r::s98t77u77v66w77x77x77z77z88z88z76z54z55z43{33{22z11z01{1~/z/|.z.|-{-{-|-z-},z,}-z-~-|-}-|--{--{--|-.|..|-.|/.|./|.-}--}-.}.-},+}+,}+*}*)})(}()})(~&(~&)~(*~*)|))|))|))|))|))|))|)(|()})(}((}()})(}((}((}((}()}))}**|*+|++{++{++{++{++z+-z--y-.x..y.
0x02{28~<{AGvKQrUZm_dhfhdkmbln_nl\ijZda`^]`ZWbWSeOEk<}5r.{*{*|)(|('|''|''|'(|('|',|/4}=~H}Sx^~jrqwlzycz|_|{byrfpdlUDr8/z+|)({(({((|()|/3{;~G|Tx`}gtr~xn{}jf}}e}|hwsjk`jUOkEBl>9q4~+t)~$y!}!{#!| /{egz[FxLdv]VvSQvUUvRDwN~_}i~RB|NXzTWz^|V{Yx[{[sXzUqUxSpPxBoHvWo]whn^{fpcftk~^{<z2xEzCt={DsE|7x1|:}C|Q}\|hyq}vtxxlxxhvvhvtkoil^Oo@6r.~)w'}'|'|'}(|)|)|)|)|(|*})|*}*{*}){(}(z(}(z%}%{'}({)}*y*|+y-{-x1{6v>yEpNzWlb}kgqtcvx`xx`yza{{dxvgrnhc[iVSkUXj^djhmhs{b~acddc~e~}gy~wfw|ugrynimyljlxmkkylnm{pos~torqno~nno~qor}rqq}rql|ipeycn`wbnetfohqlnnpolrpumwswqxvzt}z{v{}zvz~yv{|}w{v{u}r~poprm}vgl
eab
_a^^b_`bcf`hl_ot`vya|b}c}yczzcyvdtmgg^hODm;6r43y3{:}@xO|]vizvxx|tqrssu
~t~{tw~yswvrvtqogqY
MtC={989|>OÿOÿMÿMÿLÿKÿIÿHÿGÿFÿEÿEÿCÿBÿBÿ?ÿ;ÿ:ÿ9ÿ9ÿ8ÿ7ÿ8ÿ7ÿ6ÿ7ÿ8ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ:ÿ;ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ7ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ)ÿ&ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ5ÿ8ÿ<ÿBÿGÿLÿQÿWÿZÿ^ÿcÿgÿiÿlÿmÿpÿqÿpÿqÿmÿkÿgÿcÿ`ÿ^ÿ]ÿZÿWÿTÿOÿHÿ@ÿ9ÿ3ÿ,ÿ)ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ)ÿ-ÿ1ÿ8ÿBÿNÿXÿeÿnÿuÿxÿzÿ{ÿ{ÿzÿzÿzÿvÿqÿgÿZÿIÿ=ÿ/ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ-ÿ1ÿ7ÿ?ÿMÿXÿdÿoÿwÿzÿ}ÿÿÿ~ÿ~ÿ}ÿ|ÿzÿuÿoÿfÿ[ÿPÿGÿAÿ:ÿ4ÿ/ÿ)ÿ&ÿ"ÿ!ÿ!ÿ!ÿ ÿ!ÿ7ÿYÿgÿhÿZÿ`ÿ]ÿcÿ_ÿHÿMÿWÿTÿRÿOÿQÿ^ÿlÿTÿ5ÿ@ÿIÿKÿGÿSÿ[ÿ^ÿ\ÿWÿOÿFÿEÿEÿBÿHÿKÿJÿVÿ`ÿNÿ]ÿ`ÿ`ÿeÿ[ÿQÿGÿDÿTÿGÿ9ÿ(ÿ(ÿ3ÿ:ÿCÿQÿ\ÿhÿqÿvÿxÿxÿvÿvÿvÿvÿuÿsÿoÿiÿ^ÿOÿ@ÿ6ÿ.ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ/ÿ/ÿ3ÿ8ÿ>ÿEÿQÿWÿbÿkÿqÿtÿvÿxÿwÿwÿwÿyÿ{ÿ{ÿ{ÿwÿuÿnÿiÿaÿ]ÿ\ÿ]ÿ`ÿgÿlÿpÿsÿwÿ}ÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ|ÿzÿyÿuÿtÿpÿoÿkÿjÿiÿiÿhÿjÿkÿmÿpÿsÿtÿrÿqÿoÿnÿoÿqÿrÿrÿrÿpÿkÿhÿcÿaÿ`ÿbÿbÿdÿgÿjÿlÿmÿoÿtÿwÿwÿyÿ{ÿ|ÿ{ÿzÿzÿzÿyÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿlÿeÿdÿbÿaÿaÿaÿbÿeÿhÿkÿoÿsÿvÿzÿ{ÿ}ÿ~ÿ~ÿ}ÿ|ÿyÿzÿzÿyÿvÿsÿlÿeÿYÿMÿEÿ;ÿ6ÿ4ÿ3ÿ5ÿ<ÿDÿSÿaÿmÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿyÿwÿvÿvÿvÿvÿtÿlÿdÿYÿMÿCÿ=ÿ8ÿ9ÿ;ÿ@ÿNMkL~KmJ~GmI}HoG}FqD|Bs@}Bw=}=x:~9y8~7z7~6{6~6|6}7~8~8~9~;~:~:|::z<<y<<x<<w==v>>u>>t>>r==q>>q==r>>r<<s==t==u;;v:9u99v97x88x88w99x99x88x67{88{76z66|65z44z33{33{33{2~1{0}0|.|.|-{-},{,},{,~,{,~,{,-{--{--{-.{..{..{..{./|//|..|..|--}.-},,~,,~++~))~))}))~))~*/~28-'))|))|)*|*)|))|))|))|)(|((}()}))})(}((}((}((}((}()}))}*+{*+{,,{*+{+,{,+{++z+,z,,y-/y/0y01{59~>{DIuKQpW[l_cgeiekmcop`pp^rm]he]a__\Z_XWbSLgC}<q5|/z)|('|''|''|''|&'}'&}&(|''}*/|5<}I{U}avi~sox{fzz`{|b{xeskkaQq@3w+}(}(y('{''|''|+.{6=|IxU|avm}uqy|jfd}f}yhrjj]SkE>m6/t,}&x#~ }!~!| !}#~O~h|ymhzc{abv`_vS~PuS}Xv\}Z|d~TEC=D|L~Pz[|`yV|PyV}?{B}EvB|EtF{HvPwQsPvOtZyWw\x_|TuIwOyUq\Ot,~.|4{9~CzP}\{hxq~urvvluuhuuhvtjnhl]No@4q,~(v'~&|(}(}%}%}'}'}&}&})}){(}(|)}&|%}%|%}%z&|&y&|'w(z*w,y.w1z1v4y8t?yFsQzWoa}ijosevxauu`wx`yzczyivsjmgjdcgdhglrgv{ecacdd}}i{}zhx|uiu{qjoymkkwgkdvckfwhljxmno|rot}tnr~qmm}omp~qmr~qpp}mmk|gmexapaubrbscrfpioinmnopqnuryryuzuzyzvz}ztyyt||v{v|s~pnopp
l}vfk
f`f
dac
cad
f^jk^mo_uw_y{a}}b}}c|zc{{ezwhqkhcXjMAn95s2}3x7z?|IwV{dvox{z
v}rprstv
~t|~xquuruwrvtqphq[OsB<y7:=~E~MÿLÿKÿJÿJÿHÿGÿFÿEÿCÿCÿBÿ@ÿ=ÿ9ÿ9ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ6ÿ6ÿ6ÿ6ÿ7ÿ8ÿ:ÿ:ÿ:ÿ9ÿ:ÿ<ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ=ÿ<ÿ=ÿ=ÿ=ÿ=ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ/ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ6ÿCÿCÿ4ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ/ÿ0ÿ2ÿ5ÿ8ÿ>ÿCÿGÿMÿRÿVÿ[ÿ`ÿdÿfÿiÿkÿmÿoÿqÿqÿpÿpÿoÿkÿfÿcÿ`ÿ]ÿ[ÿXÿUÿTÿOÿFÿ?ÿ8ÿ1ÿ-ÿ*ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ)ÿ-ÿ2ÿ8ÿCÿRÿ\ÿfÿpÿuÿyÿ|ÿ|ÿ{ÿ|ÿ|ÿyÿuÿoÿdÿVÿFÿ6ÿ,ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ,ÿ3ÿ;ÿCÿQÿ]ÿjÿuÿyÿ|ÿÿÿÿÿÿÿÿzÿuÿmÿ_ÿQÿFÿ<ÿ3ÿ-ÿ*ÿ%ÿ"ÿ!ÿ!ÿ!ÿ ÿ!ÿ5ÿWÿmÿÿyÿ\ÿ^ÿbÿaÿ^ÿYÿXÿJÿKÿPÿZÿbÿ[ÿ]ÿYÿMÿBÿHÿFÿGÿKÿYÿWÿPÿPÿYÿCÿ4ÿ:ÿKÿOÿRÿJÿHÿHÿFÿJÿLÿPÿPÿPÿLÿNÿ\ÿhÿVÿOÿ)ÿ-ÿ5ÿ:ÿDÿOÿ\ÿgÿrÿvÿtÿtÿuÿuÿtÿvÿuÿrÿmÿiÿ]ÿNÿ@ÿ4ÿ+ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ(ÿ(ÿ'ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ*ÿ,ÿ.ÿ0ÿ3ÿ4ÿ5ÿ9ÿ?ÿFÿQÿXÿaÿjÿoÿsÿvÿxÿuÿuÿwÿxÿyÿ{ÿ{ÿzÿxÿvÿrÿmÿjÿiÿiÿoÿrÿwÿ|ÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ}ÿzÿxÿwÿuÿsÿpÿpÿlÿiÿeÿaÿaÿbÿfÿhÿkÿnÿqÿrÿtÿtÿqÿpÿnÿoÿpÿqÿqÿpÿoÿmÿiÿeÿbÿaÿaÿbÿcÿcÿeÿfÿhÿjÿnÿrÿuÿyÿxÿzÿ{ÿ{ÿ{ÿzÿyÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿkÿfÿfÿdÿdÿdÿdÿfÿiÿlÿnÿsÿuÿwÿyÿ{ÿ{ÿ{ÿzÿzÿyÿyÿzÿ{ÿyÿvÿpÿjÿbÿWÿIÿ>ÿ8ÿ5ÿ3ÿ4ÿ8ÿ@ÿMÿ\ÿiÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿvÿtÿtÿtÿuÿvÿtÿoÿgÿ\ÿQÿEÿ<ÿ9ÿ=ÿCÿJÿONlL~InG~FmF}EoD}Ar?|=u<}9w6}6x6~6y5~4z4~3{4~6|6~5}5~5}7~7{88z::y;<x<<x==w>>u==t==t>>q??q>>q>>q??q>>q==r<<s<;s::v::w::x::x99y::y::z99z88y99y75y77z76z55z55|55|3~3|2}2|0|0|.{.|-{-}+{+}+{+~*{+~+{++{+,{,-{--{--{--|-.|./|..|//|//|.-}--},,|,,|+*|*)|)){)*}*)~)2~:9~-)|((|((|((|((|()|))|))|)(|((}()}'(}((}((}((}()})(}(+|++|,,{+,{-,{,+{,,z,,z,,z,,z.,y..y02z37|<{CGvMRoW\j_efhjemneppcrr`po_nk_gc`^[cZWeTPiJCn9}3v,|)|)|)'|'&|&'|'&|''}''}&(|''|&+|/~5}@|M}Xvc~notxgzza{|`|{dxshj\nL<u0~){'}&&}''|'(|)-}2}9~B{NYuenqw|n~hfg~hwqkgVkH;p1-v*}%z$~#z!~!~#}#\zc_tuQs>TwJX}d~UA=~O~@}L~T|SP}N~Q~:|;HxMKvHHsNJsUYq\^obephhqa|JrBtKuFsDxBwMzNwLxYyfuV~Wv+,}2{:~EzQ}]{gxq~srsrlrrhsthusjnin^OpA4t,~(y%~%|$}$}$~$}$~$}&}&~&}'}(}(}'}&}%}%|'}'z'{(y){+w-y.v/y1u2x4t7x;s@wFpOxVm`{hio}sduwbuwaxxa{}d}|eyxfvseqqdrwgy~gebce~g|{g{~xjw{ujqzpknykjgwcj_vajcvfihwjklzpns}sot~snqpmooloolp~nlm|klgzclaw_p`u`r_rarcpepfnholnqotpxs{u{v{y|xz}wuxyu}|v{v|t~qnopp
k|tel
gbf
ebeeaef^jk^nq\rt_yza{{dzyeyyfxxixsjmgj`UkG=o97s6|7y;zD{Rw`zlxxx{vrprstvtxuqssottottpqgp]QqE=w>BzG~NyLÿKÿJÿGÿGÿFÿDÿCÿBÿ@ÿ>ÿ;ÿ9ÿ7ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ3ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ9ÿ8ÿ8ÿ:ÿ:ÿ9ÿ:ÿ;ÿ;ÿ=ÿ=ÿ>ÿ>ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ@ÿ>ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ;ÿ;ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ&ÿ)ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ-ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ.ÿ/ÿ.ÿ/ÿ0ÿ4ÿ7ÿ:ÿ@ÿEÿIÿPÿVÿ[ÿ`ÿdÿgÿiÿlÿnÿqÿrÿrÿrÿoÿpÿnÿlÿhÿdÿaÿ]ÿ[ÿYÿVÿRÿLÿEÿ<ÿ6ÿ/ÿ+ÿ*ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ+ÿ5ÿ<ÿ/ÿ&ÿ(ÿ-ÿ2ÿ;ÿGÿTÿ_ÿkÿtÿyÿ{ÿ{ÿ{ÿ|ÿ|ÿ|ÿ{ÿuÿnÿaÿQÿBÿ5ÿ+ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ(ÿ)ÿ-ÿ1ÿ:ÿAÿNÿYÿaÿnÿtÿzÿ~ÿÿÿÿÿÿÿÿzÿsÿiÿZÿJÿ;ÿ/ÿ(ÿ%ÿ&ÿ%ÿ$ÿ!ÿ!ÿ"ÿ7ÿuÿYÿKÿBÿ<ÿ;ÿKÿOÿVÿaÿTÿ?ÿ1ÿ2ÿ;ÿ@ÿ?ÿEÿBÿGÿJÿAÿ8ÿFÿIÿKÿEÿIÿEÿNÿ[ÿ^ÿ`ÿbÿdÿeÿeÿcÿZÿ>ÿ=ÿIÿCÿEÿBÿ^ÿNÿCÿPÿ[ÿ\ÿJÿ(ÿ+ÿ3ÿ<ÿEÿQÿ]ÿgÿqÿsÿsÿrÿrÿrÿsÿtÿuÿsÿmÿhÿ^ÿOÿ@ÿ6ÿ,ÿ)ÿ%ÿ%ÿ&ÿ&ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ%ÿ'ÿ)ÿ+ÿ,ÿ/ÿ0ÿ1ÿ3ÿ4ÿ6ÿ:ÿ>ÿCÿIÿOÿXÿaÿiÿoÿsÿvÿxÿxÿwÿvÿxÿyÿ{ÿ}ÿ~ÿ}ÿ{ÿzÿwÿwÿwÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿ{ÿzÿxÿuÿsÿoÿoÿlÿgÿbÿ_ÿ^ÿ_ÿaÿcÿgÿjÿlÿoÿrÿsÿsÿuÿtÿqÿpÿoÿoÿoÿoÿpÿnÿoÿkÿeÿbÿaÿ_ÿ^ÿ]ÿ_ÿ`ÿ`ÿdÿdÿgÿlÿqÿtÿwÿxÿyÿ{ÿ|ÿzÿwÿxÿyÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿrÿlÿgÿeÿeÿeÿeÿfÿgÿgÿhÿmÿpÿqÿsÿvÿxÿzÿzÿyÿxÿyÿyÿxÿxÿvÿsÿlÿfÿ_ÿQÿGÿ=ÿ:ÿ9ÿ9ÿ:ÿ=ÿFÿTÿbÿmÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿuÿsÿsÿtÿtÿtÿtÿqÿgÿ]ÿQÿDÿ>ÿAÿHÿLÿPÿLLmI~GmF~EmC}Ao@~>t<~9u6~5v43x22x12z22{33{4~4|55|78}88{99x9:w;;w<=v>>v>>u??s??r??p@@p??q??q?>r?>s==s==t<:x;;x<<x::x::y:9y::z99z99z86z77z77z75z68z77{4~4{3~3{2}1{1|/|-{-|-{,~,{+~+z+},z,,{,-{--{--{--{--{-.|..|..}..}//}//}.-}-,},,}+*}**}))})(}((}((|(+|5B|<,|''|'(|((}((}((}((}((}()}((}((}((}((}((|((|()|)+|++{++{+,{++{,,{,,{,-z--z--z-.z/.x.0z15|9}>}DwHNqRYl_biehekleopesscoobmmakgbb_e\YfXShO~Ik@~8s1},{*|))|*'|'(|''|''|''|'&|+4}-'}''}+.}7}C}OvZ~hopxhz|bzza||a~ygqflWEr7,z(~'~&|'&|'(},/~4|9BwKUtakpszm}ifeg}vkl_lO;q-$v$}#z#~#{$~#%|<k|L;y45y3
=|LL}bybatUBr=
'u79v9~:wB~At=GrKJqLPmTXk]_n`aodboc^sT~<u5w@uCsExEujzmwMyIzCvD}Hw1}0~5|<FzR~[|gxqsqttosskttittkmgl^PnA5q-)v%}%{%|%}$}$~%}%~$}$~%}%~%~%~%~%|&}%{'|)y)z*w,y.u0y2s3x5s7w9t:u?tEtKqQvYnayhho}sevxbwubuwbz}e}~f~fggg
g
hhgg}~{jz}wkv|qjnznlkxembw`j`vaibudjhujklvpkpyrnu~umtrmrploplppnnlmm|jnfzbo_u^p]s]o]q\q^p`rbngojnprspvt{u{v{z{tz}ytv}zr~|u{v|t~qmnqok{rcjf`ee`dccbacbfbikaosavwcxwdwwexyfwwitpjjdl]PmD=q:}8t8z<xBvJzXveyry|w
|tqqruuv|swtqsspstptspqip_RpGCuFJuP~VtKÿIÿGÿFÿGÿDÿCÿ?ÿ<ÿ:ÿ9ÿ7ÿ5ÿ4ÿ3ÿ2ÿ1ÿ1ÿ0ÿ1ÿ0ÿ0ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ4ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ:ÿ;ÿ;ÿ<ÿ=ÿ>ÿ>ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ7ÿ6ÿ7ÿ9ÿ8ÿ8ÿ7ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ'ÿ6ÿ1ÿ*ÿ*ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ1ÿ3ÿ7ÿ:ÿAÿFÿMÿSÿXÿ\ÿ`ÿdÿiÿjÿmÿnÿpÿrÿqÿqÿpÿnÿlÿlÿhÿdÿ`ÿ]ÿ[ÿXÿWÿQÿKÿCÿ;ÿ5ÿ.ÿ+ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ%ÿ$ÿ%ÿ'ÿ(ÿ'ÿ*ÿ.ÿ5ÿ?ÿLÿVÿcÿoÿwÿzÿ|ÿ{ÿzÿzÿ|ÿ|ÿzÿtÿlÿ\ÿKÿ<ÿ/ÿ(ÿ'ÿ&ÿ'ÿ&ÿ'ÿ*ÿ+ÿ0ÿ6ÿ:ÿAÿJÿTÿ`ÿjÿrÿyÿ}ÿÿÿÿÿÿÿÿÿyÿqÿbÿRÿ@ÿ1ÿ(ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ'ÿBÿnÿYÿ>ÿ6ÿ;ÿ5ÿ8ÿAÿQÿbÿiÿoÿqÿhÿPÿ=ÿFÿ>ÿ4ÿ6ÿCÿGÿFÿPÿNÿMÿNÿVÿYÿ[ÿ^ÿ`ÿaÿbÿbÿ`ÿ]ÿXÿTÿRÿ5ÿEÿGÿGÿBÿNÿaÿMÿJÿUÿ]ÿKÿ.ÿ1ÿ6ÿ=ÿFÿRÿ]ÿiÿqÿsÿtÿtÿsÿsÿtÿtÿtÿtÿlÿhÿ]ÿOÿAÿ5ÿ-ÿ)ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ*ÿ+ÿ.ÿ0ÿ0ÿ2ÿ4ÿ5ÿ7ÿ:ÿ<ÿ<ÿAÿEÿKÿQÿYÿaÿhÿoÿsÿuÿyÿxÿwÿvÿxÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿ}ÿ|ÿyÿvÿsÿrÿmÿlÿiÿfÿcÿaÿaÿbÿbÿcÿfÿgÿkÿoÿqÿrÿtÿuÿuÿtÿrÿpÿoÿoÿoÿoÿoÿnÿlÿkÿhÿdÿ`ÿ^ÿ]ÿ\ÿ\ÿZÿZÿ[ÿ\ÿaÿcÿiÿnÿsÿvÿ{ÿ{ÿ}ÿ|ÿyÿwÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ|ÿsÿiÿeÿdÿdÿaÿ`ÿ_ÿ^ÿ`ÿcÿfÿjÿoÿsÿuÿvÿvÿuÿuÿuÿuÿwÿwÿwÿtÿpÿkÿcÿZÿNÿEÿ>ÿ<ÿ;ÿ;ÿ?ÿFÿQÿ^ÿiÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿwÿtÿsÿsÿsÿtÿtÿsÿqÿiÿaÿUÿKÿFÿJÿPÿVÿZÿKImG~EnE~Co@};q9~8s6~5t4~2t21u00w//x//z11z33z33z56z89y78w89w;;u=>u=>t??s??r@@q@@qA@q@@qAAq@@r?>r==s==t=<w<<w:;x<:x;<y;:y::y99z:9z77z88{88{88z77z77{5~5{3}3{2|1{0{.}-z-},{,~+{+~+z+}*z**{*,{,,{,,{,-{--{-.|..|.,|./|.-}--},-}-,},*}**}*)}))})(}((}(*|*'|&(|((|()|)(|((}('}''}'(}((}((}()}'(}('}))})*|**|**|*+|++{++{++{++{,,{,-{--z-.z..z.0z00x03z6:|>|D~JwLTp[_laeiijfmoenoeooenkejjefbf^\g[XhTOlG}?q7{0x+z*~)|))|*2|/(|('|''|''|'&{&&|%$}&#}(-}1;}HzU~asmumy|h|zbz|c{zfunk`Pp?2w*'}%~&(})*~/|39y=AtISr]fpovn}kheh{ltipZFp6,t&~$x#~#{%~((}[k}X<{6<}B}A}IwYesntqwtmV
KnHCpADqLPpMGpCLnTXmZ]m``mcbpa]uXTyN}F{>yBzAsCyDpKzZrTwPwLvL{9z1|17z>H{R~^|jxrtqttmusittisrklin_QpB5r-~)w%}'{'|'}%}%~&}&~%}%~%}%~&}&|%}&{'|'z(z,x.y.v0x1s3w5t6w:s<u>s@tCrHrMoRt\lbxhho}sgvxcyxaxya{~dgghhh
h
iij}|jy}vmt|pnlzimiydnbw`navakbucjesfiiuljnvqjsztkutkrqkoojoolpomn~lni|emby_l]u[p[tZr[r[s]p]s^nbsgnltrpvwzu{w|z}uz}wtz}}u|v{w|t~qopq
o~kvnfhdbcbb_`d\\d`edhkbnsbuueuufuuhsujuujsoij`jYOnD~?r>z>v?wEyKuVybumxvyu|rqqruwtypvsnrrpttpvuprlpeVoOJqMTq[~_pHÿHÿFÿCÿAÿ?ÿ<ÿ9ÿ8ÿ6ÿ5ÿ5ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ1ÿ3ÿ3ÿ5ÿ6ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ;ÿ;ÿ=ÿ>ÿ=ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿAÿ@ÿ@ÿ@ÿAÿAÿAÿAÿ?ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ.ÿ/ÿ.ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ'ÿ(ÿ(ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ3ÿ6ÿ:ÿAÿFÿLÿSÿWÿ\ÿ`ÿcÿgÿiÿkÿmÿnÿlÿlÿjÿjÿiÿiÿhÿhÿcÿ_ÿ\ÿ[ÿ[ÿVÿSÿLÿCÿ;ÿ4ÿ-ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ&ÿ&ÿ&ÿ$ÿ&ÿ%ÿ(ÿ+ÿ.ÿ7ÿCÿQÿ]ÿhÿrÿyÿ|ÿ}ÿ{ÿ{ÿ|ÿ|ÿ{ÿvÿoÿfÿWÿDÿ3ÿ)ÿ'ÿ%ÿ&ÿ(ÿ*ÿ.ÿ2ÿ6ÿ:ÿ>ÿCÿJÿRÿ\ÿeÿlÿuÿ{ÿÿÿÿÿÿÿÿÿ|ÿyÿpÿaÿOÿ>ÿ0ÿ'ÿ#ÿ#ÿ#ÿ$ÿ%ÿ)ÿDÿKÿFÿ8ÿ?ÿ?ÿ=ÿ>ÿJÿZÿdÿoÿsÿwÿrÿ[ÿTÿNÿAÿ>ÿAÿBÿMÿKÿEÿPÿTÿYÿZÿ[ÿ^ÿ`ÿ`ÿaÿ_ÿ\ÿXÿTÿMÿGÿBÿ:ÿ8ÿMÿBÿ@ÿGÿUÿZÿZÿVÿEÿ,ÿ2ÿ4ÿ7ÿ>ÿJÿTÿ^ÿjÿrÿtÿtÿtÿuÿsÿsÿsÿsÿrÿlÿiÿ^ÿPÿBÿ7ÿ/ÿ+ÿ(ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ+ÿ.ÿ0ÿ3ÿ4ÿ5ÿ7ÿ:ÿ;ÿ>ÿ@ÿBÿEÿKÿQÿTÿ\ÿbÿhÿoÿsÿvÿxÿwÿwÿvÿwÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ|ÿ{ÿvÿsÿoÿkÿhÿfÿbÿ_ÿ`ÿbÿbÿaÿcÿeÿeÿhÿkÿlÿpÿqÿsÿtÿuÿtÿrÿqÿoÿoÿnÿnÿoÿmÿnÿlÿiÿeÿbÿ^ÿ\ÿZÿYÿYÿYÿXÿYÿZÿ\ÿ`ÿgÿlÿrÿvÿzÿ{ÿ}ÿ{ÿxÿwÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿsÿlÿgÿdÿbÿ`ÿ^ÿ^ÿ\ÿ\ÿ`ÿeÿiÿlÿoÿqÿtÿtÿsÿsÿsÿsÿsÿtÿvÿsÿrÿnÿjÿ`ÿXÿNÿGÿCÿ@ÿ?ÿBÿIÿOÿ[ÿdÿpÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿuÿrÿrÿsÿtÿtÿuÿuÿsÿmÿdÿ[ÿSÿOÿQÿYÿ`ÿdÿH~EnD~Bo@~=q9~8q45r53q10r11s00w..x..y/1y11y23x56x66x99v::v;;u==t>>s??r??q@@pAApAAp?Ap@@qA@s??s?>t==t<<u==v==w==w<<w;:x::y::z::{99{99{77{99}77}66~5~5~5}31|0.{.~-{,~+{+~+{+~+{+*{*+{++{++{+~-{-~-z.-|-+|--|/-}-,},-}-.}.,|,+|+*|*)|))})(}((}''}'(}((}('}''}''}''}''~''~''~''~''~''~'(}((}((}()})*|*+|++|,,z,,{,,{,-{-,{--z--z.0z30y0.y./y00y04|7<~C}INwRWp[_jcghikhl~kgi}ihg}ghg~fhecia]i\[kYUlQIp?}8w0|,)|)){)({((|((|('|&'|'&|&&|&&}&%}%&}')}0~5}?{O~[uepowzj{zf||c||exrii[pK;t-&{')})-|15z7;v?DpKOpYbpkrpz~mifgj}unhVoE6s+&w"$z#~$~(@NJ>|7~9|9z<|JuU~bpqtpwtnlNmCBn9}7o>FoFPnRWn[]n^_ma`p^[uWRxL}F{@{85x2TtC{;oGxPlYuXu\u@|0|2~69{@K{V~a|kxsuputktsittitrklhn_QtA6u0,y)}(|'|'~'|%~'|'~&|&~&|$}&|&|'|(z*{,x-y.v0x3v5w7t:w;u=v=tBtDsFsHrOpSmXr^jdvjio{sjv~wfyyby{bdhkl}m|l|j}
l~~l~}ny}toq|nqjyhrcx_r^v`p_vaneuelfsgjhsieltkgowqgr|tit~tjsqkooknmnnmmlime|bn^y[n[vYpXtYrXsWtWrYt\o`tfmlwqpvyyt|z}y{w{}{tz}~u|v|v}tpopq
o}wlqjffcd_^d^\e]^dafdincprbtsdsshrrjs~tlt~tlr~olg~`kV}NoHzEsCxCxFuKxUs_xgvrv{z
r}qppsvx
w~zsvtpstnuuputorkne_nWVnX^od}ioGÿEÿCÿ@ÿ=ÿ9ÿ8ÿ6ÿ4ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ1ÿ2ÿ3ÿ5ÿ6ÿ6ÿ6ÿ9ÿ9ÿ:ÿ:ÿ;ÿ;ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿ@ÿAÿAÿAÿAÿAÿBÿBÿBÿAÿ@ÿ?ÿ?ÿ?ÿ>ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ2ÿ1ÿ0ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ4ÿ6ÿ9ÿ?ÿDÿKÿPÿTÿXÿ[ÿ_ÿcÿgÿhÿiÿiÿhÿfÿfÿeÿeÿdÿcÿaÿ`ÿ^ÿ^ÿ\ÿ[ÿXÿSÿNÿEÿ=ÿ4ÿ.ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ+ÿ.ÿ2ÿ<ÿIÿVÿcÿlÿwÿzÿ{ÿzÿyÿyÿ{ÿ|ÿzÿuÿkÿ^ÿNÿ?ÿ2ÿ*ÿ)ÿ)ÿ+ÿ/ÿ3ÿ7ÿ9ÿ>ÿ@ÿEÿKÿOÿWÿaÿhÿpÿxÿ~ÿÿÿÿÿÿÿÿÿ~ÿwÿnÿ_ÿNÿ=ÿ/ÿ'ÿ#ÿ#ÿ$ÿ%ÿ&ÿBÿuÿwÿDÿ&ÿ)ÿ3ÿ:ÿHÿTÿcÿpÿvÿxÿuÿnÿKÿ;ÿ.ÿ/ÿ2ÿ7ÿ.ÿ;ÿVÿYÿZÿ]ÿ^ÿ`ÿaÿaÿ]ÿ[ÿVÿRÿLÿFÿAÿ9ÿ3ÿ.ÿ-ÿ7ÿ?ÿEÿQÿOÿOÿLÿ_ÿMÿ9ÿ5ÿ5ÿ:ÿAÿJÿUÿbÿlÿuÿwÿvÿuÿuÿuÿuÿtÿuÿtÿlÿhÿ_ÿQÿAÿ6ÿ0ÿ,ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ&ÿ'ÿ(ÿ*ÿ,ÿ-ÿ.ÿ0ÿ3ÿ5ÿ7ÿ9ÿ<ÿ=ÿ?ÿAÿDÿFÿHÿJÿQÿUÿZÿ`ÿdÿjÿoÿsÿuÿvÿxÿzÿ{ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿyÿvÿrÿlÿiÿeÿaÿ`ÿ]ÿ^ÿ`ÿ_ÿaÿcÿcÿfÿgÿhÿiÿlÿlÿoÿpÿqÿsÿuÿsÿqÿpÿpÿpÿnÿmÿnÿmÿkÿgÿeÿaÿ]ÿZÿYÿWÿVÿWÿTÿSÿTÿTÿYÿ\ÿdÿjÿqÿwÿ{ÿ|ÿ|ÿzÿyÿyÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿuÿpÿfÿcÿ`ÿ^ÿ]ÿ\ÿ]ÿ]ÿ^ÿaÿfÿjÿlÿpÿrÿsÿrÿpÿpÿoÿoÿpÿrÿrÿrÿpÿmÿeÿ]ÿTÿMÿJÿGÿFÿFÿJÿNÿXÿbÿlÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿvÿtÿsÿtÿuÿvÿuÿuÿsÿnÿjÿaÿ[ÿYÿ\ÿ`ÿfÿkÿF~CnA~=o:8q67q43r11r1.s/0t//u..w..x.0x11y34x45w68w::u99u;;s==s>>r??q??q@BpAAp@@pBBpAAq@@s??s??t>>t==u==v==v==w<<x;;y;;y99z:9{98{99{88{77{66{66}4~4}1}1~1|/~-{-~-{,~+{+~*{*~+{++{++{++{++{+~+{+~+z--|--|-,|.-}--}--}--}-,|*)|**|)*|))})(}((}'(}((}('}'&}&&}&'}''}'&~&&~&&~&&~&&~&&~&'}'(}('}(*}*+|*+|+,|,,z,,{,-{--{--{.-z-.z.-z-.y..y//y03z68};~@HyNSsTYn\_jbeiheic~bka}alb}bl`~`l__m`^m^[mWRoK}At:{2z,|**|**{*){)(|((|('|''|''|'&|&&}&&}&%}%)}.3}:|D~Rv`lpswkzzfzycy{eztimcmSCs4+w**z-2y57w<?tCFqJNpU]pgnov~mkgdgylsfoVCq3'u%$z#~#~"{5ixd,y#'|/~:|CxS~bsntousmk`o6%q%*p+7mLYmZ[m_`o_ao_YqUQwK}E{={71y/.w3
3u8HoP{QkOwUsNvR|Wz4}5:{AL{Y~c|mxuwpyxluujtwktrloko`RrB9w1}-z*{*|*z(~'|'~'|'~'|'}'|'|({){+{-y/z0x1y2v4w6v8v;t=s?uArCtFpIsKoNpSnWo[oalfulkpzthv}wgxzc}dhm~p~t|uztzq{p||qy}tro|lsgzdr`x\t\x^t`v`qatcmesgkfrfjgrihktmhnwphr}tiusjqqiooinogmkii~fkd|al]xYnVtUpTtSrTrSsTqUuXo\vdnkyppuzxt{xzyxux}xtz}~u|v|u}soopq~oysllefb`f^]f\[d]^cbebjmbpqbrrdnnhnojr~sms~umq}mng}_lW{OpMxIsGtIvNrUx\sfvpv{u{r~qppsvxw}xtusqttovvnwwmuqlldk]]k_dnj~onCÿAÿ<ÿ:ÿ9ÿ8ÿ6ÿ5ÿ4ÿ3ÿ1ÿ1ÿ1ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ1ÿ1ÿ1ÿ2ÿ4ÿ5ÿ6ÿ8ÿ8ÿ8ÿ9ÿ9ÿ;ÿ;ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ@ÿ@ÿBÿAÿAÿBÿBÿBÿBÿAÿAÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ9ÿ8ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ1ÿ1ÿ1ÿ/ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ*ÿ)ÿ*ÿ*ÿ)ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ6ÿ9ÿ>ÿDÿIÿNÿSÿWÿ[ÿ^ÿ^ÿbÿeÿdÿbÿ`ÿ_ÿ]ÿ[ÿ\ÿ\ÿ]ÿ]ÿ_ÿ`ÿ`ÿ`ÿ]ÿ[ÿVÿOÿFÿ>ÿ6ÿ.ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ(ÿ+ÿ2ÿ8ÿBÿOÿ\ÿiÿqÿvÿyÿ{ÿzÿxÿxÿxÿzÿvÿoÿgÿXÿFÿ8ÿ/ÿ-ÿ-ÿ/ÿ3ÿ8ÿ;ÿ>ÿ@ÿDÿGÿJÿMÿTÿ\ÿdÿlÿvÿ}ÿÿÿÿÿÿÿÿÿÿ}ÿvÿlÿ]ÿJÿ8ÿ+ÿ&ÿ#ÿ"ÿ"ÿ ÿ3ÿLÿ=ÿ"ÿ&ÿ/ÿ4ÿ8ÿDÿRÿaÿnÿtÿwÿxÿZÿ=ÿ$ÿ2ÿ1ÿ?ÿMÿUÿZÿ[ÿ[ÿ_ÿ`ÿaÿ`ÿ]ÿYÿVÿQÿLÿEÿ?ÿ8ÿ0ÿ,ÿ-ÿ/ÿ2ÿ0ÿ.ÿ?ÿMÿQÿRÿTÿMÿIÿIÿ6ÿ5ÿ:ÿAÿMÿZÿcÿmÿuÿwÿwÿwÿvÿuÿwÿwÿtÿsÿpÿiÿ`ÿRÿBÿ9ÿ1ÿ-ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ,ÿ,ÿ-ÿ0ÿ1ÿ3ÿ4ÿ6ÿ8ÿ:ÿ;ÿ?ÿAÿCÿEÿJÿMÿOÿRÿWÿ[ÿ_ÿcÿgÿlÿqÿuÿvÿwÿyÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿyÿtÿnÿjÿgÿcÿ`ÿ\ÿ[ÿ\ÿ^ÿ`ÿ`ÿaÿcÿfÿfÿfÿgÿjÿkÿlÿnÿpÿpÿsÿvÿuÿsÿqÿqÿoÿoÿoÿlÿlÿjÿfÿdÿ`ÿ]ÿ[ÿWÿVÿUÿSÿRÿQÿPÿQÿRÿSÿ[ÿaÿiÿoÿuÿyÿ|ÿyÿwÿxÿxÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿpÿgÿaÿ_ÿ^ÿ]ÿ\ÿZÿZÿ\ÿ_ÿdÿhÿjÿmÿoÿpÿpÿpÿnÿnÿnÿoÿrÿsÿtÿtÿqÿkÿfÿ^ÿVÿPÿNÿLÿKÿMÿRÿXÿbÿkÿtÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿxÿvÿtÿuÿuÿvÿvÿwÿwÿuÿrÿnÿgÿaÿaÿbÿgÿmÿrÿ@=p;:p88q65q43r22s00s..u--v..w..w/1v11w22w56w77w::v::u<<t==t<>s??rAArAAqAAqAAqBBq@@r@@r??s??t??t>>u>>v==w==x<;y;;z;;y99z:9|98|87z88{66{66z55{55{41|1~0|-},|-|,}*{*~*{*~+z++z+}+{+~,{,}+{+,{,,{,,{,-|-,|,,},,},,},,},,},)})(}))})(}((}('}'&}'(~((~(&}&&}&&|&&|&%~%&~&&~&&~&'~''~''}'(}((})*}*+|++|+,{,,{,,z,-z--z-.z//y/0y0.y./x//x/2z04{8;A{GKtOUnW\j]_ibbja^k\}\n[}[oZ~[p]]o`apa`o^YnTLrD|;v3{,|*{**{**{*){))|('|&'|''|'(|('|''}'&}&&}&'}*/~5~?~NyXerovmxyh{zdwxdwtgoil[Mo>4u11w26w9>v@AsEGqJMoSZobhmszlkiehkxrneSo?0s(#w$~.}+|10{&$|&2|2|6~CvQ`rmtpy|liIkJ[j[YfX
XgY\k_`l__n]YqUNuJ}D{={70z--y,.z--x.^tS{RmOvNqOuRzIz4|7<zAO{Y~d}nwvxpwwkuukvvlwumokobRqD:v2~-{*{*})z)~*{**{'(|(~){*},{-{.{0z1z4z5x7y9w:w<v?uBsEtHqIrLnPqQmSoZl\nbmekirmjpyshv}yg{~ghot|wz{yzxxxvzt{{urn}isd|av^y\wZx[w]w]v_t_sbrdndqelfpgiiqkjlsojpxqjt}thsriqpiooinngkigf~bj^|[lYwVnUtTqQsPsPrOsOqOvSoYvanixopvyxtzyzyyux}ysz}~t|v|u~qoorr{pvmlb^e^]d[[b[Zc]abdiaknbqscqpeoohmokrsnt}tnp|kmdy]mVxQoQtOsOpQvVq]wfrouwwt}rqoqsv
w}w|}ytvuqt~vovvmxxkvujqjjecjehll}sm=ÿ:ÿ:ÿ8ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ1ÿ4ÿ3ÿ5ÿ6ÿ7ÿ7ÿ9ÿ9ÿ9ÿ:ÿ<ÿ<ÿ=ÿ=ÿ>ÿ?ÿ?ÿ?ÿAÿAÿAÿAÿBÿBÿAÿAÿBÿBÿAÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ7ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ5ÿ5ÿ4ÿ3ÿ5ÿ3ÿ2ÿ2ÿ/ÿ.ÿ-ÿ,ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ+ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ3ÿ3ÿ6ÿ:ÿ?ÿDÿIÿMÿSÿVÿZÿ\ÿ]ÿ_ÿaÿaÿ^ÿ\ÿZÿZÿYÿYÿXÿZÿ]ÿ_ÿbÿbÿbÿ`ÿ]ÿXÿRÿIÿ?ÿ6ÿ1ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ*ÿ-ÿ3ÿ;ÿHÿTÿ_ÿmÿsÿxÿyÿyÿxÿwÿwÿwÿuÿrÿjÿaÿQÿDÿ7ÿ3ÿ3ÿ7ÿ:ÿ<ÿ?ÿAÿCÿBÿEÿHÿLÿOÿVÿ`ÿgÿpÿzÿÿÿÿÿÿÿÿÿÿÿ}ÿtÿkÿYÿFÿ5ÿ+ÿ&ÿ&ÿ=ÿ6ÿ&ÿ%ÿ%ÿ!ÿ%ÿ+ÿ/ÿ6ÿCÿQÿ`ÿmÿtÿxÿzÿzÿxÿrÿmÿeÿ]ÿ[ÿZÿ\ÿ^ÿ_ÿ`ÿ_ÿ\ÿYÿTÿPÿJÿDÿ=ÿ5ÿ.ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ7ÿuÿXÿQÿNÿLÿJÿSÿ?ÿ/ÿ6ÿ;ÿDÿQÿ[ÿcÿmÿuÿwÿwÿuÿvÿvÿvÿvÿwÿuÿpÿjÿ_ÿRÿDÿ:ÿ3ÿ.ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ0ÿ2ÿ4ÿ6ÿ8ÿ9ÿ<ÿ>ÿ@ÿDÿGÿJÿKÿNÿRÿUÿWÿ[ÿ_ÿcÿgÿkÿnÿsÿtÿvÿxÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿuÿoÿiÿdÿ^ÿ[ÿYÿYÿZÿZÿ[ÿ^ÿ_ÿ_ÿaÿcÿdÿeÿfÿgÿiÿkÿnÿoÿqÿqÿtÿtÿrÿqÿqÿpÿoÿoÿnÿnÿjÿhÿfÿbÿ]ÿXÿWÿTÿSÿRÿOÿNÿNÿMÿLÿNÿQÿWÿaÿhÿpÿwÿzÿzÿzÿzÿzÿ{ÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿtÿlÿbÿ^ÿ[ÿZÿYÿYÿZÿZÿ]ÿaÿfÿjÿjÿmÿqÿpÿqÿoÿnÿnÿmÿoÿqÿrÿsÿsÿoÿlÿdÿ]ÿXÿSÿPÿPÿSÿUÿZÿaÿkÿsÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ|ÿyÿvÿuÿuÿuÿvÿvÿwÿwÿwÿvÿqÿjÿhÿgÿiÿkÿoÿsÿ;:p87p66o44o32q20r00r.-t..v..w..w/1v22v33v56s66s99t:;t<<s==s>>s?@rBBrBBqBBqBBqBBq@@r??r?>s>>t??t==u>>u==v<<w;:x;:y::y99z99y88y89z89y:
:x9
9u:8u99u79s75s52u0~+x*})|*}*|+}-{+}-z,,z,-y..z-,z,~,|,~)}+|+~,|+,},,},,},,},+}*)}**}))}((}((}('}'&}&&~&&~&&}&%}%%}%%}%%~%%~%%~%%~%&~&&~&(}((}()})*}**|**|+,{,,{,,z,-z-.z..z//y//y//y/0x01y23{5:~>~CGwKOpUXjZ[i_`h_^i]ZlYYoYYpZ^racqcdqb`p\VnPEr<|6w/{+|*{**{**|*)|)*|*)|))|)(|'(|('|'&}&'}''}''}(+~09~CzP]uiroyziywewvcwxevmkeWoH=r67s9;t>AsCCqDFpJMoMSo^fmlun|ljfeizno^pM<q0'w&~#}"~$~%}$}$}$|*}/{5~AxQ^smvp{|l|{jv
nhh
ch]Zi^
_k`
an]YoUOsI~Dy=|6-z+*y+
-y..x..x<
jvf}PpQvNnTr^wXw8{7}>zDP{[~f}owuwpuvkwwkvvlwumoho`QqE:w3~.|-{,~+z+,{,~,{,~-{,~+{,}-{.}1z2|3y5z6x9y;u>x@tDvGqItKnNsQkTpWj[o^iboekilmpnjrvsiv|xh{~in}uz|xwvv}
vzxyvt{mtd|_vZ|XxWzWzWyYzZwZv]t^s_rapdqemfpgljqmhoupgpyrgs~shsqipoipoinmkhhje~aj[|VmTvRnQtPqNsMsLrIsKrMtOpVv_nhzppt{yvxyx{wux}xsz}t|v|w~rporrypriga]dZXdXXbYZ`]a`fi`mparoconenohnqku}vnt|soozjncx]mXtTpRqSsUnYw]ogumrut~w
s}pprstu}v~|~v||ytx}uqw~uovvmwwkwtjrohighinkq}ul:ÿ9ÿ8ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ2ÿ3ÿ3ÿ5ÿ6ÿ6ÿ6ÿ9ÿ9ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ>ÿ>ÿ?ÿ@ÿBÿBÿBÿBÿBÿBÿBÿBÿBÿBÿ@ÿ@ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ?ÿ?ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ;ÿ:ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ9ÿ:ÿ=ÿ>ÿ@ÿ@ÿ?ÿ?ÿ@ÿAÿBÿBÿBÿAÿ>ÿ>ÿ<ÿ8ÿ3ÿ.ÿ+ÿ*ÿ*ÿ*ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ3ÿ5ÿ8ÿ<ÿBÿFÿKÿOÿRÿWÿXÿ\ÿ^ÿ^ÿ^ÿ_ÿ]ÿZÿWÿVÿVÿVÿVÿYÿ\ÿaÿcÿcÿdÿbÿaÿ\ÿVÿMÿCÿ;ÿ4ÿ-ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ-ÿ4ÿ>ÿLÿXÿgÿpÿuÿxÿxÿwÿvÿuÿvÿwÿtÿqÿhÿZÿPÿBÿ:ÿ:ÿ;ÿ=ÿ?ÿAÿDÿCÿEÿFÿJÿJÿLÿPÿZÿbÿjÿrÿzÿ~ÿÿÿÿÿÿÿÿÿÿzÿqÿgÿVÿCÿ4ÿ(ÿ&ÿ#ÿ#ÿ$ÿ2ÿ5ÿ!ÿ#ÿ)ÿ-ÿ5ÿAÿNÿ^ÿlÿuÿzÿ}ÿÿ{ÿxÿpÿlÿfÿ`ÿ^ÿ_ÿ`ÿ_ÿ]ÿYÿRÿNÿIÿBÿ:ÿ5ÿ/ÿ,ÿ+ÿ*ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ2ÿQÿbÿSÿSÿSÿOÿbÿgÿBÿ7ÿ<ÿFÿSÿ^ÿgÿpÿuÿwÿvÿuÿvÿvÿvÿvÿvÿsÿpÿiÿ]ÿQÿEÿ:ÿ3ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ-ÿ.ÿ/ÿ1ÿ4ÿ6ÿ8ÿ8ÿ:ÿ<ÿ>ÿAÿDÿHÿJÿMÿPÿSÿVÿYÿ\ÿ_ÿcÿdÿhÿmÿqÿsÿtÿwÿwÿ{ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿmÿgÿ`ÿ[ÿWÿUÿUÿVÿWÿYÿZÿZÿ\ÿ]ÿ_ÿaÿdÿeÿgÿgÿkÿmÿoÿpÿpÿrÿsÿsÿsÿrÿpÿoÿpÿoÿnÿmÿiÿgÿcÿ^ÿYÿUÿRÿPÿNÿMÿLÿKÿKÿIÿGÿIÿMÿTÿ]ÿfÿmÿsÿvÿyÿxÿwÿwÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿoÿfÿ^ÿZÿYÿVÿWÿWÿXÿYÿ]ÿaÿhÿjÿmÿmÿqÿpÿoÿnÿoÿoÿpÿqÿuÿvÿtÿsÿoÿjÿbÿ\ÿZÿXÿVÿUÿWÿ]ÿbÿiÿqÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ}ÿ|ÿyÿxÿuÿsÿuÿvÿvÿwÿwÿwÿtÿrÿoÿjÿhÿiÿoÿqÿuÿ98n76o65o44o32o11p10q.-s..u..v./w/1u23t34t56u77u::u:;u==t>>t>>s@AsBBrCCrCCqBBqBArA@r@@s?>s??t??t??u<=v==v<<w:9x9:y::y99{98|88y9
;w@DtFHrHHoHIkIKjJJhHIiFAl:3p.~,w-/w14u56s79r::p:8q8
5r41r10u/~-x+}*|-|-~-|,,|,,}+*}**})(}((}((}((}(&~&~&~&&}&%}%&~&%~%%~%%~%$~$%~%%~%%~%%~%&~&(~('|')|)*|**|**|+,{,,{,-z-.z.-z..z..y./y/0y01y13{68~:>EzKOsTWlX[h[\g^_h_\j[ZkXWoYXo\`ocdoefoc^o[TqK}Au9|2|,{*+{++{++|++|++|*)|))|))|))|)'|'(}((}((}(&}&)},2}:|GUvbmqtvjxxduuauxdvsikbmSHo=;p=?qACqDEpEFpFJpMPpW]nf}npw~~okhgi~nwmp[Ip7+t#~#z!#~*-}##~'.|5@xO~\uitqz}m~{jyuimfic`i_]m[WnQLrI~Bw9{2}-{+,z++z,
,y-/x/1w15wFWpQzKnNvWuVw<y8=yGT|^h}rwxwpxukwwkwwjvsknho_PqF:v4}2}.{..z-,{,,{,+y+-y/~1z2}3x3|5x:z:v<y>t@xCqFvIoLuOmRqTkVp[i^nafcogiklporjvvwhx|yh|k~t{{xvtsst|{yxrwkzdvZ{TwUzU{TzT|UyVzXvZxZt[t^r_pcqengphllqmjntoioyqhssirqipogqpiqmijeia[lXzTlQvPnMtKoHsGqEsDsErFuKqQxZqfylrs|wxxyx|yuw|yt||u|w}v~rrrss~wmnfe\X`WWbVWbX[a^bagj_lm`qrcomfnnho~pkr|tnszrnoxjnbv\nYrYqWoWu[k`vfolttu|ryq~qqstu~v}~|x{z|w||ytv~souwmvvlwxjwvirohmkhlpjs~wk8ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ2ÿ3ÿ4ÿ5ÿ5ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ@ÿAÿBÿBÿCÿCÿCÿCÿBÿBÿBÿAÿ@ÿ@ÿ@ÿ@ÿ?ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ9ÿ:ÿ=ÿAÿFÿJÿMÿOÿOÿOÿPÿRÿSÿSÿRÿRÿSÿRÿOÿIÿCÿ9ÿ3ÿ1ÿ2ÿ5ÿ8ÿ;ÿ>ÿ@ÿAÿDÿDÿDÿEÿCÿBÿ?ÿ<ÿ:ÿ9ÿ7ÿ3ÿ0ÿ/ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ<ÿ@ÿEÿKÿOÿSÿVÿ[ÿ\ÿ\ÿ]ÿ_ÿ^ÿ]ÿ`ÿ^ÿ\ÿ[ÿZÿZÿZÿ]ÿ`ÿaÿcÿdÿeÿdÿcÿ]ÿYÿPÿGÿ>ÿ7ÿ0ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ'ÿ+ÿ0ÿ7ÿBÿPÿ\ÿiÿqÿuÿwÿxÿvÿtÿtÿvÿvÿsÿmÿgÿYÿNÿBÿ=ÿ?ÿ@ÿBÿDÿDÿEÿFÿGÿFÿHÿKÿOÿSÿYÿaÿhÿrÿ{ÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿcÿPÿ?ÿ0ÿ'ÿ$ÿ"ÿ!ÿ#ÿ"ÿ"ÿ#ÿ'ÿ-ÿ2ÿ@ÿMÿZÿiÿtÿxÿ~ÿ~ÿ{ÿyÿuÿnÿgÿbÿ_ÿ]ÿ[ÿWÿRÿMÿFÿAÿ:ÿ2ÿ-ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ/ÿ0ÿ0ÿ2ÿ4ÿ2ÿLÿWÿKÿIÿIÿ[ÿNÿ6ÿ8ÿ@ÿJÿUÿ`ÿjÿtÿxÿxÿwÿwÿwÿwÿwÿwÿwÿtÿoÿjÿ^ÿOÿEÿ<ÿ5ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ1ÿ2ÿ3ÿ3ÿ5ÿ:ÿ;ÿ=ÿ>ÿAÿDÿHÿJÿMÿPÿRÿVÿXÿ[ÿ^ÿbÿeÿiÿmÿqÿuÿvÿxÿ{ÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿtÿnÿgÿ`ÿWÿSÿUÿRÿSÿSÿRÿTÿVÿYÿZÿ[ÿ^ÿ_ÿaÿdÿgÿjÿlÿmÿoÿoÿpÿrÿsÿsÿrÿqÿpÿoÿpÿoÿmÿkÿgÿbÿ_ÿYÿUÿQÿOÿMÿKÿHÿFÿDÿCÿAÿAÿCÿHÿOÿYÿcÿlÿrÿwÿxÿwÿwÿxÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿwÿnÿfÿ]ÿYÿYÿXÿVÿWÿYÿ\ÿ_ÿbÿhÿkÿnÿpÿoÿoÿpÿnÿoÿoÿrÿsÿtÿuÿtÿsÿpÿiÿbÿ\ÿYÿYÿYÿ[ÿ]ÿcÿiÿpÿwÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿ~ÿ|ÿ{ÿxÿ{ÿ}ÿ}ÿyÿwÿuÿvÿwÿvÿwÿwÿxÿxÿwÿtÿqÿmÿkÿkÿnÿsÿwÿ66n66o55p33p32p11r0/s-,t..u//v//u02t34t67t67u78u::u:<u<<s==s>?q?@qCCrCCrCCrBBrB@s@>s>?s?>t==t==u==v==v==w<<w;:x::y99y89{::{;=x@
EuINqRVlWWjXYi[[hZZfZ\eYTfK
Cj<:n;
>mACiIKfMPcSTbROdNJeJIhE@l=8o1/s-~,w-~+z+}+|*},~*}*~)})~*}))}((}''}''~'&~&&}%%}%%~%%~%$~$$~$$~$$~$%~%%~%%~%%~%'~''|'(|)*|*+|++|+-{--{--y..y//y//y//y1/y11y22{48<}?DyIPqRVjZ[g]
^ea
bcccc`_d^\i\\m]`pbapadpecoa]pVMrD|;w4{.|+{+*{*+{++|++|,+|+*|**|*)|+)|)(|('}''}''}'&}&(})/}5~?}LxX~frorkuueuuctvcwtepki_SlHAm@BpCEpFFpFGqGGqIMqMRq[}drm|vr}}mhfil}tpiXqE5s($x"$|#~$}#}#}&,{4@wMZthsqz}m||lyujlejc^j\WnQLrF~Aw:|2|-{**{**z+
+z,-y./x/.w.3wY
TvP}IsJuMuVv=y:}AyKV}_|l~wvyypxuluvlxxlvsmnho]OqD=v6}2}/{/.z..{.-{,.{.-{/~0{2~3z5}6x8{:v<z?uAxFrIwJoMvQlSrVjXq\g_odffpjinmrovkxv{j||}j~n
~v{~xvt
ut}xur}nxhyazZyW{S{QzP|OyR|RxRySvYwZt[u\r_raqcqhpjnlqnlpupis{shsrfrofoofpoglghdah\WjR{OlNwKpJuGqCtAq>u<q<s?rErLvVs_{iup{xx{yy|wuw|yw|x
|w}v~rrrssymnee]X`XXaWX`Z^``c`hk`mnappdppgopiq~rlu|untzrnnwikbt]lZqXqZn^ubkgvmovt|uq{ppqst~t|zux{xvxy|v|{zqw}vnuuowxmyylywjtqhmkhmqjt~vk6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ3ÿ5ÿ3ÿ4ÿ6ÿ7ÿ7ÿ7ÿ8ÿ9ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ>ÿ?ÿ?ÿ@ÿBÿBÿBÿBÿAÿAÿAÿAÿAÿAÿ@ÿ?ÿ=ÿ=ÿ?ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ7ÿ9ÿ:ÿ:ÿ<ÿ=ÿAÿGÿKÿPÿSÿXÿZÿZÿ[ÿ\ÿ^ÿ^ÿ_ÿ_ÿ_ÿaÿ_ÿ\ÿUÿMÿEÿCÿDÿIÿMÿPÿVÿXÿ[ÿ^ÿ_ÿ`ÿbÿ_ÿ_ÿ]ÿYÿVÿRÿNÿGÿBÿ=ÿ6ÿ2ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ5ÿ8ÿ=ÿAÿDÿIÿNÿRÿWÿ[ÿ]ÿ^ÿaÿbÿdÿgÿfÿfÿdÿbÿ_ÿ^ÿ^ÿ^ÿ_ÿaÿcÿcÿdÿdÿeÿcÿ_ÿ\ÿRÿJÿAÿ9ÿ1ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ,ÿ2ÿ;ÿFÿSÿ`ÿkÿrÿuÿwÿuÿuÿuÿuÿwÿvÿqÿnÿgÿYÿNÿGÿCÿBÿDÿFÿGÿFÿGÿFÿFÿFÿHÿIÿIÿLÿTÿ^ÿhÿqÿyÿ~ÿÿÿÿÿÿÿÿÿÿxÿoÿ`ÿMÿ<ÿ.ÿ'ÿ#ÿ%ÿ#ÿ$ÿ$ÿ$ÿ(ÿ/ÿ9ÿAÿNÿ\ÿgÿrÿwÿzÿ{ÿ{ÿxÿtÿlÿeÿ_ÿ\ÿUÿQÿKÿCÿ?ÿ8ÿ2ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ/ÿ.ÿ.ÿ2ÿLÿdÿXÿJÿLÿLÿXÿ=ÿ9ÿAÿKÿWÿbÿmÿvÿyÿyÿxÿwÿuÿvÿxÿxÿvÿsÿmÿgÿ]ÿOÿCÿ<ÿ6ÿ2ÿ2ÿ/ÿ1ÿ1ÿ0ÿ/ÿ.ÿ,ÿ.ÿ.ÿ/ÿ1ÿ0ÿ2ÿ3ÿ5ÿ6ÿ8ÿ:ÿ<ÿ@ÿCÿFÿIÿKÿNÿRÿSÿVÿYÿ]ÿ_ÿdÿfÿkÿoÿqÿuÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿxÿtÿpÿiÿcÿ]ÿWÿSÿQÿOÿOÿOÿPÿPÿRÿUÿVÿYÿ[ÿ\ÿ^ÿ`ÿcÿhÿjÿlÿnÿpÿpÿqÿrÿrÿqÿqÿqÿqÿqÿpÿoÿmÿiÿeÿ_ÿZÿTÿNÿMÿKÿIÿFÿCÿ@ÿ?ÿ;ÿ:ÿ:ÿ<ÿAÿJÿUÿ_ÿhÿoÿvÿwÿwÿuÿwÿyÿ~ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿqÿgÿ`ÿZÿZÿZÿYÿ\ÿ[ÿ_ÿcÿfÿhÿkÿoÿoÿpÿpÿoÿoÿoÿqÿrÿsÿuÿrÿtÿrÿmÿhÿbÿ]ÿ[ÿZÿ[ÿ^ÿdÿjÿqÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿxÿtÿtÿxÿ{ÿ{ÿxÿyÿvÿwÿwÿyÿyÿyÿyÿxÿwÿtÿqÿmÿkÿnÿpÿrÿuÿ77k44l55n43o32p11q11q0.q.1s22s11s34s57t77t78s99s::r;;r==r??r@>r@ArB@rCCrDDsBBsA@s?>s>=s>>t==s==t>>u>=v;;v::v:9x99y87z88{9:y;=xBGvKOrRVmZZl[\j]^iadgbaeb`c]WbPNcOScX\b_abcebjjbhg`ffac_d]XeVQeIAh<5o2-u+~+{-~+}+}**}*~)}(}(}(}'}'~'}'~&~&'~'&~%%~%$~$$~$#~##~#$}$$}$$~$%~%%~%%~%%}&'}''|')|)+{+,{,-z-.z./y//y//x00z0/y00{12}69=|BFxJNsTVmZ
]f]
acc
e`hj`ki`ee`c`f__o`aqcerfdqcap[WqM}Dw>{7|/{+,{,+{+,{,,|,+|+,|,+|+*|**|*+|+)|()})(}((}((}(&}'*}/7~DxQ]rirnuufuuastcxwftpkk_jTJjCDkDGmEGoFGpGFrDFsEFsM}Wtc|nsx~~pjghk{nsdpSAq1't$$z%~&|&'{,5y8DtNZpgrqvwlyykurkmel]WmQJqE~?w8|0{+{)~)z)+z+*y+-x-
.x./w/0w01wCfxV{RtNtPsVu8x=|CzMX|c~nuwxzpxxlwwlxxkvtlmgn]PrD<x6}2}3{2~2z22{2~/{-~.{/0{22z1}3z5|7y9|:w=z@uCxGrJvMoOuRlUsXiYq^faocegqlfporlvmzu~l|l~
qw|}yxuv|xvtpwk}cy\{WzSzP|O{O{OzNzPyQySxTxVwWv[t^s_rcrdpesgmmtojoxoip}qgrqfqqfqripoimhhc^gYSiNzKjHxCnBv@p=w;q9v7r7s:s@uKwTu_{ivp|uzwzw}uvw{zu}{x{w|urrrrqziriad]^ZX_Z]]\_^ae`hlboodqqfqqhprksumu}tnsypmlvglbq\m\o\r^l_sflmuspztvt}rprtt}ty}vusztwxx|wz{xry~xnwymxxmzzkxwiurfmkem~qht}uk7ÿ7ÿ5ÿ4ÿ3ÿ3ÿ4ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ0ÿ2ÿ2ÿ3ÿ3ÿ3ÿ5ÿ6ÿ8ÿ9ÿ9ÿ7ÿ7ÿ8ÿ8ÿ:ÿ:ÿ;ÿ;ÿ:ÿ<ÿ?ÿ?ÿ?ÿ@ÿ@ÿAÿAÿAÿAÿAÿBÿBÿBÿAÿ@ÿAÿ>ÿ=ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ8ÿ7ÿ7ÿ9ÿ<ÿ>ÿ@ÿDÿIÿLÿOÿRÿUÿXÿWÿXÿ[ÿ\ÿ_ÿbÿbÿdÿfÿdÿcÿ`ÿ[ÿWÿZÿ^ÿ^ÿcÿeÿgÿgÿiÿkÿkÿmÿiÿkÿkÿiÿgÿeÿcÿ_ÿYÿUÿOÿHÿ@ÿ9ÿ2ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ8ÿ;ÿ?ÿDÿHÿLÿPÿTÿVÿ^ÿ_ÿbÿdÿeÿiÿjÿkÿmÿlÿlÿiÿfÿeÿcÿaÿ`ÿbÿbÿdÿdÿeÿdÿdÿ^ÿYÿSÿJÿAÿ9ÿ2ÿ.ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ)ÿ-ÿ4ÿ@ÿMÿYÿdÿnÿsÿvÿuÿuÿtÿuÿwÿxÿwÿsÿoÿfÿYÿOÿHÿDÿDÿFÿFÿFÿFÿGÿFÿDÿAÿ?ÿ>ÿBÿFÿPÿ]ÿjÿtÿ|ÿÿÿÿÿÿÿÿÿÿ|ÿvÿjÿZÿFÿ6ÿ+ÿ%ÿ%ÿ&ÿ&ÿ)ÿ.ÿ1ÿ7ÿ>ÿFÿNÿ[ÿgÿrÿvÿwÿxÿxÿuÿrÿmÿcÿXÿRÿKÿDÿ>ÿ8ÿ0ÿ)ÿ)ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ?ÿbÿaÿQÿLÿOÿLÿEÿ<ÿBÿNÿYÿeÿoÿvÿyÿzÿxÿxÿwÿwÿxÿxÿvÿtÿmÿgÿ]ÿPÿDÿ<ÿ8ÿ4ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ-ÿ.ÿ/ÿ/ÿ0ÿ2ÿ1ÿ3ÿ5ÿ7ÿ9ÿ:ÿ=ÿ@ÿCÿGÿJÿMÿPÿSÿVÿXÿZÿ^ÿaÿcÿgÿlÿpÿrÿvÿ{ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿyÿtÿqÿkÿcÿ^ÿWÿSÿQÿPÿOÿOÿPÿRÿSÿUÿUÿWÿYÿZÿ[ÿ^ÿaÿbÿdÿhÿjÿmÿoÿoÿoÿpÿqÿtÿsÿqÿqÿqÿrÿpÿoÿlÿiÿcÿ\ÿWÿPÿLÿFÿCÿ@ÿ=ÿ<ÿ9ÿ8ÿ6ÿ5ÿ6ÿ8ÿ>ÿJÿUÿ`ÿiÿpÿuÿwÿwÿuÿxÿyÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿyÿpÿfÿbÿ^ÿ\ÿ\ÿ\ÿ]ÿ^ÿ_ÿbÿfÿiÿnÿpÿqÿrÿrÿqÿrÿrÿtÿuÿvÿvÿvÿtÿqÿkÿeÿaÿ]ÿ\ÿ]ÿ_ÿbÿhÿoÿwÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿxÿsÿqÿvÿyÿ{ÿzÿzÿxÿwÿwÿvÿxÿxÿxÿxÿxÿwÿuÿrÿoÿmÿmÿnÿqÿsÿ66k54l44m32n10p12q11q21q11s33s34s44s65r77r89s9:s99r::r<;r=?r??r@Ar@ArAAr@@r>@s@@s?>s=<t=>t==v<<v==v<<v;:v99w99x88y76z66{56y9;x?BvFJrMPnSUmUTkSTjX]ha
cfeedgecc
`b`
bccfde
gbi
iai
ial
lbk
ldi
idgddcbc`[cULfE=l5.q,~*w*}){*}*})}(}(}(~&}&~'}'~&~%%~%%~%%~%$~$#~##~##~#$~$#~##~$%~%%~%%~%$}%'}''|'(|)*{+,{,-z-.z./y//y//x//z01|567~<@zEIuM
SpWYk\_hdhdjm_np]pr\qo`jiehdkcapbcscbrcaq`[rT~MtC{<z5z/.{,,|,,|+,|,,|,,|-,|,+|++|++|++|+*|**}*(}()})(}((}(*}-2~<{HUudmosvjvvevvavycywetih_SjIEkDEmGGoFEpEBs>;u;~<x@{LxX{dvr~zslihjnwpo_Lq;.t&&x')y-0x5;s@InP\lgqlvwjwwkspkk_mUMqF?v5|.z+{(~)z)*z*+z+,y,,x-
.x./w/1w11w4NweRsKwOsUs^yMu?{Qy[|g{q~wuzzoxxkwwkwwkuslmho\OsE=w8}5|4{3~3z22{20{//{//{//z12z47y9|;w>{AuCzHrKxLoNvSlUtWiZr^fbpcegqmfqotlxm~ul|k~q~w}z{}zx}yxtqzk|f{_yX}TuR~OuOOuRRsUXtX~ZsZ|]r^{_pbycpexgojxklmynho|pgq~rftshsrjrsipniniid]hVQhK~DkA{>m;y:o7x6p2w2q4v8s?uJwVt`zkvqyvyywx{vsx{{t{v{w|uqq
rroxhog`b`]^]__`]`c^eh`jncqrerrgssiuvluxov}vnsyrmivcl^q]m\n^qbkfrlksryrqyq~qqsvw{uv}twrxtyxw|x|zxtx~xowwlxxlyykxwitqemkdm~ofr}th6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ7ÿ7ÿ9ÿ8ÿ9ÿ:ÿ:ÿ:ÿ;ÿ;ÿ<ÿ=ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿAÿ@ÿAÿAÿ@ÿ@ÿAÿ@ÿ?ÿ?ÿ>ÿ>ÿ=ÿ<ÿ<ÿ=ÿ<ÿ<ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ8ÿ9ÿ9ÿ6ÿ6ÿ6ÿ5ÿ4ÿ5ÿ8ÿ9ÿ<ÿ?ÿBÿFÿHÿKÿJÿJÿIÿJÿJÿIÿOÿTÿXÿ]ÿ_ÿeÿhÿhÿhÿeÿdÿeÿgÿfÿeÿeÿdÿdÿdÿcÿeÿeÿfÿgÿgÿfÿgÿiÿhÿhÿhÿeÿbÿ[ÿSÿLÿAÿ6ÿ0ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ4ÿ6ÿ9ÿ;ÿAÿGÿLÿQÿRÿXÿZÿ`ÿdÿgÿiÿmÿoÿqÿsÿsÿwÿuÿrÿoÿmÿjÿfÿeÿcÿaÿcÿdÿcÿbÿbÿ`ÿ[ÿUÿOÿFÿ<ÿ5ÿ1ÿ/ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ,ÿ1ÿ9ÿDÿRÿ_ÿjÿqÿvÿuÿtÿtÿtÿvÿyÿzÿzÿuÿmÿbÿXÿMÿGÿFÿFÿGÿGÿFÿEÿCÿ?ÿ;ÿ8ÿ7ÿ8ÿ<ÿFÿTÿbÿmÿvÿÿÿÿÿÿÿÿÿÿÿzÿsÿeÿTÿBÿ2ÿ)ÿ'ÿ)ÿ+ÿ0ÿ4ÿ9ÿ?ÿEÿMÿTÿ]ÿhÿqÿwÿxÿwÿwÿrÿoÿiÿ\ÿPÿHÿ@ÿ8ÿ/ÿ+ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ2ÿ2ÿ=ÿgÿhÿRÿPÿIÿZÿaÿJÿMÿ[ÿhÿsÿxÿzÿyÿxÿwÿwÿwÿwÿwÿuÿsÿmÿhÿ\ÿOÿEÿ=ÿ8ÿ5ÿ4ÿ3ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ4ÿ6ÿ7ÿ9ÿ;ÿ>ÿAÿCÿHÿKÿMÿOÿRÿTÿXÿYÿ]ÿ^ÿcÿgÿlÿpÿtÿxÿ~ÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ}ÿyÿtÿpÿjÿdÿ]ÿXÿVÿTÿUÿSÿUÿXÿXÿZÿ\ÿ]ÿ_ÿ`ÿ`ÿbÿcÿdÿhÿiÿiÿkÿmÿoÿoÿpÿqÿpÿqÿsÿrÿsÿrÿrÿsÿrÿpÿoÿmÿfÿ`ÿZÿTÿMÿCÿ=ÿ:ÿ9ÿ7ÿ5ÿ3ÿ1ÿ0ÿ2ÿ8ÿ@ÿKÿWÿaÿkÿrÿvÿwÿxÿvÿxÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿxÿoÿgÿbÿ`ÿ`ÿ_ÿ_ÿ_ÿaÿdÿfÿiÿlÿoÿqÿrÿsÿsÿtÿtÿvÿvÿwÿxÿvÿvÿrÿpÿiÿcÿ^ÿ\ÿ[ÿ^ÿbÿgÿoÿwÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿxÿsÿpÿpÿsÿyÿ{ÿ{ÿyÿxÿxÿyÿyÿxÿxÿyÿyÿxÿwÿtÿqÿoÿmÿnÿpÿrÿtÿ55m55n44q42q23p22p33q33q34r55s55t66t66t67t9;s;;s;;r<<r<>s>>r??r??rABr@@r@@r@?s>>t==t==u>=u;;u;;u::w::w::y99y88x77y55z55{44{55{7:x9<u>~=r>|>r:}9r9};rCInQWj_dghigh
ggfefebca~\fW{TfT{Xf\~\f]~]f]`dadefhbjkamj``VcLAj4-t*~)x*~*z(}(|)}(|'}&}&}&~&}&~%}%~%~%%~%$$$$#~"#~##~#"~$#~#$~%&~&&~&&}''}'(|(*{**{+,{.-z--z-/y/0z00{12}47<}?DwKPqTVlY
_eb
gbj
m_o
q]rv[uy\xw^tqbmilednddqdcscbr_~YtV|QwF|Az9{2,{,,{,,{,-z--z-,{,.{.-{--{-.{.-{,-|--|-+}**}()|))|))}))},2|7~?}Nw]iqqtkuuetucvweyygwpgg\iQIjGGmHEpGEqC=s74x4}4{9|A{O|_yk}yvo
jhho~wpkZpH9s.*u-0u4:r>
CmJQkX
_khqjxxkyvlqolh\nODq90x*|)){))z)*z**z*+y,
-y-
.x.
.x/
/v10x3;wRdtT|TrGsNvNrRzQwZ~gxtzu|zpzylvvjvvktqlngn\OrF>w:|7|6{65y55z31{10|00|00{1~3z5}6y7}:x=}AuC|GtJzMoOyRlSvVjXtYg`sbfhrjhnqslxo}un|n~q~s~v}y|z|||y|vzp~hwc\pXVpWWqVYm[]kabkcemegkjkml~nmn}pkq~ris~sfqqdrscsrerriqtirriqnhjeg_XgQJgB~<j6{5o5y3p2y0p2w7s@uKwVtazlvs|vzwzw|vvz|}t|
u}u~t~rrsr
nyeoh^db\`_\^_[bg`fjbnrcqsesshtukxxoxzqw}unrylkfuai]p]l]l`pejjqrlyst
rzp~ppsut|wtq{mupwuxyu{x{zxqw~vmvvnxxnwwlyxhutgpodpresuf5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ9ÿ;ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ=ÿ?ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ@ÿAÿ@ÿ@ÿ@ÿ@ÿ?ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ4ÿ5ÿ6ÿ6ÿ5ÿ3ÿ3ÿ0ÿ0ÿ0ÿ2ÿ9ÿ@ÿHÿQÿYÿ]ÿbÿeÿgÿeÿfÿbÿ_ÿYÿTÿOÿHÿFÿFÿFÿHÿKÿLÿPÿQÿTÿYÿ\ÿaÿfÿkÿmÿpÿnÿlÿeÿYÿLÿ>ÿ1ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ,ÿ.ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ4ÿ9ÿ<ÿAÿFÿKÿPÿUÿYÿ\ÿcÿgÿiÿnÿnÿrÿtÿvÿxÿzÿ{ÿ|ÿ{ÿwÿtÿrÿmÿjÿgÿfÿdÿcÿcÿbÿ`ÿ^ÿYÿVÿOÿHÿ@ÿ8ÿ2ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ/ÿ5ÿ>ÿKÿYÿeÿnÿsÿuÿuÿtÿuÿvÿwÿyÿyÿxÿtÿjÿ_ÿUÿMÿHÿFÿGÿFÿEÿCÿ@ÿ:ÿ5ÿ1ÿ0ÿ2ÿ4ÿ=ÿJÿZÿgÿvÿ~ÿÿÿÿÿÿÿÿÿÿÿyÿqÿaÿNÿ>ÿ3ÿ0ÿ1ÿ3ÿ9ÿ>ÿBÿHÿNÿSÿZÿ_ÿhÿqÿvÿxÿyÿxÿsÿnÿhÿ\ÿLÿ>ÿ4ÿ-ÿ(ÿ)ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ4ÿ=ÿTÿNÿQÿOÿKÿQÿLÿBÿMÿ]ÿjÿvÿ|ÿ|ÿ{ÿyÿxÿvÿvÿvÿvÿtÿqÿnÿhÿ_ÿRÿFÿ>ÿ:ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ2ÿ4ÿ6ÿ7ÿ:ÿ=ÿ@ÿBÿEÿHÿJÿMÿPÿSÿUÿYÿ[ÿ^ÿ`ÿeÿiÿlÿrÿxÿ~ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ~ÿ{ÿwÿtÿpÿjÿdÿ^ÿ]ÿXÿZÿ[ÿ[ÿ]ÿ`ÿbÿdÿeÿiÿiÿlÿlÿnÿpÿqÿqÿrÿrÿvÿvÿvÿvÿtÿtÿsÿtÿsÿrÿrÿrÿtÿuÿtÿtÿuÿsÿoÿkÿfÿ^ÿXÿPÿFÿ@ÿ;ÿ8ÿ6ÿ5ÿ3ÿ2ÿ3ÿ8ÿ@ÿKÿVÿaÿlÿsÿvÿwÿvÿxÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿoÿhÿeÿcÿ`ÿ_ÿ_ÿ`ÿcÿeÿfÿjÿnÿrÿsÿuÿtÿtÿuÿvÿwÿyÿyÿyÿwÿuÿqÿlÿeÿ^ÿ]ÿ]ÿ^ÿ`ÿfÿnÿvÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿpÿlÿoÿsÿyÿ{ÿ{ÿxÿxÿwÿvÿvÿxÿxÿwÿwÿxÿxÿuÿtÿqÿpÿoÿqÿsÿuÿ66m55n44p44p44p44p55q55q55r55s66s55s66s89s9:r::r;;s;=s==r==q??r??r??r??r??s>>s<;t<<u=;u=;u;;u::u::x::x99w88x76x54y54z32{11{/.{.0z11z0~-x-|.x,}+y,}.y5~8uAJrQYm\]laaia}\jUxMmCw:q5x5s6x8u7y8t:y;tA|FqKQmYchjlbr
vbt
mbcUcJ:k1-s.,v+*w))x'&z&&|&&}&&}%%~%$~$$##"~"#~##~##~##~#$~%%~%&~&'}''}((|(*{*+{,,{--z--z./z00|13}48?|BHvN
QoY]ja
cdfjbm
r_s
v^w
z]||`yzazverniljmedpdbrbas]|YuRzMyIzA|:z3~-{+,{,,{,-{--z--z-.{..{..{..{./{/.{/0|12|21}0-}-*|))|))|))|,/|3;}IxWcrmrlutettavvcx{dwuhoejWMjIGmFEqC@t=7w2~-{,}.}2{8}E{U{b|qx|s
lhhl{ovjpYHp:7p78l>
BiGKfO
ShZ
bhjqjw{lyxltnlh]nM>q3,x)})){)*z**z*,z,*y,
.y.
.x.0w/
0x02x3OxRYt\yRqPsRrTrKyAs[|kvuzt|zoywlvwjwvjurkmgn^QrF>x9|7}6{65y43z22{21|10|0~1{1}1z3}4y5}:x=}>uA|DtGzIqLzPnSxUkWvYh^u_gcsfijrqmwp~un|p~o~q
~t~v}w}~{vxtqojmeah__f]^eabcdebhiajkdnpgrtjuvnwvkxxhwvdvvdttcrrersitujvvjwuhtrjmil`\lSHjA:j7~4j3{1o2x6t@vLxWubzlwt|xzwwv|xsz|t|u}u~t~rrsrn~weoi^b`\_^\`b[dg_hlbprettfuulwyozzoy~zpw}tnnyikcu^j]p]m_lcohlqpxppup|ppqsu
ty}rsmyktovtzxwzyzzwsv~unvvmxxmxvkxygvuerpcquctte6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ9ÿ9ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ=ÿ=ÿ=ÿ>ÿ>ÿ?ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ,ÿ+ÿ+ÿ,ÿ.ÿ0ÿ4ÿ:ÿDÿIÿNÿRÿVÿYÿYÿXÿRÿIÿ>ÿ4ÿ0ÿ.ÿ.ÿ-ÿ,ÿ+ÿ,ÿ.ÿ0ÿ2ÿ7ÿ?ÿGÿPÿZÿcÿjÿqÿuÿwÿsÿkÿaÿUÿHÿ<ÿ7ÿ5ÿ1ÿ/ÿ+ÿ)ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ2ÿ3ÿ5ÿ8ÿ=ÿDÿKÿNÿSÿ[ÿ^ÿcÿfÿhÿlÿnÿoÿtÿwÿxÿzÿ|ÿ{ÿ}ÿyÿxÿwÿsÿpÿlÿiÿhÿeÿcÿbÿ`ÿ]ÿZÿXÿRÿKÿFÿ?ÿ9ÿ2ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ4ÿ4ÿ4ÿ3ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ,ÿ-ÿ2ÿ:ÿFÿSÿaÿkÿsÿuÿtÿtÿtÿuÿuÿwÿyÿzÿxÿoÿeÿ]ÿQÿKÿGÿFÿDÿBÿ>ÿ:ÿ5ÿ0ÿ,ÿ*ÿ*ÿ0ÿ6ÿAÿRÿ_ÿmÿyÿÿ
ÿ
ÿÿÿÿÿÿÿÿ~ÿwÿoÿaÿSÿCÿ<ÿ;ÿ>ÿBÿGÿKÿNÿSÿWÿ\ÿdÿjÿqÿwÿ{ÿzÿyÿvÿqÿjÿ^ÿOÿ?ÿ3ÿ,ÿ*ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ0ÿ1ÿ1ÿ2ÿ3ÿ?ÿbÿoÿcÿRÿJÿKÿHÿPÿSÿaÿlÿuÿzÿ|ÿzÿyÿwÿvÿwÿwÿvÿuÿrÿnÿhÿ^ÿQÿFÿ>ÿ9ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ3ÿ4ÿ5ÿ7ÿ:ÿ=ÿ>ÿCÿEÿGÿKÿMÿPÿSÿWÿWÿ[ÿ\ÿ_ÿdÿjÿpÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿzÿxÿtÿoÿiÿeÿaÿaÿ_ÿ`ÿaÿaÿcÿdÿfÿiÿiÿkÿmÿmÿnÿqÿtÿwÿvÿxÿxÿzÿzÿyÿxÿxÿxÿuÿtÿrÿrÿrÿsÿuÿvÿyÿyÿzÿzÿyÿvÿsÿpÿhÿbÿZÿQÿJÿAÿ;ÿ8ÿ6ÿ5ÿ3ÿ8ÿ@ÿLÿYÿdÿnÿtÿwÿyÿxÿzÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿwÿnÿgÿbÿbÿaÿ`ÿ_ÿaÿdÿgÿiÿmÿqÿtÿtÿtÿuÿuÿwÿyÿ{ÿ{ÿyÿxÿvÿrÿmÿfÿaÿ^ÿ]ÿ]ÿ`ÿcÿjÿtÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿqÿlÿlÿoÿtÿxÿzÿzÿwÿvÿuÿvÿwÿxÿxÿxÿvÿxÿyÿxÿuÿrÿpÿrÿvÿvÿuÿ65l55m44m44m55m55o55n55p55q55q55q77q88p::p:;q;;q<<p==p><q=?q??q>>q>>r>>s>=s==t;;u;;u::v;:v:9v::v9:w;9x88x97x6~5y5~4z3~2{1~1|0~0{/~/{.~.{-~,{+}+|+}+|+|+|*|*|+}/w5};uAGoL~LnOzNnJxCq9x3u-y*x)z)y*z*{*{*{*{*{+|.y4~<tFOmZcemrcwubsjc`
ReMDh=:k53o-*r(&u&&x&'y'&z&%{$#{$$~#"#"}##~#~##~$$~$$~$$~$%~%&}&'}'(|)){)+{,-{-.z./z//z02}58<~AJxP
To[
_kcggjmfp
rcs
uaw
waz|a|ybxwdqognklgdo`br^[sX|SuOzKzDx=6x1.x-,z,,z,-{--{-.z-.z./{/.{/.{..{./|/0|22|44|65}40}/-|-,|,*{)){*,{09|B~P|\ugpntthttbtt`vxdzygqih_ViLGkFCr@<v8~3y.}+~(|).|5~?{L~Z|i{vv
ohgkn}tqk
[lLCiA
CeG
JeNQbSVd\ceirhw{kzzkwqmi^nPAr4.x*}**|**{*+z+,z,.y..y./x/
.x.
0x0
2y2
MvnasAKnHyJpAwMwUx`|mxv|u|{pxvmvwkwvivtjoho^QtG?x:|8}5{55z54z4~1z1~2}2}0}0}1}1}1{3|3z5|5y6};w>|AtE|ErH{JoMzPkTxVhWv\h]tbihqonwn}vp|nn
n~p}~s~}}u{~ysvromhfdb`bc`ba`cd^df]gh^klcnofqsjvxlxylz{k}~ygyxduscrrgrsjuwk{~~l|~|mz~}p|xpqkocZnRImA:k6~4n4z8qAwMwXud{ows{vyzxy|yu~|u
|v}v}ussrq
owgmf]b^Z]\[]__c}fbingrsituiuukvxoyypz~wot{pmlwdj_t]j^o^mcjgpokwq|p
sws}qqsuvvw}ptkyispuuxwxyxy{wqwwnwxmwwlxvixzhywesobpreu~uf6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ>ÿ=ÿ>ÿ?ÿ?ÿ?ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ:ÿ:ÿ:ÿ:ÿ<ÿ8ÿ:ÿ:ÿ;ÿ9ÿ9ÿ9ÿ8ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ4ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ-ÿ4ÿ:ÿ=ÿ@ÿCÿAÿ@ÿ:ÿ4ÿ,ÿ+ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ.ÿ2ÿ;ÿGÿPÿ[ÿeÿlÿuÿvÿtÿqÿiÿaÿZÿRÿOÿJÿEÿ@ÿ:ÿ3ÿ,ÿ(ÿ'ÿ'ÿ*ÿ+ÿ+ÿ*ÿ(ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ-ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ1ÿ4ÿ8ÿ;ÿBÿGÿMÿTÿZÿ`ÿdÿeÿkÿnÿqÿqÿtÿwÿwÿxÿyÿ{ÿyÿvÿtÿsÿpÿmÿkÿhÿhÿdÿbÿ_ÿ]ÿXÿUÿRÿMÿGÿBÿ;ÿ5ÿ1ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ1ÿ1ÿ3ÿ5ÿ4ÿ5ÿ6ÿ5ÿ2ÿ0ÿ.ÿ-ÿ,ÿ,ÿ,ÿ*ÿ*ÿ,ÿ-ÿ1ÿ8ÿBÿMÿZÿeÿnÿsÿuÿtÿtÿtÿtÿvÿxÿyÿwÿrÿjÿ_ÿVÿLÿGÿEÿBÿ>ÿ9ÿ5ÿ1ÿ-ÿ)ÿ(ÿ)ÿ,ÿ3ÿ:ÿFÿVÿdÿrÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ{ÿqÿbÿUÿMÿHÿIÿKÿMÿOÿQÿSÿXÿ\ÿcÿgÿpÿtÿvÿwÿwÿwÿsÿmÿbÿSÿEÿ>ÿ;ÿ,ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ0ÿ2ÿ4ÿ6ÿVÿcÿJÿBÿJÿGÿKÿIÿHÿWÿ`ÿmÿvÿ|ÿ|ÿ{ÿzÿwÿwÿxÿxÿwÿvÿtÿoÿhÿ_ÿRÿHÿ@ÿ:ÿ8ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ0ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ5ÿ6ÿ8ÿ;ÿ>ÿAÿDÿFÿIÿKÿNÿQÿSÿUÿXÿ\ÿbÿhÿpÿwÿ}ÿÿÿÿÿÿ
ÿÿÿÿÿ~ÿ}ÿ{ÿ|ÿyÿtÿpÿlÿgÿcÿbÿcÿcÿcÿcÿ`ÿcÿdÿeÿfÿhÿiÿjÿmÿoÿpÿqÿrÿtÿwÿyÿ{ÿ|ÿ|ÿzÿzÿxÿuÿsÿrÿrÿrÿsÿvÿyÿ{ÿ~ÿ}ÿ~ÿÿÿÿ|ÿyÿrÿkÿdÿZÿRÿHÿ?ÿ7ÿ2ÿ4ÿ8ÿAÿMÿZÿfÿoÿvÿxÿyÿyÿ{ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿwÿnÿgÿbÿ^ÿ\ÿ[ÿ]ÿ_ÿbÿeÿjÿoÿrÿsÿvÿvÿuÿvÿxÿzÿ|ÿ{ÿxÿwÿsÿnÿhÿaÿ^ÿ\ÿ\ÿ^ÿbÿhÿpÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿtÿlÿjÿjÿoÿtÿwÿyÿyÿwÿwÿwÿvÿwÿvÿvÿwÿxÿzÿ{ÿyÿwÿtÿsÿtÿuÿvÿwÿ55k66l77m55m55l56m76n66p77q77q77q88q::p::p:;q;;q==r<=r==q>>q>>q==q<<q<<r<<t;;t;;u;;v99v<;v::w88w87x77x65x55x5~5y2~2z3~/{/~/|.~,{-~-{-~-|+~+|)})|)})|*|){(|({)|(z)|,x1{5v6{9v8x4v0x,x*y(y({(z*{*|*{*{)|)|)|)|)})|+}-y3~9tEPo[ekquhvudr
lbg
b`^YbQMdF>g80i,-j23i32j2
-l'%q$$v##|#~#}#~#~#~##~#$~$%~%%~%%~''}&(}()|)*{+,{--{-.z//z01{47~:~?FyMSsZ^ndgijlgn
pdqrbvwawwbusfrogm~kli~fnb|_p\|[qX{TuRzNyHxD|?w73y0-z--z-,z,-z--z-.z./z-.z0/{..{/1{11{12z2
2z2
4{46{76z41z/-z--z-+{++{+.{15|>~J}UvdlrrtltsfsscvwcxwhsljaWkLFnBAs<8w3}/z,|)~'{'*{28zCR{a{o~zyrjfh
l}nwlj_
UeONbONbQRcUXe[`efjhrwlwwmwtnmbpTIrNJu-})|)|)*{*+z+-z--y..y./w/
/w/
0x16{Ql}G7y<}9t?xIrIuJwVza|mzw{u|{pyxkwyixwivujqlnaRsG@x<}9}6}65{54|3~3|3~3}3~1}1~1|1{3~3x4~4x3}6y8y9|<w>|AsE}GpJ|MoNzPlRwTj[u`khsqoyq~vn{n}nmn~}p|}zqy~vqrnliedbaa`a`aaaaaab~`aa|bbdzeggygkhxjnnyrpsyupxzzmz|zhxvdtrcrrfrtivwky~{oz}~p}s|u~~{rqkobZnQEl<5k4|9oDwPu[uf{pww{yyywy|zt~|u|v}v}u~s~stsmyepj\e\Y[Z[Z\a`|dfj~ngs~uku~vkvxly~zo{~{qx}vpq{kldw_h[rYiYmZj`khnsm{ptrzs~qqsuv}vs}krhyjunusyxxyvx{wqwunuvlwxjvxj{{izwestbuudv~xb6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ<ÿ=ÿ=ÿ=ÿ>ÿ>ÿ>ÿ>ÿ=ÿ=ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ*ÿ,ÿ-ÿ0ÿ0ÿ-ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ-ÿ2ÿ8ÿCÿPÿ\ÿiÿpÿuÿwÿuÿtÿpÿlÿlÿfÿaÿ[ÿTÿKÿEÿ?ÿ:ÿ;ÿ?ÿ@ÿAÿ?ÿ?ÿ7ÿ1ÿ,ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ3ÿ7ÿ:ÿ>ÿCÿJÿPÿWÿ^ÿcÿfÿkÿlÿmÿoÿpÿsÿtÿtÿtÿsÿrÿpÿnÿkÿiÿfÿdÿaÿ]ÿ\ÿZÿXÿSÿPÿMÿIÿDÿ@ÿ;ÿ6ÿ2ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ5ÿ6ÿ5ÿ2ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ0ÿ3ÿ;ÿGÿSÿ`ÿjÿrÿtÿsÿrÿsÿsÿtÿvÿwÿwÿsÿlÿbÿXÿNÿFÿ@ÿ?ÿ9ÿ5ÿ0ÿ,ÿ,ÿ)ÿ'ÿ'ÿ)ÿ.ÿ6ÿ?ÿLÿ[ÿiÿuÿ}ÿÿÿÿÿÿ~ÿÿÿÿÿÿ}ÿsÿgÿ]ÿUÿOÿOÿPÿUÿUÿWÿZÿ\ÿ^ÿcÿgÿlÿrÿvÿvÿvÿsÿkÿ_ÿRÿPÿGÿBÿ/ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ2ÿ5ÿaÿÿ]ÿ9ÿ8ÿIÿQÿOÿOÿKÿUÿcÿlÿxÿ}ÿ~ÿ}ÿyÿxÿxÿxÿxÿzÿxÿuÿqÿlÿbÿTÿGÿ@ÿ<ÿ9ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ4ÿ6ÿ8ÿ8ÿ7ÿ7ÿ5ÿ5ÿ6ÿ7ÿ9ÿ<ÿ>ÿAÿDÿFÿHÿJÿLÿNÿPÿXÿ^ÿgÿoÿvÿ~ÿÿÿÿÿ
ÿÿÿÿ~ÿ~ÿ}ÿ{ÿyÿwÿvÿsÿmÿgÿdÿaÿ`ÿ^ÿ]ÿ^ÿ]ÿ\ÿZÿ[ÿZÿ[ÿZÿ\ÿ]ÿ`ÿcÿbÿdÿfÿiÿmÿqÿuÿvÿwÿwÿtÿsÿqÿqÿrÿrÿrÿtÿvÿvÿxÿzÿxÿ|ÿ~ÿÿÿÿÿ~ÿzÿrÿjÿaÿYÿOÿEÿ<ÿ9ÿ;ÿFÿQÿ]ÿhÿqÿxÿzÿzÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿtÿlÿfÿ^ÿXÿWÿYÿ[ÿbÿhÿmÿrÿtÿtÿtÿuÿvÿxÿyÿzÿ{ÿyÿuÿtÿnÿhÿ`ÿ[ÿZÿYÿXÿ[ÿbÿkÿtÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿrÿlÿiÿkÿnÿsÿyÿyÿxÿwÿvÿwÿvÿuÿtÿuÿvÿxÿ{ÿ{ÿzÿwÿtÿsÿuÿvÿuÿwÿ77k75j77j55k77l66m76o55q88r67r88q99q89p::p9<q==q;>r>?r<<r==r==s<<s==t;;u:9u99u99t99t99v98v88w98w96x55x5~5x3~3x2~3y2~2z/}.{.},{-},|,}+|+}*{*}*|*}*|)|)|(~(|'~(|(}'|&}'|(|({)|){'{'|({(|'{({){){({'|){)|(}(|(}(|(}({(}(z*~.v4~=sGSq^hmrwiyveuuaspbnhdb]`VQ`NO]PQ_SSbOHdA
7g,%n#}!w""z$$~#~##~$%~%%~%&}&'}''|''|(){*,{,,{-.{.0{11|25}8=B{IOvV\qaflikjm
ohpreutbrqaniei~fjf}cl]|ZqYyVsTyPvLyJwGyE|Bw>~8w20w..y.-z--z--z--z--z--z--|.0|01{11{13{34z4
2y2
2z11z3
4y4
2z2
0z0
0z0
.z-
,{.
-{-
-z/3{9E~P{_gsqtlssfrsdtvevugsnjdYlNEpA=v8~4{.}+~+})'{'){.3{:G{W}g|pyz~slf}~ehlzkoec\
V`TSaUVcX
ZfY[h\cmioosvoutnmaoXSt7~>y7~+~,{,+z+,y,+y,-x-.x./w00w01x36xUw{gI{J|IuTsTtQrOxYwd|pzx}}u}|pyylxzlyzmyynslncUqG@x<~9}6}64|44|32|21}3}4~6x9=t<=p<<q;8w6|5y7z9{<v=|@sC}DoF|InJzPnUv]ngsrrypzn}o
nn~n}{qzxrv~vnp~lkebf^\gYYgU|VjUzTmSxSnSwSrRvTtTtUuXrZv]qaufrkpnunkqyqfq~rbrrbrrerrissls~uox~xsz|}w|{|x}xrrinbVjKBi>~?nJxSt_wj{qxy{{z{vz|~t|
u|v~w~t~r~str~l}cwp[g`Y]YV\^_c~jhpslutmtumwxoz{pz~wrs}qnj{dj]uYgWrWgWm\jdjmmwn~qvt}spqt~vx{wp|ksixktmusxyxyux|wpuuovvlwwkvwj{{h{vetsdvuet~tf7ÿ7ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ:ÿ9ÿ;ÿ;ÿ:ÿ;ÿ;ÿ;ÿ:ÿ;ÿ;ÿ<ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ4ÿ4ÿ2ÿ2ÿ1ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ+ÿ.ÿ6ÿ=ÿHÿRÿ]ÿhÿrÿvÿyÿxÿxÿwÿuÿtÿqÿnÿhÿfÿaÿ`ÿaÿcÿdÿfÿfÿdÿ[ÿSÿHÿ:ÿ+ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ.ÿ/ÿ1ÿ1ÿ1ÿ4ÿ7ÿ:ÿAÿFÿMÿSÿYÿ`ÿdÿgÿjÿlÿnÿpÿqÿsÿsÿoÿlÿhÿfÿcÿ`ÿ\ÿ\ÿYÿUÿRÿNÿKÿHÿEÿEÿAÿ>ÿ<ÿ7ÿ5ÿ0ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ4ÿ5ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ/ÿ2ÿ8ÿCÿPÿ\ÿgÿoÿrÿsÿsÿrÿrÿrÿtÿtÿtÿrÿmÿdÿYÿNÿEÿ@ÿ<ÿ7ÿ2ÿ.ÿ+ÿ)ÿ(ÿ'ÿ'ÿ'ÿ+ÿ1ÿ7ÿAÿQÿ`ÿlÿwÿÿÿÿÿÿ}ÿ~ÿÿÿÿ
ÿÿ~ÿuÿhÿ`ÿYÿWÿVÿWÿWÿWÿWÿUÿWÿXÿ^ÿfÿmÿtÿuÿuÿrÿhÿZÿXÿLÿ7ÿ@ÿ:ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ>ÿXÿ_ÿ\ÿTÿWÿCÿ@ÿNÿUÿUÿXÿfÿpÿxÿ}ÿ}ÿ|ÿyÿyÿxÿzÿ{ÿzÿyÿyÿsÿlÿcÿUÿIÿ@ÿ:ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ8ÿ9ÿ<ÿCÿFÿEÿFÿEÿDÿBÿ>ÿ:ÿ7ÿ7ÿ7ÿ8ÿ;ÿ<ÿ=ÿ@ÿCÿGÿJÿPÿYÿ`ÿiÿrÿyÿÿ
ÿÿÿÿÿÿÿÿ~ÿ{ÿzÿzÿxÿvÿtÿnÿiÿcÿ_ÿYÿTÿSÿPÿNÿMÿLÿJÿIÿJÿKÿLÿLÿLÿLÿMÿPÿQÿTÿXÿ]ÿeÿhÿkÿnÿqÿpÿqÿqÿqÿpÿrÿrÿrÿrÿrÿqÿqÿqÿrÿuÿvÿzÿ~ÿÿÿÿ|ÿxÿpÿgÿ[ÿTÿIÿAÿBÿKÿTÿ`ÿkÿrÿwÿyÿyÿ{ÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ{ÿtÿmÿeÿ`ÿ^ÿ^ÿaÿfÿkÿpÿsÿuÿtÿuÿwÿxÿzÿyÿzÿxÿuÿrÿnÿfÿ`ÿZÿVÿWÿUÿXÿ]ÿeÿoÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿoÿhÿgÿjÿmÿsÿyÿyÿxÿwÿvÿuÿvÿvÿvÿvÿwÿyÿ{ÿ{ÿzÿvÿrÿrÿtÿtÿsÿsÿ77j68i98j88k69m88n77n77p66p78p78q::q::p;;p;;q<<q<<r<<r<<r<=r<<s<<t<<t::u99u88u99u88u88v88v77x77x55x55x2~1x1~1x1~1y1~0z-}-{-},|+}*|*}*|)})|)})~)})}(|(}'}'}'}'}(|'|'|'|'}'|'}'|&{&{'{'{'{'|({(|(|(|)|)|)})|'}'|'}'{(}(z'}(y,}2y4}>uG~Qr[~cmjoksvhvugttds
saop^oq\ru\vv]rj^eW`I9g)~"p!}!x"}"}$~$$~%$~$%~%&}&&}&'|()|*+{,,{,,{-/{01{23|69~=}CIxOVr\amfiklnhopgsqgl~khf{bk\zZpVxRtPwLxJvHyGwCzAw=}:w98w42w/.w..w.-y--z--z-.z..z.-z--z-0{00{12{23{35y5
5y5
5y54y45{54y43y22y21z0
/z/
/{/
-{.
.z02{8B|M}X~euosmrqfqqcqsdtsgqmjeZoOEs>;w6~0},}''|(({'){+.{5?{L~Z{fxt}|to}i~gi
lkynhf
]cYXbX
WfURkRPnV\rbjsrvqurma|XoY{@u6}<|2},+{,,y--y--y--x-.x./w0
1w0
0v3;v[fw^yZv`r[w;oHvToXwVvhzpyz}}u}{pyxlxylyykyzltmncUqJ@x9~6}5}45|54|32|4~6};x?ErJNiPPeROeKDm>9t6}7y7x7|9v<|>tAzFrKwRsZsctlpsxyo{
o~mmk}m||ozxpuqpmgo_YoS~PqMzGvFyFxExDyEwDyDvDzEuFzGsGzIqJzMpRwYq^rdtgljylfo~qbrrcsrerrgonjkkml~mqm{oxqvv{|x|zv{~tolbf[PeJFjN|Wsbxlzsyyz{z{v}|t|u|v~v~s~r~s~t~s~n
f}v_qj]fe\efamphstlu~unw~xoyyoyzpw~spn}hmby]iWuRgSpShXn]jgmooyqsyt~rstv~wwwul|etfwivouuxyxyuw|vouumvvlvvkuwjzzhzvftreqqfq~ph8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ7ÿ:ÿ8ÿ8ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ8ÿ8ÿ7ÿ8ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ:ÿ:ÿ:ÿ:ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ5ÿ7ÿ7ÿ4ÿ5ÿ5ÿ5ÿ5ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ*ÿ+ÿ0ÿ5ÿ=ÿGÿNÿTÿ]ÿcÿfÿiÿlÿnÿqÿrÿtÿuÿwÿxÿzÿ|ÿ~ÿÿ
ÿ
ÿÿzÿsÿfÿVÿFÿ5ÿ(ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ*ÿ)ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ2ÿ3ÿ5ÿ8ÿ;ÿAÿFÿLÿSÿYÿ`ÿeÿhÿjÿlÿmÿnÿoÿqÿnÿhÿcÿ\ÿYÿTÿPÿNÿHÿDÿAÿ?ÿ>ÿ<ÿ;ÿ8ÿ7ÿ6ÿ4ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ/ÿ0ÿ/ÿ-ÿ/ÿ.ÿ0ÿ2ÿ8ÿ@ÿLÿVÿcÿoÿrÿrÿqÿrÿoÿpÿqÿrÿqÿqÿmÿeÿZÿOÿEÿ=ÿ8ÿ4ÿ/ÿ+ÿ(ÿ(ÿ)ÿ(ÿ(ÿ)ÿ+ÿ-ÿ3ÿ:ÿEÿVÿdÿpÿzÿÿÿÿ}ÿ|ÿ}ÿÿÿÿÿÿÿ{ÿqÿiÿ`ÿ[ÿXÿWÿUÿQÿQÿOÿLÿOÿTÿ^ÿiÿrÿuÿtÿvÿbÿQÿKÿ?ÿ8ÿ8ÿ2ÿ.ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ2ÿ5ÿMÿkÿ_ÿWÿdÿVÿ8ÿ;ÿPÿWÿWÿdÿqÿzÿ}ÿ}ÿ{ÿyÿxÿxÿyÿyÿyÿ{ÿyÿtÿmÿcÿUÿJÿ@ÿ:ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ5ÿ9ÿ@ÿGÿMÿSÿXÿ[ÿ\ÿ[ÿYÿUÿNÿBÿ;ÿ8ÿ5ÿ6ÿ6ÿ7ÿ:ÿ>ÿ@ÿFÿOÿVÿ]ÿfÿnÿvÿ}ÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿzÿxÿuÿqÿkÿeÿ^ÿTÿLÿGÿCÿAÿBÿBÿAÿAÿBÿAÿAÿAÿAÿBÿCÿCÿDÿFÿKÿPÿWÿ]ÿ`ÿcÿjÿmÿqÿpÿpÿqÿqÿrÿtÿtÿpÿlÿiÿeÿeÿbÿbÿfÿhÿnÿtÿzÿÿÿ}ÿyÿoÿgÿ_ÿWÿPÿOÿQÿYÿbÿlÿtÿzÿ{ÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿuÿqÿkÿkÿjÿkÿpÿsÿuÿuÿvÿvÿxÿxÿyÿzÿzÿyÿuÿrÿlÿfÿ^ÿUÿRÿPÿRÿRÿWÿ]ÿhÿrÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿwÿmÿeÿcÿjÿpÿvÿyÿyÿwÿuÿuÿuÿvÿvÿvÿvÿuÿvÿxÿxÿxÿvÿsÿrÿpÿqÿpÿnÿ99j99j99j99k99m88n88p77p87q77q89q::q::q::q;<r<<r<<r<<r==r<;r<<s;:t::t88u77u76u77u77u77v66v75x46x5~4x5~3x1~1z2~0z0}/y.}-z,|+{+|+{*|*})|)}'|'|'|'|&|&}(|(}(|(|)|)|&|%|%|&|'|'{'|'{&|&{'|'{'|'{'|'{'|'|'|'|'|'z'|'z'|'|&|(|'|'z'|'y)}+x4|;uB{HsN{RoX}Zm]}bidhgnuew}a```
`|
r`eUeG5n&}#w!}"}$~$$~$$~$%}%%}%'}(*|**|*,{-.{./z01z13{58};=~B|GNwV\qbemikklokooimiia{\mTxNsHvEyAv?|=w<~9w65v34v32x1/x//w/-x--y-.y..z..z..z..z./z/0z01z12y33{34{55z5
6y6
7z75z56z65z54y43y32y20y01y1
.z.
/{13{7>|J|T}`wkppqrhooenpfqphpmle[oPCs;5z1~-*}''{()|()|*,z/7zBP|_zl|wvq~k}}ij
k
n~uimcf^ZdUQiMIoG~GtH~Pv[~jxq~vst|kmRvBmK|NqF>w6~0},|,-z-,y,-y--x-/x0/x/
1w0
/v25vH~[u[v[vio[y7o6wPqTvVwdyryz}v~zoyykyykzzjxxksmndUsJ@y:~7~5}56|44|46~;|@HrPXh^b`gh\f
b]_VeKBn96t4|3{5z8{@yFwKySsZ{dqk{rpy{o~n
lmn}|o~}pzxqvrpjbpZMqG~Cv>y?|>x>}@x?|@w@|@u?~?u?~At@~CrC|GrNzTs[saufkizmfo~qcrqdqsfsqhpjje]lY}UsVyZx]uc~kts|wwzv}{{nrlfd[aXUdY}^ng{owuyzx}z}v~{u{v{w|w}t}u}u~tr~
o|i{{bxxatp^qqatugvvkv~wowyq{zsywst~oqi|cm[zTiPuOgOqPiUo\kgoqq~t
s{ststwxx~suk|fufwkxquwyzxyvw|touvlvvluuluvkwwiwsdqnfnnhp}ni9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ=ÿ=ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ4ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ(ÿ*ÿ0ÿ5ÿ9ÿ<ÿCÿDÿHÿKÿOÿTÿ\ÿdÿnÿvÿ|ÿÿÿÿÿÿÿÿ
ÿÿ|ÿoÿcÿRÿAÿ.ÿ$ÿ"ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ3ÿ4ÿ6ÿ7ÿ;ÿ?ÿDÿJÿQÿXÿ^ÿdÿgÿjÿlÿoÿnÿmÿmÿhÿ`ÿZÿPÿJÿCÿAÿ=ÿ:ÿ8ÿ6ÿ5ÿ4ÿ4ÿ3ÿ1ÿ1ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ1ÿ3ÿ7ÿ=ÿHÿSÿ_ÿjÿpÿsÿsÿpÿnÿmÿnÿoÿrÿoÿlÿgÿ\ÿQÿEÿ:ÿ4ÿ/ÿ+ÿ)ÿ(ÿ'ÿ(ÿ)ÿ(ÿ)ÿ*ÿ+ÿ/ÿ5ÿ<ÿJÿZÿgÿsÿ|ÿÿÿ~ÿ|ÿ|ÿ|ÿÿÿÿÿÿ~ÿxÿpÿgÿ_ÿYÿTÿPÿHÿEÿCÿCÿGÿPÿZÿfÿpÿtÿfÿMÿ>ÿDÿ\ÿ\ÿMÿ?ÿ6ÿ0ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ.ÿ2ÿ=ÿRÿYÿ^ÿcÿdÿTÿIÿQÿQÿOÿVÿgÿqÿzÿ~ÿ~ÿzÿyÿyÿyÿyÿzÿzÿyÿwÿsÿmÿfÿXÿKÿAÿ:ÿ7ÿ5ÿ5ÿ4ÿ3ÿ4ÿ4ÿ8ÿ=ÿEÿNÿUÿ^ÿfÿlÿqÿrÿpÿlÿeÿ]ÿPÿEÿ<ÿ6ÿ5ÿ3ÿ8ÿ<ÿBÿKÿPÿVÿ`ÿhÿoÿuÿ}ÿÿ
ÿÿÿÿÿÿÿÿ~ÿ}ÿ~ÿ~ÿ|ÿzÿvÿrÿjÿbÿVÿMÿFÿAÿ?ÿ=ÿ>ÿ=ÿ?ÿ<ÿ>ÿ=ÿ=ÿ>ÿ<ÿ=ÿ?ÿ>ÿ?ÿCÿGÿNÿTÿ[ÿaÿeÿiÿoÿpÿpÿqÿpÿqÿsÿqÿqÿoÿiÿcÿ[ÿQÿJÿHÿMÿTÿZÿcÿkÿqÿuÿzÿyÿvÿoÿiÿaÿ]ÿ\ÿ]ÿbÿgÿoÿvÿ{ÿ{ÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿyÿzÿzÿ{ÿ{ÿzÿxÿvÿxÿwÿwÿwÿwÿwÿxÿyÿxÿ{ÿzÿyÿwÿsÿmÿfÿ_ÿWÿPÿPÿNÿLÿOÿUÿ^ÿiÿtÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿrÿiÿfÿfÿjÿqÿwÿzÿxÿwÿuÿuÿvÿvÿvÿuÿuÿuÿvÿwÿwÿvÿrÿpÿmÿnÿmÿkÿjÿ88j88j99j99k99m87n88p77p88q88q9:q::q::q::q;<r<<r<<r<<r<<r;:r::s::t98u77u66w66w54w55w55v65v66x55x5~2y1~1y0~0x0~/y.}-z+}+z*|*|)|*|)|)|)|(|'|&|&|%|&|%}%|%}&|&|%|%|&|%|%|&|'|'{'|'{&|&{&|&{'|'{'|'{'|'{'|'{'|'z&|&z&|&|(|&|%|%|&|%z%}%{%}'y*|/x1{1w2|3t8|@qI}PnYckjsjzhf
da^xob]Lk8|'t#|#}$~$$~$$~$$}%(}((}*)|)*|+-{-/{.0z01z35{78}=~AGyOUtZanehmknjoljljkc|\qTyIvCw=z9v6~6v54v44v43v12v20x00x00w/-x--y--y..z./z/.z..z./z/1z11z23y34y45y56y6
7y77y77y77y76y65x54x33y30y21y1
0z0
/{/4{8;|E}R}^yi~psrqknmfkmfnrgpmjh]oQEs:3x.~+~)|(&{()|)*|+*{.2{;F|V~e|q{zv~o|zj{~hjmzlrjj\VjPKqB?w=|={D|M|X}f{p{huIx7oBy^mi`mQDr80|-}.-{-,y,.y.-x--x./x0.w/1x3Az]|]xbuavUsSvYrXtSqPvYudyqx{}~v}{oyxkyzkzzjywktnneWsKAy;~8~4}44|44{6;|AzJ|Tp]}fhotayy]zs_j`fUGn=7t47x;~BwJ~PsV}_of~omszmon
lm~~o}~}p||p}{qwqrh_rUJuB~?z>{<}<y<~=x<>w=~;u;~<u=~=t?~@sD|GsNxVt[qawgjj{lgnndoqeqrhrpilgk_WmKCq<|?vGvQ{Ysa{htmvuyulv~qdkg`dbbadlj|rtyz}x~z|v{u{v{w|w}t}u}u~vt}}p{|{i}}{f}}e}{b{zezzjyynxypzzs|{szusoiqa~ZnSyNiLuJgLqOkUn_mkovruv|urstwxxzquh|drevjuruwxzxxuv|uouulvvjvvjuvkwwiwsgooilllj}hm9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ3ÿ3ÿ3ÿ4ÿ5ÿ2ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ*ÿ+ÿ+ÿ*ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ&ÿ(ÿ)ÿ)ÿ*ÿ)ÿ*ÿ0ÿ<ÿCÿKÿTÿ]ÿfÿlÿrÿvÿyÿzÿ|ÿÿÿÿ
ÿ~ÿtÿdÿSÿ?ÿ*ÿ!ÿ!ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ(ÿ(ÿ(ÿ)ÿ*ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ3ÿ6ÿ8ÿ9ÿ>ÿDÿJÿRÿXÿ]ÿbÿgÿjÿmÿnÿmÿmÿjÿdÿ]ÿUÿLÿBÿ9ÿ8ÿ7ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ0ÿ4ÿ8ÿ;ÿCÿNÿZÿiÿoÿqÿqÿnÿlÿlÿoÿmÿpÿqÿoÿgÿ^ÿSÿGÿ;ÿ2ÿ-ÿ*ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ,ÿ3ÿ9ÿBÿPÿ_ÿlÿvÿ}ÿÿÿ|ÿzÿzÿ{ÿ~ÿÿÿÿÿ{ÿvÿjÿ[ÿSÿLÿEÿ?ÿ:ÿ9ÿ:ÿAÿKÿWÿbÿpÿkÿNÿ9ÿLÿjÿoÿcÿRÿEÿ9ÿ0ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿFÿbÿiÿbÿSÿMÿjÿbÿRÿUÿSÿWÿfÿrÿ{ÿ~ÿ}ÿ{ÿyÿxÿxÿyÿyÿyÿzÿwÿtÿnÿeÿWÿKÿAÿ:ÿ7ÿ4ÿ4ÿ3ÿ3ÿ4ÿ6ÿ<ÿBÿMÿVÿbÿlÿuÿ{ÿÿÿ}ÿuÿlÿbÿUÿIÿ<ÿ7ÿ7ÿ;ÿBÿHÿOÿVÿ]ÿfÿlÿsÿyÿÿÿ
ÿÿÿÿÿÿÿ}ÿ}ÿ~ÿ|ÿ}ÿ~ÿ}ÿ{ÿwÿqÿhÿ_ÿTÿJÿCÿ?ÿ=ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ=ÿBÿGÿNÿVÿ[ÿ`ÿfÿkÿkÿmÿpÿqÿpÿqÿrÿoÿmÿjÿeÿ\ÿRÿJÿ?ÿ7ÿ6ÿ=ÿEÿPÿXÿ`ÿfÿlÿsÿwÿrÿmÿiÿiÿfÿgÿiÿmÿtÿzÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿyÿwÿsÿuÿwÿ|ÿ}ÿÿÿÿ~ÿ{ÿ{ÿzÿzÿxÿyÿzÿzÿ|ÿ{ÿyÿtÿmÿgÿ]ÿVÿNÿHÿIÿHÿIÿOÿUÿ`ÿmÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿmÿeÿbÿhÿlÿrÿwÿxÿyÿwÿvÿvÿvÿvÿvÿvÿvÿuÿvÿwÿwÿvÿsÿoÿmÿhÿhÿgÿeÿ::i::j::j:9j99l87m77o77q88p88p:9p99p99p;;p::p::p;;q<<r<<s:;s:;t99u88v77w65x55x54x55x54w44w43x34x3}2z0}1z/}-x-}-x+|,x*|)y(|({'|'}&|&|'|&|'|&{%|${%|%z%|%z%}%{%}%{&}&z%}%y&|&|'|'z&|&y&|&y&|&y&|'y&|&{'|'{'|'{'|'{&~&|%~'|&~&{&~&{$~$z%~%z%}%|%}&|%{&|'}+{0~5w?}FuN|TsZ{aqe|imlpgx~a
^yai}XgDy-r"z$|#}##~#$}%&}'(}))}*+{+-z,-z..z/0{12{46}9>A~IRxV\rbfkhkjnnknmlg~`qXzPwFx;|7w44w44w43w33w32w12w10x00x00y/.x..y.-y.-z-/z/.z.0z01z11z13z34z55{55{79y99y99x99x98y87y76x65x54y42y21y11y1
1z14z8;|@L}W|dmvppponhkmenneqohldm\NrA5x-~)~)|*){*)|)*|*+|,1}8=|KZ~h|t{w~~s}zlyyi|~im}nwjm_RnHAu<}5}5|7?zI~T|`yj{qtJx?oa{umqgnVGr<4x/}.,{,.y..x.-x-.x/0w00x03{;Zz{{zeuHuNu\s\wHsIuRvYvd{qvz}v|nyxlvxkxymyxluooeXtJ@z8}43|32z13z7<zE|P|Zrd|piy~cc~udmahUIo?<r>AsHPsW_oemlrxl}l
kkl~~m}~}n~~~o~~p|pwqqj_qSIu@~;z<{;}:y99x9<x<;x;;v;~<v9~=sA}GuOvVu]nczigl~ncoodooenofnmggbj[OmG:r3}0v4y;yEuNyVt`ufwnlq|scoj_jkblokr~uu{z}x~{v{
u{v{wzv{u}t}v~v~|sv}rnn{iilytgxz}i~}h~h|l{{o{{p{{r}{txqrkdn\|TjLxHgFtFfIpMjWndnpp|q
wu}u~ttuxx~wu~kscxaqgtlurtwwyzyrw}unuukuukvtjvwhvvfuqgnkjj~hke|bm:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ:ÿ9ÿ9ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ:ÿ8ÿ7ÿ7ÿ6ÿ6ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ/ÿ0ÿ/ÿ.ÿ,ÿ,ÿ,ÿ*ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ'ÿ&ÿ'ÿ&ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ'ÿ&ÿ&ÿ&ÿ&ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ%ÿ$ÿ&ÿ&ÿ&ÿ*ÿ0ÿ9ÿ@ÿEÿJÿMÿNÿSÿZÿbÿnÿyÿÿÿÿyÿmÿ\ÿDÿ-ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ,ÿ-ÿ-ÿ-ÿ/ÿ0ÿ/ÿ1ÿ3ÿ6ÿ9ÿ<ÿBÿGÿLÿTÿYÿ^ÿeÿgÿjÿlÿnÿnÿmÿgÿcÿ\ÿUÿJÿBÿ;ÿ7ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ6ÿ7ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ3ÿ4ÿ8ÿ;ÿ@ÿKÿVÿaÿlÿpÿpÿoÿnÿlÿnÿnÿnÿpÿpÿmÿgÿ_ÿRÿCÿ6ÿ.ÿ*ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ/ÿ6ÿ;ÿIÿXÿeÿrÿyÿ~ÿÿ}ÿzÿxÿxÿzÿ}ÿÿÿÿ|ÿwÿlÿ_ÿRÿFÿ=ÿ6ÿ1ÿ4ÿ8ÿ=ÿFÿRÿ^ÿkÿdÿ=ÿBÿgÿvÿoÿfÿXÿJÿ=ÿ5ÿ.ÿ,ÿ+ÿ+ÿ,ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿCÿnÿ{ÿvÿ^ÿOÿNÿYÿTÿ>ÿNÿYÿcÿeÿrÿ{ÿ~ÿ}ÿ{ÿyÿxÿvÿxÿyÿzÿyÿxÿuÿoÿdÿWÿJÿ@ÿ8ÿ4ÿ3ÿ1ÿ1ÿ2ÿ4ÿ8ÿ@ÿHÿPÿ\ÿgÿsÿ|ÿÿÿÿ~ÿuÿlÿ`ÿTÿHÿAÿAÿFÿIÿOÿWÿ[ÿcÿjÿpÿxÿ}ÿÿÿÿÿÿÿÿÿ~ÿ~ÿ}ÿ{ÿ|ÿ~ÿ~ÿ~ÿ~ÿ{ÿwÿqÿhÿ^ÿPÿHÿ@ÿ;ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ=ÿAÿHÿPÿYÿ^ÿdÿiÿlÿnÿoÿoÿoÿnÿnÿoÿmÿjÿdÿaÿWÿOÿBÿ6ÿ1ÿ.ÿ/ÿ3ÿ;ÿFÿQÿ[ÿcÿjÿnÿqÿrÿoÿoÿnÿpÿrÿtÿwÿ{ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿrÿkÿeÿbÿeÿmÿrÿxÿ|ÿ~ÿÿÿÿ|ÿ|ÿ{ÿ{ÿ{ÿ{ÿ{ÿ}ÿ{ÿvÿnÿiÿaÿXÿOÿIÿCÿDÿEÿGÿMÿXÿfÿrÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿqÿjÿbÿbÿgÿlÿtÿyÿyÿyÿwÿuÿuÿuÿuÿuÿuÿuÿwÿwÿvÿtÿsÿpÿmÿkÿiÿfÿeÿbÿ;;i;;j;;k;;k::l98m87n88o88p88p:8p99p9:p::p:9q::q:9q99r;:s88s88t77t76u54v54x44x54y44y23w44w41x22x0}/z/|0z.|-{+|+z*|*y)|({(|({(|(}'|&|'|&|%|&|&|%|&|&}%|%}%|%{%|%{$|$z$}%y&}&x'}'y'}'x&}&x%|%y&|'y%|%z&|&z&}&{&}&{&~&|&~&|&~&|&~&|%~%|%~%|$~${%~%{$${$${$~$z'~,z1}5x8|;v=~CqKWhiva
^z`n|[jCy,t#z#|$}$$~$$}%&}'(}))}*,|,,{--z-
/z0
/{13~6:>|DKwQXu\apfjmjjknmlidm`|XrOyFw>x9~6w54w54w44w44w43w33w32x10x00y//x/.y--y--z-.z..z00z01z13z34z45z56y77y89y98y89x99x99y88y77x66x54y43y32y23y2
2{13{6=}B~I~Tz_iuqrppngkmeoodprgpkjcWoI<v0+|+|+){)*|++},+}-/~6~;~GyVbunwv}t~{mxxjz}i~m~oyopbSqD8x2}03|6<zEQ|]zj{atDxFoo{wlrhl]Lr@5x/}--{-,y,-x-.x..x.-w.0y2<}_|klwrzXtGuNzNtZ}StQzRvgvj{tv|}~w~{pyxlwwkyzkxwnuongYsJ@x8}4}2|11z24z9={GzR|_sj|wj|g~g~ukk`lTLnGEpIPpVZp`gnnvn|mmlln~~n||p|~|qq~{qwqqi^qPFs?9w8|6}8y57x77x77x8~7v76w8<wBzGxQsZw_mg{iem~nbmmcnmemnhjgic[jRHn=6s.}+w-z0y5v=yFtSt\weml{ofq~q`qqatujxzs||~v}{t{
u{v{xzv{u}t}v}u~~wrl~an\{Yj^wfikvsjyy~k|k~}n||p|}r||s|yusltf\nS|JjEwBiAtBiFpOm[ohrtstxu~s~suvxx~{vp|frawcsfrkwstzxyzxsw}unttkvvkvvjvvhusfpngjhjg~eoa|^q;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ8ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ;ÿ9ÿ:ÿ;ÿ;ÿ9ÿ9ÿ9ÿ9ÿ:ÿ8ÿ7ÿ7ÿ7ÿ5ÿ6ÿ6ÿ6ÿ6ÿ4ÿ3ÿ4ÿ4ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ1ÿ1ÿ1ÿ/ÿ.ÿ-ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ&ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ$ÿ&ÿ'ÿ,ÿ.ÿ/ÿ6ÿBÿQÿdÿsÿÿÿÿyÿlÿXÿ>ÿ)ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ0ÿ0ÿ1ÿ3ÿ6ÿ9ÿ?ÿDÿJÿNÿUÿ[ÿ_ÿfÿfÿgÿkÿnÿnÿlÿhÿcÿZÿSÿJÿCÿ<ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ6ÿ7ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ6ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ3ÿ1ÿ2ÿ4ÿ7ÿ<ÿAÿIÿRÿ]ÿfÿnÿrÿpÿnÿlÿnÿoÿoÿpÿrÿrÿlÿfÿ[ÿMÿ?ÿ4ÿ,ÿ,ÿ)ÿ+ÿ)ÿ*ÿ+ÿ+ÿ+ÿ*ÿ,ÿ0ÿ5ÿ<ÿFÿUÿaÿlÿvÿ}ÿÿ~ÿ{ÿxÿxÿyÿ|ÿ}ÿÿÿ~ÿ{ÿrÿeÿSÿBÿ7ÿ2ÿ0ÿ3ÿ6ÿ:ÿDÿOÿ[ÿgÿ[ÿEÿRÿuÿtÿoÿjÿ_ÿNÿAÿ6ÿ/ÿ-ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ3ÿKÿjÿjÿuÿlÿLÿGÿFÿHÿMÿLÿNÿRÿoÿnÿsÿ{ÿ}ÿ~ÿ{ÿyÿxÿwÿwÿwÿyÿxÿwÿtÿnÿgÿYÿJÿ@ÿ8ÿ4ÿ2ÿ1ÿ2ÿ2ÿ3ÿ9ÿ>ÿHÿUÿbÿmÿyÿÿÿ
ÿÿ~ÿuÿkÿ`ÿTÿLÿHÿMÿSÿXÿYÿ\ÿdÿmÿtÿ{ÿÿ
ÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿ}ÿ{ÿÿÿ~ÿ{ÿvÿpÿgÿ\ÿPÿDÿ=ÿ;ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ7ÿ7ÿ6ÿ7ÿ>ÿCÿJÿSÿYÿ_ÿfÿjÿmÿoÿmÿnÿmÿmÿnÿmÿkÿfÿ_ÿYÿPÿEÿ:ÿ2ÿ.ÿ+ÿ+ÿ-ÿ1ÿ8ÿAÿLÿVÿaÿjÿmÿtÿtÿtÿuÿxÿyÿ{ÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿgÿ[ÿSÿPÿXÿ`ÿkÿqÿvÿzÿ~ÿÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ|ÿzÿwÿrÿkÿcÿZÿQÿGÿBÿ?ÿ?ÿCÿHÿQÿ]ÿjÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿnÿfÿ_ÿbÿgÿlÿtÿxÿyÿxÿvÿtÿtÿtÿuÿuÿuÿuÿuÿuÿuÿsÿpÿnÿjÿhÿfÿdÿaÿ]ÿ;;i;:j::j;;j:9k98l77n88p88p99q98p99q9;q;;q::r::q99q98r88s77s65u55u54w55x55y44y33y33y23x55x41y0~0y.}.y.}.z,|+z+|+z*{*{){'{'|'{'|'{&|&|&|%|&|%|%|$|$|%}%|%}$|${$|${$}%y&}%y%~&y'~)x'~&y&|&y'}'y'}'z'|'{'{'y&|&{'|'{'}'{&}&{%}%|%}%|#~%|$~$|$~${#~#{##{""{#~"{$~${#%|&'z,4u>
Nk`qb}^yak~Wj<{'t#{#|$}$$~$%}&'}()}**|++|+,|,.|12|24~9=B}FKwQTp[
and
glhilmnllinf`qW|NtGw?z8w7~6w65w44w44w44w42w22w21x10x00y0/x//z.-z--{--{-.{00{01{12{34z55y55z7
7y7
9y99y9:x:9x99x98x88x86x65y54y43y33y32z4
5|8=~BIQ|ZcwkppqojmmfoodqrevqijbmQCr6.{)})+|*+}**|,+~.3:|?GuS`sjst|u}oyykx{k|~mo{spgWrF:y31}4|9:zBN{YzezTvKv^qz~tkqkj`PoC7v0},,{,+y,,x,-x--x-.y0/z3R}qzzwv_yFwEtL{>r>|LrUxUtetizqt|~t~{qyxnwwlxxmyxmuoofYsJ@y941|21z14z;AzI|W{cuo{zq|m}mxnm`nUQmQSoVXp[apgpmw}m
om
om~~n~}o{{q}~~q~q}|quppf[pOBs<8x6|4}4z56y7~6y6~5y5~4x4~5x7};wByKxSsZz`mg~jhllcmmdlkemlgicj]VlMAp8/s,}*y*{,|0x6y?uJsTv^mizmfs}vbwwcz{k~~r}t|t{
u{w{xzw{v}u}u}uzpqeXnK}JkPy\ldvkmrxwm{{}n~}~p~}}q}~}s{~{txutogq^~XmM{Dl?v=k=s@mGoSq_plv{twyv~stvvx
xutl{dp`ucthrnytuxyxzwst}snsslssksukutitqepngjfie~cl`{[q;ÿ;ÿ:ÿ;ÿ:ÿ:ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ8ÿ9ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ:ÿ:ÿ8ÿ9ÿ8ÿ8ÿ6ÿ6ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ%ÿ&ÿ%ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ'ÿ(ÿ+ÿ+ÿ-ÿ-ÿ+ÿ(ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ"ÿ#ÿ#ÿ#ÿ$ÿ%ÿ&ÿ+ÿ1ÿ>ÿMÿ_ÿpÿ}ÿÿÿyÿkÿWÿ>ÿ'ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ0ÿ2ÿ5ÿ9ÿ?ÿDÿHÿMÿQÿVÿ[ÿ]ÿdÿeÿiÿjÿkÿjÿjÿjÿiÿcÿ[ÿSÿJÿAÿ9ÿ7ÿ5ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ6ÿ6ÿ5ÿ7ÿ7ÿ7ÿ8ÿ9ÿ9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ:ÿ>ÿCÿIÿQÿ[ÿdÿkÿpÿqÿoÿnÿnÿoÿqÿsÿtÿvÿtÿnÿfÿZÿJÿ<ÿ0ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ,ÿ/ÿ2ÿ6ÿ<ÿBÿKÿTÿ`ÿkÿtÿ|ÿÿÿ~ÿzÿxÿxÿ{ÿ|ÿ~ÿÿÿ|ÿvÿlÿ[ÿKÿ=ÿ5ÿ0ÿ2ÿ5ÿ:ÿBÿMÿWÿdÿPÿSÿgÿvÿuÿrÿmÿcÿSÿDÿ;ÿ2ÿ-ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ0ÿ8ÿlÿzÿuÿuÿbÿNÿEÿDÿ>ÿEÿQÿQÿTÿfÿfÿqÿ|ÿÿ}ÿ{ÿyÿxÿwÿwÿxÿxÿyÿxÿtÿnÿeÿXÿJÿ@ÿ9ÿ4ÿ0ÿ1ÿ/ÿ1ÿ5ÿ;ÿCÿMÿYÿfÿrÿ~ÿÿÿÿÿÿ|ÿpÿdÿYÿVÿVÿXÿZÿ\ÿ]ÿdÿiÿrÿxÿ~ÿ
ÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ~ÿ|ÿyÿtÿoÿfÿ[ÿOÿCÿ:ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ7ÿ<ÿDÿMÿUÿ\ÿbÿhÿkÿlÿlÿmÿmÿlÿkÿiÿiÿfÿaÿ[ÿTÿIÿ@ÿ3ÿ0ÿ+ÿ)ÿ)ÿ+ÿ0ÿ4ÿ:ÿEÿPÿ\ÿfÿmÿrÿvÿyÿ{ÿ|ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿmÿcÿUÿJÿJÿNÿWÿaÿhÿpÿwÿzÿ}ÿ~ÿ~ÿ}ÿ~ÿ~ÿ~ÿ|ÿ{ÿyÿtÿlÿeÿ^ÿRÿHÿAÿ<ÿ:ÿ>ÿBÿKÿWÿcÿoÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿuÿjÿbÿ`ÿcÿhÿnÿtÿxÿwÿuÿtÿtÿsÿrÿrÿrÿsÿtÿtÿsÿqÿrÿoÿmÿjÿfÿcÿ`ÿ]ÿ[ÿ;:i:;j;;k;;k9:l87k77l88m87n77p88p88q9:q::q::q98q99r87s77v66v55w43x33w33x44y33y3~3y2~2y11y00y/~/y.|-y,|+x*|*y+|+z+{*z({({'{'{'|'{&|&{&|%|%|&|%|%|%|$|$|$z$|$z#|$z$|$z$~%y&'x,0w34v42u-~+u)~(u)~)w)~&w&|&x%|%z$|$z$}${$}${#}%|$}$|&~$|#~#|#~#|$~#|$~#|#~"|"~#{"~"{! |$'y)/v:Jn\mdy`
zakXgA{)s"{${#}$~%~%&}'(}))})*|+*|,.}-0~259AEzJ
NtS
Vp]_lc
dkfhkkklkkljfm_~VqMzDw>x8}6w56w55w44w43w33w32w22w21x10x..y/0x.-z--z--{-.{..{00{01{11{23z46y66z7
8z8
9y98y9:x::x:9x99x97x76x66y55y53y33y35|67}:?~D}IQxZcujorqplonfqrdstevuiqkmaQrB4w*~*}+|+,~+.0~36z;AuE
MmV
alluo}ro~zk{{j|jl~yooarOAw71}1|48|ALyW|dxLvLxjqv~vnqlmeVqF;w2}-~+{+*y+,x,-x-,x-,y00|I}zrzU|K|^w]}TuPxWuLuMrQsPtirizrr~~s}{rzynwylyylyxltnnfXrJ?y841}1/{27{?F{P{[{hvv{r
|s}ssrgn]XmXYo\]p`eplrnznommn~}o~|p~~p}~~qq|yquoqeYqMAt95x2}2|2|3~2{2~3{3~4{4~3z3~4y8|=xExNyVq]{ekjkgmmcmnellgkihe^kZPoE:s2+u)}){*{,|/x3y8wEtOw[ocxjir{wey|}e~{n}q|r|t{u{w{x|w}v}v}v
}~vujq`VlK~GiMzTj^wimrxxp{{}q}s~}}t~}t{~zuwqulds[~OoD{=m:w:n;sBoLpWtgpswux{v~tuvv~w~w}~ssgzar^ubtgroytuvxwzurr}nnqqlrrksskssirqhnljjfla{^o[xYt;ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ8ÿ7ÿ8ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ9ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ%ÿ$ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ$ÿ&ÿ'ÿ+ÿ0ÿ5ÿ:ÿ=ÿ9ÿ7ÿ2ÿ2ÿ1ÿ0ÿ0ÿ.ÿ,ÿ(ÿ&ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ%ÿ$ÿ$ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ#ÿ#ÿ"ÿ#ÿ"ÿ#ÿ"ÿ"ÿ"ÿ!ÿ"ÿ%ÿ(ÿ/ÿ9ÿGÿVÿjÿyÿÿÿ|ÿnÿ]ÿJÿ1ÿ$ÿ%ÿ#ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ-ÿ.ÿ0ÿ3ÿ6ÿ:ÿ@ÿFÿKÿOÿSÿXÿ\ÿ_ÿaÿcÿfÿjÿkÿkÿkÿjÿjÿiÿcÿ\ÿSÿJÿBÿ;ÿ6ÿ4ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ8ÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ8ÿ<ÿ@ÿEÿIÿQÿXÿaÿiÿpÿrÿrÿpÿoÿqÿrÿsÿtÿvÿvÿtÿnÿeÿWÿFÿ8ÿ.ÿ-ÿ.ÿ,ÿ+ÿ.ÿ1ÿ5ÿ7ÿ;ÿ@ÿDÿJÿQÿXÿ`ÿjÿsÿ}ÿÿÿÿÿ|ÿ{ÿ{ÿzÿ}ÿÿÿÿ{ÿrÿeÿUÿDÿ8ÿ2ÿ1ÿ4ÿ8ÿAÿIÿTÿcÿKÿFÿiÿvÿvÿrÿlÿfÿWÿHÿ=ÿ2ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ1ÿDÿtÿwÿmÿZÿ8ÿOÿOÿMÿSÿWÿQÿRÿRÿOÿcÿjÿsÿ}ÿÿ}ÿ{ÿzÿyÿxÿwÿyÿyÿyÿxÿuÿnÿeÿVÿIÿ>ÿ8ÿ4ÿ3ÿ1ÿ2ÿ5ÿ;ÿAÿIÿSÿ^ÿlÿxÿÿÿÿÿÿÿÿtÿhÿ`ÿ[ÿ[ÿ\ÿ]ÿ]ÿbÿfÿlÿrÿvÿ|ÿÿ
ÿÿÿÿÿÿÿ~ÿ}ÿ}ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ}ÿzÿxÿtÿnÿdÿZÿNÿAÿ9ÿ5ÿ2ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ4ÿ9ÿ>ÿGÿPÿYÿ`ÿgÿjÿmÿmÿmÿmÿmÿlÿlÿkÿhÿcÿ]ÿWÿMÿBÿ7ÿ/ÿ*ÿ)ÿ)ÿ)ÿ+ÿ/ÿ3ÿ:ÿFÿOÿ[ÿcÿiÿpÿvÿ{ÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ}ÿvÿlÿ`ÿSÿKÿJÿMÿTÿ^ÿhÿpÿvÿ{ÿ}ÿÿÿ~ÿ}ÿ}ÿ|ÿ{ÿzÿvÿpÿjÿbÿVÿKÿCÿ=ÿ:ÿ:ÿ;ÿBÿNÿZÿiÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿpÿeÿ`ÿ^ÿbÿhÿnÿtÿvÿvÿtÿrÿpÿqÿqÿqÿqÿrÿrÿrÿrÿqÿpÿmÿjÿgÿcÿaÿ^ÿZÿXÿ;9h;:i:;j;;l::l89m87m87n88m87o78q99q99r98r88r88r77s66u66w55w2~2v2~2v1~1x1~1x2~2y2~2y10x00y0/{00{/}/{.}.{,{+{*{*{){){){){){)|'{&|%|%|'|'|%}%}&}&}%}%|%}${$}${#}#{%}%y$}$y#|#x$|&x+.w37w;;w:6w77u77u6~5t/}*u%|$w$|$y$|${$~$|#~#{#~#{"~#{#~#{#~#|"~"|"~"|"~"|"~"|!~!|!~!{#~${)-x6BpRcfs~a
~asceP|8q'z&z$|$~'}''((}(*|*+}+,}./25=}CHwMQqTYn]_kb
dhhkhk
jimkihije~^oV|LtDy?z9x76x65x56w65w53w33w33x32x21x1/x//y..y--z--z--{-.{./{10{01{11{23z45z56z7
8z89y98y99y99y99y87y89y97y64y43y43y45z6
7|6:~>@F}KPxX`siqrstmrpgpqdsudvxfvpig\pK;t2-z--}-1}69y=BsE
JmMTe[afjsm|q
n~j{zh}~llnxks[Jv;3{3|47{>IxS}avJvJvjrv}toromfXrI<x4}-~,{,*z+,z,-y--y--y.Kzxz}qzJ~7{UyN{LuIvMuSsMqRoWtcphzrq{~t~{pzymxxmyymywltmmeYqK=w752~54}8=~E{L}Xxc|ps|zs{v|v}ttiob^m\]m]_paepj~ppv}yq~q
om}n}}o}}o~~p}~~p}ryxrumscYsK?u73z2{1|0z0~0{0~0{0}0{00z/}6y:z@yGvQzYoa}himlemmcmmdlkgieh`ZlUKo?5s-~+z+|+}*{*|.y4w?yIrSy\lcyjiqyug{zh{o{r{t{t{v|x|w|w|v{w}w
~~wvlr_RnJ~KlQ{Vmawjoqyyq|{~s~}~u~|t|~|u{xtsnve\sQ~Gp?{:o8v9o=sDrOq_vlryyxw|s}s~t}u}u~w~}wv|ltcy^t^sbvgsnxuuuvt{sqrqoppnpplqqjqqjomjkgkf}cn`z\qWuVw;ÿ9ÿ;ÿ:ÿ9ÿ:ÿ;ÿ;ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ+ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ(ÿ*ÿ.ÿ1ÿ6ÿ6ÿ4ÿ5ÿ6ÿ6ÿ9ÿ9ÿ8ÿ7ÿ2ÿ-ÿ'ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ!ÿ#ÿ&ÿ+ÿ2ÿ<ÿMÿ]ÿoÿ{ÿÿÿvÿfÿUÿ=ÿ*ÿ&ÿ%ÿ'ÿ+ÿ-ÿ,ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ-ÿ/ÿ2ÿ6ÿ<ÿBÿIÿMÿSÿUÿ[ÿ^ÿ_ÿbÿeÿfÿhÿjÿjÿjÿiÿiÿgÿcÿ_ÿXÿOÿFÿ@ÿ9ÿ6ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ5ÿ6ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ7ÿ8ÿ9ÿ9ÿ7ÿ6ÿ6ÿ5ÿ3ÿ5ÿ5ÿ4ÿ5ÿ6ÿ8ÿ:ÿ<ÿ@ÿCÿGÿKÿPÿUÿ`ÿhÿoÿsÿtÿtÿqÿpÿpÿsÿuÿvÿxÿwÿsÿlÿaÿPÿ@ÿ6ÿ/ÿ.ÿ.ÿ1ÿ8ÿ<ÿ?ÿBÿEÿJÿPÿRÿWÿ]ÿcÿkÿsÿ{ÿÿ
ÿÿÿÿÿÿvÿ{ÿÿÿÿÿxÿoÿ`ÿOÿ>ÿ5ÿ3ÿ4ÿ7ÿ>ÿGÿRÿ^ÿIÿHÿlÿvÿtÿsÿoÿfÿXÿJÿ=ÿ4ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ;ÿ`ÿpÿjÿQÿ8ÿ[ÿWÿNÿLÿPÿYÿUÿMÿTÿcÿ`ÿfÿvÿ}ÿ~ÿ}ÿzÿyÿxÿxÿyÿyÿyÿwÿtÿoÿfÿXÿKÿ@ÿ9ÿ6ÿ6ÿ8ÿ9ÿ>ÿEÿLÿSÿ]ÿhÿtÿ~ÿÿÿÿÿÿÿ|ÿrÿhÿcÿ`ÿ_ÿ^ÿ]ÿ]ÿ^ÿ`ÿdÿjÿoÿvÿ}ÿÿÿÿÿÿ~ÿÿ}ÿ}ÿ}ÿ~ÿÿ~ÿ~ÿ}ÿÿ}ÿyÿvÿrÿmÿcÿYÿKÿ?ÿ7ÿ3ÿ1ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ4ÿ:ÿBÿKÿSÿZÿcÿjÿmÿlÿlÿlÿmÿmÿlÿkÿiÿcÿ]ÿWÿPÿEÿ<ÿ3ÿ-ÿ+ÿ)ÿ,ÿ+ÿ-ÿ0ÿ9ÿBÿLÿUÿ]ÿcÿjÿrÿvÿ{ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿlÿ_ÿRÿNÿNÿSÿZÿbÿkÿsÿyÿ|ÿ~ÿ~ÿ}ÿ~ÿ}ÿ|ÿ|ÿ{ÿwÿrÿkÿfÿ\ÿQÿGÿ<ÿ8ÿ8ÿ8ÿ=ÿFÿSÿcÿoÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ|ÿsÿiÿcÿ^ÿ^ÿbÿiÿoÿvÿvÿtÿsÿpÿoÿpÿqÿqÿqÿqÿqÿqÿqÿoÿmÿjÿgÿdÿbÿ^ÿZÿWÿWÿ::h<<i::j::l;9l::n98p87p88q88r88r88r99r77r76s55s55t44v55w43w2~2w2~2w1~1y2~2y2~2z0~0z/~/y/~/z/~/|.~/|.}-|-},|+}+{*}){(|'|'|'|'|'}%|%}%|%|%|#z#}%{%}%{$}${$}$z%}%{#}"{#}#y#}#y#|#y$|$y%}'y(~*y+~+w-~/w//u26u6~6v1}.x(|%w#|#y"|"y"~"{#~#}#~#}$~$}#~"}"~"|"~"|"~"|"~!|!~!{"~"{"~"{"~#{%)y0:sJZjkxa_wic[Fl/~&u%~*{/23,)}))}*+~,-28;}A
GwKQrUZk^`gcdgfhgi
ifiiggfidalY{PrHyAw<w7}6v45w55w55w55w54w43w33x22x20x0/y/.y-.y--z--z--{-.{.0{01{11{12{32z56z66z7
8z88y88y89y98y89w88w88w87w77y54y45z56{7
:|<=AEJzMQuX_qgoouunusirrftvftvhwshndnUGs:1x02x7<u@EoHMiPTdX[c_deiqg{nn~ns^nj|pyozq|sseTtD8z3}36|<FyP}_vJxGvksv|soqnlfXoK>u5}.},{,)z*,z,
,y,,y,-x+2xLexn~a|P|n~|zi{QxOx^u^sNrLuaq^{Vrk~s|qzymyyjyylxxkvolfYnJ?u;;z=A|CHyLTvZbsm}xu|w{z|z~zvngpa`p_]pZXrY}\rbzirp|wt}~qo~m|}n}|p}~p~~p~~~q}{rztrqkscWsI=t63y0}/|.{..{--{-~,{,}-z/|5y<zCyLvUz]od}jjmkelkdlldmjfheh]WkODp91t,*y*},{+{/z5{@vH{RpX{^mc{kkr{wj}{ny
syv
{u{u{w|x|x|w|v{w}w~|vvls`SnQ~RlU|[mezlotzyq}|~s}~|t}~|v||v{wupivbXsM~Aq9{6p8w8s>sFuXqdxqs~zxv|u}t~u~x~x~x~{xq}ft`y\t^tcxjsrzvwwwt|soqplqrkrrlssjqojljjlgme}^p\yZrYwYr:ÿ:ÿ<ÿ<ÿ:ÿ:ÿ;ÿ;ÿ;ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ)ÿ*ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ'ÿ)ÿ*ÿ*ÿ+ÿ/ÿ/ÿ/ÿ+ÿ)ÿ&ÿ%ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ%ÿ)ÿ/ÿ8ÿEÿWÿfÿtÿÿÿzÿnÿaÿPÿ6ÿ)ÿ)ÿ.ÿ5ÿ9ÿ;ÿ3ÿ*ÿ)ÿ)ÿ*ÿ+ÿ.ÿ1ÿ6ÿ;ÿ@ÿFÿLÿQÿVÿZÿ\ÿaÿdÿcÿfÿiÿhÿhÿiÿiÿhÿgÿcÿ_ÿYÿRÿKÿBÿ<ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ2ÿ5ÿ6ÿ6ÿ6ÿ6ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ7ÿ7ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ8ÿ:ÿ=ÿAÿDÿHÿIÿLÿQÿWÿ_ÿeÿmÿtÿwÿuÿsÿsÿsÿrÿtÿtÿvÿvÿtÿpÿhÿYÿJÿ>ÿ8ÿ5ÿ9ÿ<ÿAÿDÿIÿNÿRÿVÿYÿ\ÿ_ÿdÿhÿlÿuÿ|ÿÿÿÿÿwÿnÿkÿyÿ~ÿ|ÿpÿ{ÿÿ}ÿuÿjÿXÿIÿ:ÿ3ÿ3ÿ7ÿ=ÿEÿOÿ^ÿKÿHÿaÿrÿsÿrÿnÿfÿZÿKÿ>ÿ5ÿ.ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ4ÿFÿSÿkÿkÿuÿtÿeÿbÿ`ÿaÿZÿNÿBÿSÿfÿkÿzÿÿ}ÿ{ÿzÿyÿyÿyÿyÿyÿxÿxÿsÿmÿfÿ\ÿPÿDÿCÿAÿCÿGÿJÿNÿRÿZÿaÿjÿrÿ|ÿÿÿÿÿÿÿÿuÿiÿcÿ_ÿ\ÿZÿUÿRÿSÿTÿXÿ`ÿfÿnÿwÿÿÿÿÿÿ~ÿ|ÿ}ÿ{ÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ|ÿyÿvÿuÿqÿkÿbÿVÿIÿ=ÿ6ÿ3ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ/ÿ4ÿ<ÿCÿMÿWÿ_ÿgÿjÿmÿnÿlÿkÿlÿlÿlÿiÿeÿbÿ]ÿWÿOÿDÿ9ÿ1ÿ,ÿ+ÿ+ÿ+ÿ/ÿ5ÿ?ÿGÿMÿUÿ\ÿdÿiÿoÿtÿxÿ}ÿ~ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿvÿlÿaÿUÿQÿSÿZÿ`ÿgÿoÿvÿ{ÿ~ÿ~ÿ}ÿ|ÿ{ÿ}ÿ}ÿ|ÿ{ÿuÿpÿiÿaÿTÿIÿ=ÿ7ÿ6ÿ6ÿ9ÿ?ÿJÿ\ÿjÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿnÿdÿ[ÿ[ÿ^ÿcÿiÿqÿvÿwÿsÿrÿqÿqÿrÿrÿrÿrÿsÿsÿrÿoÿkÿjÿhÿfÿbÿ]ÿ\ÿ]ÿ^ÿ^ÿ::i::j;;j;;l:9l98m77o87p88q77q88q77r87r77s66t4~3t33u33u22x22x1~1y1~1{1~1{0~0z1~1z0~0z0~/{/~/{.},|.}.|,},{,{,{*|)|)|)|)}'|&}&|'}'|&}&|%}%|$}$|%}%{$}${$}$y$}${$|$y#|#y#|#z#|#z#|#z#|$z$}$z%}%z&}&z%}%z&|&y'|%y(}*w'}'w%}$x"}"x"}"x"}"y"~"{"~"{"~"|#~#|!~!}"~"}""| | | z!~!z!~"{$'z+4t>
Qmbody`|
scgWjB}-t)~1z;?~A7,**~,-169=CwJQsVXm^bgfecggcjhbi
idhfga~]iVzOqLxDu<w9}7w75v45v45v56w64w44x44x32x21x10z0/z/.z..{--z--z--{-.{./{01{11{12{33z45z66z6
7z76y6
8y89x98x98y87y76y66y65y66y66{7
6}:=@CEzIJwMQtW\pemnsvnwukspisshssiuuiqjl_PpC<r<>qAElJ
NgRXd[
^ba
eagk`pvczi
oxwqujrfqvf[{h~s{zyxo^wM=z42~4~9C~P|[vPxOu_rq{smrkneZqK>w6}/,{*+z+
+z++z+
,z,
-{-+z++w8Yxrl}gd}p{dxWuVtTrSu]qh{us{~t}{p{zlyzjyxkxxjvoki_mUMpJJsLMuPTtYaphopv~t}y|{|zz}rue]rZVqRMtJ|MtO|Su]{fvr{|upn~m||n|}n~~o~~~q}~q{xsxuqpiqbTrI>u52y.}-}-{--{,+{++{+~*z0{5y>xGyOuY{aoh}kjnlemkdkkcljegciZUmLBq8/u+}+z+|0z4{=xG|NrV}]ob~inn~snw}yp|{rzvzy
{x{v{xzzzz{y{yzx|x~|xulraVlRUj\|bmi|qpx||r~|~t~}~w}}~w~~}ux~uvmew^RuE~:r6{6s4w8wAsMy\rl}yu{yw}u}u~v~x~yz~vyl}`uYxYu^texltsyvwwtu|snqrkqqkrrkrrkroklhkgcl`{\l\x^n`wbm;ÿ;ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ7ÿ8ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ7ÿ7ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ.ÿ+ÿ,ÿ,ÿ,ÿ,ÿ)ÿ(ÿ)ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ#ÿ#ÿ%ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ"ÿ#ÿ$ÿ)ÿ/ÿ9ÿKÿ]ÿlÿwÿÿ~ÿtÿkÿ\ÿJÿ3ÿ)ÿ,ÿ4ÿ<ÿ?ÿ4ÿ)ÿ)ÿ*ÿ,ÿ/ÿ3ÿ8ÿ<ÿCÿGÿNÿSÿXÿ\ÿaÿdÿfÿgÿhÿhÿhÿjÿiÿgÿdÿaÿ[ÿVÿOÿHÿDÿ>ÿ9ÿ5ÿ5ÿ6ÿ5ÿ5ÿ5ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ8ÿ8ÿ:ÿ=ÿ@ÿBÿEÿEÿIÿLÿMÿQÿVÿZÿdÿlÿsÿwÿwÿvÿtÿrÿrÿrÿrÿtÿsÿsÿqÿlÿbÿWÿNÿEÿBÿDÿJÿMÿQÿUÿ[ÿ_ÿdÿfÿiÿkÿpÿsÿvÿzÿ}ÿÿÿÿvÿuÿtÿ|ÿdÿPÿDÿ?ÿ@ÿDÿ^ÿ^ÿXÿoÿhÿJÿ8ÿ0ÿ4ÿ9ÿ?ÿKÿZÿNÿMÿ^ÿlÿrÿrÿmÿeÿZÿKÿ>ÿ6ÿ/ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ5ÿMÿ]ÿcÿhÿkÿcÿDÿHÿMÿLÿSÿ`ÿiÿrÿ{ÿÿ}ÿ{ÿyÿyÿwÿxÿyÿyÿyÿxÿuÿqÿkÿcÿ\ÿXÿRÿQÿPÿTÿVÿ[ÿ`ÿfÿnÿuÿ|ÿÿÿÿÿÿÿÿxÿmÿcÿYÿSÿLÿHÿDÿBÿEÿMÿUÿ^ÿiÿvÿ}ÿÿÿÿÿÿ~ÿ|ÿ|ÿ}ÿ}ÿÿÿ~ÿ~ÿ}ÿ|ÿ{ÿxÿwÿtÿpÿiÿbÿTÿIÿ>ÿ5ÿ2ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ1ÿ9ÿ@ÿHÿPÿ[ÿbÿhÿlÿmÿkÿlÿlÿkÿkÿkÿiÿeÿaÿXÿSÿJÿ@ÿ5ÿ/ÿ*ÿ+ÿ-ÿ3ÿ;ÿEÿPÿUÿ]ÿcÿiÿoÿrÿwÿzÿ|ÿ|ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿuÿiÿ^ÿWÿUÿXÿ_ÿfÿmÿtÿyÿ}ÿ~ÿ~ÿ|ÿ}ÿ}ÿ}ÿ|ÿ|ÿxÿuÿkÿfÿ\ÿPÿBÿ8ÿ4ÿ4ÿ5ÿ9ÿDÿPÿ_ÿoÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿuÿhÿ^ÿYÿYÿ^ÿeÿlÿsÿwÿwÿuÿsÿrÿrÿqÿqÿsÿsÿsÿsÿrÿoÿlÿhÿeÿbÿ^ÿ^ÿ^ÿ_ÿbÿdÿ<;i;;j::l::m98l98m86o77p88p77p66q77s65t66u42v1~1v11v11w00x00x/~1y1~1{0~0{/~/{/~/|0~0|/~.|-~-|,},|-},|,}+|,{,|)|)|(|'|&}&|%}%|&}&|%}%|%}%{%}${"}#y#}#y#}#y#}#z#|#y$|$y"|"y"|"y#|#y#|#y#}#z$}$z$}$z$}$z$}$y$}"y"}"y$}$y$}#y"}"y"}"y"}"{"~"{"~"{"~"|"~"|!~!| ~ | { { | z~ z!~"{##z'-w5CqV
hks}b{araeR>n.}+w/}7}5}/*}+,~.05;>|D
JsPWm\`gcfaii`jjajicece_}ZiTzOoGwBw<x7|6x54v56v54v45v54w44w54x43x22y21y10z/.z.-{--{,-{-,{,-{-.{..{01{11{12{33z35z55z56z6
7y77y78y88y98y86y66y66y66z5
6{79}<=@CE|FIxLKsMOqTYpclntvowvltrhrrgrrirsiqojh^lTLkHJjO
SgY^dbhak
n_stcvyez}imqm~YuZ}qzhU}[b~i^Px}Vf|`_T<:?Yn{bzKwGvNs_zpprlneZrK>x5}.-{+,z,
,z,,z,
,z,
,|++,2;|PV|Wb}f~QxOy]rIsEqLuVue{qwz~~v~|pxwlxyjz{k{{jyviphic^l[
YpWZq^cpgmmrylq~v|z|{v~kv_QsL~Dv=|8w;z@zM{Yxe|qv{~rpn~n~}prr~~}r|{rzxruuspirbTtI=v30y-}-|,{,~+{+*{*+{+~-z3{:yBwJyTr\|dnh}kijlejjekjfigic_kXOnF<r3-x)}-z2|;yF{OvV|]qd}jop~rnv~zn{}}p~{uxxzzzw{x{zz{z{{y{yzy|y~}xvmrbWkSZla|inp|xp{|}r~|~t~}}u}~}v|~}vwuwicwWJu?~6t2{2v2w9yDsT|brp}}w{zw~u}w~x~x~yyzqwh}]uWxXw_tf{mttyvxtru|tmqrksrjssjtsjqnkkfkcak_|`l_zajdxfj<ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ"ÿ$ÿ#ÿ"ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿ!ÿ!ÿ"ÿ#ÿ#ÿ%ÿ)ÿ4ÿ@ÿNÿ`ÿmÿzÿÿ}ÿuÿfÿYÿIÿ5ÿ,ÿ+ÿ-ÿ,ÿ+ÿ*ÿ+ÿ-ÿ0ÿ4ÿ7ÿ>ÿCÿGÿNÿUÿZÿ^ÿbÿdÿgÿiÿiÿjÿjÿjÿfÿaÿ\ÿVÿQÿJÿDÿ@ÿ;ÿ7ÿ6ÿ6ÿ5ÿ6ÿ6ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ5ÿ6ÿ7ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ8ÿ9ÿ=ÿ=ÿ?ÿBÿEÿHÿIÿKÿLÿMÿOÿPÿSÿ[ÿbÿkÿpÿuÿzÿyÿuÿsÿqÿqÿqÿqÿsÿuÿtÿsÿnÿeÿ[ÿTÿPÿQÿUÿZÿ_ÿeÿjÿqÿuÿxÿÿÿÿÿÿÿÿÿÿÿvÿXÿ@ÿPÿZÿRÿPÿ@ÿXÿ`ÿ^ÿdÿlÿNÿJÿAÿ@ÿGÿKÿhÿÿÿrÿIÿCÿLÿcÿsÿqÿkÿfÿ[ÿKÿ>ÿ5ÿ.ÿ-ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ/ÿHÿnÿ}ÿÿÿÿtÿcÿfÿjÿVÿ[ÿJÿNÿ\ÿXÿeÿqÿzÿ|ÿ}ÿ{ÿxÿwÿzÿzÿyÿ{ÿ{ÿ{ÿzÿxÿvÿoÿgÿaÿ_ÿ]ÿ[ÿ_ÿcÿhÿmÿsÿxÿ}ÿÿÿÿÿÿÿÿ|ÿuÿiÿZÿLÿ@ÿ7ÿ3ÿ3ÿ9ÿCÿPÿ\ÿhÿvÿ}ÿÿÿÿÿÿÿ~ÿ~ÿÿÿÿÿÿ~ÿ}ÿzÿyÿxÿxÿwÿsÿpÿiÿbÿTÿIÿ=ÿ3ÿ0ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ3ÿ9ÿCÿKÿTÿ\ÿdÿjÿkÿkÿjÿjÿjÿiÿhÿhÿeÿaÿ[ÿVÿMÿBÿ8ÿ1ÿ,ÿ+ÿ0ÿ7ÿBÿKÿSÿ]ÿfÿjÿoÿtÿyÿ}ÿ~ÿ}ÿ}ÿ~ÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿjÿ_ÿXÿWÿ]ÿeÿmÿqÿyÿ{ÿ}ÿÿÿÿ~ÿ~ÿ~ÿ|ÿ}ÿxÿrÿhÿ^ÿUÿHÿ;ÿ3ÿ1ÿ2ÿ2ÿ;ÿFÿXÿgÿsÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿtÿkÿcÿ[ÿWÿXÿ_ÿfÿmÿtÿvÿuÿrÿqÿqÿqÿrÿrÿqÿrÿrÿqÿpÿmÿjÿeÿcÿaÿ`ÿaÿaÿdÿeÿfÿ;;j;:k::l99m98n77o77p66p55p66q55r56t4~2v2~2v2~1v0~0v11w00x//y//y.~.z-~-|-~-|-~/|/}.|.}.|-~-|+~+|+}+|+|)|)|)|*|)|(}(|'}&|&}&|&}%|%}%|$}${$}$z$}$z$}$x$}$x$|#w#|#w#|#x#|#y#}#y#}#y$}$y"}"y#}#z#}#z$}$z"}"z#~#y"~"y#}#y#}#y#~#z#~#z""{""|""|""|!~!{!~!| ~ { ~ { { {{} {!~!{"~"{"~"{$~(z.8tGWog
shz|ex
neaRk>~-t*~(z(~*~*~,-058~>EyLRsY\j`cedh`ij`ih_d_cY|TjNyHqBx>v9w7}6w33w54w44w45w55w53w34w43x32x21y10y00z/.z..{.,{,-{-.{--{-.{./{00{12{02{34z45z55z55z55z5
6z6
6z6
8z8
7y66y64z47z6
7|9:<>ABF|HJtJLqNLpNQpQYobhnptmz{kvthqqgpphrthvviqkid]eXWa[]ddjcq
wd|cjlnpt~jxV~Q}ZVyZ\wcf|ib\EDLQD@|Lh{wmyYG{Zxwsqolpe[sK=y6}/-|-,{,
,z,
,z,
+z+
-x/[x~wvxw}x{i~izwykvRsIrVr_vXwd{q{|~y~|qyxkwwjy{j{|k{zjwokj
enb
`nbdmimlryi}lru}
{}{{qeyVFw;4x0}4{={H|U{axm|xx
s
popqrr}|r{xpxwrwvtrktbVuJ=w3.y,~,|-|-~-},,}+~*{*{/y3y:yDwNzXqa{eli~ihiidiidjifebg\WjQHn>5r.~-v/{5v>{HtT}\qd}kqo~vq{~~p~}p|}}r~{uxzyzzx{w
{yz{zz{y{yzy|z
~~zujs^Zn\`nh{oqs{yr{|}s~}~u}u|v~~|ux~pvh\vSEt80s-},v0w<yJs\{itw}xz|w}w~y|y|x|zyyumxb|XuTwWvauhzots{vyusr~pmrrjsriqtisrioljiejccjcdjd~ehf~hj<ÿ<ÿ;ÿ:ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ/ÿ.ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿÿÿÿ ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ#ÿ&ÿ*ÿ3ÿ?ÿPÿ_ÿmÿvÿ|ÿzÿsÿjÿ[ÿMÿ6ÿ,ÿ+ÿ*ÿ+ÿ,ÿ/ÿ0ÿ4ÿ6ÿ;ÿAÿHÿMÿTÿYÿ]ÿaÿdÿgÿjÿmÿkÿjÿdÿ^ÿZÿRÿJÿEÿAÿ>ÿ9ÿ5ÿ5ÿ4ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ0ÿ/ÿ0ÿ0ÿ1ÿ2ÿ0ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ5ÿ6ÿ5ÿ6ÿ6ÿ7ÿ9ÿ;ÿ=ÿ>ÿ@ÿAÿBÿEÿGÿIÿKÿLÿMÿMÿMÿOÿPÿSÿVÿ^ÿgÿnÿuÿzÿzÿxÿuÿsÿqÿsÿsÿsÿuÿwÿwÿtÿpÿjÿdÿ_ÿ^ÿ`ÿdÿiÿoÿuÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿpÿVÿIÿIÿWÿ_ÿmÿrÿkÿlÿnÿYÿGÿQÿCÿFÿGÿIÿIÿKÿTÿbÿ^ÿOÿ[ÿtÿnÿmÿiÿcÿ[ÿLÿ>ÿ6ÿ/ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ,ÿ.ÿ>ÿYÿhÿpÿkÿfÿdÿ_ÿnÿkÿPÿOÿ^ÿ^ÿTÿdÿpÿyÿ~ÿ~ÿ|ÿyÿxÿxÿxÿzÿzÿ{ÿ|ÿ{ÿyÿvÿoÿjÿeÿcÿcÿfÿiÿmÿqÿvÿzÿ}ÿÿ
ÿÿÿÿÿ
ÿÿ{ÿqÿeÿTÿDÿ8ÿ2ÿ1ÿ8ÿBÿMÿ[ÿfÿrÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ|ÿ{ÿxÿxÿwÿwÿvÿqÿlÿbÿVÿIÿ<ÿ3ÿ.ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ.ÿ5ÿ<ÿGÿNÿXÿaÿfÿiÿiÿiÿiÿiÿiÿiÿhÿeÿ`ÿ\ÿXÿOÿEÿ9ÿ2ÿ/ÿ0ÿ4ÿ<ÿDÿOÿYÿbÿiÿnÿsÿyÿ~ÿÿ~ÿ}ÿ|ÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿuÿkÿaÿ\ÿ_ÿcÿiÿpÿvÿzÿ{ÿ}ÿ~ÿÿÿÿÿÿÿ|ÿuÿpÿhÿ\ÿOÿ@ÿ5ÿ.ÿ+ÿ+ÿ3ÿ>ÿOÿ_ÿlÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿyÿrÿkÿ^ÿVÿSÿXÿaÿiÿpÿuÿvÿsÿrÿqÿrÿrÿrÿrÿsÿsÿsÿsÿoÿlÿiÿeÿdÿdÿeÿfÿfÿgÿhÿgÿ::j;;k;;l99m87n77o66p65p66p55q44s42t00v11v11v10v/0w./x..y//y.~.z-~-|-~-|-~-|-}-|,},|,~,},~+}*}*|(|*|)|'|&|&|(}(|(}'|%}%|&}%|&}&{%}%{$}$z#}#y$}$x$}$x$}$y$}$y#}#x#}#y"}"y"}#y#}#y"}"y#}#z"}"z"}"z"}"z#~#z#~"z"}"y!}!y!~!z!~!z!}!y!}!z!~!z!~!z!~!{!~!| ~ { ~ { !{ {{}{ ~ {!~!{ ~!{#~$z'.w8GrWfmp
zg|wcocfTCn2,u--y01{36{:@zEKwR
UqY]haeci
j`jjahacY|SiLzGqBx=x:w6{7v4~4w45w55w54w44w55w54w33w33x33x21y10y0/z/.z.-{-,{,-{--{-.{./{/.{/1{11{11{11z24z45z55z55z5
4z4
5z5
5z5
5z4
4{55|68~<<>@A}BF{IIvJLtMMqNOpNOpSXn[cnkrpwxqxtlsrhrqgsvgxxhxuhojffdbe
hclqbw
bdjnrtvv^;yBXv]Wqgasg}tpVtd\xKDCGOU[VO}U}i~jznlwcZxL>|7}0,|,,{,
,z,
+z+
,z,
,{,6{SK}NV}Wa{PIxW}QtNyXtRw[vXwe{p{z}}x}~{rxwkxyjzzi{|i{{hxsjjfkcfkfjjotiwzj}msw}
x}zxqdySCw72x5}<{E{R|]{jxv|vrnnpqss}|ryvpvxrxvtqltdWuI;w2-y+}+|,{,~.{-~+{+}){,}/z6z=yGtRz\pb{fkh~ifhheiieigge`iZTkMCo81s2~7u;}DrK}Vq^~epk~rpv~|o~~o~~}o{}}s{xx
}y
|zy{
y{zz{zz{y{yzz||
~~xukr`Ym_}dnk{qrw{|t~|}u~}~v~v|v~{ww~owdYvL=t4-u+},x5w@{Rta}pt}xz|w}w~y|y|z|
{y{phyZ|VvSwWyaui|ourztysro~omqqkqrhrrisrimkjhejffigikihjgek:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ9ÿ9ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ2ÿ2ÿ1ÿ1ÿ2ÿ1ÿ0ÿ/ÿ1ÿ2ÿ1ÿ2ÿ2ÿ1ÿ2ÿ2ÿ1ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ)ÿ)ÿ+ÿ+ÿ+ÿ)ÿ*ÿ*ÿ*ÿ)ÿ(ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ!ÿ ÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ ÿ!ÿ"ÿ#ÿ%ÿ*ÿ2ÿ?ÿOÿ`ÿjÿtÿ}ÿ}ÿwÿlÿ\ÿOÿ@ÿ2ÿ2ÿ2ÿ5ÿ6ÿ8ÿ:ÿ?ÿCÿGÿMÿQÿWÿ[ÿ_ÿbÿfÿhÿiÿjÿgÿbÿ[ÿTÿMÿFÿAÿ=ÿ:ÿ7ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ5ÿ6ÿ8ÿ:ÿ<ÿ=ÿ@ÿBÿCÿDÿFÿHÿJÿJÿKÿLÿNÿNÿOÿOÿNÿOÿSÿTÿYÿ^ÿhÿnÿuÿxÿwÿuÿsÿrÿrÿqÿsÿvÿzÿzÿzÿwÿuÿoÿjÿfÿiÿjÿmÿtÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿ^ÿVÿYÿPÿ3ÿ]ÿsÿÿÿ|ÿwÿjÿ\ÿTÿWÿRÿSÿMÿMÿYÿWÿQÿNÿUÿ^ÿfÿdÿ`ÿYÿMÿAÿ7ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ2ÿAÿ]ÿaÿPÿZÿ[ÿQÿ]ÿTÿ:ÿBÿEÿJÿTÿ^ÿeÿqÿzÿ}ÿ|ÿ{ÿxÿwÿxÿyÿyÿyÿ{ÿ|ÿ{ÿ{ÿvÿoÿjÿfÿeÿhÿhÿlÿpÿuÿuÿxÿ{ÿ|ÿÿÿ
ÿÿÿ
ÿÿzÿqÿdÿSÿCÿ8ÿ3ÿ7ÿ>ÿIÿVÿaÿoÿyÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ}ÿ|ÿyÿvÿvÿxÿxÿvÿqÿlÿdÿWÿIÿ;ÿ2ÿ-ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ+ÿ-ÿ2ÿ9ÿAÿJÿTÿ\ÿbÿfÿhÿiÿhÿhÿiÿiÿiÿgÿdÿ_ÿYÿQÿJÿ@ÿ7ÿ2ÿ5ÿ=ÿBÿJÿSÿ\ÿbÿhÿnÿsÿwÿ{ÿ|ÿ}ÿ{ÿzÿzÿ{ÿÿÿ
ÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿtÿiÿaÿ^ÿ`ÿhÿlÿsÿyÿ|ÿ~ÿ}ÿ~ÿ~ÿÿÿÿÿÿ{ÿwÿoÿdÿYÿJÿ;ÿ4ÿ-ÿ,ÿ/ÿ6ÿCÿXÿdÿtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿxÿmÿeÿYÿUÿRÿXÿaÿjÿpÿtÿrÿrÿqÿpÿqÿqÿsÿtÿsÿsÿrÿpÿmÿkÿjÿiÿjÿjÿhÿhÿhÿfÿdÿ^ÿ<<j;;k::m99n87n66o66p55p33q33s1~0t11u02v11u46u67s43t10u//v..y--{,,{,+{,,},~,{+~+|*}*}+}+~)})})}'}(|(|)|)|&~&|%~%|%~%{%~#{%~%{%~%z#~#z#~#y#}#x#}#x#~#y#~#y#}#y#}#y"}"y"}"y$}$y"}"y"}"y"}"y"}"x#}#y#~#z"~"z!}!z!}!z!}!z!}!z!~!z!~!{ ~ { ~ {!~!{ ~ { ~ {~{!{ {{{! { "{!!{""{#&y,9tEUqc
njx}ezrbeYfK;l66q9;t>>uC
HtLRqT
Xm]ahdfehicgcc]|UiPyJoBx=v8u7{6v66v54v34v44w45w54w45w54w43w33x33x21y1/y/.z.
.z.
.{.-{--|--|--{..{..{/1{10{01{24{33{34{45{55z54z55z5
4|4
3|6
8~8:<>@}BDzEGwGIuJKsLMqNNqOOpNPpOQnSYnbjpqyqvtptsgrqesvey|f}ygwrfmidjicnuczb
dhlp~swwqikSjylmypobr]LvEV{^YUQOMOPRYa\}QC|71-~-,|,,{,
,{+,{,-z*
-y5Z|g~W}Ta|VcxT:vG{LuSyXx^zh{u||~}w}zqzymwwkxzj{{k{xkslkgcmcckfimlokrukyyo}u~
x
}z|zxrdxTEw:5y8|AzN{[{g{rx{}v~tpp~qrs~s{zryvrvwrxwtsltcVuH;x2-z+}+}+|+~*{*}*{*|+z.z4z;xFyNsVz\nc}fkg~hhggghhhhhkd_mWOmG<o59p<~BpH~PpX~_pdipnrpvypywov~xoy}ztyzx
x
~y{{
z{z{{{{zzzz|{|{}xujpbalb~jop{utz{}u}|}u~~~v~w}w}{wvnwdWvG:u3,v*|0y;wH|Ztj~xvzz|w~x}x}y}|}|~v}lbzU|QvRwY|atj|qvvxu{rqsqlppjqrjsskqojmlglkfljijhkddiaZl<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ:ÿ8ÿ8ÿ7ÿ6ÿ6ÿ5ÿ5ÿ6ÿ4ÿ3ÿ3ÿ2ÿ1ÿ/ÿ/ÿ0ÿ0ÿ1ÿ/ÿ1ÿ4ÿ5ÿ8ÿ;ÿ<ÿ:ÿ8ÿ5ÿ3ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ+ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿ ÿ ÿÿÿÿÿ ÿ ÿÿÿÿÿÿÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ%ÿ%ÿ)ÿ2ÿ<ÿIÿZÿhÿrÿ{ÿ}ÿvÿmÿcÿWÿGÿ<ÿ;ÿ=ÿ?ÿBÿDÿIÿMÿRÿVÿXÿ]ÿ^ÿbÿeÿeÿhÿhÿfÿ`ÿYÿSÿKÿFÿ=ÿ9ÿ7ÿ6ÿ5ÿ5ÿ4ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ0ÿ0ÿ1ÿ2ÿ2ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ7ÿ8ÿ;ÿ<ÿ<ÿ?ÿAÿCÿDÿFÿFÿHÿIÿIÿJÿKÿLÿMÿNÿNÿOÿOÿOÿOÿPÿOÿRÿVÿ]ÿdÿlÿuÿxÿvÿtÿsÿrÿqÿsÿvÿwÿzÿ}ÿ{ÿyÿsÿnÿjÿjÿmÿqÿsÿvÿ{ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ|ÿ{ÿ|ÿÿÿÿÿÿ{ÿsÿgÿ\ÿNÿGÿVÿ[ÿXÿZÿPÿLÿJÿMÿOÿLÿLÿYÿ^ÿVÿHÿ=ÿ4ÿ.ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ5ÿEÿ]ÿ\ÿGÿ[ÿVÿWÿOÿFÿWÿVÿGÿ>ÿPÿdÿmÿxÿ~ÿ}ÿzÿzÿyÿxÿwÿyÿzÿzÿzÿxÿuÿoÿjÿdÿbÿbÿbÿbÿgÿkÿkÿoÿsÿuÿxÿzÿ}ÿÿÿÿÿÿyÿpÿeÿTÿEÿ9ÿ6ÿ:ÿCÿPÿ^ÿjÿvÿÿÿ
ÿÿÿÿÿÿ~ÿÿÿÿÿ~ÿ~ÿ}ÿ{ÿzÿyÿvÿwÿyÿxÿwÿsÿlÿcÿVÿHÿ;ÿ1ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ0ÿ6ÿ=ÿGÿOÿWÿ_ÿfÿhÿiÿgÿgÿgÿhÿhÿgÿfÿbÿ]ÿWÿOÿGÿ?ÿ<ÿ@ÿDÿIÿQÿXÿ\ÿbÿhÿlÿlÿrÿuÿvÿvÿsÿrÿsÿwÿzÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿuÿkÿ`ÿ`ÿfÿlÿrÿxÿzÿ|ÿ}ÿ}ÿ~ÿ~ÿÿÿÿÿ~ÿzÿsÿkÿbÿSÿDÿ8ÿ1ÿ,ÿ+ÿ1ÿ;ÿMÿ_ÿnÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿsÿiÿ^ÿSÿPÿRÿ[ÿbÿjÿrÿtÿtÿrÿrÿrÿrÿrÿrÿsÿtÿtÿsÿqÿoÿnÿlÿlÿlÿlÿjÿhÿfÿ`ÿ[ÿVÿ;;i99j99j98k76n66o53p43p22r1/s/~/t./u/2t27s8<o>BlB@k=9m55p42s11v/.v..v.-w--y++|*)}))~)})~(}(~'~'~(~'~&~&|%~%|%~%{$~${%~%{%~%z&~&y%~%y$~${%~%{$}$y#}#y#}#y#}#y#}#y#}#y"}"x"}"x"}"w"}"w"}"w"}"x#~#z"~"z"}"z!}!z!}!z!}!z ~ z ~ {~{~{~{~{~{~{{{ { { {!!{!!{""{#%|&*z3BuN^mhtf}z`s
jcbUfG
AkBCmHLlPTkWZh]_db
dbe
fbgebb\gW~OmGzBs;w8|5w4}5v55v56v65v54w45w56w65w55w54w44x42x20y00y00z/.z.-{--{-.|.-|--|-/|//|01|10~11~21~22~22~22~24~23~43}5678<=>|AByDEvEEtHIsHIsKLqLMpOOpMNpONpNMqOPrU~^sg~rtvvptsjtsfttfwyg|}hzugolejlflmfpsbz_`fmsu~w~vp|{n{}k~n{ptkr^RvNU}OPUVVMGKOSS~TWRG;4+.|.-{,
,{,-{--z,
.y.0z5:z=Q{N}QuE{DsOzPvC>{Pk~g~w~|w{xqwwmyykyzkyxlutllilb[l]^laenigomppquqw}yu{{~x{{}}wzlbyRFx8}9y={HzT{bzn{yw}v~rpp~q~r~t}|t{yrxvrxyrxwtsltbVuG9u0+x)})})|)~*|*){)~+z1{8z@wIyRqZ{anf}gii~hghhghghgekaZmUNpFBoAEnJPnY[o_dohkompor~qoo~mno|rpv{zu~y|x
x~y{zzzz{z{z|z|z||||~~wrgobblg}mrt{yvz{{v}|~u~v~w}x}}xyriy]NtB7t.+v,|3y?vO}btru}zy|w~x}y}z}|}|y~q{g[wQ|PvRx[|cul|qwuxv{tpsrlsskttkttjrrhppfolgkkikfkc\nUPp;ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ1ÿ6ÿ:ÿ;ÿ@ÿCÿFÿIÿFÿDÿAÿ>ÿ<ÿ;ÿ8ÿ5ÿ5ÿ4ÿ3ÿ1ÿ1ÿ2ÿ0ÿ.ÿ.ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿ ÿ ÿ!ÿ!ÿ"ÿ#ÿ"ÿ"ÿ"ÿ#ÿ%ÿ'ÿ/ÿ:ÿGÿVÿcÿnÿxÿ|ÿwÿrÿjÿaÿSÿHÿHÿKÿNÿRÿVÿZÿ]ÿ`ÿaÿbÿdÿeÿfÿeÿdÿcÿ`ÿ\ÿUÿNÿFÿ>ÿ9ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ0ÿ1ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ7ÿ8ÿ9ÿ<ÿ=ÿAÿAÿCÿCÿEÿGÿHÿHÿIÿIÿIÿJÿLÿLÿMÿNÿNÿNÿMÿLÿMÿLÿKÿHÿIÿJÿOÿWÿbÿlÿsÿvÿuÿsÿqÿrÿtÿtÿwÿyÿ{ÿ|ÿzÿuÿoÿlÿiÿhÿfÿgÿgÿlÿpÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿzÿ|ÿ~ÿÿ~ÿ~ÿyÿqÿjÿ]ÿUÿUÿRÿRÿNÿNÿLÿNÿIÿDÿGÿOÿPÿNÿJÿMÿPÿJÿDÿ9ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ3ÿ=ÿPÿWÿPÿLÿCÿIÿSÿQÿCÿAÿOÿmÿiÿwÿ|ÿzÿxÿwÿwÿxÿyÿyÿzÿxÿwÿtÿsÿlÿgÿ_ÿYÿXÿZÿ`ÿfÿkÿnÿoÿlÿnÿnÿoÿrÿvÿyÿ|ÿ}ÿ{ÿuÿkÿaÿPÿDÿ8ÿ9ÿ@ÿJÿVÿdÿqÿ|ÿÿÿÿÿÿÿÿÿ~ÿÿ~ÿÿÿ~ÿ}ÿ|ÿyÿvÿvÿvÿxÿyÿxÿwÿsÿlÿbÿVÿGÿ9ÿ0ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿ2ÿ9ÿAÿJÿSÿ[ÿbÿfÿgÿhÿiÿhÿhÿhÿgÿfÿbÿ_ÿYÿUÿMÿIÿDÿGÿJÿRÿXÿ^ÿ_ÿbÿeÿjÿlÿlÿmÿlÿkÿiÿiÿlÿpÿuÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿrÿhÿcÿfÿjÿpÿuÿzÿ{ÿ|ÿ}ÿ~ÿÿÿÿÿÿÿ|ÿwÿqÿhÿ]ÿNÿ@ÿ6ÿ.ÿ+ÿ*ÿ6ÿCÿTÿdÿtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿnÿdÿVÿOÿOÿTÿ[ÿdÿlÿtÿtÿsÿsÿsÿrÿrÿrÿrÿsÿtÿtÿsÿsÿqÿpÿoÿoÿnÿmÿhÿcÿ_ÿWÿNÿIÿ<<j::j99i88j85l54o54q41r21r/.s..t./u03s6:q=AmFJhMMgKHfG
EgB
?j?<n<;n87o76q54r11v.,v))y)~)|(~'|'~&{&~&{%~%|%~%|&~&{%~%{&~&z%~%z%~%z%~%z%}%y$}$y#}#x$}$x#}#y#}#y#}#y#}#y"}"y#}#y#|#x#|$x"}"x#}#x$}$x"~"x!~!z!|!z ~ z!~!z ~ { ~ {~{~{{{{{ ~ z~z~y~ { | !|$~$|#~#|!~!|$~&|+2w?Mq\hht~b|v_rkbdYcTRcVXd\`dcccefaffaifbdaf_|XmQzKqCw<x6u6~8u7~6u6~7u77u77v76v65v66x68x97y65z53y33y31y00y00{0/|-.{/.|..~.-~/.}/-~/~01224~4555668~66}78}66}88};>{??y?AwCDwFFtHHsIIpJJrKKpKLnJKnKKoMLoKJqH~FsC}DuI{Sv^}iuqtrvtksshsuhwxiy{izuiqkifdj`_jbedhp_w{[
alu~w~x~w~qy~ymxzn{xqsnvhaz^[~YVTMMJIJFCG~KGCH~PML
H/).-{,,z+
-{,-z-
-z-
,y06yFX{Q|QvIzQvU~GzA
={>J}ky~|y{zrwvmuwkzzjwulurmldmZWlWYq^gsmcxF@}D}c}kznxqzuxzy~z{}uzj~_xP~Cx9}=zD{O{]{hzu}}v~tq~~p~~p~~p~~s}~u{ztwwqvvqwxrxvtrktaSuF8v0,y(}('|'~'{(({)~-z4z;zBuK{Sq\|cmf~gjhhhiihiiifcl`[lXRjLKhLQjV\k_cmeiniipjiohdmd|doi|nrt{xw{{|y~y}y
|
yzzz{z|z|z||{~|{
ttkoggnm}stv|yv{||s}|}t~}w~~~w}x}xwofvXKq?5s-+w.{8|FwXjuw~u|zx}w}y}y}{~|~~|ulybWtP~NuSy^}gvm}txtys|sqq~qmr~rjttjt~uju~ugtsgpogolhf`kZQqI}Hv<ÿ<ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ3ÿ6ÿ:ÿ?ÿDÿHÿLÿRÿRÿPÿNÿMÿKÿIÿHÿFÿDÿDÿCÿ?ÿ@ÿ@ÿ>ÿ<ÿ;ÿ9ÿ9ÿ5ÿ2ÿ-ÿ+ÿ*ÿ)ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ%ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ"ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ#ÿ"ÿ#ÿ#ÿ"ÿ!ÿ$ÿ&ÿ'ÿ.ÿ8ÿFÿXÿeÿpÿzÿ}ÿ{ÿwÿrÿlÿeÿ`ÿ^ÿ^ÿ]ÿaÿdÿfÿfÿiÿjÿiÿiÿiÿgÿcÿ_ÿ[ÿTÿMÿFÿ>ÿ9ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ:ÿ8ÿ9ÿ9ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ/ÿ0ÿ/ÿ/ÿ0ÿ0ÿ.ÿ1ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ8ÿ:ÿ:ÿ<ÿ=ÿ>ÿ=ÿ>ÿ@ÿ@ÿAÿBÿBÿDÿDÿFÿFÿFÿFÿHÿIÿJÿIÿIÿIÿKÿKÿKÿKÿLÿLÿLÿLÿKÿKÿKÿKÿLÿKÿLÿJÿHÿGÿCÿBÿ@ÿ?ÿCÿLÿYÿdÿqÿtÿvÿtÿsÿsÿtÿtÿwÿyÿyÿyÿxÿvÿpÿiÿbÿ\ÿXÿWÿZÿ\ÿ`ÿkÿrÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿyÿwÿvÿtÿnÿeÿcÿaÿZÿYÿWÿVÿYÿWÿMÿKÿLÿJÿEÿ>ÿ<ÿCÿGÿGÿGÿIÿPÿSÿSÿ\ÿLÿ%ÿ-ÿ.ÿ,ÿ-ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ.ÿ4ÿLÿMÿWÿfÿ[ÿSÿTÿ@ÿ<ÿ7ÿ:ÿIÿiÿzÿzÿ{ÿxÿvÿvÿuÿwÿyÿyÿsÿrÿuÿrÿjÿdÿ\ÿXÿZÿ[ÿjÿhÿMÿ=ÿDÿ@ÿ8ÿ]ÿiÿeÿjÿsÿyÿzÿxÿsÿhÿ]ÿLÿ@ÿ;ÿ?ÿGÿTÿ`ÿmÿwÿÿÿÿÿÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿÿ~ÿ}ÿ{ÿyÿwÿvÿvÿvÿwÿxÿxÿvÿqÿjÿaÿSÿFÿ8ÿ1ÿ+ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ.ÿ4ÿ=ÿFÿNÿWÿ^ÿeÿgÿhÿgÿhÿiÿiÿiÿiÿfÿcÿaÿ^ÿZÿVÿPÿOÿRÿWÿ[ÿ^ÿaÿeÿgÿhÿhÿiÿfÿbÿ_ÿ^ÿ_ÿcÿiÿmÿtÿxÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ|ÿsÿjÿiÿiÿoÿtÿzÿ|ÿ{ÿ|ÿ|ÿ}ÿ~ÿÿÿÿÿÿ}ÿwÿoÿfÿXÿHÿ=ÿ4ÿ-ÿ-ÿ1ÿ;ÿLÿ\ÿnÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿtÿhÿ^ÿRÿNÿPÿTÿ]ÿeÿlÿrÿuÿsÿsÿrÿrÿsÿsÿtÿtÿtÿuÿuÿuÿuÿtÿqÿpÿmÿhÿcÿ[ÿSÿMÿHÿJÿ;;j:9j87k77k54l33o32q10r1/r--s..t..u.2s6;q?EkILfRSdSRcQOeOMeKLgKJgIIgJIhFDkB@l=9m51p.*v)~(x'}(z'~&{%~%}%~%}&~&{&~&{&~&z&~&z%~&z&~&z%}%y%}%y%}$y%}%y$}#y#}$y$}$y#}#y#}#x#}#x$|%x$|%x%}%y%}%y%|%{$}#{"}"z!~!z ~ z ~ z~{~{ ~ {~{{{{{~z~z ~ y ~!{!~ |~| ~ |!~!|!!}$&}(-x5@rQ`klvd}c|xatp_lk_hh`hk`kkakkajjaiebb^fYyRlKvDt?t;z9t99t97t7~7t78u89v88v88z9|;~<z>?x?>w>~:y6}4}2{21{/1{10|/2~1~13}21{34x57w7;t<<s<
?qB
BqCGpGIpJLoLMnOOnPRnRTnSRnTTlSSiSSjRRjSShRTgRPgPOhNNiLJjKKjIIlIFoCBr>~;v:|9x=}IyU~cwotruskssgstgwwhyziwtkpek\SpP~NoP~Sk\camqVyXeo~u~v~w|rxwnwwopft]Y{VRQMNV
QFGFC;;?CGHKM~OMO
Q]~@#}*,-~,
-{,,{,
-{,
-{-
0z<
<zWcy`^zS=|:3|AZ}p{~~xyxpwwmvvkusnjhppprjeq`\p\atZ\{UE@7Jca|ayjzsywzvyv~pyh[xM~@x<|BzJzW{dzo{z}x~ur}}r}}r}}r|~s~|tzxsvtqutqvwrwutqjt`TuG8w0+z(}('|'&{'({(}0z6z@zIuOzZoa|elg~hihhghhhjhjhfib`g\XdVVbVZe]`hdfjeeldbm_YnX~Xp\{brh{ovtzxxz{~zy}y}y
|y|z|{{|{|||}{}|z
~swmnkloq~tuy|{w||{uz||v~~~x~x}x|}uwpetXIq=4s..w5{>|Nu_ot}x||y}y}z}y}{~{~{{qfw[QqO~QsYy`zixq}tytyq|qqq~qmr~slstkt~ukw~wiu~tgroglgh`XlNHtE|Jy:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ4ÿ4ÿ2ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ2ÿ4ÿ7ÿ=ÿBÿGÿKÿQÿRÿRÿRÿSÿRÿPÿOÿNÿMÿNÿPÿPÿPÿQÿRÿOÿPÿLÿGÿDÿAÿ=ÿ9ÿ4ÿ0ÿ+ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ!ÿ"ÿ%ÿ'ÿ)ÿ.ÿ4ÿ>ÿJÿZÿfÿqÿ{ÿÿÿ|ÿyÿwÿtÿuÿsÿsÿqÿpÿoÿoÿpÿpÿoÿoÿjÿgÿdÿ^ÿUÿNÿHÿAÿ<ÿ;ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ9ÿ;ÿ;ÿ=ÿBÿFÿIÿJÿMÿNÿKÿIÿCÿ@ÿ8ÿ5ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ6ÿ7ÿ:ÿ;ÿ=ÿ<ÿ?ÿ@ÿDÿFÿFÿIÿKÿPÿPÿPÿRÿUÿWÿWÿYÿ[ÿZÿ]ÿaÿaÿaÿcÿdÿgÿfÿfÿeÿeÿhÿgÿeÿeÿeÿeÿeÿdÿdÿbÿaÿ_ÿ]ÿ\ÿ[ÿXÿTÿQÿNÿLÿJÿHÿGÿDÿBÿ?ÿ<ÿ:ÿ8ÿ5ÿ5ÿ<ÿDÿRÿ^ÿjÿtÿvÿtÿsÿsÿsÿtÿvÿwÿxÿwÿwÿtÿmÿcÿZÿMÿGÿAÿEÿLÿUÿ^ÿiÿoÿvÿ~ÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿxÿvÿwÿxÿsÿkÿcÿ]ÿXÿSÿMÿJÿMÿRÿQÿJÿFÿDÿDÿAÿAÿCÿCÿGÿLÿOÿQÿNÿLÿNÿJÿ^ÿiÿLÿ*ÿ&ÿ-ÿ-ÿ,ÿ-ÿ,ÿ,ÿ-ÿ,ÿ-ÿ/ÿ0ÿ7ÿ>ÿ`ÿkÿ_ÿ_ÿKÿ:ÿ9ÿ6ÿMÿaÿqÿyÿ}ÿyÿwÿwÿwÿuÿvÿwÿpÿ_ÿVÿ_ÿhÿiÿeÿaÿ\ÿaÿSÿ=ÿEÿHÿ?ÿKÿXÿ`ÿYÿWÿ[ÿfÿrÿyÿxÿuÿnÿeÿXÿLÿ?ÿ@ÿEÿNÿ\ÿiÿtÿ|ÿÿÿÿÿÿ}ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ~ÿ}ÿ|ÿzÿyÿuÿtÿrÿrÿsÿuÿtÿqÿoÿhÿ^ÿRÿEÿ8ÿ0ÿ+ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ)ÿ,ÿ1ÿ9ÿCÿKÿSÿ[ÿcÿgÿhÿhÿhÿhÿhÿhÿiÿiÿhÿhÿfÿeÿaÿ]ÿ[ÿ[ÿ[ÿ\ÿ_ÿaÿcÿeÿcÿ`ÿ]ÿYÿWÿTÿRÿWÿ^ÿeÿkÿqÿtÿxÿzÿ~ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿpÿmÿnÿtÿxÿzÿ{ÿ|ÿ{ÿ|ÿ}ÿ~ÿ~ÿ~ÿÿÿÿ}ÿwÿqÿgÿZÿKÿ=ÿ4ÿ/ÿ/ÿ3ÿ?ÿRÿcÿrÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿoÿdÿ[ÿQÿNÿSÿYÿ`ÿjÿqÿsÿsÿsÿsÿrÿpÿrÿsÿtÿuÿuÿvÿwÿwÿwÿwÿsÿpÿmÿeÿ^ÿUÿLÿEÿDÿPÿ88h88j77j76l64n31p11r1/r.-r--s,,u,,x,.v05u9?pC
GlLPhQQeQQgQPfPOfQSfTTdVVdSUfSSfPKgGAh<5l2~-p)~(v'}(y''z%}%{&~&{&~&y&~(y'}'y%~&z&~&z%~%y&~&x&~&x%~%x%}%x%}%x%}%x&}&x$}$x%}&x&|&y&|'y&|%z%|%z$}$z$}$z#}#z"}"z ~ { ~ {~{~{~z~zzzzz~{~{~{ ~ |!~!|!~ |!!|""|"${$&z+/y4;sDQn`jjs
|g
~c|
z_zz\xv_xv^vu^r
t^rranibd}]gUxNlFt?u;s:{;t;:s:9r99r9:s::w:}=|AyGPrTYk\_g`^f[UhMAp65x45}49{;=x@
CuEFoIMmMOmSVlVZkZ\h^_fbefdgfhifknemodrscstctudvucvvbwwcvvctudttcrp`oo`li`gd`_[`V
ObKGfD@k=8r7~4v3~4y<GyP]wirrttlssgstfvxivyjvthndjVLqA:t>GqR}[genZu~Y
dou~w~
tzqxvouwnvorlfv^X}RVWTQKHDBACBDDILN~TSLMJDl}\9z+--~-
,z/
2{//{/1|@\zrmz_[{C9{8>}Xf}]|p~|vzxpvvmvulvon]]sb^x`[{XZ|U[}I^~F
@{D]xVMtQbulqvuruspwgYxKBxB{H{Sz_|lzvz}w~s~r}}r}}r}}s}~s}{rxvqurpqqprsprprmhr]QuE8y2+|)}(~(|&'|()z-}4{<xEyNsW{^md}hii~ifhhgiiihjijifgfdc^`\\`]_c`bfcdjc]mURoONoP{Wq_zgvm{rxuzyz{y~{y}x}z
|x}z}|||||}|~{}}z~tzrpqqqv~zu||{uz|zu{}}w}~x}~x}y|}xwpfuZLq>5r22v9zD}Vugvuy{{x}z}z}{}{}|~xynbrYQmNSq[{bzkyq}r{rwq|qoq}qmq~rlrtmvwkvvjwwhuqfoeg]
TmJEvH|U}8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ3ÿ4ÿ3ÿ1ÿ1ÿ/ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ.ÿ2ÿ6ÿ9ÿ>ÿBÿGÿHÿJÿJÿLÿLÿMÿLÿNÿPÿPÿRÿSÿSÿWÿYÿZÿXÿXÿXÿVÿSÿPÿLÿGÿBÿ:ÿ4ÿ/ÿ+ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ"ÿ!ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ"ÿ"ÿ"ÿ#ÿ%ÿ%ÿ(ÿ+ÿ/ÿ2ÿ7ÿ>ÿJÿWÿbÿlÿuÿ}ÿÿ~ÿ|ÿ|ÿ|ÿ|ÿzÿ|ÿzÿzÿyÿwÿwÿuÿuÿsÿmÿgÿ]ÿUÿNÿEÿ?ÿ=ÿ<ÿ;ÿ;ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ9ÿ;ÿ;ÿ@ÿEÿMÿVÿ^ÿeÿkÿnÿpÿrÿqÿoÿjÿ`ÿQÿCÿ;ÿ:ÿ=ÿ@ÿCÿHÿIÿMÿQÿTÿVÿXÿZÿ[ÿ]ÿcÿdÿgÿiÿkÿmÿnÿpÿrÿrÿqÿtÿtÿuÿwÿyÿzÿyÿxÿ{ÿ|ÿ~ÿ}ÿ~ÿ~ÿÿÿÿÿÿ~ÿ~ÿ}ÿ|ÿ}ÿ}ÿ}ÿ|ÿzÿzÿzÿyÿwÿtÿpÿjÿfÿ_ÿXÿPÿJÿBÿ=ÿ9ÿ4ÿ3ÿ4ÿ8ÿ>ÿFÿRÿ^ÿjÿsÿuÿuÿsÿsÿsÿtÿwÿxÿwÿxÿxÿtÿmÿcÿPÿ@ÿ2ÿ2ÿ8ÿBÿNÿXÿaÿjÿuÿ}ÿÿÿÿÿÿÿÿÿÿ
ÿÿ|ÿxÿvÿvÿwÿxÿxÿuÿqÿjÿhÿaÿ^ÿXÿSÿPÿPÿKÿGÿFÿFÿGÿEÿEÿDÿFÿHÿIÿOÿYÿSÿDÿBÿBÿJÿ`ÿhÿ6ÿ)ÿ+ÿ-ÿ-ÿ,ÿ.ÿ.ÿ1ÿ1ÿ;ÿOÿhÿhÿVÿWÿOÿ9ÿ9ÿ9ÿJÿ`ÿeÿSÿ]ÿ{ÿ{ÿyÿuÿuÿuÿtÿoÿhÿ^ÿcÿ_ÿVÿWÿUÿPÿOÿLÿOÿbÿ\ÿ]ÿ]ÿEÿOÿRÿ_ÿgÿsÿmÿtÿpÿ}ÿuÿqÿdÿWÿJÿAÿCÿJÿVÿcÿoÿxÿÿÿÿÿ~ÿ}ÿ|ÿ|ÿ}ÿ}ÿ}ÿ}ÿ}ÿ{ÿ{ÿzÿyÿvÿrÿqÿqÿqÿqÿrÿrÿpÿmÿhÿ]ÿQÿDÿ7ÿ0ÿ,ÿ)ÿ(ÿ(ÿ)ÿ'ÿ(ÿ+ÿ.ÿ6ÿ>ÿIÿQÿXÿ`ÿfÿhÿiÿiÿhÿhÿiÿiÿhÿjÿjÿjÿiÿhÿfÿaÿ]ÿ]ÿ]ÿ_ÿaÿaÿ_ÿ]ÿYÿTÿQÿMÿJÿLÿPÿWÿ`ÿhÿmÿrÿwÿzÿ{ÿ~ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿuÿtÿtÿxÿ{ÿ{ÿ|ÿzÿzÿ{ÿ}ÿ~ÿ~ÿ~ÿÿÿÿ~ÿyÿrÿhÿ[ÿMÿAÿ8ÿ3ÿ3ÿ;ÿGÿXÿhÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿnÿbÿWÿRÿRÿWÿ_ÿfÿkÿqÿrÿrÿoÿoÿpÿqÿqÿrÿrÿtÿvÿwÿzÿzÿyÿxÿwÿtÿpÿiÿ`ÿTÿKÿGÿJÿ\ÿ99h97j65j53l33m1/o//p..p..q,+r++t,,v++v+.u/3r79o=AlBEjG
GjH
IiKNgNNgPPgUWdXYdZZdWWeVSdQIeC>g84l.+p*'r'}'v&~&w&~'x(~(y(}'y(~(y(~(y'~'x'~'x'~'x'~'x'})x%}%x&}'x'}'x(})x(}'x'|&y'|'y'|'z%|%z$}$z$}$z%}%z$}$z"~!{ ~ { ~ {~{~z~zzzzz ~ {~{~z!~!{!~!|!~!|""|!"|$&|(*{,/z26w<BsNWodmktxg|{b}}`}
}_~
~^}
}[{{[y
x]vs^ldc[~RgIzBo?w>w>t>~<s<~=t<<t;=x>}A|GxQVn_icsz\}\]{bqchUHnE
HrKOoSXl[_hchdhlamn`qr_st]uw]wy]|z]{{\{|\~~[ZWWXWY
Z\]^^]]_}z_uo_ibbZQgLFj@<m>AnCIpR^qhqpuuktsjsshvvfxyfy
xdy{e{|g^/m-?sISo^jdt|a
iruw~
u|rywpxvnxynzxpxrricxWV~QRNM
IKHFHEFGIMVLCAD~B?UK$~*/,}//~1<`SK|Q{P\xSN{9
;};Q}_jkzetvzzptsmwqlebo^ZtRP|SQJJ
FDRS|datZJoNOhWbfjiih_mWlubUwI~CwE{OzZzhzpz{z}v~r~}q||q||q||r|{rzyqwtppoonnoqrpqorlgs]PwC7x0+{)}(~(|('|)+z1{8{BvJzTq[{blg}ihi~ifhhfiihijhjieigcd_]__]^^b_^eYWkPLnIGnG}KrQ{Xubzjup{uxvzzz|y}~yx}z|x}z}|||||}|~z~{{|
xxtuurx~{v|||xz|{u}}}w}}~x~x}~y|}xvqhs^QpD;q44v>zK}]tm|vz{|x}y}y}{}{}{~xxmaqWTlT[qa{gzmyq{s{sup|oop}qmr~slrsmuwmz|k{zjwuiohgaUlLIvP|b}8ÿ8ÿ7ÿ6ÿ5ÿ4ÿ4ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ/ÿ1ÿ4ÿ8ÿ8ÿ:ÿ=ÿ=ÿ@ÿCÿCÿEÿFÿHÿJÿJÿPÿSÿUÿWÿYÿZÿ[ÿXÿYÿXÿXÿRÿMÿIÿBÿ;ÿ6ÿ2ÿ.ÿ+ÿ(ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿÿÿÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ"ÿ%ÿ'ÿ(ÿ*ÿ+ÿ.ÿ2ÿ5ÿ:ÿ@ÿEÿNÿXÿdÿjÿqÿsÿuÿyÿ{ÿ~ÿ}ÿÿÿÿÿ~ÿ~ÿ|ÿ{ÿyÿyÿuÿlÿdÿ\ÿTÿMÿFÿCÿ>ÿ>ÿ=ÿ<ÿ<ÿ<ÿ=ÿ<ÿ?ÿDÿIÿOÿXÿ`ÿkÿvÿÿÿÿÿÿÿÿÿ}ÿqÿfÿYÿSÿVÿ\ÿ`ÿdÿgÿlÿoÿqÿsÿtÿvÿwÿwÿyÿ{ÿ{ÿ|ÿ}ÿ}ÿÿ~ÿ|ÿ}ÿ}ÿ}ÿ{ÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿÿÿÿ
ÿÿÿÿ|ÿxÿtÿmÿfÿ`ÿYÿVÿRÿOÿNÿNÿPÿVÿ_ÿiÿrÿtÿtÿtÿsÿsÿsÿtÿvÿvÿwÿÿÿÿ ÿÿ£ÿ ÿzÿ(ÿ3ÿEÿPÿ]ÿgÿuÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿyÿwÿwÿxÿyÿ{ÿzÿvÿkÿiÿaÿQÿRÿXÿYÿRÿRÿNÿKÿIÿJÿJÿIÿJÿOÿQÿTÿQÿQÿAÿ6ÿDÿAÿ<ÿ;ÿWÿTÿ,ÿ(ÿ1ÿ2ÿ1ÿ<ÿOÿgÿoÿWÿbÿ^ÿYÿTÿMÿ7ÿ<ÿAÿSÿ^ÿdÿqÿuÿwÿxÿwÿvÿtÿvÿrÿdÿYÿOÿRÿ[ÿSÿNÿMÿMÿJÿKÿQÿ_ÿ[ÿZÿZÿJÿSÿ_ÿ^ÿ_ÿ\ÿIÿ[ÿaÿQÿ^ÿlÿaÿVÿHÿGÿJÿRÿ\ÿhÿuÿ}ÿÿÿÿ~ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿ{ÿzÿxÿuÿrÿoÿmÿnÿmÿpÿpÿpÿnÿjÿfÿ\ÿOÿCÿ7ÿ0ÿ+ÿ)ÿ(ÿ(ÿ(ÿ'ÿ)ÿ.ÿ4ÿ<ÿGÿMÿUÿ`ÿfÿhÿhÿiÿhÿhÿhÿiÿiÿiÿjÿjÿiÿhÿeÿaÿ]ÿ]ÿ]ÿ\ÿ\ÿYÿVÿQÿLÿIÿEÿBÿBÿFÿLÿSÿZÿbÿjÿpÿuÿvÿzÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿyÿzÿ{ÿ|ÿ|ÿzÿ{ÿ}ÿ}ÿ}ÿ~ÿÿÿÿ~ÿ|ÿzÿsÿhÿ_ÿRÿFÿ=ÿ6ÿ6ÿ?ÿOÿ`ÿpÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿlÿ`ÿVÿUÿVÿ^ÿdÿjÿpÿrÿsÿsÿpÿoÿpÿqÿqÿrÿtÿuÿuÿwÿzÿzÿ{ÿzÿxÿvÿrÿlÿeÿ[ÿQÿNÿZÿiÿ77i65k33j31l10n//o..p--p--q++q+*s))t**u))v**u(+r--p/1p32p5:m;<m@AkDEjHLiORhTVgXYeYYcZXdVReNGfA;i51l.,p)(r**u*~*w)~(x)})w(|(w)~(w(~(w(|(w'~'w(~(w)~)w)|)w)|)w*|*v+|)v)|)y'|(y(|(z'|&z&|&z%}%{&}&{$}$x#~"{"~"{ ~ {~{~z~zzzzz~z~z~z~ z!!z""{"~"|$~%|%~&}(~)}-.{04y6;v@HsOWp_fnimkpshw{f{}c_\~}]~}\{
u^pibcZhSMmGzBt?v@{>v=~>w?}B{HyOXp`
ket~_YY[^
|br
eeachgkeptbuw^z|\}}[||X}}WY~~X}
}W}
}W{zV{{V{{U}~STTRS
V
W
W
XYU
X
VX[
_|azvdsoglggd_f\\g_fiksmuvkuqisrgs
tdwb£_¡¢`¥«d®¨ih,m>OrZipumr~w~v~v~t~|q{ypwwoxzozzownrkcxOD|T`XYQLMKLQRUUPJBO~FE~<=~:BcXJzVF|0^ys~rn}W~>~Q{UQ|:;}JY~_VizvrspvmxvlvrkiUoRWw]T~HJJJLRTM~T[sL[lUVg\Ui@`jg^l_ntbRuGHyK{Vzazl{v|~w~s~~|q{~{pyyp{{r{{s|{qzwpspoonnllnoononqjes[OuA8x0+{*})(|((z)}.z6z>{HuQ{Xob}giijgiihiijiikjlkiggidca^aZYaWXbUOfHEkA=n>CqIPqW~]tf}kvqztwxzzy}z~~y~y}y
|y~z|{{{|{~{zz|z
}u|}s||v{}{v{{{v|||w~|~y{y|}{{{{tr~hn^SlG=n89uByS|dtsvz{|x}y}z}{}{}y}vvj_nVRkW_pe}nyrzuxt|sqp}oop~qnr~qnv~unv}xk{}}i~~~f|zewqhjclYXub|r{7ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ*ÿ)ÿ)ÿ)ÿ,ÿ+ÿ-ÿ.ÿ1ÿ0ÿ2ÿ5ÿ8ÿ<ÿ=ÿ@ÿCÿEÿIÿMÿQÿUÿVÿWÿXÿ[ÿZÿ[ÿYÿXÿSÿLÿDÿ?ÿ:ÿ6ÿ1ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ(ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ$ÿ%ÿ%ÿ&ÿ'ÿ+ÿ-ÿ.ÿ0ÿ4ÿ6ÿ9ÿ<ÿ@ÿHÿNÿUÿZÿ`ÿdÿdÿhÿnÿpÿsÿxÿzÿ|ÿ|ÿ~ÿ}ÿÿÿÿÿ|ÿzÿtÿnÿhÿaÿ[ÿUÿMÿGÿEÿCÿ@ÿ@ÿDÿIÿNÿVÿ`ÿhÿpÿ|ÿÿÿÿÿÿÿÿÿÿÿÿwÿmÿkÿnÿrÿsÿuÿwÿ|ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ}ÿ}ÿ|ÿ|ÿ|ÿ|ÿ|ÿ|ÿzÿzÿzÿ{ÿ{ÿ{ÿ}ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ}ÿzÿwÿwÿtÿpÿnÿmÿrÿtÿwÿwÿwÿsÿpÿnÿnÿxÿÿÿ¡ÿÿÿ¡ÿ¨ÿ¬ÿ°ÿ±ÿ®ÿÿ4ÿ8ÿIÿZÿiÿuÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿyÿxÿxÿxÿzÿzÿ{ÿ{ÿyÿrÿbÿNÿCÿPÿSÿSÿ^ÿXÿQÿQÿIÿMÿUÿVÿWÿQÿNÿ>ÿ<ÿMÿJÿCÿ:ÿ:ÿ;ÿ:ÿDÿcÿ^ÿRÿlÿcÿ\ÿpÿiÿfÿÿZÿ<ÿPÿ[ÿTÿ=ÿ=ÿ@ÿFÿLÿBÿXÿÿÿ{ÿoÿxÿxÿzÿxÿrÿbÿVÿcÿbÿRÿIÿMÿKÿJÿJÿMÿRÿTÿ^ÿ]ÿ[ÿVÿSÿJÿDÿ[ÿhÿgÿdÿ^ÿ`ÿsÿ\ÿQÿKÿJÿKÿXÿcÿnÿyÿ~ÿÿÿ}ÿ|ÿ{ÿ{ÿyÿyÿ{ÿ{ÿ{ÿ{ÿ|ÿzÿyÿuÿrÿoÿmÿmÿlÿlÿoÿoÿoÿnÿjÿeÿ[ÿOÿ@ÿ7ÿ0ÿ+ÿ*ÿ)ÿ(ÿ(ÿ)ÿ+ÿ1ÿ8ÿBÿKÿTÿ\ÿdÿhÿiÿjÿiÿhÿiÿiÿhÿhÿhÿiÿgÿeÿbÿ`ÿ[ÿXÿXÿXÿSÿNÿIÿDÿBÿ>ÿ:ÿ;ÿ?ÿDÿLÿSÿZÿaÿgÿmÿsÿvÿyÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ}ÿ}ÿ}ÿ{ÿ{ÿ{ÿ{ÿ|ÿ|ÿ}ÿ}ÿÿÿ}ÿ|ÿzÿwÿrÿiÿ_ÿUÿJÿ@ÿ:ÿ;ÿEÿWÿfÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿuÿiÿ^ÿVÿRÿ[ÿbÿhÿoÿsÿtÿtÿsÿpÿoÿpÿqÿqÿsÿvÿuÿwÿzÿ{ÿ}ÿ~ÿÿÿ~ÿyÿuÿnÿhÿ`ÿbÿmÿyÿ77i55k43k12m0/n..o--p,,p,,q++q++r**s*)s))t''t'(t))u))u*~*v)~)u*-u..s0~3r59q:?nCGmM
SkVYh[]f__d]ZcVPcJCf?:j63n1,p,},r+}*t*}+v+|,v+|+u+|+u+|*u*~*u*~)u+~,u*|,u,|,u+|+v*|*v+|+w)|)w*|*y(|'y&|&y&}'z'}'{%}%z$~${#~#{"~!{ ~ { ~ z~zzzzz~z~z~z~ z!!{!!|"~"~#~%~$~%}(~(},/|04{79y:>w@~DvJ~PtQ}TqW}\mcgiinfqvcx{`~_]
\
}\z
v^o
hbaXgVQnK~FqG~KqRXna
gepz^[[[
]
_`xs_s
v_wy]zz[{}Z}~Y}~X}
{Y||[{{\||^zz`zz`zxayzazz_}}^~~]]\^`abca^\[
Z
ZZ\
_beh}i}}k}
~i|jxsgv
b¢`c
e¦«g®¯g®«dAh5GqYfvtsw~x~w~w~u~r||pzwoxwnz{n|{osir`SySR}\~h~b~WTLNW~XT{RK|8~5I}C=|;:~7:~>Thf^zD},{`}U|UoxV}RyQ}ZI}<>KUH;V}z~ZTa~bf|d_}]ZasiGGJJILLTXXuPMlINeQdfc`jSRo_cmnmpN~IsP{^yhzs|{|y~~u|~{rz~zryzr{{s{{t{yswsoqnkllkklnoooomrjdt[PwB7x0+{*})~*|)~)z/{4y<vEzMqW{_le}iikkghggghhggjfdjdbi`[eYWeRPeKFiC=m87q6<rBHsPUs[~`uh}ovtzwxzz{z}z~~}y}~y}y
|y~z|{{{|{~{zz|{}x~}v}~|v{}{wz{{w||}y~|~z~{}z}|zxy{vsp~im`UlMBn;=uJy[|jty
vz{|x}y}z}{}{~x~trg]jWYi]fpl~swt{uvt}sqo}nop~qnp~snv~unw}zo|}p}o~l}yltnpjntv}y5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ1ÿ1ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ*ÿ,ÿ.ÿ1ÿ2ÿ5ÿ8ÿ>ÿCÿHÿOÿUÿWÿ\ÿ^ÿ`ÿ^ÿ^ÿ\ÿZÿUÿOÿIÿDÿ@ÿ=ÿ7ÿ3ÿ1ÿ/ÿ-ÿ+ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ#ÿ$ÿ%ÿ(ÿ(ÿ*ÿ,ÿ/ÿ2ÿ5ÿ7ÿ:ÿ>ÿBÿDÿDÿDÿEÿHÿLÿSÿYÿ^ÿbÿfÿiÿmÿoÿvÿyÿzÿÿÿ
ÿÿÿÿÿ|ÿyÿvÿpÿiÿdÿ`ÿZÿVÿUÿWÿZÿaÿhÿnÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿwÿwÿxÿxÿyÿwÿzÿ{ÿ{ÿzÿ{ÿ{ÿyÿxÿyÿyÿyÿxÿxÿuÿuÿtÿtÿrÿrÿrÿsÿtÿuÿvÿvÿwÿwÿzÿzÿ}ÿ}ÿ}ÿ~ÿÿÿÿ~ÿ~ÿ~ÿ|ÿ|ÿ~ÿÿÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿÿ
ÿÿÿÿÿÿ ÿÿÿÿ¡ÿ¦ÿªÿªÿ¨ÿ¦ÿ¥ÿÿÿTÿ1ÿHÿXÿgÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿ|ÿxÿyÿyÿzÿ|ÿ~ÿ}ÿ|ÿxÿrÿdÿ_ÿVÿWÿaÿdÿbÿ[ÿRÿPÿSÿPÿRÿVÿPÿKÿ7ÿ.ÿ3ÿ8ÿ;ÿ;ÿ8ÿ8ÿ>ÿCÿLÿbÿqÿkÿDÿVÿvÿkÿYÿTÿZÿmÿ_ÿPÿ@ÿ>ÿ?ÿQÿJÿAÿ@ÿBÿ:ÿ9ÿ<ÿ=ÿ<ÿ<ÿ<ÿ=ÿ=ÿGÿOÿQÿeÿ\ÿBÿEÿJÿKÿKÿKÿHÿOÿPÿUÿSÿPÿQÿRÿVÿWÿPÿ=ÿMÿOÿTÿTÿaÿbÿWÿJÿVÿaÿkÿvÿ~ÿÿÿ~ÿzÿzÿzÿzÿyÿ{ÿ{ÿ{ÿ{ÿ{ÿ{ÿyÿwÿsÿpÿmÿlÿlÿkÿlÿoÿoÿpÿnÿjÿdÿ[ÿMÿAÿ6ÿ0ÿ+ÿ+ÿ*ÿ)ÿ+ÿ,ÿ2ÿ7ÿAÿJÿRÿYÿaÿgÿjÿkÿkÿjÿiÿgÿfÿfÿfÿdÿbÿbÿ^ÿZÿVÿSÿOÿLÿGÿBÿ=ÿ7ÿ7ÿ3ÿ3ÿ7ÿ<ÿBÿHÿPÿWÿ^ÿeÿkÿoÿuÿyÿ|ÿ}ÿ}ÿ~ÿ}ÿ}ÿ~ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ|ÿ{ÿ{ÿzÿ{ÿ}ÿ~ÿ~ÿ~ÿ~ÿ}ÿ|ÿzÿxÿuÿoÿhÿaÿYÿPÿEÿ?ÿAÿLÿ]ÿlÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿtÿgÿ^ÿYÿYÿaÿhÿoÿvÿvÿtÿsÿpÿoÿnÿpÿqÿqÿrÿvÿuÿwÿzÿ{ÿ~ÿÿÿ
ÿÿÿ}ÿyÿuÿtÿxÿÿÿ55h54j20k0/m.-m-,n-,n++o**q((q((q))r''r''t((u('u''u((u''w('w'(x))y*~*w*~*w,.u/5r:ApFNkUXg\^e`ac_^a[XbROcJ
EfA<g;8k0~-q-}+s+}-t-}-t-},t+}+s,},u,},u-}.u.|.u-|-u-|-u.|.v,|,w+|*w)|)w)|(w'|'w'|'y'}'z%}%z$~$z$~$z"~!z~z ~ {~{z z zzz z~{~ {!~!|"~"|##|#$}$~%'~')~+}.~1|3~6z9~=w>|>x?|?w@}DvG}LsQWl\`jcggimdpucz~`b
_
\}\yu^rodieiddhf
kdp
x__
]^\\\
}\xt\uw\uv^vu_uvauuawtbrrdomekjgjhlg~glgekf~fkdfkikilmgpqersdtubuudutguugttiuvjvwixyf|}d~a^]]\
Z\_`dgih
d`b £h¤¤m¢{o}i]s\3DlXguuvy~yxw~v}t}ypz~ynyymz|n}}owssicy][|dd}ec~X~X~X~Y|VVzSCz62/~7=}<:|7>{KS}xx}]H|Ph|p}jgyXU}QQ
@A?<7>BDFGGFEGFFMTF?
BBCM
PNKK
JI
IO}VWtYShEEdO6e8FjUUlG~CpX}MuXzd|m{x}~y~}tz~yry~ysy{rz{s{~{vz~xsvspommkklklmmoonlqhbtYKv?4y0,y+},y,}.x1~8s@}GrN|Up\}dlj~lgjjghhihgkfflc_l^ZkUQhLHjB>l:4p/~1v3}6w:~@vD~JuQ~Yt`~hum}swwzzx}z~{~z~|z~~y}y}z~z|{{{|{~{{{z~{{~z~~}y{|{xz|{x}|}y~|~z}{}yz|yxv|rsn}gmbZlPGmBEtPza|ot}vzz|y|z|z|z|z~
x}roh_gZ[ickps~wtw}vts}pomnnqqmq~rnt~vow}wpy{~r{r|o
~m|o|sv3ÿ3ÿ4ÿ2ÿ1ÿ1ÿ/ÿ/ÿ.ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ1ÿ9ÿ?ÿDÿLÿOÿVÿZÿ]ÿ_ÿaÿaÿ_ÿZÿXÿXÿUÿQÿKÿGÿBÿ>ÿ7ÿ3ÿ0ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ ÿ ÿ ÿ ÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿÿ ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ'ÿ'ÿ)ÿ*ÿ-ÿ0ÿ3ÿ7ÿ7ÿ;ÿ<ÿ<ÿ<ÿ<ÿ?ÿCÿGÿKÿPÿVÿYÿ]ÿ]ÿ`ÿdÿfÿjÿmÿrÿwÿ{ÿÿÿÿÿÿÿ
ÿÿ}ÿ}ÿxÿuÿsÿqÿpÿrÿtÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿuÿrÿqÿsÿrÿqÿrÿqÿoÿoÿlÿlÿgÿeÿeÿeÿaÿaÿ^ÿ]ÿYÿXÿWÿUÿUÿVÿWÿWÿWÿZÿ\ÿ`ÿaÿbÿdÿhÿhÿiÿiÿjÿjÿjÿjÿiÿhÿhÿjÿjÿiÿhÿiÿkÿnÿoÿrÿtÿwÿzÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢ÿ¡ÿÿÿÿÿ|ÿÿÿÿÿÿAÿFÿXÿgÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿ|ÿ|ÿzÿzÿ{ÿzÿ}ÿ}ÿ{ÿvÿ_ÿaÿ^ÿVÿ_ÿ^ÿdÿdÿZÿWÿVÿ^ÿ_ÿQÿWÿCÿ9ÿ3ÿ2ÿ9ÿ;ÿ;ÿ9ÿ7ÿ8ÿ@ÿHÿEÿ5ÿ=ÿIÿKÿCÿAÿlÿoÿiÿUÿGÿ>ÿ:ÿCÿAÿ=ÿGÿDÿBÿ=ÿ<ÿ;ÿ;ÿ8ÿ8ÿ5ÿ9ÿ9ÿ@ÿTÿNÿFÿ@ÿ?ÿEÿJÿJÿLÿKÿIÿFÿHÿNÿYÿbÿ[ÿUÿMÿFÿJÿUÿWÿNÿEÿKÿJÿEÿWÿNÿUÿ\ÿeÿqÿzÿÿÿ~ÿ{ÿyÿyÿyÿyÿxÿyÿzÿ{ÿ{ÿ{ÿzÿxÿtÿqÿmÿmÿkÿkÿkÿlÿoÿpÿnÿlÿhÿbÿXÿJÿ?ÿ4ÿ/ÿ/ÿ/ÿ0ÿ2ÿ5ÿ9ÿ=ÿDÿJÿSÿZÿ`ÿgÿkÿkÿjÿjÿiÿgÿfÿfÿfÿeÿaÿ_ÿZÿVÿRÿKÿDÿAÿ;ÿ6ÿ0ÿ.ÿ,ÿ/ÿ2ÿ5ÿ<ÿAÿFÿMÿTÿ\ÿcÿiÿnÿsÿwÿzÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ}ÿ|ÿzÿzÿzÿ{ÿ}ÿ}ÿ~ÿ~ÿ}ÿ}ÿzÿyÿwÿtÿoÿhÿdÿ\ÿSÿJÿEÿHÿRÿcÿrÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ}ÿrÿeÿ]ÿ\ÿaÿhÿmÿuÿxÿwÿuÿsÿpÿoÿpÿpÿpÿrÿqÿrÿtÿuÿvÿyÿ~ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ22i00k00k..m,+m+*n))n**o**q((q''r''t''s''u'(u((u((t''t''w&'w))x))y*~*x*~*x*~*x*~*x-0v6:q@GlNSiZ\e\[c^_b]\a[X`VS_MIaC9h3}1n.}.q.}0r.}/r/}.s.}/t/}/t/}/t/|/t/|/t/|.u/|/v-|-x-|,x+|+x+|*x)|)w(|(y(}(z&}&z&~&|%~%|$~#{!~!{!~!{~{z z z z!!z!z { {!!{"#{$$|$%}%~%&~()~*+~.}/~3|6~9z<~=x=~=wAEsGLrPToVXmX~YnX~[n_~clh}oiuze
`\[
^]}
|\z
y\{}]
_
___^^^~s^qp^mmejjhhdmc~`n\|[pZ|XqT{RqP{OrM|JtH|HuG|EuC~DtHHrIKpMRlRViXZh[^g``e`]f^\g[]g\^i_\m[[o]}]na}enj}nmrujx{i~ed
b
b`_``}abce¡k¢n|ny}{opzxg}\V\LeXhtvwx~x}w}u}tr}}qrbzoqz{zoy|l
}l
~ri[{WX}Z^xdhvYH|JNK=KG|7C}::;~95|66|9;|D;}7?~GAM~SSvdP~?<}CC~JH?F8Jr}wysntV
Tx=
MjPJC
FJHGEFCDGOXVONvKFpDGkGEkWIpQ}YtS|Sv_zlzt{{{~}w}~zsy~xry~yszzry{s{zryvotqmnjljklllmnqpomrgasUHu@6v45v79t<@sEHqLRnV^mdikl~lkkkkhfkgdldbma\mWRnLEn<7o2-t)|)w*|.{2{7{<}CxH~OtX~_rg~jtp}uvwzzy}z~~zz~y}y}z~z|{|{~{~{{{z{}}{}}{z|||xz||x~|~y~|~z~{}zz|yyw|sso}imd]mVMnHKtVzf|utv~zz|y|{|{|z|z~
w|sof^g]ciirrwxvw~utq~pqqqrppqq~rpr~rrr}ssv{|uzx{w|t~q~r~t1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ,ÿ*ÿ)ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ-ÿ2ÿ8ÿ?ÿEÿJÿNÿSÿWÿYÿZÿ]ÿ_ÿ`ÿaÿ`ÿ`ÿ_ÿYÿSÿLÿDÿ:ÿ2ÿ0ÿ0ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ0ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ)ÿ)ÿ'ÿ'ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ!ÿ!ÿ!ÿ!ÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ!ÿ!ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ(ÿ+ÿ,ÿ+ÿ.ÿ0ÿ5ÿ7ÿ;ÿ<ÿ=ÿ@ÿ@ÿEÿIÿJÿMÿOÿSÿTÿTÿUÿTÿUÿUÿWÿ[ÿ^ÿfÿmÿuÿ}ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿrÿkÿhÿfÿdÿbÿ]ÿ[ÿXÿUÿSÿPÿKÿHÿFÿBÿ?ÿ>ÿ=ÿ;ÿ8ÿ7ÿ7ÿ6ÿ7ÿ8ÿ9ÿ>ÿAÿCÿEÿHÿKÿLÿPÿSÿUÿWÿYÿWÿWÿVÿUÿVÿTÿSÿRÿQÿQÿPÿPÿOÿOÿOÿOÿQÿTÿXÿ[ÿaÿeÿjÿoÿvÿzÿ}ÿÿÿÿ
ÿ
ÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿpÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ~ÿ~ÿ|ÿnÿ|ÿÿÿÿsÿLÿYÿgÿvÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿiÿaÿmÿcÿzÿÿ
ÿÿÿÿÿwÿjÿbÿ\ÿVÿTÿaÿ\ÿFÿCÿHÿQÿRÿHÿRÿbÿ]ÿLÿ:ÿ:ÿ;ÿ6ÿ5ÿ6ÿ5ÿ6ÿ4ÿ9ÿEÿ@ÿ6ÿXÿhÿ_ÿUÿNÿTÿYÿOÿFÿ;ÿ?ÿ<ÿ@ÿAÿEÿIÿ=ÿ4ÿcÿÿ{ÿnÿWÿdÿjÿMÿKÿLÿHÿJÿIÿKÿMÿJÿFÿCÿBÿBÿAÿAÿHÿLÿMÿMÿKÿEÿ=ÿ?ÿ@ÿ@ÿKÿPÿEÿWÿXÿ\ÿ_ÿbÿlÿuÿ|ÿÿ}ÿ}ÿzÿxÿwÿwÿxÿyÿyÿzÿzÿzÿyÿxÿzÿzÿrÿkÿkÿhÿgÿkÿkÿlÿoÿoÿkÿfÿ`ÿUÿJÿ@ÿ:ÿ:ÿ;ÿ?ÿBÿDÿHÿNÿRÿVÿYÿ]ÿbÿgÿkÿmÿmÿiÿiÿhÿhÿfÿfÿdÿ`ÿ]ÿXÿSÿNÿHÿ>ÿ5ÿ/ÿ,ÿ)ÿ)ÿ)ÿ*ÿ.ÿ2ÿ7ÿ<ÿCÿHÿOÿYÿ`ÿeÿkÿsÿuÿwÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ|ÿ|ÿ|ÿ}ÿ~ÿ~ÿ~ÿ~ÿ~ÿ}ÿ{ÿxÿvÿrÿmÿiÿeÿ_ÿXÿOÿLÿNÿXÿhÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿqÿgÿ`ÿaÿgÿoÿuÿzÿzÿwÿuÿrÿqÿqÿqÿpÿpÿqÿrÿrÿrÿrÿrÿtÿyÿ~ÿÿÿÿÿÿÿÿÿÿÿÿ21j1/l00l/+n++m+*n)(o((p((q''r''t''t&&v&&v&%t&&t%&t''t''w((w))w((w)~)x*~*x*~*x*~*x)})w+},w.4s:@oEIjNSgXYb^``ab^bd\e
^`ULdA~6k1{0o/|.p0|0p0|0r0|1r2|2r1{1r2{3s3{1s1{1s1{1s/z/u/z/u.|.u-|,v+|+w*|)x)|)x(|(y'}&{&}&|%~$|"~"z!~!z~{ z z z!!z! z !y#&y()w('w&%v#$w%~%|'}$|&})*~+,/}2~5{9~<y?BvEGtIKsNMsPRoUUnWWnVWnWXm_cjjobz^\[
]
^
\
[
[\]^__`a|qdf~^g]{ZmUzRtNyIwCy?y=y:}9y5}2y1~2z2~3|0~2~2{25{9=y?BuFHrJMnPSlUUiWWhWWgVVgWShPOiNKkHDp?}>u:{:u>{BvE{JvQ{StY}_rd}inp~wm}jh
gdfdadh~n}qnwksl{or|j{{eZiZgour~u}x|w~tqlwop~qnrohf
gxpkozh]{Xfwc]t[F{GO~Zc~mf{;M~G:7~86~78~65~9BDEZ`TO}H
IzJ@z@=|?B}NZ~c
H~<2Wzmw]MrPXvRN~JMNJGFLMFAA;;?DGII
FIJLJO|O?vX~XrR{buh{ozx|~{~}w|yswvqvwrwvsyxszwrvoohomzn~sqljroryjixg\uSLrHCoEGqJOpRTnWYl[`kfhkjkhlkikjghgjfdla_n[VoQJqA8s/~+v(}(|){(({,~3}8|;|BwL~SuZ~aug|mvr{wyzz{{yzz~y|y}y}{}{~{{{{{y
y{}{}y}|{z}|}z~|~{~|~{}{|y{|xyu|qrm}jmfalYSmMQr\zl{zuw{z|y|{|{{z}z~v|rqfcggijsyr{~zuvvvsstposo~orp~qqr~rpo~nsq|uuzzz
x{zx{t}t~t1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ+ÿ*ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ0ÿ5ÿ9ÿ?ÿEÿIÿMÿSÿYÿ^ÿbÿdÿfÿiÿiÿfÿ^ÿTÿHÿ<ÿ3ÿ1ÿ0ÿ1ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ"ÿ"ÿ!ÿ!ÿÿÿ ÿ ÿÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ"ÿ$ÿ'ÿ,ÿ/ÿ0ÿ0ÿ/ÿ-ÿ+ÿ'ÿ&ÿ%ÿ$ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ/ÿ3ÿ5ÿ9ÿ=ÿ@ÿCÿFÿIÿKÿOÿPÿPÿQÿRÿRÿTÿVÿXÿZÿ\ÿ]ÿ]ÿ^ÿaÿeÿjÿrÿxÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿlÿ\ÿQÿNÿLÿFÿ?ÿ;ÿ9ÿ5ÿ3ÿ1ÿ.ÿ/ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ/ÿ2ÿ3ÿ3ÿ6ÿ<ÿ>ÿBÿEÿHÿKÿMÿPÿRÿTÿWÿWÿWÿWÿXÿXÿWÿVÿTÿSÿRÿNÿJÿFÿDÿ>ÿ:ÿ4ÿ1ÿ/ÿ.ÿ.ÿ2ÿ8ÿ=ÿBÿIÿOÿUÿ[ÿ`ÿgÿnÿvÿzÿ~ÿ}ÿÿzÿvÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿnÿsÿmÿlÿyÿÿÿÿÿvÿ~ÿvÿÿÿÿÿzÿÿÿÿÿÿÿÿÿÿtÿpÿ}ÿÿpÿxÿÿÿÿÿcÿaÿgÿjÿcÿUÿ\ÿ[ÿ\ÿPÿPÿOÿIÿZÿdÿXÿ9ÿÿMÿKÿ=ÿ8ÿ8ÿ7ÿ8ÿ;ÿ7ÿ6ÿ=ÿ?ÿGÿcÿTÿNÿ7ÿ<ÿ6ÿ8ÿ=ÿFÿDÿAÿAÿCÿKÿWÿ]ÿCÿFÿ<ÿaÿ\ÿbÿeÿ[ÿ@ÿ?ÿJÿDÿNÿZÿVÿPÿHÿFÿFÿHÿCÿ=ÿ>ÿ;ÿ8ÿ=ÿAÿGÿIÿIÿJÿLÿLÿNÿOÿQÿXÿZÿXÿYÿXÿZÿeÿpÿyÿ}ÿ}ÿ|ÿ{ÿxÿwÿuÿvÿtÿyÿxÿsÿsÿvÿpÿfÿJÿLÿ[ÿpÿÿÿÿ`ÿXÿ\ÿSÿGÿKÿ[ÿ^ÿYÿQÿLÿHÿJÿLÿQÿVÿXÿZÿ[ÿ^ÿcÿfÿkÿmÿnÿmÿnÿlÿjÿiÿhÿgÿeÿcÿbÿ`ÿ\ÿTÿMÿGÿ=ÿ4ÿ-ÿ*ÿ(ÿ(ÿ)ÿ(ÿ(ÿ,ÿ2ÿ7ÿ=ÿEÿLÿSÿ[ÿaÿhÿnÿtÿxÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ|ÿ{ÿzÿzÿ}ÿ}ÿ~ÿ~ÿ~ÿ~ÿ}ÿ}ÿ{ÿxÿsÿoÿlÿiÿfÿaÿ[ÿUÿRÿUÿ_ÿmÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿiÿfÿiÿmÿtÿzÿ{ÿzÿwÿuÿsÿsÿrÿpÿoÿoÿpÿqÿpÿpÿnÿkÿmÿqÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿ0/k..m--m+)o*)p((p)(p'&q&&q&&r%%t%%t&&t&&t&&t%%t&&t''t((w))w))w))w*~*x*~*x*~*x*~*x+}+x,},x-}-w-~.s06q<AmFLjQXf^dagl]mk]e[bN|Bh7z1n1{2p2{2p1|2q2|2q3{3q3{3q3{2r2z2r2z2r2z2r1{0s/{.s-{-t-{-u-{-v+{*w*|*w(|(x'|&z&}&{%~$z$~"y!~ z ~ { z z! z! z"z&+w/4p<>i>>i96k0,p)(u'~)y)~)|*.|16y9=uBFsGJqLOqPRpTTqTTqVZm\
`gdedeebfibqua{_
]\]
[[\^]^]`aadz~hgR|EnA{=u9z7{3y10y00{..{/-|--~-.~.04|8<y?BuFIrJLoORmRVjX
XiZ
ZhZ
ZiX
XiWSiSMmKFoB;u60y-~-|-}+}+},|0}1|7|;zA}HvR~Yq`fmmpivuf
dcfhn~sm~rjgh
n¡´w¼~½{¾~¾~¾~»{°u}py{qytqztr}oznY{pkmzm~mlc}uox~wsj~\yf~j~kh}eb{`R}TUK~EOb\+(=><;9;~@E~<9~8:~FC~@G}?:}98{7L|@A|CE}J
U|SFI_]|SZveRyRX}9B}hrzhU|IFDABB?:7;@~CGHIKMPRRUX[[zX|`yf{o{z|~z}~|vzwsvwpuunytn_`nwhobkorntna}`JG}FI~NN~EY~bx\UoPPoQVqY[p]`ndelimjopiomhkiijhjhgkeen`]pZTqLDs;1u*~)y)}){({)|){-{4~9x?}FsN}Uo\}crj|pvu{wy{z}|y~xxy}yy}{}{~{zzyyy
yz~}{zyyy{zy}{}z}{~{~{~|}{{|y|vys|psl}ineak\WlUXsbzp}~vx{{|z|{|{{{}{~u{smlkemrjw{s|zwxttssroosn~nrp~qqq~npkhsh}lupzv}~x~x{zw|u}u/ÿ.ÿ.ÿ.ÿ-ÿ+ÿ*ÿ)ÿ(ÿ)ÿ(ÿ'ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ.ÿ2ÿ9ÿ>ÿCÿKÿQÿYÿ`ÿhÿmÿrÿqÿkÿcÿUÿHÿ:ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿ ÿÿ ÿ ÿ"ÿ%ÿ*ÿ/ÿ3ÿ9ÿ@ÿGÿKÿKÿKÿGÿCÿ>ÿ7ÿ0ÿ+ÿ)ÿ*ÿ,ÿ-ÿ/ÿ1ÿ5ÿ7ÿ=ÿAÿFÿJÿLÿOÿOÿRÿTÿUÿVÿVÿXÿXÿ\ÿ`ÿeÿhÿlÿmÿnÿnÿnÿpÿrÿxÿ}ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿiÿQÿDÿ<ÿ7ÿ7ÿ3ÿ5ÿ3ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ0ÿ0ÿ3ÿ6ÿ:ÿ>ÿBÿDÿHÿJÿNÿQÿQÿRÿVÿWÿXÿYÿYÿZÿZÿZÿXÿWÿUÿUÿRÿNÿJÿFÿ@ÿ9ÿ5ÿ1ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ0ÿ5ÿ;ÿDÿLÿTÿ[ÿbÿeÿuÿÿÿÿÿÿÿÿÿÿÿyÿkÿoÿmÿÿÿÿÿÿÿÿ¡ÿ¶ÿ»ÿ¹ÿ»ÿ½ÿ½ÿ½ÿ¼ÿ»ÿ»ÿ´ÿÿÿÿÿÿvÿpÿiÿhÿjÿkÿ_ÿ]ÿ`ÿvÿwÿyÿ{ÿkÿUÿVÿrÿÿnÿUÿZÿjÿmÿjÿlÿlÿ`ÿ]ÿTÿQÿTÿNÿJÿSÿvÿuÿ<ÿ4ÿ1ÿ3ÿ;ÿ<ÿ<ÿ:ÿ@ÿKÿGÿ8ÿ4ÿ7ÿ?ÿFÿDÿLÿEÿ;ÿ<ÿ9ÿ;ÿJÿ<ÿAÿCÿEÿIÿUÿKÿHÿ=ÿMÿiÿeÿZÿVÿJÿVÿOÿ;ÿnÿnÿnÿiÿXÿLÿEÿ@ÿ>ÿ=ÿ>ÿAÿ<ÿ8ÿ:ÿ<ÿ@ÿCÿDÿGÿIÿJÿKÿLÿNÿQÿVÿ[ÿ[ÿXÿ]ÿgÿqÿuÿ|ÿ}ÿxÿxÿtÿsÿvÿvÿmÿUÿsÿmÿlÿdÿ]ÿ^ÿiÿnÿfÿ^ÿCÿDÿNÿPÿQÿJÿDÿHÿ]ÿlÿeÿ`ÿZÿUÿTÿWÿZÿ\ÿ^ÿaÿdÿfÿiÿlÿoÿqÿqÿoÿmÿkÿiÿjÿhÿhÿgÿeÿeÿ`ÿ[ÿXÿQÿJÿBÿ9ÿ0ÿ)ÿ(ÿ)ÿ)ÿ)ÿ*ÿ,ÿ0ÿ8ÿ;ÿAÿIÿOÿWÿ^ÿeÿjÿpÿvÿxÿ{ÿ}ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ{ÿyÿzÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿ}ÿzÿyÿvÿsÿqÿnÿjÿgÿdÿ`ÿ\ÿWÿWÿ]ÿeÿsÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿpÿnÿqÿuÿyÿ}ÿ}ÿ{ÿwÿsÿsÿsÿpÿoÿnÿnÿnÿnÿnÿkÿiÿhÿiÿjÿmÿqÿzÿÿ
ÿÿÿÿÿÿÿÿ--m,,o+~+n(~'o('p''p&~&r&~%r%%s%%s%%u%%u%%u%%u&&u&&u&&v''v((w))w))w*~*w*~*x*~*x*~*x*~*x,~+x,~,x-}-w.}.v-}-s.}2r7:p?
JjR]chm[qsYod^XzKf=y4o2z2p2z2p1|2r3|4r4{4q4{4q3{3p3z3p4z4q3z2q1z2s3z1s1{/r0{0t.{.u,{,v+{+v)|)x(|'z'|'{&}%z%}#z"~!z!~!z z z!~"z#$y(/t5=mGMdRV`Y[bVRdJCi<6n0.r/0v44u9=tCFrJLpORqRUpUWnZZl[\k`befm`r
t[sqXqsZu
yY~W
XV
Y
YY]c
eeeccefog]RkLDq<8w45z4~1~-}-/|/-}--~.22~68z>BuEIpJLmOPkSSkVWkYYjZ[hZZhYWiUTiPLmHBr=6y1.}+~+,~,,~,,~,-1}48y?IuNTnYmhd
bi~n|r}]s5cr
khhk¸¾|¼»¿~¿¾~½¼~¾¶}³~º¤wlh|g_}Rmg|cusprg~wrz{ozlqW~\vd~X~L}[cfb~]a~gW}HG}NM}LZ~mhJ:9ID
><8DA9673}EY~SA39~<<~IMBD}EC}J
V~ED~9M~wpk}2B@Auywngr`RyNF??=>><;:>BDCDFGFHJPS
WY[}]|gzq~w{xyz|ywv}ttslqrnlmojrfmG6o@dxaHK~KL}LH}FL\kpxkhpc]nYWoYZp_aqchpikkopjqphnlgjhhhhigfldcn`ZpWOpG?s5-u)~'{'}'~+|,~0|4{:AuDKqRZp^}erm|rvv{yy{z}|xw~ww}y}z}z}{~{zzzzy
y~z|zzyzx{yz{{{|{{|}}{}|y|v|s{pxl{irh|foc~`l\[mY~^tgzu~wy~{{}{|{|{{{}{~t{uhqrduyj{~r}zuxtssrrpprooro~oop~mojgqf}fxl{p}wx}}v|xy{v|u-ÿ-ÿ,ÿ,ÿ+ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ1ÿ/ÿ.ÿ2ÿ4ÿ;ÿDÿLÿXÿbÿjÿsÿvÿqÿiÿYÿLÿ=ÿ5ÿ3ÿ3ÿ2ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ1ÿ/ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ,ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ%ÿ%ÿ#ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿÿ ÿ!ÿ"ÿ$ÿ(ÿ-ÿ5ÿ>ÿHÿRÿWÿ^ÿdÿeÿgÿfÿaÿYÿQÿGÿAÿ;ÿ5ÿ4ÿ5ÿ7ÿ:ÿ?ÿCÿGÿJÿLÿNÿQÿTÿVÿYÿYÿZÿ[ÿ[ÿ\ÿ`ÿeÿjÿnÿrÿvÿvÿvÿtÿtÿvÿyÿyÿ~ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ
ÿÿ~ÿzÿyÿyÿzÿÿÿÿÿÿÿÿÿ
ÿyÿlÿcÿ\ÿWÿPÿIÿDÿ:ÿ7ÿ1ÿ.ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ0ÿ1ÿ6ÿ8ÿ:ÿ@ÿDÿHÿIÿMÿNÿOÿPÿSÿSÿVÿWÿYÿYÿ[ÿ[ÿZÿZÿXÿXÿUÿRÿPÿJÿEÿ?ÿ9ÿ4ÿ/ÿ-ÿ,ÿ,ÿ+ÿ,ÿ+ÿ,ÿ-ÿ.ÿ0ÿ3ÿ6ÿ<ÿBÿHÿNÿTÿTÿ
ÿÿÿÿÿ~ÿkÿyÿnÿ]ÿpÿxÿÿÿÿÿÿ{ÿ\ÿ]ÿÿÿ¶ÿ½ÿ¾ÿ½ÿ¼ÿ½ÿ¾ÿ¿ÿ¾ÿ¹ÿ¦ÿlÿsÿ³ÿ¸ÿ°ÿÿÿÿ¡ÿwÿIÿZÿhÿuÿÿÿzÿ|ÿ{ÿ{ÿ{ÿsÿcÿRÿWÿbÿOÿQÿQÿ_ÿQÿAÿAÿKÿXÿ^ÿQÿNÿLÿIÿOÿLÿQÿXÿ>ÿ8ÿFÿEÿAÿ<ÿ5ÿCÿOÿQÿ9ÿ8ÿ7ÿ<ÿIÿ`ÿJÿLÿPÿNÿUÿOÿCÿDÿBÿRÿLÿJÿTÿBÿFÿ<ÿVÿxÿkÿDÿ>ÿ<ÿ<ÿqÿyÿwÿqÿeÿfÿXÿHÿFÿFÿDÿBÿ@ÿ=ÿ:ÿ9ÿ;ÿ=ÿ@ÿCÿDÿFÿGÿHÿIÿJÿMÿQÿRÿVÿXÿWÿWÿhÿuÿxÿwÿ{ÿyÿyÿvÿsÿnÿ`ÿdÿmÿjÿoÿjÿiÿ_ÿYÿ_ÿOÿJÿKÿKÿHÿDÿSÿeÿkÿlÿnÿnÿmÿkÿfÿaÿ\ÿZÿ]ÿ^ÿbÿbÿeÿgÿlÿmÿmÿoÿoÿoÿmÿkÿjÿhÿhÿhÿfÿeÿcÿbÿ^ÿYÿUÿMÿEÿ=ÿ4ÿ,ÿ)ÿ'ÿ'ÿ'ÿ+ÿ/ÿ3ÿ8ÿ;ÿBÿEÿLÿTÿZÿ`ÿhÿmÿrÿvÿyÿ{ÿ}ÿÿÿÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿ|ÿzÿyÿxÿyÿ{ÿ{ÿ{ÿ{ÿyÿyÿwÿtÿrÿoÿjÿgÿgÿeÿcÿ`ÿ\ÿ[ÿZÿaÿkÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿuÿqÿtÿwÿ{ÿ}ÿÿ}ÿzÿvÿuÿsÿrÿpÿpÿoÿoÿoÿoÿpÿmÿjÿgÿfÿfÿiÿnÿuÿ{ÿÿ
ÿÿÿÿÿÿÿ,,n,+p*~(p'~'r&&t&%t%~%t$~$t$$t$$t$$u$$u$$u$$u$$u$$u&&v''v((w))w))w*~*w*~*x*~*x+}+x-}-x-|-x-|-x-}-w.}.v1}3u1}1s23t8
>nGTd`j\stZqj^[{Me<y5m4z4p3z3p2|4p5|5p4{4p6{6p6{6r4z4r5z5r5z4r3z4r3z3r1z/q0z1s1z/t.z-u-{-u*{)w*|*x)|)y'}&x$}"x"~"z"~"z {{!#z&+w3
;pGPhXaehnaoq^ql_g]aT
LdF?i<:m=AnDHlJKkPQlS
VmX
\l[
\l\\j`chhnaruZvvUv
uTvvVx{V~
WUTTX
]d~~ky{vpvzsqlzoqu~|mjgdbw
qcm
iea[iVOlH?p93u/-x.~-|/0|38z=@vBFrIJoLNlPQkSTkVWkYZk[[jZXkVUlTRnMIrB<v7~2{/~-~,~--~-~-.~/0|28z=@wFKqOTmqgg ¤l~rsY}guhutwssl~vhv~op»y¾~¾¾²°¼¾~¿½}¸wv§zº}¯~
r~nsQ}<zH|^}ov{~~xvt|wuz|ozT}As~gZ|Y\|^H{=?z;<{Ma{PM{NM}KBKOD@@A=8~BI}D8}96}89|>6{OVzO@zKC{FC}^~Y}L}Q}A
E~:n~yM0=Bprzwxpteoh^vJG~GDC@:~78~:<~>A~CEFHHJPUUUX~Q}Xmzv~z{xyx~ssHZrs~pqg~Zqafreqvo_}TL
J~NN~LOf|lktllomnmnknhdn_\o]]p_`qccpgimiklmkkkjijhigfkedlc`n]XpRKqC:s1,v)~'z&}(z,~0z4~;w>BsGMpV^pb~htp|tww{zz|z}~~x~~w}~wwyz~z~{~{zzzzy
y}z{}yzw{x{y|{{{}{{z~y{x|u|r|n{jxg{erf|doc~`m^\n\~dtnzy~xz~{{}{|{|{{{}y~p{ugsudy|j}~q}yswtstsrpproorpoqo~lqifqc}exi{n{txx{~wzyx|w}v+ÿ+ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ1ÿ3ÿ1ÿ1ÿ2ÿ3ÿ8ÿ>ÿGÿTÿ`ÿjÿrÿuÿsÿiÿZÿJÿ=ÿ4ÿ3ÿ4ÿ3ÿ3ÿ3ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ0ÿ1ÿ0ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ'ÿ&ÿ%ÿ#ÿ#ÿ#ÿ"ÿ"ÿÿÿÿÿ!ÿ#ÿ(ÿ-ÿ5ÿ@ÿIÿTÿ^ÿfÿoÿuÿyÿzÿxÿrÿlÿfÿ_ÿUÿQÿMÿGÿEÿGÿJÿIÿJÿNÿOÿPÿTÿUÿXÿ[ÿ^ÿ]ÿ_ÿ_ÿ_ÿaÿeÿkÿnÿoÿtÿvÿvÿuÿsÿsÿtÿuÿyÿ|ÿÿÿÿ
ÿÿÿÿÿÿ~ÿ~ÿ~ÿ{ÿwÿsÿpÿlÿgÿcÿ^ÿaÿgÿqÿÿÿÿÿÿÿÿÿ~ÿxÿxÿuÿqÿnÿkÿdÿ^ÿVÿLÿ@ÿ9ÿ1ÿ.ÿ/ÿ1ÿ5ÿ8ÿ<ÿ?ÿBÿDÿHÿJÿKÿMÿNÿQÿRÿSÿTÿVÿWÿXÿYÿZÿZÿXÿVÿUÿTÿRÿNÿIÿDÿ?ÿ9ÿ3ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ.ÿ1ÿ4ÿ8ÿ:ÿ?ÿDÿHÿKÿPÿ_ÿÿÿÿ¡ÿ¢ÿ£ÿÿÿ{ÿÿÿÿÿxÿmÿ`ÿyÿuÿ{ÿaÿhÿ|ÿ£ÿ¹ÿ¾ÿ½ÿºÿÿÿ¬ÿ·ÿ½ÿ½ÿ½ÿ¬ÿÿÿ°ÿ¥ÿÿ|ÿÿÿiÿOÿTÿOÿnÿzÿoÿYÿ^ÿdÿlÿmÿlÿeÿjÿÿeÿ]ÿXÿTÿLÿ?ÿ:ÿ9ÿ>ÿEÿIÿTÿRÿMÿKÿKÿKÿTÿMÿKÿEÿAÿBÿ?ÿ>ÿ<ÿCÿEÿ?ÿ=ÿ;ÿ:ÿ;ÿAÿ:ÿ;ÿKÿLÿIÿAÿ@ÿEÿFÿJÿWÿXÿPÿJÿCÿBÿ>ÿzÿVÿ1ÿ2ÿ?ÿbÿ_ÿfÿvÿvÿsÿkÿ^ÿYÿQÿFÿFÿFÿCÿBÿ>ÿ;ÿ9ÿ8ÿ;ÿ=ÿ>ÿBÿDÿGÿHÿKÿLÿOÿWÿ]ÿ[ÿYÿYÿdÿmÿvÿyÿwÿuÿqÿ;ÿQÿsÿdÿ>ÿ[ÿoÿkÿvÿwÿNÿCÿSÿOÿJÿEÿQÿbÿiÿhÿgÿjÿjÿlÿnÿoÿoÿmÿgÿcÿ_ÿ\ÿ\ÿ\ÿ\ÿ^ÿ\ÿ\ÿ^ÿaÿdÿfÿhÿjÿjÿjÿiÿgÿgÿfÿeÿcÿbÿ`ÿ\ÿWÿPÿIÿAÿ8ÿ0ÿ+ÿ(ÿ'ÿ(ÿ+ÿ.ÿ3ÿ8ÿ<ÿ?ÿAÿGÿNÿVÿ]ÿdÿkÿpÿtÿwÿzÿ|ÿ}ÿ~ÿ~ÿ~ÿ}ÿ~ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿxÿwÿxÿyÿ{ÿ{ÿzÿyÿxÿwÿrÿoÿlÿhÿfÿdÿeÿcÿcÿ`ÿ`ÿ^ÿ_ÿhÿpÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿvÿtÿuÿ{ÿ|ÿ~ÿ}ÿ{ÿyÿuÿtÿsÿrÿpÿpÿoÿoÿpÿoÿnÿkÿgÿfÿcÿeÿiÿnÿpÿwÿ~ÿÿÿÿÿÿÿÿ)'q))p'~&r%~%r%}%t%%t$~$s#~#t""t""t##w##w##v##v$$t$$t&&u&'u(~(u)~)u)~)w)~*w+~+x,~,x+}+x-}-v-|-w.|.w.|.u.|.s1}4t1}/t23q8>mGUe_j]tyXrhZ[{Hc;y3m3z3p5{6o5{5o6{6n6{6p6{6p6{6q6{6q6{6q6{6q5y5q5y5q4z4q3z2q1y1r0y.r,z.t-{,t+|*u+}*v'|&w%|$w#~#x"~!x!y!!x#%x*1t8BmMWg`hcry^}{`ys`oiac^bZWeTSeNNgPTgSSgRTfWYg[_g`
`ea
adcf_ik[orTr
tPsrOr
rRt
vVy
}W~
ZY[~`{}xet|pllzgrcw_xXvQ{OuR{Yzewq|nhe_|]}
|]|y`xteniibYjNBl;6p5:q=@t@DqGJpLLnMNmOPmRTkVWkX
ZkXVlWSmROpMIqD>t:~6y3~0|..~//..13}58y<?tCErKRoR{om}¢tzzwijy¥|~}
{}h{Z|T|^|fw|~y¶~«~»
¼±
¥¯|¤ v²£t}w| |r~v}}^^tq
_|Y
L{KS~hi}]i|nizq^{XRz::z;=z?8{JP{OF{LF|ES~KEDHJLSB~8<}?;~:;{=P{MS{HGzPLyADy@ax]{RwQyAzD@|Jh}27};M}Z]~gytuspjq_UwNJ~FBCCB~><~<>~@BEFIJOPRU[}^Yw\zb{n{v}x}w{ttsqupsdpQdqov{mK?GKJGWljwihmiknlkmnnomjohbp^\pZZrWTrRSrU}Yp\}_nf}hmjjjigjgflecma^n\WoNGq?5s.(v&}&z*|,{0|5w9~<t@DqHOqV_rfmtq}uxz{||{y|}x}|x|~xxxz{~||zzzx
yy{yy~yyx|x{y{y{y{xzx}v|s}p|m{h|evb|cse|cod~an`aoe|ltty}x{||y|z{{{{{y}u
~n{vewxbz}jt{yuvsrrqroosoosporlkrgerb}dvgzlzpwvy}x
y{y~vv*ÿ)ÿ(ÿ(ÿ'ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ3ÿ2ÿ1ÿ2ÿ3ÿ8ÿ>ÿFÿSÿ^ÿiÿtÿxÿsÿhÿ[ÿJÿ;ÿ5ÿ4ÿ4ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ-ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ&ÿ&ÿ%ÿ$ÿ$ÿ"ÿ!ÿ!ÿ!ÿ#ÿ#ÿ$ÿ'ÿ*ÿ1ÿ9ÿCÿMÿWÿbÿjÿrÿvÿyÿxÿvÿpÿqÿlÿhÿeÿdÿ^ÿ\ÿ[ÿYÿWÿZÿ\ÿZÿZÿYÿ[ÿ\ÿ_ÿaÿeÿfÿfÿfÿfÿhÿiÿjÿlÿpÿqÿrÿsÿrÿrÿqÿqÿqÿtÿyÿ}ÿÿÿÿÿ~ÿ}ÿyÿvÿrÿmÿhÿdÿ_ÿ[ÿYÿTÿGÿAÿBÿDÿMÿYÿgÿvÿÿÿÿÿÿÿ}ÿ{ÿ}ÿÿÿ~ÿ~ÿ}ÿzÿvÿsÿkÿdÿYÿNÿCÿ?ÿ?ÿ@ÿCÿDÿFÿIÿKÿLÿLÿMÿNÿOÿPÿQÿTÿVÿWÿYÿXÿUÿRÿSÿNÿMÿMÿIÿDÿ?ÿ:ÿ8ÿ4ÿ2ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ2ÿ5ÿ8ÿ:ÿ>ÿ@ÿCÿFÿHÿJÿNÿ^ÿÿÿÿÿÿÿÿwÿWÿ\ÿÿ¤ÿ§ÿÿÿtÿbÿYÿaÿdÿIÿZÿÿ¾ÿ®ÿÿÿ²ÿµÿ©ÿ®ÿÿnÿqÿÿ¡ÿÿ}ÿeÿÿÿÿÿÿgÿaÿgÿfÿ]ÿcÿdÿYÿTÿWÿfÿ\ÿTÿeÿiÿiÿcÿ\ÿbÿRÿ;ÿ<ÿ>ÿDÿ=ÿ<ÿFÿRÿNÿJÿRÿQÿCÿIÿNÿOÿQÿMÿNÿRÿZÿEÿ<ÿ>ÿEÿ=ÿ8ÿ;ÿ:ÿHÿ[ÿfÿLÿKÿPÿCÿCÿBÿGÿ`ÿRÿUÿXÿAÿFÿ=ÿWÿ=ÿ>ÿCÿGÿPÿXÿKÿNÿqÿuÿpÿgÿ_ÿWÿNÿJÿCÿAÿCÿCÿ@ÿ=ÿ<ÿ>ÿ@ÿAÿCÿFÿMÿLÿNÿVÿYÿWÿZÿZÿ[ÿ]ÿ`ÿhÿrÿxÿyÿvÿuÿpÿoÿqÿpÿnÿnÿrÿyÿfÿJÿBÿJÿJÿCÿAÿJÿbÿeÿfÿhÿhÿhÿhÿkÿlÿmÿlÿkÿiÿeÿ_ÿ[ÿYÿXÿTÿOÿMÿJÿLÿKÿPÿYÿ_ÿcÿgÿiÿjÿiÿgÿfÿeÿdÿbÿaÿ]ÿZÿUÿNÿGÿ>ÿ4ÿ+ÿ'ÿ&ÿ(ÿ+ÿ.ÿ0ÿ5ÿ9ÿ>ÿ@ÿEÿKÿRÿXÿbÿgÿnÿsÿwÿzÿ|ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿyÿxÿwÿwÿxÿxÿxÿyÿyÿvÿsÿpÿjÿfÿbÿ`ÿaÿdÿdÿdÿaÿaÿbÿgÿoÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿxÿvÿuÿwÿ{ÿÿÿ~ÿzÿwÿuÿsÿrÿqÿoÿoÿoÿoÿpÿoÿlÿkÿfÿcÿbÿdÿfÿkÿpÿvÿ}ÿ
ÿÿÿÿÿÿÿ)(p''s%~$u$~$u#~#u""u"~"t!~!u""v""v""w##w"!v!!v!"v$$v&&v&'v'~(v)~)w)~*w+~+w+~+x,~,x-}-x-}-v.|.v.|.v-|-u-|-s/}1s3}3s12p8>lHTd^j\uxUrhX]{Lb=y5i6z6o6{5o5{7o6{6p6{6p6{6p6z6o6z6o6z6o6z6o6y6p6y6p5z4p4z4p2y2p1y0p0z0s/{.t,z+t*{)u'|&w'|&w%~$x#~"x""{""y#&w+2t:
CoM
Wh`
gcn
o_poao
maljbjjdhgdcbd`^c^_c_aa_c_ef`hl`kj^ii]jj[lnWqtOs
rMqoLn
pPq
vRz
~TX|z]xvcqzkkew_r[tWyRsN}JrG@r>@uBFzP}`qtkdazu]y{^~]^~a}yeuofeYgP
IiHGjGGlJJnJJmLNmNNmQSlUVlVSmQNoMJqIGtD@w:6y44}00}12|35{7:y<>w@BuE
GqI
JnP
Smpu{}tvrYQtxxx~wwfx_y( y}½»©z}u¬x®zw[ye{}>|Z|v |{z{UYz\]ztwzm_|\]{ZS|Vm|hg|hV|HEz9=zMSzF@y>Tzd\|`u|[F|LN~NG}CU{A=z@=|CA{77y9DyWSy?5{CB{DCz;@yO\yO>|D
?};9}@J~MU~S|L}Yvj~ntqjtZOzMHB@
BB?~<:~=A~ACFSUS]xbbpa`n_^qbzjyszx}x}wzs~ptrpnkHkTxrv
FFMNFLgjfudfoeengimjkmkkllfm`\nVUrQOtGCt@~CtD{NrV{]pd|hmi~ikhgjfcldcma]nZUpMEr<3t,(w'})z+|/y2|5u:~?qAFqLRq[driptu}xx{{{||y||x|{x}}xxxz{~{{zzzxy~y}{yy~wyx|w{x}x{x}w|v}t}q}l}hzc}_v^|^sc|doc}bna}eoj|qtyy}y~|||{{{{{{{{y}ukxsbuwd{}m}|sxustrrrqrnnqooqqprljrgcub}eygzlyswxxxy{y}vv(ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ$ÿ$ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ2ÿ2ÿ1ÿ2ÿ8ÿ>ÿDÿOÿ[ÿeÿpÿvÿtÿkÿaÿPÿ@ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ2ÿ2ÿ3ÿ2ÿ0ÿ0ÿ/ÿ.ÿ,ÿ+ÿ*ÿ)ÿ'ÿ&ÿ'ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ&ÿ+ÿ2ÿ;ÿAÿKÿUÿ\ÿcÿeÿdÿdÿcÿaÿcÿfÿiÿjÿjÿjÿhÿgÿeÿcÿdÿcÿeÿfÿfÿhÿkÿlÿmÿoÿpÿnÿmÿmÿmÿnÿnÿnÿqÿsÿtÿsÿrÿrÿpÿpÿqÿvÿzÿ|ÿzÿzÿzÿwÿuÿpÿkÿfÿ]ÿWÿQÿJÿHÿDÿDÿAÿ@ÿ>ÿ>ÿ=ÿ>ÿBÿJÿZÿlÿ}ÿÿÿÿÿ|ÿrÿgÿkÿqÿvÿ{ÿ~ÿÿÿÿÿÿ}ÿ{ÿtÿlÿeÿ[ÿSÿNÿKÿIÿIÿIÿJÿJÿMÿMÿNÿNÿPÿRÿSÿRÿOÿKÿKÿIÿFÿFÿDÿCÿ@ÿ;ÿ8ÿ6ÿ3ÿ3ÿ2ÿ2ÿ5ÿ7ÿ9ÿ;ÿ<ÿ>ÿAÿCÿDÿFÿJÿLÿMÿQÿQÿdÿÿÿÿ ÿÿÿÿÿ
ÿcÿQÿfÿÿÿÿÿÿÿÿÿZÿ*ÿ#ÿxÿ¼ÿ»ÿ´ÿ¥ÿÿ]ÿ|ÿÿÿÿbÿVÿkÿÿÿ{ÿlÿpÿÿ¤ÿfÿCÿ\ÿ_ÿcÿzÿÿÿvÿmÿdÿbÿ\ÿhÿaÿVÿ\ÿXÿ=ÿ9ÿ?ÿDÿKÿWÿYÿDÿ9ÿBÿBÿcÿ\ÿWÿbÿYÿKÿFÿCÿ@ÿ:ÿLÿTÿ3ÿ8ÿ>ÿIÿJÿ<ÿ4ÿ3ÿ5ÿ>ÿFÿJÿHÿDÿAÿGÿ@ÿEÿ7ÿ@ÿGÿTÿ=ÿ@ÿ<ÿ=ÿ:ÿHÿ]ÿXÿ[ÿ]ÿ?ÿEÿOÿMÿ^ÿ^ÿ_ÿZÿOÿ?ÿ?ÿ@ÿAÿBÿBÿBÿ@ÿ=ÿ>ÿAÿAÿCÿFÿTÿYÿWÿ_ÿcÿeÿcÿbÿaÿbÿgÿlÿtÿxÿwÿtÿsÿlÿ`ÿ\ÿrÿlÿfÿ_ÿTÿMÿCÿLÿ[ÿdÿsÿmÿhÿeÿdÿcÿbÿcÿeÿhÿjÿkÿkÿkÿhÿcÿ]ÿYÿRÿOÿJÿCÿ:ÿ7ÿ6ÿ<ÿDÿMÿVÿ]ÿfÿhÿiÿiÿhÿfÿfÿcÿcÿbÿ`ÿ\ÿXÿSÿKÿBÿ8ÿ0ÿ,ÿ(ÿ'ÿ)ÿ+ÿ/ÿ2ÿ5ÿ:ÿ?ÿBÿGÿLÿRÿ]ÿfÿlÿrÿuÿxÿ{ÿ{ÿ|ÿ|ÿ{ÿ{ÿ{ÿ}ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿxÿvÿvÿvÿwÿxÿxÿvÿtÿtÿoÿkÿeÿ`ÿ^ÿ]ÿ]ÿ`ÿcÿbÿbÿcÿfÿmÿsÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿxÿsÿuÿwÿ{ÿ}ÿ}ÿ|ÿxÿuÿrÿqÿqÿpÿnÿnÿoÿoÿpÿoÿlÿjÿfÿdÿeÿeÿiÿoÿrÿzÿÿÿÿÿÿÿÿÿ&~%p&~&s$$v$$v$#u!!v ~ v!~!v!!x!!x!!x""x""x##x"!x$$x''v&'w'~(v)~*w+~+x,~,w--w--w-}-x.}.x.|.w-|-w.|.v.|.v0}/u0}1u01q6<nD
LhXf_nsXtmYbS]H|=d6{5j6|6m5{5m7z7n8z6n6z6n6y6o6y6o6y6n6y6n7z7o7z7o5z5p4z4p4z4p2z1p1z1q/y/r-z,t+{*u({'u'|&w%|%x#}"x"~"x!"x%&x+2u9AqHPnU[j\XhTRdV\cafbjlakibggdfhbhh_ll_ln]pr^rr]poZopXprVttUwxPtrMrsJrsHx
yMyvUvs]o~jdew_nYtRwLpD@p?@q@?q==q=;u=AzHVgvylfwdh}XdZafipew{d_
^
_{buodf\fUQhN
JkI
JlL
LmNNnOPoPNoHFpDBqEDtBAv>;x75z57z87z9<w>?wBDsF
HqK
LpM
OpP
QnT
n£q¢|zv
ptsuPuW|v{uvss{ t@u*#yhº¹
¶²¦{
`xdvxi`}~}rp{cwxOxDfwryz|}|{ca{cSzRXx\Ry3>zKSx^kwF/y0
5yMSxMQxJIzFD|DD|W;|88}=@{73{25{48{DD};>?B~AS}@A}F@~=
B~JRHV~gQ}Q|W~@yF@x0OvOMvP@{;?DFHB?>?@BBDHQYZ_wfgoddle~fri{nyu{y}x~uxqpnc]hjgnJH~SNMYes|nitecncbmccmeeniinhgod_oZRpLGr@9u2}1v5|:uC|LrV|]pd}fohhlfeledlcao^[pXQqIBr7/t*~(w'|)|,{0z4}6v<~BsCHpOWp_hsmruw}yxz{|}|y|{y{{y{}y
yyy~z}zzyyzzy~}y|yxw~vzw{v{w}w|w}v|u|r}m|i}cy]{Yv\z[s_zaoc}cmd}hnp{xu~z}z}{{|zz{{}{|{z}tyjtrdswg{|o|{uxusrpqpnqnnonopn~mpl~jrhgve}fyizn{qw}zxy{x~x~w&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ4ÿ:ÿ@ÿKÿWÿbÿjÿqÿrÿoÿfÿYÿNÿDÿ<ÿ7ÿ8ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ1ÿ1ÿ1ÿ/ÿ,ÿ,ÿ+ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ"ÿ#ÿ%ÿ(ÿ0ÿ6ÿ>ÿCÿKÿQÿSÿSÿOÿJÿIÿOÿTÿZÿaÿeÿgÿhÿgÿhÿhÿgÿiÿjÿjÿmÿnÿpÿqÿqÿqÿrÿrÿqÿqÿqÿuÿuÿwÿ{ÿ{ÿ{ÿzÿwÿtÿtÿuÿvÿvÿwÿuÿtÿrÿoÿlÿgÿaÿ[ÿSÿKÿDÿ@ÿ>ÿ>ÿ?ÿ>ÿ=ÿ>ÿ<ÿ<ÿ;ÿ<ÿ=ÿAÿGÿRÿcÿxÿÿÿÿÿuÿcÿOÿJÿOÿXÿbÿkÿrÿzÿÿÿ
ÿÿÿ
ÿÿÿyÿvÿoÿdÿ[ÿSÿOÿKÿKÿLÿLÿMÿMÿMÿMÿHÿFÿCÿAÿ@ÿAÿBÿCÿBÿAÿ=ÿ;ÿ<ÿ:ÿ9ÿ;ÿ>ÿAÿAÿBÿCÿDÿFÿHÿJÿLÿNÿOÿSÿUÿQÿSÿcÿÿ¡ÿÿÿÿÿqÿYÿ~ÿÿtÿ^ÿMÿdÿ}ÿ{ÿvÿqÿuÿÿÿ2ÿ*ÿ*ÿMÿµÿ´ÿ±ÿ°ÿ«ÿÿÿrÿiÿjÿmÿoÿhÿuÿ|ÿmÿdÿtÿvÿbÿ>ÿ>ÿXÿÿ¡ÿÿÿÿÿÿuÿfÿ[ÿUÿKÿMÿXÿ_ÿ^ÿGÿKÿBÿFÿZÿUÿDÿ3ÿ2ÿ9ÿ>ÿ5ÿ:ÿNÿIÿIÿCÿ>ÿ;ÿFÿSÿ:ÿ5ÿ8ÿ9ÿ<ÿFÿ;ÿ2ÿ2ÿ2ÿ8ÿ@ÿNÿFÿ=ÿ?ÿ7ÿNÿYÿQÿYÿKÿ9ÿBÿCÿQÿAÿHÿVÿUÿOÿRÿNÿ7ÿ@ÿ2ÿ0ÿIÿKÿYÿ@ÿ6ÿ:ÿ>ÿCÿFÿIÿFÿAÿ?ÿ?ÿAÿAÿCÿGÿHÿUÿ\ÿYÿZÿcÿhÿcÿeÿaÿgÿmÿrÿ{ÿwÿsÿjÿ^ÿgÿgÿ^ÿ^ÿRÿGÿFÿ:ÿ:ÿKÿSÿ_ÿrÿmÿiÿeÿcÿbÿaÿdÿdÿdÿhÿgÿgÿgÿfÿ`ÿ]ÿUÿMÿDÿ=ÿ5ÿ-ÿ,ÿ.ÿ3ÿ<ÿFÿNÿXÿ_ÿdÿfÿgÿfÿeÿeÿcÿaÿ`ÿ`ÿ^ÿ[ÿTÿOÿHÿ?ÿ7ÿ/ÿ*ÿ(ÿ'ÿ)ÿ,ÿ0ÿ6ÿ9ÿ;ÿ?ÿCÿHÿOÿYÿaÿiÿoÿuÿwÿyÿzÿ|ÿ|ÿ|ÿ{ÿ{ÿ{ÿ{ÿ~ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ}ÿ}ÿzÿyÿwÿuÿvÿwÿxÿwÿwÿuÿtÿqÿlÿfÿaÿ[ÿXÿWÿZÿ_ÿaÿbÿcÿfÿlÿqÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿvÿqÿpÿrÿuÿxÿzÿ|ÿyÿwÿtÿrÿpÿpÿnÿnÿnÿnÿnÿmÿkÿjÿjÿhÿgÿeÿeÿhÿmÿqÿ}ÿÿÿÿÿÿÿÿÿ&~$v$~$w##v#!v w !x z zy y x x!!x!"x$$w%%w''w()w)~)z)~*z+~+z,~,x-~-x.~.x.}.x.}.x-|.w/|/w/|/v.|.v.}.u/}/u0}0s27p<FkQ\efk^p
pZib]WM_C}<d9|7i5|5l6{3n4z5o5z5o5y5o5y5o4y4n4y4n5z5o4z6o6z6p5z3p3z2p3z3p1y1p/x-q+y+r*z(r({(t'|'v%|%x$}$x"~"x"#x#%y).w3:t>DpHKnKHlGFkIOiV
[g^
`da
dac
ecgidjjdkmbop`qq_sr[stXvwVxzU{{S||Qy
wPvvOtrOppQol]i|eg^vVqLsD{?r@?q=>q=
=r=
=r;
;s;;v<?xFPawsmhwfd}Mj;{@pE}NrYcomxjgb
`
a~
ydu
kd_
VdP
MhLMkLMnLFsC?u;:t:=uACuCCtCCsCCrD
DrC
DrD
DqG
JqK
LpNOpQ
OnQRmUUmp£p¡~yywJLwntluPOvqxvwwupsp3t10w5£|®§}¨¤||xj{`T}YW}fj}c{m{lhzS8w?Tv¥x z|{tIz<^xobw[TzNL|FC~CK}RD}3@|D9{::z8>zG4|3B|KK|?3}A>|?7|13{05|9D}C><Pe`~i_~;=~<A~NQ~I
E}OH}MM;<F}=C|@?y81}7?A~@@~AA~@@~AAHIJMMLR~`hqg~fhZ_lpejdcfTK_DE`JEhOOzJ@BNC~I~d~uymgsdapbdnd~cne|fnhhnfco^XnPFp<3t+)w)|/y5|>xI{QtY|`qe}gngemddncbn`_o\XpUPqG>r4-u)~(x(~*{,}0y5}8v:~=sBIqRZqb}jsp}vuv}xxy{{}{y|{yzzy{}yyyyzzzyyxxy}|yzwxu~tzu{v{v|v|u|t|r|o}j|e}_yZ{UvW{Yu]{`qa|dmh|pnt{zuz}{}|||{{|{|{z{y}r}tjppfptlvxuz~xuu~rsqqqonqnnommpn~lpk~kriiuf}gxhzmysw~zxyzx~v~w$ÿ!ÿ#ÿ#ÿ"ÿ!ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ"ÿ!ÿ"ÿ$ÿ$ÿ%ÿ%ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ2ÿ9ÿAÿKÿWÿ`ÿeÿkÿpÿnÿhÿ`ÿUÿLÿFÿ>ÿ;ÿ:ÿ8ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ6ÿ6ÿ6ÿ6ÿ5ÿ3ÿ2ÿ3ÿ3ÿ2ÿ1ÿ/ÿ-ÿ,ÿ,ÿ+ÿ*ÿ(ÿ(ÿ(ÿ(ÿ&ÿ%ÿ%ÿ$ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ&ÿ+ÿ0ÿ3ÿ9ÿ>ÿ@ÿCÿBÿBÿCÿGÿIÿNÿSÿWÿYÿXÿ[ÿ\ÿ\ÿ]ÿ_ÿaÿaÿdÿgÿiÿkÿmÿpÿqÿtÿsÿtÿuÿxÿyÿyÿ|ÿ|ÿ|ÿ|ÿ|ÿyÿzÿxÿuÿqÿnÿlÿiÿfÿcÿ_ÿXÿPÿGÿBÿ?ÿ>ÿ>ÿ=ÿ=ÿ>ÿ<ÿ=ÿ<ÿ<ÿ:ÿ:ÿ9ÿ:ÿ;ÿ=ÿCÿMÿ^ÿpÿÿÿÿÿ|ÿjÿSÿ7ÿ1ÿ5ÿ=ÿHÿOÿ^ÿjÿuÿ}ÿÿÿÿÿÿ
ÿÿ~ÿ{ÿvÿpÿiÿ[ÿSÿOÿMÿLÿKÿHÿBÿ?ÿ;ÿ;ÿ;ÿ>ÿAÿEÿFÿGÿGÿIÿIÿJÿJÿJÿJÿJÿJÿJÿJÿLÿMÿNÿOÿQÿRÿRÿRÿRÿSÿWÿVÿvÿ£ÿÿÿÿÿnÿ:ÿIÿcÿÿÿxÿXÿNÿYÿrÿxÿzÿxÿÿ`ÿ8ÿ5ÿ4ÿ.ÿzÿ¤ÿÿ¢ÿ¤ÿÿÿ~ÿmÿdÿXÿNÿBÿ?ÿDÿ]ÿjÿhÿcÿJÿ<ÿEÿQÿ~ÿÿÿÿÿ¢ÿÿÿcÿCÿNÿ|ÿ}ÿvÿmÿdÿ`ÿSÿJÿPÿYÿXÿZÿPÿGÿ<ÿ:ÿFÿGÿ:ÿ;ÿ=ÿEÿAÿ?ÿAÿDÿUÿPÿBÿAÿ7ÿ0ÿ5ÿ=ÿ6ÿ,ÿ0ÿ3ÿ6ÿ:ÿ?ÿUÿbÿYÿVÿNÿ<ÿ;ÿ=ÿDÿ^ÿXÿHÿDÿFÿHÿQÿ]ÿWÿLÿJÿMÿKÿMÿ@ÿ>ÿ@ÿ;ÿ<ÿ?ÿ>ÿ:ÿ;ÿ>ÿ@ÿAÿ@ÿ>ÿ?ÿ?ÿCÿFÿGÿHÿIÿTÿaÿhÿkÿ\ÿFÿNÿNÿ`ÿkÿZÿOÿTÿ]ÿTÿ4ÿ/ÿ@ÿHÿCÿAÿFÿGÿGÿTÿrÿrÿlÿhÿeÿdÿdÿeÿcÿcÿdÿeÿiÿhÿeÿ`ÿ[ÿUÿKÿ@ÿ5ÿ-ÿ(ÿ)ÿ)ÿ/ÿ6ÿ>ÿIÿQÿ[ÿbÿfÿhÿgÿeÿdÿcÿbÿbÿaÿ_ÿ\ÿXÿTÿOÿEÿ=ÿ3ÿ-ÿ)ÿ(ÿ(ÿ*ÿ,ÿ0ÿ5ÿ8ÿ:ÿ=ÿAÿJÿSÿ[ÿbÿjÿpÿvÿvÿxÿyÿ{ÿ{ÿ|ÿ{ÿzÿzÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿ{ÿyÿxÿvÿtÿuÿvÿvÿxÿwÿvÿtÿnÿiÿcÿ\ÿXÿTÿTÿXÿ\ÿ`ÿaÿeÿjÿqÿxÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿyÿoÿlÿlÿnÿpÿvÿwÿzÿxÿuÿrÿqÿoÿnÿnÿnÿnÿmÿmÿmÿmÿmÿkÿjÿjÿjÿhÿjÿoÿvÿÿÿÿÿÿÿÿÿÿ"~!w!~!w ~ x!~ x wwyyzzy!!w!~"w!~"w"#w%%x''z()z)~)z)~*z+~+z,},z-}-y.}.y/}/y.}.x.}.x.}.x0|0w.|.v.|.v.|.v,|/s/~1p5;mD
NhYcaho^pk^d^^UN_GCa@|=e:|8h5y3k3y1n4y4o4y4o4y4m4y4m5y5n6y6n6y6p6y5p4y4p3y3p2y/p/y-p,z+r+z)s(z)u'|'v&}&w&}$y$~#x#~"y"$x&'x-1v5;t:<r;=o?CoEJnOQlTSjSQhQRhT
Yi]`dad`gj^mp\rrVuwTy{T}TT~U{xWtqXmiZd~`^_{YiRwLtEs>|<s<<s<<s<<s;:r:9s99t99u9;x>~I~Yxm~offq\j>{*s+z/v7|?uK~Vrdnnv}me_
^
`yrbi]gSLiKGmDBq=<r;>sCGqHJqMNoNPnRSmTSoPOmO
OlOOoP
QnR
SoR
SpUVoV
Vm|£q~{zjz6Uya{w}tdRuLfvu}usV9s78u8Iyz£|
~}wl`U
L~FI{\
c{\ZzGAyFJvrvz{zzZxsvylsi\uZV{]l~qyvjb[TU~VUzU\zhP|C?z;FyNIz;0z19{G7{--}/4}8K~f`~UR~P=~>C}JA}=>|JM{FD}MN~I>~ALV
VVSWF:=A=~:>~AA~CEGEAMRQ^SO{Y\tIEeRZ]ZdZY[_TCi:>t9E|G<}0<xRpywruooqihpn|crjxhrbvbofxemeal[SmH<r1(v('y*{/y6z?wJzSt[{cqe~fngfndbnaan`_o^YpRMpD;r2,x**|)~+}-~1{5~7x8~<w@}ItS}\td~ltq~vvv|xyzzz~{x{{xzyxzxxxzzzzyyxx}w{xxxvyu}vzv{vzv{u{u{s{q|m~h|_~ZyV}RwS|Wt[{_rb|gmm|uq{|w{}z~{}||z~|}||{x}}ttnliihlpru}wyy}vwu~rqqopnmpmmpl~npn}nqm}mql~ksk~ixiyoxxxyyzzz~y~y"ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ"ÿ!ÿ"ÿ#ÿ#ÿ%ÿ%ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ.ÿ.ÿ1ÿ5ÿ=ÿHÿQÿ[ÿdÿhÿmÿmÿhÿdÿ\ÿXÿQÿLÿGÿEÿ?ÿ<ÿ9ÿ6ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ1ÿ1ÿ/ÿ-ÿ,ÿ+ÿ+ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ#ÿ#ÿ#ÿ!ÿ"ÿ%ÿ$ÿ(ÿ-ÿ2ÿ4ÿ3ÿ5ÿ7ÿ8ÿ:ÿ>ÿCÿHÿLÿMÿMÿKÿKÿLÿLÿMÿOÿQÿWÿ[ÿaÿdÿgÿjÿmÿpÿrÿrÿxÿyÿzÿ|ÿÿÿÿÿ~ÿ{ÿxÿsÿoÿkÿfÿ^ÿZÿWÿSÿMÿFÿ?ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ:ÿ=ÿDÿRÿeÿyÿ
ÿÿÿÿyÿhÿLÿ0ÿ*ÿ)ÿ,ÿ0ÿ9ÿCÿMÿZÿfÿqÿyÿ~ÿÿÿÿÿÿÿÿÿvÿmÿbÿUÿKÿFÿAÿ@ÿCÿCÿCÿEÿHÿKÿNÿQÿTÿUÿUÿUÿVÿWÿXÿWÿUÿTÿSÿSÿQÿQÿRÿPÿTÿSÿQÿUÿTÿUÿTÿZÿÿÿÿÿÿÿsÿ,ÿ<ÿXÿqÿÿ}ÿhÿMÿGÿLÿfÿuÿ|ÿuÿJÿ:ÿ;ÿ<ÿ<ÿ:ÿgÿÿÿÿÿwÿrÿtÿmÿeÿYÿPÿPÿTÿZÿUÿQÿQÿIÿFÿGÿGÿfÿÿÿÿÿÿÿÿÿ}ÿ|ÿ]ÿNÿQÿTÿMÿOÿUÿNÿRÿnÿÿÿpÿsÿ
ÿpÿbÿZÿMÿFÿKÿXÿOÿNÿFÿ;ÿGÿOÿ@ÿ2ÿ.ÿ3ÿ7ÿ<ÿ8ÿ/ÿ-ÿ.ÿ.ÿ3ÿ<ÿ?ÿCÿ?ÿKÿVÿJÿMÿIÿ;ÿ7ÿ8ÿ;ÿ;ÿ:ÿ<ÿ>ÿ>ÿAÿ=ÿ8ÿ:ÿ;ÿ<ÿCÿHÿJÿMÿPÿ@ÿAÿDÿAÿ>ÿ?ÿAÿAÿBÿDÿCÿCÿFÿJÿJÿIÿMÿRÿPÿHÿCÿPÿQÿBÿEÿJÿIÿTÿSÿ@ÿ?ÿBÿ@ÿAÿKÿQÿfÿkÿtÿyÿxÿuÿnÿfÿtÿxÿwÿdÿXÿ]ÿTÿ^ÿ^ÿcÿfÿeÿaÿ]ÿUÿFÿ;ÿ1ÿ(ÿ(ÿ'ÿ)ÿ0ÿ7ÿ@ÿKÿUÿ\ÿdÿfÿgÿgÿfÿdÿbÿaÿaÿ`ÿ_ÿ\ÿXÿRÿMÿDÿ;ÿ2ÿ,ÿ)ÿ)ÿ)ÿ+ÿ-ÿ1ÿ5ÿ7ÿ8ÿ<ÿAÿJÿVÿ_ÿfÿlÿqÿvÿxÿyÿzÿzÿ{ÿ{ÿ{ÿzÿyÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ{ÿxÿvÿuÿtÿuÿuÿuÿuÿvÿvÿtÿpÿlÿgÿ_ÿXÿSÿOÿQÿUÿ[ÿ_ÿeÿiÿoÿvÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿoÿhÿdÿgÿkÿpÿuÿwÿwÿuÿsÿqÿqÿoÿnÿmÿmÿmÿlÿnÿnÿnÿnÿnÿmÿmÿkÿkÿmÿsÿ|ÿÿÿÿÿÿÿÿÿÿ!~!w ~ w ~ x~xyyyyzzy w ~!w!~"w#$z&&{'(z()z*}*z+},z+},z,}.z.}.y/}/y/}/y/}.x.}.x.}.x-|-w-|-v/|/w.|-w,|+u,}-t,~1r7=kG
Qg\
dci
ibifbd
_aZUaQN`IDa@|:g6{5j3z3l3z3n3y3m4y4m4y4n5y5n4y4n4y3n3y3o2y2o1y0o/y.o-z,q,z+r*z)s(|'u&}&v&}&w$~$w$~#x""z#$z$(x-0v13v26t89r>ApDFnHHmGJiLNgQUcZ[b_c_fk]nr[rsXw
{W|Y~[}|\yw_qnag~ce]{XiPxMpGvBx=t;~:t::t9:t:9t99t98s87t66u66v67x9>|Kz_rskf}nhW;o)|)v'|)|-}4z=}IuS~]shooxkgh{uiv
xipaiTIhEDjGIlJMiRUhY[h]^g]YhWXkZ]nZXmVVlUTlS
RnSTpRSrSVq]brx~{{}{+9yN`w{ytcNtKDuIPrRIqA=r>>u=<v9ny|nmjb_W}PPS|QJyMPxIJwGEvJvtwyw~u[AsIOqMLpJEq>5s?[uajx{t|e[}NR}WH|JL{=?z:5z28|92|55|0-}*'}*1;?>T}L=|74{77|43|95|35~8;;:98~==~;:}BQ}DA~?EM
GC
A=??
BG
IR^Z]d}TV|^\rJ=nRZuQ:@LGE~PaxpvvsstwtwssvmptiesJ{SuAsHv]rcseygmeal[SmG;r1+w&'z*{1{9zByK{Tt]}dqf~gngfncbnaan_^p[WqRJsC9t2-w*+{)~,|.~0z4~6x8~=wC|LuW|`ug|mvr|vxx|zzzzz{yz{y{zy}yzz{{zzyyxx~w|xxvvyu}uzu{uzu}v{u}u{o}j~e|]~VzQ}OwQzWu^ybrf|kpq{yt}{z{}{~|}||z{|}{}|y~vtmeleekj~nvs{wyw{tvq}opqpponpm~mpn}mpn}pqp}pqn~mqn~lrp{uu~yyzz|z~z~z!ÿ!ÿ!ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿ ÿ!ÿ"ÿ#ÿ$ÿ&ÿ&ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ,ÿ,ÿ-ÿ-ÿ0ÿ4ÿ7ÿ@ÿFÿRÿ[ÿ^ÿcÿeÿfÿeÿbÿ`ÿ[ÿXÿUÿSÿMÿHÿEÿ=ÿ6ÿ4ÿ3ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ0ÿ0ÿ/ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ(ÿ(ÿ&ÿ&ÿ%ÿ#ÿ%ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ'ÿ+ÿ,ÿ-ÿ3ÿ7ÿ8ÿ9ÿ=ÿ?ÿ?ÿ@ÿCÿEÿFÿMÿRÿUÿ[ÿ^ÿ`ÿdÿgÿiÿkÿlÿnÿqÿuÿwÿzÿzÿ}ÿ|ÿ|ÿzÿwÿvÿsÿoÿjÿdÿ]ÿWÿPÿIÿCÿAÿ?ÿ>ÿ;ÿ;ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ8ÿ:ÿFÿUÿjÿ{ÿ
ÿÿÿÿvÿdÿGÿ-ÿ(ÿ&ÿ(ÿ)ÿ)ÿ,ÿ5ÿ>ÿHÿUÿ]ÿgÿpÿvÿ}ÿ|ÿpÿeÿ_ÿfÿ{ÿxÿpÿeÿZÿOÿNÿPÿVÿZÿbÿiÿlÿnÿoÿoÿpÿlÿfÿ_ÿZÿYÿ[ÿ_ÿ^ÿZÿVÿVÿVÿTÿTÿRÿPÿOÿPÿPÿOÿOÿYÿÿ ÿÿÿÿÿÿ3ÿ1ÿ8ÿVÿnÿpÿSÿJÿPÿHÿAÿ<ÿ;ÿ=ÿ@ÿAÿAÿAÿ@ÿ>ÿ7ÿEÿrÿ~ÿsÿeÿdÿaÿXÿZÿRÿKÿNÿNÿJÿKÿMÿLÿFÿGÿGÿJÿLÿYÿuÿÿ
ÿzÿ{ÿÿÿwÿFÿ:ÿAÿBÿCÿAÿAÿ@ÿ>ÿ;ÿ6ÿ/ÿ2ÿVÿÿÿÿvÿbÿ`ÿUÿQÿSÿIÿ=ÿDÿ9ÿ8ÿ8ÿ4ÿ9ÿ?ÿ=ÿ2ÿ.ÿ1ÿ/ÿ.ÿ-ÿ)ÿ<ÿAÿ9ÿ8ÿ<ÿ8ÿ6ÿ3ÿ4ÿ7ÿ;ÿ<ÿAÿCÿ:ÿ4ÿ3ÿ6ÿ6ÿ2ÿ<ÿBÿCÿGÿ<ÿ9ÿ9ÿ;ÿ=ÿFÿFÿBÿ>ÿ8ÿ<ÿ?ÿ@ÿAÿ>ÿAÿEÿCÿBÿIÿIÿYÿQÿjÿuÿMÿPÿiÿpÿrÿaÿIÿAÿ@ÿHÿIÿHÿ=ÿTÿhÿnÿpÿqÿrÿjÿZÿpÿÿfÿlÿ^ÿUÿRÿ@ÿ@ÿLÿUÿ]ÿcÿhÿeÿdÿbÿ[ÿSÿHÿ:ÿ1ÿ+ÿ'ÿ(ÿ,ÿ2ÿ:ÿCÿLÿUÿ]ÿdÿfÿgÿgÿfÿbÿbÿaÿaÿ_ÿ^ÿ\ÿXÿRÿJÿBÿ:ÿ3ÿ,ÿ+ÿ+ÿ)ÿ,ÿ.ÿ0ÿ1ÿ5ÿ8ÿ=ÿEÿNÿWÿ`ÿiÿpÿtÿvÿxÿzÿ{ÿ{ÿ{ÿzÿzÿzÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿxÿvÿvÿvÿuÿuÿuÿuÿuÿvÿuÿuÿoÿjÿeÿ]ÿVÿQÿOÿQÿWÿ^ÿbÿgÿlÿrÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿrÿhÿaÿ`ÿbÿgÿnÿsÿvÿvÿsÿqÿoÿoÿoÿoÿnÿoÿoÿnÿmÿnÿpÿoÿoÿnÿmÿnÿnÿtÿzÿÿÿÿÿÿÿÿÿÿÿ!~#x#~#x!~x~xyyzzzzy y y"#y#~$y%~%y()x()x+}+x+},x-}-z,}.y.}.y.|/y.|.y/|.z/|/y-|-x-|-y-|,w,|,w,|,u,|,w-},w,},u.~1q8@mHQiVZi^
`eabaaa``]_ZV_PJbE~@c:{5f3z3j2y2m3y3o3y3n4y4n3y3n4y3n3y3n3y2n0y/n0y0n.y-q+z+q+{*r({'t'|'u'~%w&}$x%~#z#~${$~${##z%'x*,x/3t7;p>@m?@lCIhMVfZ]cai^kn\no[opYs
v\xw^yw_vwbutcqnfj}ci^zXnQxJtCv<y9v8|;w<}:x:8v76v56v77u76u66u65u42v33v33w56|=|L`tsnf
|fpWj9'q%}%w'(|(,{/7w@KpVamjopjdsonqSjk
}lthj_]icliv}jjj~}o}uongphhoien_YjV
SiRPpNLtKLvJIxGLz ||{zE.z6Owg\tGIuRJvEBu?@t@@t?>w=2x6FzQq~nc]ZWVPOMI{HFyKLwIIvPRtW ZtX itx|srmrC;r<;s89s9:u97u63u/;uf~uysa{SN}I
C}EK~J=2
6~J;}4
,~34/+*3?=DE95~34|96|<B~D<~76~55}43}43}34|57~7:|:>|JV~I6~0,38~:<~E?;=@F~Z~\Q{CL|[gwb]rHA|HJE=YmmmvprttZwC~l~v}t}FxW`rW|GpIsPs[tfqg|hmgbl[RnH:s1+x)*{/{4{;zDwN|Wt`}dqf~gngfmdbma`o_^q\YqRIsB:s0*v()w*-w11v4~7u9~>uH|SuZ{cwk|quu|xwy|{||{||yzyyyyy|xyy{{zyyyy
xzwxwxvuyu|vzv{vzv|u{u}s{m~g~c|\~SzO|NvS{Yu_zcrg{mpt{|u{{{}{~|}{|{}{~|}
~{ymrd]o\`sg{o|t{u|u}stqopn~ppo~npn~non~non}npm}mpn~npq~qqv|}v
zz{z}z~z~z!ÿ#ÿ%ÿ%ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ!ÿ ÿ!ÿ#ÿ$ÿ%ÿ%ÿ(ÿ)ÿ(ÿ)ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ1ÿ6ÿ?ÿFÿLÿOÿSÿXÿ[ÿ]ÿ^ÿ`ÿ_ÿ`ÿ_ÿ\ÿWÿSÿNÿHÿAÿ:ÿ5ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ4ÿ3ÿ3ÿ3ÿ2ÿ1ÿ/ÿ/ÿ/ÿ0ÿ/ÿ-ÿ,ÿ,ÿ-ÿ+ÿ)ÿ)ÿ'ÿ'ÿ'ÿ&ÿ&ÿ$ÿ$ÿ#ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ'ÿ+ÿ0ÿ5ÿ9ÿ=ÿAÿDÿFÿJÿKÿOÿUÿ[ÿ^ÿcÿfÿjÿnÿrÿrÿsÿtÿvÿuÿwÿwÿwÿtÿsÿqÿoÿmÿkÿiÿfÿ`ÿ\ÿVÿPÿKÿBÿ:ÿ7ÿ5ÿ6ÿ8ÿ8ÿ9ÿ:ÿ:ÿ9ÿ8ÿ6ÿ7ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ7ÿBÿVÿjÿyÿÿÿ
ÿÿyÿeÿFÿ,ÿ%ÿ'ÿ%ÿ'ÿ)ÿ*ÿ0ÿ6ÿ;ÿDÿSÿbÿmÿhÿkÿ]ÿdÿxÿqÿxÿyÿxÿyÿrÿsÿrÿwÿÿÿ
ÿ
ÿÿÿÿÿ
ÿ
ÿÿÿ}ÿÿÿ~ÿxÿpÿgÿ[ÿQÿOÿLÿLÿGÿGÿEÿCÿCÿCÿAÿzÿÿÿÿÿÿÿYÿ.ÿ5ÿAÿ`ÿVÿCÿHÿMÿJÿHÿEÿBÿCÿCÿBÿAÿ@ÿ?ÿ^ÿnÿ^ÿBÿXÿlÿgÿ_ÿXÿSÿRÿNÿOÿMÿKÿGÿHÿMÿMÿKÿMÿLÿTÿVÿWÿVÿXÿZÿoÿÿÿÿoÿUÿBÿ8ÿ;ÿ:ÿ5ÿ7ÿ6ÿ4ÿ2ÿ1ÿ.ÿ-ÿ-ÿDÿjÿ|ÿÿÿÿeÿTÿNÿJÿDÿAÿIÿJÿ/ÿ)ÿ,ÿ/ÿ3ÿ2ÿ<ÿ:ÿ,ÿ*ÿ1ÿ9ÿ,ÿ'ÿ4ÿ?ÿ9ÿ4ÿ3ÿ4ÿ3ÿ4ÿ1ÿ3ÿ4ÿ0ÿ0ÿ3ÿ3ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ1ÿ4ÿ9ÿ<ÿ:ÿ=ÿCÿLÿDÿ/ÿ)ÿ.ÿ3ÿ7ÿ5ÿBÿMÿ;ÿ?ÿUÿXÿQÿSÿVÿ`ÿmÿZÿ@ÿDÿPÿbÿ]ÿKÿDÿ@ÿBÿ[ÿnÿjÿnÿpÿmÿpÿvÿwÿeÿEÿbÿ¨ÿÿKÿdÿ]ÿLÿBÿJÿQÿ`ÿhÿhÿiÿgÿbÿ\ÿSÿHÿ:ÿ1ÿ+ÿ)ÿ*ÿ.ÿ3ÿ=ÿGÿOÿYÿ`ÿdÿfÿgÿgÿfÿdÿbÿaÿ`ÿ_ÿ^ÿZÿWÿQÿJÿBÿ:ÿ3ÿ2ÿ2ÿ3ÿ3ÿ7ÿ;ÿ;ÿ<ÿ<ÿ@ÿEÿMÿTÿ\ÿeÿmÿsÿwÿyÿyÿ{ÿ|ÿ|ÿ}ÿ{ÿxÿxÿxÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ~ÿ{ÿyÿwÿvÿtÿsÿvÿvÿvÿvÿuÿuÿsÿmÿhÿbÿZÿQÿNÿOÿSÿYÿ`ÿdÿiÿpÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿrÿhÿ^ÿZÿZÿ^ÿeÿkÿrÿvÿuÿsÿqÿoÿpÿnÿoÿnÿnÿnÿnÿnÿmÿmÿmÿmÿnÿnÿrÿrÿzÿÿÿÿÿÿÿÿÿÿÿÿ#~$y&~'y%~ z~z{{||{{{ {!"y#$y%~%y&~'y(~)z)~)z*}*z*},z.}.z-}.y-|-z.|.z-|.z/|/{/|.z,|,y-|-y,|,w+|+w+|+u+|*x,}+x,}*y*|+x-}/t4;q?DnIMjQ
VgY\d]_a`b_^]]UN^L~Bb>{7g3y1l2y2m3y3n3y4n3y3o3y3o2y2p1y1p1y1p/y/p.y.p,z,p-{*r*{)t'{'u&|&w&}%w$~#y&%z%%y%)t+/q3
9n;@jEHgNQeVWaX_]fj[mn[quYxzZz
y[xw^urapkckgge~bk^{YnVzQrMxFuAw;{7u44v46w57y8~7y88y66w55w44v44v44v43v21w10w00x02{3};LwbuphereY=j)~%p$~%w%(u.1x:GzS}[{^{Ya{cf~o~qqwkisopru~pm~k||ix
yh}
ffhlmj{lh[LhA>p>~Bw>~<{9};}X}|{|q4}/=zX[xIKwMKwKTtFDuCEt@>v:_{|jgx`excZ{SP}LP|OMzGHxJLtMRsMMu[VuKQsTdollm]QrE;w22w47v64v1.w,*v-Hte|vzze~V
]WQSwODu::~249~?<007:+(~+4}46|44|43~8:EOD=7542/--0.0~38}:?~>E>5/1:~?~@~?8~5EURY~W}_~m~j}[L~N}LZymSx]e|tu|lszwtwtty{v|UcyO{DS}[uTzAqHvWrfxgqghnhcn[RoG9q2-u+,x2}7y@|JvS|Zta}eqg~gngfmeclbam_^m[XmTOkJDk@?nBGoHMnOPlPPmSSmX]ocipq~vuz~zw{}{{{|{zzxyzyyy}xyy{{zyyyyx}ywwwxuuyu|wzw{vzv~v{v~r{l~g~_|X~NyL|OwTzYv`yetlzssxzy{}z{~|}{}{{}y{pdtYRpU~[xcxl~tyw{t|ssp}nom~opo~npnnqnlqk}krk}krn}oqo}ts{zvyz}z~z~|~|#ÿ$ÿ&ÿ'ÿ&ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ"ÿ#ÿ$ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ,ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ)ÿ)ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ.ÿ3ÿ6ÿ<ÿ@ÿDÿGÿMÿPÿVÿXÿ\ÿ_ÿ`ÿaÿ`ÿ_ÿZÿRÿKÿEÿ=ÿ5ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ$ÿ%ÿ$ÿ%ÿ(ÿ*ÿ+ÿ/ÿ4ÿ9ÿ?ÿDÿHÿLÿRÿVÿYÿ\ÿ`ÿdÿeÿiÿpÿtÿvÿwÿxÿyÿzÿyÿyÿxÿtÿpÿnÿiÿgÿdÿ`ÿ[ÿWÿQÿNÿJÿHÿCÿ>ÿ8ÿ4ÿ3ÿ1ÿ1ÿ1ÿ2ÿ4ÿ4ÿ5ÿ5ÿ7ÿ6ÿ6ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ/ÿ/ÿ-ÿ/ÿ0ÿ1ÿ5ÿCÿXÿlÿ~ÿÿÿÿÿ|ÿjÿQÿ;ÿ&ÿ%ÿ)ÿ4ÿ5ÿ.ÿ0ÿ<ÿKÿNÿGÿGÿGÿOÿNÿNÿRÿMÿQÿTÿWÿVÿUÿZÿhÿrÿuÿyÿvÿvÿuÿwÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿrÿ[ÿHÿ>ÿ8ÿ8ÿ9ÿ7ÿ5ÿ9ÿ~ÿÿÿÿÿÿÿQÿ=ÿ?ÿQÿ_ÿPÿMÿNÿLÿIÿJÿFÿCÿDÿBÿ@ÿ>ÿ7ÿUÿÿÿÿvÿgÿVÿZÿ`ÿYÿPÿMÿRÿNÿHÿEÿEÿJÿHÿKÿNÿNÿNÿYÿYÿSÿHÿJÿSÿÿÿÿqÿ]ÿIÿCÿ4ÿ3ÿ7ÿ8ÿ9ÿ9ÿ5ÿ2ÿ-ÿ*ÿ*ÿ*ÿ0ÿJÿ`ÿ|ÿÿÿÿnÿaÿdÿcÿsÿkÿ\ÿXÿSÿMÿDÿ7ÿ3ÿ8ÿDÿHÿIÿ@ÿ7ÿ+ÿ/ÿ3ÿ5ÿ5ÿ3ÿ2ÿ6ÿGÿLÿIÿKÿFÿ6ÿ7ÿ:ÿ8ÿ6ÿ4ÿ:ÿAÿ=ÿ8ÿ/ÿ+ÿ-ÿ7ÿ>ÿAÿMÿ]ÿAÿ2ÿ?ÿCÿHÿBÿHÿGÿ=ÿGÿMÿQÿRÿUÿ^ÿgÿdÿ]ÿUÿVÿUÿ`ÿeÿgÿIÿWÿoÿrÿpÿmÿoÿÿ~ÿhÿiÿbÿ[ÿ\ÿ[ÿYÿMÿDÿIÿ[ÿ`ÿHÿ;ÿFÿOÿfÿfÿiÿjÿfÿaÿ]ÿSÿJÿ<ÿ2ÿ-ÿ,ÿ/ÿ3ÿ8ÿBÿKÿSÿZÿaÿeÿgÿgÿgÿgÿeÿcÿdÿcÿdÿdÿeÿeÿbÿ_ÿ^ÿYÿUÿXÿXÿ[ÿ`ÿeÿhÿiÿlÿlÿnÿnÿpÿpÿsÿtÿwÿzÿ|ÿ|ÿ{ÿzÿzÿzÿxÿwÿxÿxÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿyÿwÿuÿvÿvÿwÿwÿvÿvÿvÿvÿrÿlÿgÿ_ÿWÿMÿKÿNÿTÿYÿaÿfÿnÿuÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿkÿ^ÿSÿMÿQÿYÿdÿmÿsÿvÿsÿrÿpÿnÿoÿqÿoÿnÿnÿnÿnÿlÿkÿkÿkÿkÿkÿmÿnÿsÿ|ÿ
ÿÿÿÿÿÿÿÿÿÿÿ!~#z&~'z&~#y ~y~z~z~{~{{y~z ~ z"#z$%z&~'y'~(z'|(z*|+y*|*x*|,{-|/{-|-{-|-{.{.{.|.z0|0z.|,{,|,{*|+y+|*y)|)x*|*x)})w)}*w*}*x)}+x+}+w+},w.2v59r=@nEJjQ
Vf[]b__``a_\U_N}Gb;z5g2y2k2x2m2x2o3x3n2x2o2w1p0w0p0y0o.y.p.y.r-y-t-z+t*z*u({(w(|'v'&s&'s)-q/3n8
;kBHfMQdT[`_b[fiYkpWstXwy[{{]z
y^xu`roblied~`h[}XlR|LqH{Du>y;x9x5{2x1~1x10x00x00x03x33x33x32w22w22w42w22w22w21w0.x--y--{.4~<yLeszkeguejO9k)6qSUsJ}GzTv\ZuQ
PwQSxTYx_PwJFzEF|G
?}FIL~U^zgps{l
d
dd~_x]ywbjl{naHn93r46v7H{}}jK|GW~c}UOxSMvEFvCBvBBt<7wFrxwyrmZpTXuZVxPIyFEzFGwIGuHHsIKtOPwIFwJQphheAp52v>Cy>;x72y0)y()y**x5Lwdy{
}~wt~uvrcobXlLKoLBv<MNF
>}7;{:7|54~27}<<:>??;
:758DEK;.112>~JOi
qO/=RNLT~Q}R_~sZ{LRzZgzQ]|R;~=}\}fyYOxT`u\|^zZ{Ybz^V{P|Q}ID|@L|J@}EN~Ax<|Ht:z1r^|krkjphbp\TqI
;r1-v-/z5~<yE}MtT}\pd~gnigmhfmfhmjljnqkrqktskppnp
tmy
zm}onooortuy~}{|}{zv|u|yz}~yyz{{yxxxxw|zwxwxwwzwx{xwzwuztr|le^}T~MzJ{LzSy\ycyivqzxv}z{y{{~{~{~{~{|~v|gWwMIvOzY~bvltyuzt|rrp~npnnpnnpnnpmkqj~ise}fti|ktnwuuw{y|}{~{~{~{ ÿ"ÿ$ÿ%ÿ%ÿ"ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ!ÿ"ÿ#ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ,ÿ-ÿ.ÿ.ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ-ÿ2ÿ5ÿ7ÿ=ÿBÿGÿKÿRÿYÿ\ÿ_ÿbÿbÿaÿ]ÿTÿLÿFÿ;ÿ5ÿ2ÿ2ÿ2ÿ1ÿ2ÿ3ÿ3ÿ2ÿ1ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ+ÿ+ÿ.ÿ1ÿ4ÿ:ÿ>ÿEÿIÿNÿUÿXÿ]ÿbÿhÿjÿmÿpÿsÿtÿvÿxÿzÿzÿ{ÿyÿxÿuÿtÿqÿoÿjÿfÿaÿ^ÿXÿRÿOÿIÿCÿ=ÿ:ÿ6ÿ2ÿ3ÿ2ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ3ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ-ÿ.ÿ1ÿ6ÿEÿZÿpÿÿÿÿÿÿÿyÿiÿOÿ:ÿCÿXÿWÿQÿRÿKÿ]ÿmÿpÿbÿeÿhÿ]ÿeÿ`ÿYÿdÿcÿRÿKÿEÿ>ÿDÿDÿEÿFÿGÿDÿIÿ\ÿmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿYÿAÿ7ÿ<ÿGÿLÿhÿÿÿÿÿÿ}ÿrÿnÿnÿgÿXÿLÿOÿMÿDÿDÿBÿ?ÿAÿ?ÿ<ÿ;ÿbÿÿÿÿÿ~ÿsÿiÿ^ÿXÿUÿQÿMÿHÿDÿFÿIÿJÿJÿHÿGÿFÿIÿLÿEÿFÿEÿGÿMÿMÿ~ÿÿÿyÿmÿFÿ<ÿ7ÿBÿGÿDÿ@ÿ8ÿ0ÿ(ÿ#ÿ#ÿ%ÿ'ÿ)ÿ*ÿ8ÿPÿ`ÿxÿÿÿÿÿuÿjÿuÿiÿqÿxÿdÿiÿkÿYÿJÿWÿ^ÿQÿCÿ<ÿ9ÿ7ÿ8ÿ4ÿ0ÿ/ÿ0ÿ2ÿ5ÿ6ÿ6ÿ8ÿ:ÿ8ÿ9ÿGÿOÿGÿ9ÿCÿCÿ7ÿ*ÿ*ÿ+ÿ1ÿNÿhÿkÿmÿdÿ_ÿHÿBÿGÿCÿTÿPÿOÿSÿ\ÿmÿcÿLÿ]ÿfÿZÿMÿGÿ9ÿCÿNÿjÿ[ÿCÿcÿiÿeÿXÿIÿIÿCÿ?ÿ?ÿEÿLÿFÿ7ÿ7ÿ?ÿMÿYÿRÿMÿGÿKÿQÿ[ÿ4ÿOÿgÿkÿiÿjÿhÿbÿ\ÿTÿHÿ>ÿ5ÿ1ÿ1ÿ3ÿ9ÿAÿHÿPÿXÿ_ÿcÿfÿiÿjÿlÿnÿpÿqÿsÿvÿzÿ}ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿzÿxÿwÿwÿwÿwÿxÿxÿwÿwÿuÿtÿqÿkÿeÿ^ÿSÿKÿIÿNÿSÿ\ÿdÿkÿrÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿrÿcÿQÿIÿGÿPÿZÿeÿoÿtÿuÿtÿrÿpÿnÿnÿnÿnÿnÿnÿnÿmÿkÿiÿgÿbÿdÿfÿhÿnÿvÿÿÿÿÿÿÿÿÿÿÿÿÿ~z"~$z$~"{~{~|~|~|~||z~ {!~!{"}#z$}&z'~'z'~({)|*{)|)z+|+y-|-{-|-{-|-{-|-{.{.{.|.{.|-{-|,z,|,z*|+z+|*z*|*z)|)y(}(z)})z)}*y*}*y*}*y)})y*}*w+}+u/}/t48q<BnGNjU[dad`dd^]U_M}Ec9z3g3z1l1z2m2z2n2z0o0y0o1y1o/y/o.y.p.y.q-y-r-z*s+z*t)|(t)~*s,/l58i<
BdGMaPT^[_ZdhYmpWrtWwyWx
xYxwZvt^rpcmkhgbj^}YmT|OpJ{Fu>z;z5x1{1y0}0y0.y./y//y//y//y/.y/1y11y11y12x21x11x32x11x1/x..x.-y,+y+,{./}2|<Pug|pifxkrwow|s~}uuf|Qz?x?Pucusy\rOWsX_unwvtavICxCCzDF|CA|C
D|GK}\}q}~z~}}s
jfgjkhhyic`jn
yk
rz~|tuq}XLyMIuGCvA?v=EtLVrqrnxmkb]n]ZsRIwHG{HHzIHwFEtFHtE<t=EuSYoletmciQj@>sBFwDBx=3z&!{ !{$'z*+y>Qv`qx{{tnziu~~ztvwpe|b}cZ{\Gz;<z;9}61345|36{95~85D;;?|;7|;6|5-~/}9^}pq}eV~]U~TO}OD{LSzV]zWS{N\{KE|PH~BZ~L^{m]z_}`zV|M{+}.|95|36|74|=~M|Q|cl{_U|GDyO}evc~ishiskkrgcr^VsI
?u83x54z;FzK~TuY~brg~nlprkvvjz}jjklmnmlllkmmmlnsuwxz|{z{{yxxy
xw|{wyxxxwywwzwwzwuztr|kd\R~I{H{M{Sy\zexlzrxzyy~yz{~{~{{{|ymy^MuE~GxSy]hvqtyu|s|rtp~nqnnpnnpnoqmkrh~euc}cud{hzowx~vy}}z~z~{~{ÿÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ"ÿ#ÿ$ÿ&ÿ'ÿ'ÿ'ÿ(ÿ)ÿ*ÿ)ÿ)ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ0ÿ4ÿ:ÿ@ÿGÿOÿUÿ[ÿbÿeÿiÿfÿ]ÿVÿJÿBÿ8ÿ3ÿ2ÿ2ÿ1ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ,ÿ1ÿ4ÿ8ÿ?ÿCÿGÿNÿTÿ[ÿ\ÿbÿhÿkÿoÿrÿuÿwÿyÿyÿxÿvÿuÿsÿqÿpÿlÿkÿiÿgÿbÿ^ÿYÿTÿOÿKÿEÿAÿ:ÿ6ÿ2ÿ/ÿ-ÿ-ÿ.ÿ/ÿ-ÿ-ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ.ÿ.ÿ,ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ-ÿ/ÿ7ÿEÿ[ÿrÿÿÿÿÿtÿqÿÿÿÿÿÿÿ~ÿ{ÿqÿXÿMÿQÿdÿZÿ`ÿQÿJÿPÿeÿlÿeÿ~ÿ|ÿ\ÿQÿHÿEÿDÿDÿGÿFÿCÿAÿEÿFÿEÿGÿIÿVÿ_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿ|ÿ~ÿÿ
ÿXÿhÿXÿJÿLÿKÿGÿCÿ@ÿAÿ=ÿ=ÿCÿoÿÿ¡ÿ§ÿÿÿÿ}ÿuÿkÿgÿdÿcÿaÿaÿMÿHÿ]ÿfÿ`ÿWÿIÿFÿEÿEÿCÿ9ÿ2ÿ9ÿHÿ\ÿhÿÿxÿ_ÿ`ÿXÿGÿDÿCÿEÿBÿ?ÿ>ÿ8ÿ-ÿ"ÿ!ÿ#ÿ'ÿ*ÿ+ÿ,ÿ/ÿ:ÿMÿ\ÿjÿwÿÿÿÿxÿwÿ}ÿ~ÿÿzÿuÿjÿ^ÿOÿUÿHÿ<ÿDÿHÿOÿXÿTÿ=ÿ:ÿ@ÿ<ÿ6ÿ7ÿBÿ4ÿ2ÿ>ÿ?ÿCÿFÿZÿUÿ<ÿ>ÿ3ÿ1ÿ5ÿ4ÿ7ÿUÿiÿdÿ\ÿVÿ\ÿ[ÿQÿ@ÿ;ÿCÿaÿjÿhÿ^ÿ\ÿZÿUÿFÿDÿLÿ?ÿAÿMÿXÿHÿVÿhÿ^ÿ\ÿNÿEÿKÿ?ÿGÿ;ÿ?ÿ6ÿ?ÿCÿHÿ\ÿeÿhÿÿxÿoÿNÿLÿ=ÿAÿeÿfÿfÿiÿjÿkÿkÿgÿcÿ^ÿVÿLÿAÿ:ÿ6ÿ7ÿ9ÿ?ÿFÿOÿWÿ`ÿhÿoÿtÿwÿ|ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿ{ÿyÿxÿxÿwÿwÿwÿwÿwÿwÿuÿtÿpÿjÿaÿYÿPÿHÿGÿLÿSÿ\ÿeÿlÿtÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿiÿVÿIÿEÿJÿTÿ^ÿjÿrÿtÿuÿsÿrÿpÿnÿnÿnÿnÿnÿnÿoÿmÿkÿhÿeÿbÿbÿcÿhÿqÿyÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ~y~y~{~{~{~{~{~{zz~!{!~!{"~#{$~%{'~'z(~)z(|)|*|*|,|,{,|-{.|.z,|.z.{.z.{.z.{.|.{-|,|+{*|*{*|+|+|*{*|*{)|){)})z(}(z(}(z(}(z(~){(~({)}){*}'{)})x+}+v-2t9?qGNlU_fdgaj
d_\R_K~Bc:|5i5{4l3{1m2z0n0z0o1z0p.y.o/y/q.y-s-z-s.|-u,},t.1o68j?FdJO^T[Z_dXilWmpVsuVwvXvt[ro^mi_heba`h]YnU|PrL{FuBz;y7z1{0{.~-z--z-.z--z-.z--y--y-.z-.z./z/.z/0z00z0/y/0y01y21y12y2/y.+{+,{+*z*){)){,|0:yNdrxozr|u}
|}}zzwlXo]nlbVoOetuw|qn`sOEyAFyFE{HE{FHzJL}JA~?Snrioma\aqhlqy{~yx{qqm
]{HzJKwEAw?;u::uJ}p£k§lekn~lunmlkpjiwi}}xxu\Ft>7t13sKeokrineha]lMHsDDwA?y?={2(|%'|),{,,{,-x:Lw]gwpvw{txuoaQbVN|WUzXU{X>~5EC{EBxD1z6A}:F~OK~FC0.;9,~4J{Z
S|N
P|\
U}F={FKxTLuQIvL
QxUGzEG{JM}VW{HIxXZxM
@xEO{I~G|8~?~C}8}9|DXynyy}yysRy4=zQ~hzggwgitjlthbs^WtNEx=8};<~CLyU_sgqoy}mnnkkklnprrponlkijjijorw|}{zyyyww
xx|{wzwxwwywwzwwywuzso|g`Y~O~GzF|L{Tx]{fwm{vw~zw~y{{{zz|}whxTHsE~LwWz`kvruxu{r|rqp~opmmqnnrnosmjsgdta~awc{h|qv}ux|}y~{~{~{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ ÿ!ÿ!ÿ$ÿ#ÿ$ÿ%ÿ'ÿ'ÿ(ÿ)ÿ(ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ+ÿ+ÿ*ÿ+ÿ0ÿ6ÿ>ÿEÿPÿYÿ_ÿgÿkÿiÿcÿ[ÿSÿMÿEÿAÿ=ÿ8ÿ7ÿ6ÿ5ÿ2ÿ1ÿ0ÿ1ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ+ÿ-ÿ/ÿ1ÿ3ÿ7ÿ<ÿCÿIÿOÿUÿ\ÿaÿeÿjÿmÿpÿsÿsÿsÿsÿtÿrÿpÿoÿmÿjÿeÿbÿ^ÿZÿWÿSÿRÿNÿKÿEÿAÿ;ÿ6ÿ4ÿ1ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ*ÿ2ÿ@ÿTÿeÿoÿsÿÿÿÿÿ
ÿÿyÿvÿvÿ
ÿ
ÿ~ÿÿ~ÿ\ÿRÿkÿsÿnÿfÿ]ÿoÿÿÿ|ÿuÿmÿgÿ[ÿWÿPÿEÿEÿJÿRÿKÿEÿDÿCÿEÿIÿKÿGÿGÿJÿ^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿrÿwÿÿÿÿÿÿÿzÿxÿyÿÿÿÿÿcÿGÿIÿIÿEÿBÿCÿOÿGÿ8ÿQÿÿÿ¦ÿ©ÿ£ÿ^ÿ,ÿXÿ{ÿÿyÿxÿvÿzÿzÿÿÿÿ¡ÿÿÿÿyÿdÿMÿ>ÿ6ÿ/ÿ8ÿSÿeÿlÿsÿnÿgÿaÿ^ÿUÿKÿAÿ?ÿAÿ?ÿ@ÿ>ÿ8ÿ1ÿ*ÿ.ÿ/ÿ/ÿ/ÿ-ÿ+ÿ*ÿ+ÿ:ÿPÿ[ÿgÿjÿlÿÿÿ¡ÿÿÿÿÿÿ{ÿkÿcÿeÿeÿbÿXÿTÿDÿVÿMÿ<ÿ>ÿBÿ?ÿIÿAÿ0ÿ=ÿ;ÿAÿFÿ?ÿGÿNÿ:ÿ.ÿ?ÿVÿDÿ-ÿ5ÿIÿTÿ[ÿJÿAÿJÿ<ÿ<ÿLÿLÿHÿPÿ5ÿ:ÿ<ÿ@ÿFÿWÿVÿFÿEÿEÿOÿNÿ\ÿfÿ_ÿMÿ8ÿ5ÿGÿYÿKÿSÿLÿ6ÿ7ÿRÿYÿ?ÿDÿJÿfÿpÿ_ÿjÿrÿOÿCÿWÿcÿfÿgÿhÿhÿjÿjÿlÿhÿdÿ_ÿXÿOÿFÿ>ÿ<ÿ>ÿBÿMÿVÿ_ÿiÿsÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿyÿyÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿ{ÿzÿxÿwÿwÿwÿwÿwÿwÿwÿuÿsÿoÿgÿ_ÿUÿJÿFÿEÿKÿRÿ[ÿcÿlÿwÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿfÿSÿIÿJÿRÿ[ÿeÿmÿrÿsÿsÿqÿpÿpÿoÿmÿmÿlÿlÿoÿnÿmÿjÿgÿdÿ`ÿ`ÿcÿiÿsÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~|~|~{~{~{~{~{~{zz!~!z"~"z$~$z%~&z&~'|)~*|(|*|*|,},|,|,|-|,|-|.|.|.{.|.{.|.{.|,{+|+|+{+|+{*}*{)}){)})|)})|)}){'}'{(}({'}'{'~'{'~'{)~){(~){)~(|)~)z))x,1v9@qIRk\ddikai
b_\W^QK`GCc>=e;9f5}3g3}1h0|0l.|.o.|-p,}-p/~/o47l:?hGMbTZ]`c[gm[pr\r
t^sq^ql^jhceag\~XkT}OoL|HrE|Au=|9y5|1{0{.~-{.+|+,|++|++|++{++{+,{,-{-,{,,z,-z--{--{-.z..z..y..y..y//y./{/.{.,{+*{*){))|))(*+{5FwZfw|~~~qbph
ioWyXerX\lggid_n_qrrtnlgpbZuMDxFJ|PJ|EE|CD~GIKLLOxwttj`]
u`}iys|{~w}vunqtD}LKyIIwC?w>7uZn¥mª¥i+o4Mprn}ny{rv¡ smtm]Hp;4t/9uMYsbgohcm[VpTOtA>w??yAB{>8|13|31{0/{-+y*+x5Lw_hvdkst¢¤x¤¤¢~`VhqfUO}a
VE<HW>y2<y:D{OR{K>z/&}4GWE2};
G|Uc{VC{ED}5;}F[|S<}73z7:yEDzHB{1:{AFxa
b{8<~Y_X{L~[yPKySnzh`yc`{jn}NV|_]z`fwegxefvijwkkuheuaXuQ
HxBC|HNzYbskupoljjkknrxtrlvfbw``ybdygkyoryx}{v||t~~s~s
~
sokjlntz~~|zyyy
y
x
w|
{wzxxwxyxwzwwywuzrm|f\P|GD{D|J|PxZ~cwm}wvwyz{{zz|}}uftVOoPWv_|g~oxst{ryp|ntp~osnnrnnsnntmjugeua~_xdyj}tuv~y|}{~|~|~|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ!ÿ"ÿ"ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ)ÿ*ÿ+ÿ(ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ.ÿ3ÿ:ÿBÿLÿTÿ^ÿdÿhÿjÿhÿcÿ^ÿXÿVÿRÿNÿKÿFÿCÿ?ÿ=ÿ:ÿ8ÿ6ÿ3ÿ2ÿ/ÿ.ÿ.ÿ.ÿ/ÿ0ÿ3ÿ6ÿ9ÿ>ÿCÿHÿPÿVÿ]ÿaÿgÿjÿmÿpÿtÿrÿpÿnÿlÿjÿfÿbÿ^ÿ[ÿUÿSÿNÿKÿFÿAÿ=ÿ9ÿ3ÿ1ÿ1ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ+ÿ5ÿMÿpÿzÿyÿuÿqÿmÿiÿiÿ^ÿ\ÿvÿsÿoÿ}ÿwÿtÿfÿZÿ]ÿbÿTÿKÿTÿbÿuÿÿÿÿuÿiÿmÿmÿgÿVÿDÿAÿIÿHÿJÿGÿDÿEÿEÿFÿFÿHÿIÿLÿMÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿvÿvÿÿÿÿÿzÿKÿGÿHÿFÿFÿEÿAÿ>ÿ5ÿcÿÿ¡ÿ§ÿ§ÿ¥ÿÿWÿ2ÿ=ÿ?ÿFÿIÿ>ÿAÿjÿÿÿÿÿ
ÿÿuÿfÿZÿSÿ;ÿ1ÿ1ÿ>ÿLÿXÿZÿ]ÿ^ÿ`ÿXÿTÿPÿMÿCÿ=ÿ>ÿBÿDÿGÿBÿ?ÿ9ÿ6ÿ6ÿ4ÿ3ÿ1ÿ.ÿ-ÿ)ÿ*ÿ+ÿ5ÿRÿbÿ_ÿ]ÿnÿÿÿÿÿÿÿÿÿxÿiÿxÿsÿhÿ`ÿ[ÿWÿZÿKÿGÿVÿ`ÿ=ÿ9ÿ4ÿ;ÿAÿGÿFÿHÿ8ÿ9ÿ>ÿHÿ<ÿEÿ>ÿ9ÿ<ÿHÿQÿYÿNÿ<ÿ;ÿ_ÿCÿ=ÿFÿZÿ^ÿEÿ1ÿ.ÿ6ÿ6ÿ<ÿHÿEÿ@ÿ=ÿEÿHÿHÿTÿ^ÿeÿnÿkÿgÿeÿiÿeÿiÿmÿlÿfÿPÿUÿiÿ`ÿfÿlÿ`ÿeÿkÿiÿiÿeÿcÿfÿgÿgÿgÿkÿkÿkÿjÿfÿaÿZÿSÿLÿKÿNÿRÿZÿdÿoÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿrÿjÿ`ÿXÿRÿJÿFÿEÿEÿGÿKÿLÿRÿ[ÿ_ÿgÿlÿnÿsÿvÿzÿ}ÿ}ÿ~ÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿ{ÿzÿxÿwÿxÿxÿwÿwÿwÿwÿuÿrÿnÿeÿ[ÿOÿEÿAÿAÿGÿQÿ[ÿeÿoÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿhÿ\ÿRÿTÿZÿcÿlÿsÿtÿtÿrÿqÿpÿpÿoÿnÿnÿnÿnÿnÿnÿmÿjÿgÿeÿbÿaÿdÿlÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~{~{}{}{||||~{~ {!~!{"~${%~&z&~'z&}'{(}){*|*|+|+|-|.|-|-|-|-{,|-}.{.}.{.}.{.|.{-|,|,}+|+}+}(|*}*|)}(|(}(|(}({(}({'~'|(~(|'~'|&~&|&&z((z((|&&|'){**z/4t;DoMWk\dghhdgd`d`_[Y^WR^QM_IE`CA`:6d5~2g2~2i37h:>eDHcNT_Y_]ehZk
m[no]nm`jfba~^gX|UiP}LmJ{Er>{;v7{3y0{/{.|-~,|,+}+,}-+|+*}*+|++|++|++|++{++{++{++{++{++{+,{,,{,,{,,{,-z--z--z--z-,{,,{,,{+*{*)|))~)((~+0za}|oeivodi^f{zym]|f`s^|mucjdllt|mrv
u
yxud{UK}LL}GD}EDFFJMMJi|ww[{r
slfgu}{y|utxpt\DIzHHxCAv<:rrm£¨i©¦gtl0<q;9t74t/Pr~omylqemXPqG5u:BvLRtRTqVXoUOrKHuD<w9AxJKzFD{@;}97{65z1.y+.y17x@Vv^[tbkpt~pty
}~
uiU}PUT|Udzd
LzB7z52x2;x><z?@}4779<;:}=A}:
6~0-}:;}>7~gZ~3-}0>yNAx6<{JP{RL|cx|wrpznkyjiyki{i`zVh{s`}Zq~q~m}ih{fexffyghxjmwmlvjisdat\YsY\tcjtu~qoklloqo~tpi`rUKvA>z;<};;~:<>B~JO}W~a{g~oys}vyx|zyz{{{}{z}t~
n~ihkq{|yyxxv~v}{wywzwxyxwyww{vv|qk}dZQ}FA{B|H|Rx\~hwqyvv
xzzzzz{}zvjq^YnZ_tgn}u{v}t|ryp}pvp~nunntnntn~nvmjvf~dvb|azbyn~{vw~{|{|}|}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ"ÿ$ÿ%ÿ&ÿ&ÿ'ÿ&ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ(ÿ*ÿ*ÿ+ÿ/ÿ3ÿ<ÿGÿNÿVÿ]ÿbÿdÿfÿeÿdÿcÿcÿ`ÿ^ÿYÿWÿSÿSÿPÿMÿJÿEÿAÿ;ÿ9ÿ8ÿ8ÿ=ÿ=ÿAÿFÿKÿQÿWÿ]ÿ`ÿbÿhÿjÿkÿiÿhÿhÿeÿaÿ\ÿYÿTÿPÿJÿFÿ>ÿ:ÿ7ÿ3ÿ1ÿ/ÿ/ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ-ÿ3ÿGÿÿzÿqÿzÿwÿtÿwÿiÿiÿkÿgÿjÿuÿ`ÿMÿKÿ[ÿ\ÿeÿgÿeÿbÿdÿ_ÿgÿ{ÿ}ÿÿÿÿÿ£ÿ£ÿÿÿÿÿxÿUÿCÿHÿHÿEÿGÿHÿHÿIÿHÿIÿIÿGÿ`ÿÿÿÿÿÿÿÿ/ÿ9ÿOÿcÿqÿÿÿÿÿÿÿÿyÿwÿuÿwÿÿÿÿÿÿhÿ@ÿGÿFÿEÿAÿ@ÿ=ÿEÿyÿÿ¢ÿ¥ÿªÿ¦ÿÿÿ1ÿ9ÿ;ÿ;ÿ;ÿ9ÿ4ÿ9ÿfÿyÿ{ÿzÿvÿqÿgÿ_ÿXÿPÿLÿGÿGÿHÿIÿJÿNÿPÿNÿPÿRÿNÿJÿIÿFÿ=ÿ4ÿ@ÿNÿNÿKÿEÿCÿAÿ?ÿ:ÿ7ÿ6ÿ4ÿ2ÿ2ÿ4ÿ7ÿ:ÿ7ÿCÿWÿ]ÿ_ÿdÿlÿpÿqÿsÿuÿsÿvÿwÿrÿjÿhÿjÿfÿVÿIÿMÿUÿZÿQÿVÿXÿNÿ1ÿ2ÿ,ÿ9ÿEÿ?ÿ<ÿIÿJÿ2ÿ9ÿ<ÿ8ÿ8ÿ1ÿ/ÿ9ÿKÿDÿ0ÿ0ÿ7ÿ9ÿ4ÿ<ÿ9ÿ=ÿQÿ+ÿ,ÿ1ÿ6ÿ9ÿ2ÿHÿeÿmÿRÿPÿJÿqÿzÿvÿrÿoÿnÿkÿkÿjÿjÿkÿnÿmÿqÿqÿuÿ]ÿJÿmÿrÿnÿiÿgÿfÿfÿfÿfÿgÿhÿiÿkÿkÿlÿmÿmÿlÿjÿiÿjÿkÿpÿvÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿmÿeÿ]ÿVÿOÿGÿAÿ>ÿ=ÿ;ÿ<ÿ<ÿ<ÿ>ÿ?ÿBÿFÿMÿVÿ]ÿfÿmÿsÿvÿwÿwÿvÿwÿyÿ|ÿ|ÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ{ÿyÿwÿxÿxÿxÿwÿwÿvÿuÿtÿnÿjÿcÿ[ÿPÿEÿ@ÿCÿHÿQÿ\ÿhÿqÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿjÿ_ÿ[ÿ]ÿdÿkÿqÿuÿwÿuÿpÿpÿpÿnÿnÿnÿnÿnÿnÿnÿnÿmÿjÿfÿdÿbÿaÿeÿpÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~{~{||{{||{!{#~#{$~${$~&z%~&z(}'{(}){+|+|,|,|,|,|,|-|.|.{.|.}.{.}.{.}-{-}-{,},|+}*|*}*}*|)})|)})|)})|(~({(~({'~'|(~(|(~(|'~&|((|''|((|''|''|(*{)+y05t=EpLRlX^hacedc`ed^aa^^]\[[\X
T\QL^HEaECbDI`M
P_V[__b^dg_ih_gdba^f[SjO|JoEz@t:z6x1|-|-|,|,}+~+}++}+,},,},,},,},,|,+}+*|**|*+|+*|**|**|**|**|**|**|*+|+,|,,|,-|-,{,-{--{-,{,+|+*|*+|+,|,)|+*~*.7}Qd~x
df
p
pffib~OM|LNzMZtgml_ThOLi^hkpjr|¥¨¡~~xU@IFHIGIJHHHT{|~@;~;
<}Ig}}vz{|uv|wxx
nnzBA|FJyC>v>Vo~h£d¦¦e}l.9r=<v<:v54tJgrklojfo_VoTPqNKrJKuJLuNOtNNsNNtMKuGAw5AwPRwPIzFF{E
?~<
9|6
:z;;w87v99xLXx]`wejujputvvxpwZGxFHwJIwDD|G~QJyFH{G>y;Cu26s<?u<9y9E~C=9:};By<<x<3{5<1~7;}?`}SK}?
<{<Fz[e~bSOY{x~u{rnzkjyji{ijzmn{qr|sm~k~tp~k|igzffyggxgixhkxprutvruvoxzn}ppoor
st~{txrtmhsb\tYRxME|?;:<==>?ACIRYbhpt~wv}vv|wyzzzy{|{~~{w|n~hhpz~zvww
v~v|zvx
xxxxxx
vyvwzvs|oj}cXN~E?~B|HRx]kut}vvxzzzzz{}zwho__kbjrqv|w|w|t}rwq}pun~nsnounnuo~ovmjvf~eva|czfyr~vx~}|}{}|}}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ#ÿ$ÿ$ÿ$ÿ&ÿ'ÿ(ÿ(ÿ'ÿ(ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ'ÿ*ÿ,ÿ4ÿ;ÿ@ÿGÿNÿRÿVÿYÿ\ÿ^ÿ^ÿ_ÿaÿaÿ`ÿaÿ_ÿ_ÿ^ÿ\ÿZÿVÿSÿQÿPÿPÿPÿTÿWÿZÿ^ÿ_ÿaÿcÿgÿfÿfÿbÿ^ÿ[ÿWÿSÿLÿGÿAÿ;ÿ5ÿ0ÿ-ÿ+ÿ+ÿ*ÿ*ÿ)ÿ+ÿ*ÿ+ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ)ÿ+ÿ,ÿ+ÿ0ÿNÿsÿyÿsÿlÿyÿ
ÿÿÿÿ}ÿ|ÿvÿsÿyÿyÿdÿUÿPÿ[ÿ[ÿKÿ`ÿgÿdÿbÿ]ÿ[ÿVÿ`ÿcÿdÿ~ÿÿÿ¯ÿ³ÿÿ«ÿ¤ÿÿÿÿGÿ@ÿFÿIÿLÿMÿMÿLÿIÿDÿBÿGÿÿÿÿÿÿÿÿDÿ4ÿ>ÿ>ÿ?ÿEÿ\ÿyÿÿÿÿzÿtÿwÿyÿ{ÿÿÿÿÿÿÿNÿ?ÿEÿGÿDÿ@ÿ?ÿhÿÿÿÿÿÿ£ÿÿ}ÿ.ÿ6ÿ8ÿ;ÿ:ÿ8ÿ6ÿ1ÿ7ÿSÿ_ÿ_ÿ\ÿ]ÿ[ÿTÿMÿIÿIÿHÿFÿJÿNÿNÿNÿMÿMÿMÿLÿKÿLÿLÿLÿFÿ@ÿJÿRÿSÿVÿGÿDÿHÿIÿFÿDÿ@ÿ<ÿ=ÿBÿFÿBÿ@ÿ>ÿ>ÿDÿTÿXÿZÿZÿ[ÿ_ÿ_ÿeÿhÿnÿpÿnÿeÿ[ÿTÿLÿBÿ;ÿ<ÿ<ÿGÿSÿPÿVÿ]ÿ]ÿZÿ8ÿ-ÿ4ÿ8ÿ5ÿ7ÿ;ÿ7ÿBÿCÿ;ÿ>ÿBÿ0ÿ1ÿ;ÿCÿSÿLÿ8ÿ@ÿ7ÿ6ÿ9ÿ<ÿOÿYÿIÿDÿ:ÿ8ÿXÿuÿtÿ\ÿQÿLÿpÿ{ÿxÿsÿoÿmÿkÿiÿiÿiÿhÿiÿlÿnÿqÿpÿnÿpÿuÿtÿoÿkÿiÿgÿfÿfÿgÿgÿiÿjÿkÿpÿtÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿzÿuÿrÿnÿlÿiÿfÿcÿ`ÿ^ÿ]ÿWÿQÿKÿCÿ@ÿ<ÿ;ÿ;ÿ=ÿ>ÿ?ÿ@ÿ@ÿEÿKÿVÿ^ÿdÿlÿsÿyÿxÿyÿyÿwÿxÿyÿyÿyÿyÿzÿ{ÿ}ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ~ÿ|ÿzÿxÿwÿwÿxÿxÿvÿvÿvÿuÿsÿnÿiÿaÿWÿMÿDÿ?ÿAÿHÿTÿ_ÿlÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿrÿgÿ`ÿ`ÿhÿmÿuÿwÿyÿxÿvÿrÿqÿpÿnÿnÿnÿoÿnÿnÿnÿnÿlÿiÿhÿgÿdÿeÿjÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{||||||~{!~"{!~"{#~${%~'{&~'{(})|)}*|*}+|,},|,|,{,|-{.|.{.|.{.{.}-{-}.z.~-|,~,|+}*|*}*}*|)})|(}(|(}(|((|((|''}((}((}((}((|''|''|''|''z((z)~)y'~'x,0w7<sCFnJNjQ
ReV
Ya]_]^_]`_]a`[`a\^
]\[
[^\
^`___ac_de`cbb^ZgT~NlI{Ds<{5w2z-y+z*|*{)})|*)~++~**~++~*,~,,},,},,},,},,|,+|++|++|++|)*|**|**|**|**|**|*+|+-|-,|,-|-,|,+|++|++|++}+*})*|**|*)~+-/}I
~x|
~x
wwvzvpkffoghYF|c
puWWnfcldemfkv°¬|¯²}¬®}«¡wC<HKJG~JE}AA}Bm~k.:>}@@~@Sr}uzow|x|w
hfasC|HHyEAtDynf` eqm-6r89t9:w97w3>tQVrUWrVRsNJsIIrGGtJMuMNuLMsNJsJHtIKuN\se`tUEyEEzG
L}L
J}FDxSbtSCsCBu=OwWUxTPwKGsIOtSYw[Zw\XwK:x:I|LROyKIzRX{K=w:.u5?t9:v8>yB:|86}.(z*,x44w(~97~57}?H|<B|>;y<hz}}UM}_}|}urwpmzk~j{ggyhizin{t~o|o}vt|sp|m|jhyhgzhjxkowsxt|q
nlknoqq~vrplugfuc_u`av_]w]ZxU
OzH
A}><<==>?@BEOW^hp~v{~}|~{x|xz{yyzyzz|
~yz||rjhp||y
wvv~v{zvxwxwxxx
vyv
v{uqzmg}^ULC?A}IUxamtxt
wyzzzzz{yzoeqacnjowu~x~z}x|s~qxq~puo~nun~ovo~mvn~mwk~jvi}hwe{fymyy~wy~||}~~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ"ÿ#ÿ#ÿ$ÿ%ÿ'ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ)ÿ.ÿ2ÿ6ÿ:ÿ@ÿCÿGÿIÿLÿMÿSÿUÿWÿXÿ\ÿ_ÿaÿaÿdÿeÿdÿcÿaÿaÿaÿ`ÿaÿdÿeÿcÿaÿ`ÿ]ÿYÿRÿMÿIÿCÿ;ÿ2ÿ-ÿ*ÿ)ÿ(ÿ)ÿ'ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ,ÿ?ÿyÿÿjÿ|ÿ{ÿ|ÿyÿÿÿÿuÿrÿ|ÿ}ÿyÿpÿvÿyÿ~ÿvÿjÿ\ÿQÿXÿkÿgÿSÿGÿdÿwÿyÿzÿyÿÿÿ¨ÿÿªÿ³ÿ¹ÿ´ÿ·ÿ¹ÿ±ÿÿhÿ7ÿIÿHÿGÿFÿFÿCÿCÿCÿ=ÿWÿÿÿÿ}ÿÿÿÿSÿ4ÿ@ÿ?ÿ=ÿ@ÿBÿNÿpÿÿtÿqÿxÿzÿ~ÿÿÿÿÿÿÿxÿHÿHÿLÿFÿBÿGÿÿÿÿÿÿÿÿÿbÿ,ÿ6ÿ9ÿ9ÿ:ÿ:ÿ8ÿ5ÿ2ÿ1ÿ<ÿKÿQÿQÿSÿQÿNÿMÿLÿGÿGÿGÿHÿMÿJÿLÿKÿMÿMÿIÿGÿEÿEÿKÿSÿWÿWÿVÿMÿJÿFÿGÿIÿLÿQÿRÿPÿMÿQÿ^ÿ]ÿPÿMÿCÿ9ÿHÿVÿRÿMÿDÿ=ÿ9ÿ6ÿ6ÿ6ÿ:ÿ9ÿ9ÿ9ÿ5ÿ1ÿ0ÿ:ÿFÿNÿQÿAÿ=ÿAÿDÿDÿ8ÿ?ÿ>ÿ<ÿHÿIÿ>ÿ9ÿ?ÿDÿEÿ9ÿ5ÿ5ÿ3ÿ5ÿ/ÿ$ÿ2ÿ<ÿ=ÿCÿAÿ6ÿ;ÿCÿDÿ>ÿ:ÿ8ÿ7ÿ8ÿEÿNÿMÿEÿMÿhÿoÿ}ÿ|ÿxÿuÿpÿnÿkÿhÿgÿgÿhÿiÿoÿrÿqÿuÿuÿuÿsÿpÿnÿlÿlÿlÿnÿoÿpÿsÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿuÿnÿgÿcÿ`ÿaÿ_ÿ^ÿ^ÿ^ÿ_ÿ^ÿ\ÿ[ÿYÿTÿMÿGÿ?ÿ<ÿ:ÿ;ÿ<ÿ<ÿ=ÿ>ÿ@ÿDÿIÿQÿ[ÿaÿjÿvÿ|ÿÿÿÿ~ÿ{ÿyÿyÿzÿzÿzÿ{ÿ{ÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ{ÿyÿxÿwÿwÿxÿxÿvÿvÿuÿsÿpÿlÿfÿ]ÿTÿJÿAÿ>ÿBÿJÿUÿcÿpÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿmÿcÿ^ÿbÿiÿpÿuÿxÿyÿvÿsÿpÿpÿoÿoÿnÿlÿmÿnÿnÿoÿmÿkÿjÿhÿgÿfÿgÿpÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{||{{{ { ~!{!~#{$~${$~%{%~&{'~({(})|)}*|+}+|+},|-|-|.|.|.|.}.|.}/{/}-{-}-z-}-|,},|+}*|*}*}*|)})|(}'|'}'|((|((|''}((}((}((}((|((|((|''|''|&&|&~(|'~'{(~(y(~)w-~0v36r:=pAAmGKkLOgRVbW\a^b^b
c^c
c`c
cbaaea_f[WjQ}MnH|Ar;{3x.{)z'|(|({)~)}((}*+}*+~+*~*+~+,~--~--}--}--}--}--|--|-,|++|+,|,*|**|**|**|*+|++|++|,-|--|-,|,-|-+|++|++|+*}**}((}()}))*7kqXowz
~|
nvspwphcVc~}o{G4vXvt|zw
}£®|·º{»»|»¹|B~BE}LJ{DB{CD{>F|~~}
~=8|?>z>E|Ca|
wysy{~
~tm
tON
IzFAtJnfcf^m+6s77v99x97x30w-8wGNvOOuMKtKItGGuFKvKMwKMvMJwHFuCFtSMqKNrRSuIIvIJyQVyW[wairYLqJBs9EuVSxJBv>@tEHt@:v2/v/-w-0w7CzE7|22}3:~;|>Cy>DyMKz?=y9BzH9|68~64|47{9A{C94~3<}A=};9}::z=<z74|8>z@BwJOvX}ctnymwlwi{gwg~ixilzil}t}vu|t~t~qzrrutvqvxq|onkjjmpr~urmgrb^s[[v]]x^^x^]y\\yZXzRM{H
@;
:::;==
@EIS[f~oy{
{
|~}}{zy
zyz
{y{}x
w
y{
{p~fhr~|yvv{vywvvwxwvxw
vyu
uzup{ke~\RG@?C}KYxgst}
t
wyzzzzz{u|i]u]auj}o{u{xx|u}s~qxp~pwn~mwl~mwm~owo~nyl~lwj}iwg{jysy~~wy||ywusqÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ+ÿ*ÿ,ÿ.ÿ0ÿ1ÿ5ÿ6ÿ;ÿ?ÿ@ÿCÿGÿLÿPÿTÿZÿ]ÿ^ÿ_ÿ`ÿ`ÿ`ÿ`ÿ]ÿ\ÿZÿVÿQÿLÿGÿAÿ:ÿ3ÿ.ÿ+ÿ)ÿ'ÿ(ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ,ÿGÿoÿhÿWÿYÿqÿuÿ|ÿÿÿÿÿÿÿÿ}ÿÿÿÿ{ÿqÿuÿlÿpÿmÿXÿEÿdÿsÿjÿRÿGÿ]ÿfÿtÿÿÿÿÿ¨ÿµÿ»ÿ»ÿ¼ÿ»ÿºÿµÿÿ]ÿ7ÿ>ÿAÿEÿBÿCÿCÿ?ÿ@ÿCÿdÿÿÿ~ÿ~ÿÿÿÿfÿ1ÿ@ÿ@ÿ>ÿCÿHÿqÿÿxÿxÿzÿÿ~ÿ{ÿzÿÿÿÿÿÿ[ÿ@ÿEÿGÿHÿJÿÿÿÿÿÿÿÿÿQÿ0ÿ7ÿ:ÿ9ÿ:ÿ9ÿ8ÿ6ÿ3ÿ/ÿ.ÿ0ÿ;ÿEÿMÿOÿNÿMÿKÿIÿGÿGÿGÿEÿKÿMÿKÿKÿJÿHÿEÿFÿCÿDÿXÿQÿLÿNÿSÿTÿHÿIÿIÿKÿMÿUÿ_ÿrÿwÿoÿdÿ]ÿKÿBÿ=ÿMÿ[ÿRÿHÿGÿMÿQÿRÿPÿHÿ?ÿ3ÿ0ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ/ÿ/ÿ0ÿ3ÿ2ÿ5ÿ;ÿ;ÿFÿ;ÿ;ÿEÿBÿ9ÿ3ÿ8ÿ>ÿAÿ=ÿ7ÿ9ÿ7ÿ9ÿ:ÿ>ÿ>ÿ8ÿ2ÿ3ÿ6ÿ3ÿBÿOÿ=ÿ6ÿ5ÿ8ÿ9ÿ:ÿ<ÿ;ÿ9ÿ=ÿCÿBÿ9ÿ:ÿBÿEÿHÿRÿ]ÿaÿaÿgÿiÿhÿjÿoÿoÿtÿyÿzÿyÿyÿxÿvÿxÿzÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿkÿcÿ^ÿ]ÿ\ÿ[ÿXÿZÿ\ÿ\ÿ\ÿ]ÿ]ÿ\ÿ\ÿ\ÿYÿVÿRÿKÿEÿ=ÿ;ÿ:ÿ9ÿ:ÿ;ÿ<ÿ<ÿ>ÿEÿJÿRÿ_ÿgÿqÿ|ÿÿÿÿ
ÿÿÿÿ|ÿ{ÿzÿ{ÿ|ÿ|ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿxÿvÿvÿwÿwÿvÿvÿvÿuÿtÿtÿoÿiÿdÿ[ÿOÿFÿ@ÿ?ÿEÿMÿZÿfÿtÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿrÿeÿ\ÿYÿ^ÿgÿnÿsÿwÿvÿrÿqÿpÿpÿpÿnÿmÿlÿmÿmÿoÿnÿnÿmÿlÿjÿiÿhÿmÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{{zz~z~ z!~"y"~$y$~%z%~&|&}'z'}(z*|*{*|*{+|+{*|-{.|.}.|.}.|.}.|.}.|.~.|.~-|-}-|,|,|+}*|*})~)})~)}(~(}(~(}(~(|(~(|(~(}(~(}(({(({''|''|''|((|''|&&|''|''|'~'{&~&{((|((|++y*}-w1}4s6~8p>BnGLiOQeUVdVYeYWhURkN~KnE|@r8{3v){)~'{'~(}('})(}**~)*~*,~,,~+-~-,~-.~..}..}.-|--|--|--{--|-,|,+|+,|,,|,+|*+|++|++|+,|,-|--|--|--|-,{-,|,.~.0~6+~)+~+*|*)*,5Y^cw^k~x||
{{
qmvoxu\AVp}ucYz]by{{~£¯~º|¼~»{»º{¸§}p~06z=ByADyEDzDCzN}~}H1?BK~m~{|{xz
|tote?CzCEtPojeg@n3;r;:u::v98v63x/.x0=xJLuMLtLLtJJtGIwKMxIDyC
AzE
D{EEyX[uMOsTSuBHwIKwMNv[mrmlpgfoI?rD[s^RsPVrWWqVKr@8t2.u*)v-.x..z/.y./z1
4|79}8|48{52z.1y;Ay>;{;69:;<981::}14}47}28~BI~G>75>CDF~C>|9?xP
Xt[}Yn\vdsmuq|tvvyw}~~{}}z}~vsqpmmnoru{pug]tWXuY[w\\x]]y\\|\\|\\}[[}WU}PJ}D<~:99:;<<@GKS]k|vy
yy
y
}z|zx{|x{xvv
zz}k
diuyv
t}ztwuwuvxvvxvvxvtyr
m{g
a~YOE?=EzQ
^ukvstwyzyyxz}xn~_VzU~[{b{lsxvvztq}pzonwmkvklum}oxp|pxo|mvk|jwjzo{zw
wy{}rjii
iÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ+ÿ,ÿ.ÿ2ÿ5ÿ7ÿ:ÿ>ÿBÿDÿIÿJÿJÿMÿNÿKÿHÿGÿCÿ@ÿ:ÿ2ÿ,ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ)ÿ)ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ4ÿSÿ@ÿ(ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ2ÿHÿRÿnÿÿÿÿqÿiÿmÿ~ÿÿÿ
ÿ
ÿÿÿ{ÿ{ÿÿÿvÿpÿrÿmÿMÿ?ÿUÿBÿbÿxÿÿxÿ|ÿnÿXÿ_ÿjÿÿÿÿÿ¨ÿ²ÿ»ÿ»ÿ»ÿºÿºÿ¦ÿtÿ4ÿ7ÿ<ÿ?ÿ?ÿCÿEÿCÿCÿCÿFÿcÿÿÿÿyÿzÿÿÿÿDÿ-ÿCÿ\ÿÿ~ÿ|ÿ}ÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿzÿIÿFÿAÿFÿLÿÿÿÿÿÿÿÿÿIÿ5ÿ<ÿ9ÿ:ÿ:ÿ:ÿ:ÿ9ÿ5ÿ4ÿ2ÿ.ÿ+ÿ4ÿEÿLÿOÿNÿNÿKÿIÿJÿLÿLÿMÿKÿGÿLÿPÿRÿPÿKÿDÿAÿEÿ]ÿOÿNÿTÿSÿ?ÿEÿHÿJÿMÿNÿQÿ^ÿbÿ`ÿ^ÿ^ÿMÿKÿRÿ]ÿ]ÿYÿ\ÿ_ÿ\ÿWÿPÿEÿ=ÿ8ÿ6ÿ-ÿ&ÿ'ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ0ÿ1ÿ2ÿ5ÿ@ÿAÿ>ÿ<ÿ<ÿFÿGÿHÿBÿFÿ?ÿ<ÿ7ÿ9ÿ7ÿ7ÿ9ÿ3ÿ4ÿ5ÿ6ÿ3ÿ1ÿ/ÿ2ÿ6ÿ1ÿ3ÿ7ÿ:ÿ9ÿ?ÿ<ÿ:ÿ>ÿ<ÿ?ÿEÿKÿJÿGÿAÿ7ÿ=ÿNÿiÿ`ÿHÿ]ÿxÿ|ÿzÿ}ÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿlÿcÿVÿNÿLÿPÿTÿXÿYÿ[ÿ[ÿ\ÿ\ÿ[ÿ[ÿ[ÿ[ÿ\ÿ\ÿ[ÿYÿWÿUÿPÿJÿCÿ;ÿ:ÿ9ÿ8ÿ9ÿ;ÿ<ÿ?ÿBÿEÿMÿWÿaÿjÿsÿÿÿÿÿÿÿÿÿ|ÿ{ÿzÿ{ÿ{ÿ|ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿvÿtÿtÿvÿvÿuÿuÿuÿuÿtÿrÿmÿgÿ`ÿXÿKÿAÿ<ÿ=ÿEÿQÿ_ÿlÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿlÿ]ÿSÿSÿZÿcÿmÿtÿwÿuÿsÿqÿpÿoÿmÿlÿkÿkÿlÿmÿoÿpÿqÿpÿoÿlÿkÿmÿtÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{{zz ~ z ~!z!~"y"~$y$~%z$~%{'}(}(}*}*|*|*|*|+|+},|.}.|.}.|.}.|.}/|/}-|-~-|-~-|-},|+|,|,}*|*})~(})~)}'~'}'~'}(~(|(~(|(~(}(~(}''z''z''|''|''|((|('|''|'~'|'~'|'~'{'~'{))|))|)}){)|)z)|)x*|,z/}0x3~5v7:s?ArA~AqB}As={;v6{2x,z){'|&}'|'(|()}))}))~*+~+~,~-~,~+~,~,-~,-~..~..}./}//|//|/-|--{--|--|--|-,|,,|,,|,,|,,|,,|,,|,-|--|--|--|--{--|++},/}1-}**}**~*+,AX_{x~~RJTe
ttwsupbT>
Lut
r
X}_oz{¥®~´¹z»ºz¹£~i|17z:<y>AyA@y@FxCDzm}}{};~2g|w}t
x}pkoV}KzDDtK}ojfgPn59r:;u<;v;:w94w1.x,/x@KuPPtLJtHHvLLwMPxQQyQP|MG}:
0y1awVMuQMw4=xCFwGJtV]pban^^nbboddoddoc`nZRoE<r97s7.u''w)*y*+z++y,,z.0{01}9~=AvHKsFFrOOuKE{B<E>FORF
?9614~3
3~45~3
538CF=@A9:@GKIG}?7zQavV^ttzxxsrqqsrqsu
vwnwdVwMFxFL{SVzYYzYZzZZ{[[}[[}[[}ZZ}WT}OI}A:~:989;<=AJX^~alzww
wvw}xzzwy{x}xuvy{u}hejx}xu|xuvtwtuxuuxuuxutxql{f]~UI@:=
EyQ_tmzq
tw
yzyyxz
}~wi\QS}Zexnuww
vztr|q}onzlkxklwm~n{o}p{p}nym|kxmyuyv{zskg
j
l
jjÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ$ÿ%ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ-ÿ.ÿ.ÿ1ÿ3ÿ6ÿ7ÿ7ÿ7ÿ6ÿ2ÿ/ÿ,ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ-ÿ,ÿ-ÿ,ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ-ÿ8ÿOÿQÿyÿÿÿTÿbÿ[ÿOÿJÿMÿiÿÿÿÿÿÿÿÿÿ|ÿtÿyÿ|ÿzÿÿyÿoÿHÿ?ÿ^ÿxÿxÿuÿÿÿnÿSÿcÿqÿÿÿ¦ÿ©ÿ¬ÿ°ÿ·ÿºÿµÿÿNÿ/ÿ9ÿ:ÿ>ÿ>ÿ=ÿ@ÿAÿ>ÿAÿBÿBÿKÿ{ÿÿÿ}ÿzÿxÿÿÿÿ@ÿwÿÿ}ÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿmÿNÿLÿEÿIÿ|ÿÿÿÿÿÿÿÿUÿ5ÿ9ÿ;ÿ;ÿ<ÿ;ÿ;ÿ9ÿ7ÿ4ÿ3ÿ.ÿ-ÿ,ÿ9ÿIÿOÿSÿVÿWÿWÿWÿWÿVÿSÿQÿYÿ[ÿXÿOÿBÿ3ÿ,ÿ(ÿ,ÿ]ÿ^ÿOÿPÿJÿ,ÿ/ÿ4ÿ9ÿ?ÿTÿ[ÿ`ÿeÿ_ÿ`ÿjÿlÿmÿhÿiÿiÿiÿjÿ`ÿTÿFÿ?ÿ:ÿ:ÿ9ÿ9ÿ2ÿ)ÿ(ÿ,ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ*ÿ-ÿ.ÿ.ÿ2ÿ=ÿIÿKÿMÿPÿNÿXÿOÿIÿLÿHÿAÿFÿMÿQÿVÿNÿBÿ/ÿ2ÿ6ÿ4ÿ/ÿ/ÿ1ÿ4ÿ6ÿ0ÿ/ÿ8ÿKÿ@ÿ;ÿ8ÿ5ÿNÿTÿIÿAÿ9ÿ9ÿ@ÿDÿGÿAÿ9ÿ=ÿQÿ[ÿTÿkÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ}ÿ}ÿ}ÿ|ÿyÿxÿoÿjÿ`ÿVÿLÿFÿDÿGÿKÿQÿTÿXÿXÿWÿXÿXÿXÿYÿYÿYÿYÿZÿZÿZÿZÿVÿRÿLÿFÿAÿ:ÿ:ÿ9ÿ8ÿ9ÿ;ÿ<ÿ?ÿAÿLÿTÿWÿ`ÿlÿwÿÿÿÿÿÿÿÿÿ}ÿzÿyÿ{ÿ{ÿ}ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ~ÿyÿwÿvÿuÿsÿuÿuÿuÿuÿtÿtÿtÿqÿkÿeÿ\ÿTÿIÿ@ÿ;ÿ>ÿFÿRÿaÿnÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ}ÿsÿfÿXÿPÿSÿ[ÿgÿqÿvÿwÿuÿsÿrÿpÿoÿnÿlÿkÿlÿmÿmÿnÿpÿqÿpÿpÿnÿmÿqÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ||{{zz ~ y!~!y!~"y#~$y#~$z%~&z(~'|(~)|+}+}+}+},|,|.|.|/|/~/|/~/|//|/-|-~-|-~,|,}-|,}+|*}*|*})~)|)~)|(~(|(~(|(~(|(~(|(~(|(~(|'~'z'~'z&~&{'~'{'~'z(~(z((|''|''{''|''}''}((}((}(~(|)~)|)~*z)|)|*}*~*}*~*|*|,|,z-|-{-|-|+|*})|('}&~&}((}((}()~))~**~+,~,,~-.~..~./~/.~/0~01~11~1/}//}//|-.{./|/.|.-|--|--|--|-,|,,|,,},,},-|--|-.|..|..|..|..|.-|-+|++}+,-0JKP
bMHzEJ|Sh®©¡
vvopkoO:;Hb
~i
V}\y|¥§§{©³{©~|7|29|<=|<<z?Ay@Bv>?wBQy~{}{v~
|wz|wu|rw||omr{~R}OGuIxnkhh`n7<s;;s<=t=:u84x1/y+-w8Xsmvquosjau]YwV_yeby\K|4&|%%z'TxcLxLHy-.x.0vT`sfboaemxqmpilgjjkmgl\kIBm?<q==t=6u/1x22z10{.)|(
%{'
+z.
.{/9|AvL}OlIVi`QkJFu?GKLEEJF1#093-./0~,/}?M}x|_yC<{EPTSL=9:=~=Az8<vMXujysqr
r|twsurrtqswsqyokye\zTJzFE}HLRT}XX|XX|YY}ZZZZYY~[Z~VQMGA<989:<=>DIPY|boyyuuuv}vzxvzzv}u
vww
y~zp|e
eq~|
}yxxwvswtuxuuxttxtsxpj{d[S
G?
=}>HxTcsprtw
xyyyx{~{qcWQW}`
jurvuvu{rr}p}o~n{mm{mm}m~n}q}q|q|mzm{oxty~v|ojlmklloÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ)ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ-ÿ.ÿ5ÿVÿIÿhÿÿÿKÿGÿEÿXÿrÿÿÿ¡ÿ§ÿÿÿ¬ÿ§ÿ¢ÿÿÿÿÿ}ÿoÿkÿmÿsÿrÿ]ÿ?ÿ?ÿ8ÿ3ÿGÿmÿwÿÿ~ÿYÿ]ÿpÿÿÿÿ¤ÿ¡ÿÿÿÿOÿ4ÿ;ÿ;ÿ:ÿ:ÿ:ÿ8ÿ;ÿ:ÿ<ÿ?ÿ>ÿBÿMÿOÿdÿÿ
ÿ{ÿyÿwÿyÿÿÿÿ|ÿxÿ~ÿ
ÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿSÿIÿHÿOÿpÿÿÿÿÿÿÿÿjÿ:ÿ<ÿ;ÿ=ÿ<ÿ=ÿ<ÿ8ÿ5ÿ4ÿ0ÿ0ÿ.ÿ.ÿ[ÿÿÿ}ÿsÿoÿiÿ[ÿWÿUÿTÿZÿ]ÿ]ÿXÿEÿ,ÿ&ÿ)ÿ(ÿ*ÿHÿlÿUÿLÿJÿ/ÿ,ÿ/ÿTÿkÿkÿkÿgÿlÿ{ÿyÿoÿjÿhÿeÿgÿkÿmÿeÿ\ÿRÿJÿEÿAÿ@ÿBÿ?ÿ<ÿ9ÿ7ÿ6ÿ8ÿ6ÿ3ÿ0ÿ*ÿ#ÿ#ÿ$ÿ(ÿ,ÿ-ÿ.ÿ/ÿ8ÿAÿFÿQÿ[ÿ`ÿRÿKÿLÿTÿQÿZÿcÿ_ÿWÿQÿIÿ5ÿ!ÿ+ÿ<ÿ<ÿ1ÿ.ÿ0ÿ0ÿ.ÿ+ÿ*ÿ<ÿvÿÿÿÿÿeÿIÿ?ÿJÿPÿPÿQÿNÿ<ÿ7ÿBÿDÿBÿ:ÿCÿWÿhÿÿÿÿ
ÿÿ|ÿ{ÿvÿqÿlÿiÿiÿiÿkÿlÿnÿnÿmÿhÿcÿ\ÿSÿJÿFÿFÿIÿMÿQÿUÿYÿYÿXÿXÿYÿYÿZÿZÿZÿZÿYÿYÿYÿXÿVÿQÿKÿEÿ@ÿ:ÿ9ÿ8ÿ:ÿ;ÿ<ÿ=ÿAÿFÿKÿTÿ\ÿdÿqÿzÿÿÿÿÿÿÿÿÿ|ÿyÿzÿzÿ{ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ}ÿxÿwÿuÿtÿuÿsÿsÿtÿtÿtÿtÿrÿoÿjÿdÿ\ÿRÿGÿ?ÿ>ÿCÿMÿYÿeÿrÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿoÿbÿVÿSÿXÿ`ÿjÿsÿyÿwÿuÿrÿqÿpÿoÿnÿmÿmÿmÿmÿmÿnÿpÿqÿpÿoÿmÿmÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzzzz {!!{"~"z"~"z#~$z%~%z%~&z&~'z(~(z'~)z*}*{*}+{,|,{,|,{.|.~.|.~/|/.|--|-~-|-~,|+}*|)}*|*}(|(})~)|)~)|)~)|)~)|)~)|)~)|(~({(~({(~(y)~*y,~)y&~(x'~'z(~(z(({(({''z'(z''}''}((~((~(~(}(~)}(~)})|)~)})*}**|*)|)(|)~)|))|((|*)})(}()})(}()~)*~+,~..~..~//~./~/1~11~11~11~11~12}21}11|1/{//|//|.-|--|--|-.|.-|--|-,{-.{..|-.|..|..|..|..|..|.-|--|--}-./?
]n
n}nGz>a}x¨w¦¡z££|«ªyª©swsxx{|xzbt[KtHCxDJqy}o^
j~w}
|l~D:~=:}<=~AC~C?{=<y>BwHJvRmyzxu{v|z
wzvt{qwy
|ts}
q
syh}EIuQgmkhjun<9s:<t=;u:9x44x/.w+Kp{~jzvfrpge_iXQjJHmCDrGBs40v0-w.AvlZvMLw01wEjtmrqoxo|kvngifdchdlohmhg^XkTPrOIuA=x<9z<9{8
6|,
%}"#|$&{*.{.-z.2{Dt^egh\bPVeXdhlinnlvi
dZG8
846/--~4<}?zEzYzvwzz
yvzvo|Q|D}LT~UR}>9}=D}B8{=Wwo}zz{xwsoykhzgf{gh|jl}lm|oi{c\{SK{GF}IM~RS}XX|XX}XY~ZZZZ[Y~[X~UP~JD~>:989:;<AGLS\{g
sw|tss
tzvyyvw{vuuvwy
y{{m~egq
~}|yvwtrwstwttwttwsoxmg{c[~QG?@|FPw\hruqtw
yzyyx{xm`UT\ydnstwutt|q~po|n~m}mm}mmm~nq}q~p|mzp|tlinrrpqruÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ.ÿ9ÿ+ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ-ÿ,ÿ,ÿ/ÿ/ÿ.ÿ/ÿ0ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿEÿtÿzÿeÿCÿRÿBÿXÿÿ¨ÿ¥ÿ¤ÿ£ÿ¦ÿ¨ÿ¥ÿ£ÿÿ¯ÿ®ÿ«ÿ¢ÿÿÿÿÿÿ
ÿÿwÿlÿjÿ`ÿeÿ[ÿeÿWÿ_ÿwÿ{ÿ{ÿeÿfÿrÿ{ÿÿÿÿÿtÿJÿBÿ:ÿAÿ>ÿ=ÿ=ÿBÿIÿJÿOÿJÿDÿBÿ?ÿ?ÿHÿQÿRÿqÿÿ{ÿwÿuÿwÿ|ÿÿÿsÿqÿyÿ{ÿwÿkÿnÿÿwÿbÿsÿÿÿÿÿÿ{ÿEÿCÿMÿ^ÿÿÿÿÿwÿÿÿzÿ@ÿ6ÿ8ÿ;ÿ;ÿ9ÿ6ÿ2ÿ1ÿ1ÿ0ÿ,ÿ8ÿqÿ}ÿyÿwÿuÿuÿkÿ`ÿVÿSÿTÿMÿEÿAÿ:ÿ;ÿ;ÿ<ÿ9ÿ6ÿ4ÿ4ÿ>ÿhÿZÿPÿPÿ3ÿ9ÿfÿjÿqÿtÿÿÿÿ}ÿzÿrÿoÿqÿsÿlÿcÿfÿtÿxÿoÿfÿeÿ_ÿXÿQÿJÿEÿ@ÿ>ÿ@ÿ=ÿ<ÿ7ÿ*ÿ$ÿ$ÿ$ÿ$ÿ&ÿ-ÿ-ÿ.ÿ,ÿ)ÿ,ÿGÿdÿeÿgÿ^ÿ[ÿ[ÿdÿqÿyÿ
ÿÿÿ}ÿ{ÿ~ÿwÿaÿ<ÿ0ÿ4ÿ.ÿ+ÿ6ÿFÿFÿDÿ?ÿCÿLÿLÿ^ÿ`ÿ\ÿsÿ|ÿ}ÿ{ÿ[ÿFÿGÿLÿFÿGÿ@ÿ7ÿ?ÿ@ÿ=ÿAÿ[ÿsÿÿyÿpÿnÿjÿhÿgÿgÿgÿgÿhÿjÿlÿnÿmÿmÿkÿfÿ]ÿUÿNÿGÿHÿKÿOÿRÿUÿVÿVÿXÿXÿXÿXÿYÿZÿZÿZÿZÿZÿZÿXÿUÿPÿJÿDÿ>ÿ:ÿ9ÿ8ÿ9ÿ:ÿ;ÿ<ÿBÿGÿNÿVÿ_ÿjÿtÿ}ÿÿÿÿÿÿÿÿ}ÿyÿxÿxÿxÿ|ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿyÿvÿtÿsÿrÿsÿsÿtÿsÿsÿrÿoÿmÿfÿaÿYÿOÿDÿ?ÿAÿGÿRÿ^ÿjÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿjÿ]ÿTÿTÿ\ÿeÿnÿtÿwÿtÿsÿpÿpÿoÿlÿlÿnÿnÿmÿmÿmÿnÿpÿrÿmÿwÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿyy yy!!y""y#~#y#~#y"}#z$}$z%}'z'}'z(}(y)|)y*}*z+},{,|-|-|-}.|.~.|.~/|/.|--}-~,},~,}+}*}*}*~*})~)})~)})~)})~)|)~)|)~)})~)}(~({(~({((|((|'~'z'~'z'~'z(~(z(~(z(~(z((y))z((|''|((}((})~)})~)~)}))}))}))}))|))|))|))|))|))|)(|))|*)~**~++,-.,~-.~.~0~0~1~1~1~12~22~2~2~2~1~21~11}11}11}10|//|//|/.|..|-.|..|.-{--{--z..z-.{..{./{//{/.|..|..|.-|--|-,},./DmL{B@zEQ}¨zªr© t ¡t¡u¬¯v¯¥t u¢vspvnoaeoninhftep|
l`|nvz}xywT.{4>~D>?@?@~@I}EDzM>w:AvFOvIxx}yvszyzwrutrtpyWa}|wmv{y}}UBwEdolir
h~lA7r79u98x43x2/w0.rG{gy_pt]po]m`\p`s[j:Fo@:q;;s;:q8=r]\rPStA^sjnrxqm~hzzd}|dn_d_^iiwjtmmearYXxUQ|KG~DB~>2'#~"$}#*{1.z.+{*+}Kx^_haTh\|Ynaz`km|{hu~snyzvz~rjT504>}?~=y9z7w8x>wQxlwkxby]y]wc{svi[sNJuFOzPI}?=}@>yA[wx}|r{lkzih~gf|fh|jl|mm{nk{c\|VN|LI}KO~ST}VV|WW|XX~YYYYZZYWUPHB=:87:::=CIP|Xaykvv~ssrs{vuvwuy}v
utuwwx
zp}gdis~zwzsrxrswrrwrqwppyme|^X~N
C~?CzJVualqyqtwz{yxx{~tg\
T}W^vgqtvvytr}om~lmmmmm~mm}om{o}|tklnprt~u~w~x~{ÿÿÿÿÿ ÿ ÿ ÿ!ÿ!ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ2ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ.ÿ/ÿ=ÿTÿGÿCÿDÿTÿtÿÿ³ÿ²ÿ£ÿ¦ÿ¨ÿ¢ÿ¤ÿ¥ÿ£ÿªÿ§ÿ¥ÿÿÿÿ¢ÿ¡ÿÿÿÿÿqÿdÿcÿdÿiÿmÿgÿfÿkÿuÿyÿtÿ|ÿ`ÿkÿvÿ}ÿzÿ|ÿjÿ<ÿ,ÿ2ÿ9ÿ?ÿ>ÿ?ÿ@ÿ?ÿ?ÿ?ÿBÿFÿHÿJÿAÿ<ÿ>ÿAÿJÿMÿLÿyÿÿ}ÿuÿvÿzÿÿÿÿxÿlÿ[ÿVÿ\ÿwÿÿÿÿÿÿÿÿÿÿcÿCÿCÿOÿÿ ÿ¡ÿÿ^ÿÿÿÿ?ÿ7ÿ8ÿ9ÿ7ÿ7ÿ7ÿ4ÿ4ÿ5ÿ6ÿ6ÿMÿÿÿÿ~ÿqÿÿÿÿÿÿÿ~ÿÿPÿ;ÿDÿDÿ<ÿ;ÿ:ÿ<ÿ<ÿ;ÿSÿ\ÿNÿWÿeÿqÿsÿÿÿÿÿÿÿÿÿ
ÿxÿgÿ`ÿ_ÿ^ÿ`ÿbÿbÿ_ÿaÿbÿ]ÿXÿUÿSÿRÿOÿMÿHÿFÿ<ÿ)ÿ$ÿ#ÿ%ÿ$ÿ%ÿ.ÿ2ÿ/ÿ.ÿ+ÿ+ÿ2ÿNÿ\ÿ_ÿWÿGÿQÿ[ÿ]ÿ]ÿaÿmÿzÿyÿwÿwÿuÿrÿhÿiÿjÿZÿJÿFÿ>ÿ.ÿ6ÿ;ÿ5ÿ+ÿ*ÿ4ÿTÿÿÿÿÿÿ~ÿgÿ]ÿSÿNÿPÿVÿNÿLÿMÿ>ÿ8ÿ=ÿ>ÿEÿZÿÿ~ÿsÿiÿgÿeÿfÿhÿfÿiÿjÿkÿmÿmÿmÿjÿfÿ_ÿXÿPÿMÿKÿMÿQÿSÿTÿVÿVÿVÿVÿXÿXÿYÿYÿYÿYÿZÿZÿXÿVÿSÿNÿGÿAÿ=ÿ:ÿ:ÿ9ÿ:ÿ:ÿ;ÿ>ÿDÿJÿQÿYÿcÿmÿwÿ~ÿÿÿÿÿÿÿ}ÿyÿvÿvÿwÿyÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ{ÿuÿtÿsÿsÿtÿtÿsÿsÿrÿqÿpÿmÿeÿ^ÿVÿLÿCÿ?ÿDÿKÿXÿcÿnÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿrÿfÿZÿSÿYÿ`ÿkÿsÿwÿvÿtÿpÿpÿoÿnÿlÿmÿnÿnÿlÿlÿmÿmÿrÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx x x!"x""y""y#~#y$~$y$}$z'}'z(}'z(}(z(}*z*}*z+}+z,|,{-|-|.|.}.|.}.|.}-|-~.|.~,},~+}+~,}+}+}+}*~*})~)})~)}*~*})~)|)~)|)~)|)~)|)~){(~({(~({(~({(~(z(~(z'~&z(~(z(~(z'~'z((y((z((|''|'(}('}(~(})~)~)}))}))}))}))|))|)*|**|**|*)|(*|**|*,~-,~--~..~/0~0}/~.}0~0}2~0|1~1}3~3}3~3~2~2~2~43~33}33}31}10|00|00|/.|./|/.|.-|-.{--{--{..{-.{..{..{..{..{..{..{..{.-z--{+/|/>zFFwCNxqz¦²w±ru®±z²¶|·¯|¬£zr~vni[oojodenhnnvzt}{j}frx{{vx_v1,z01}:CDDA>~=<}9C{JHx::w=HwMCxRy|yy|x|vvkHzQTkt
p
v~}mJxCDqvn ¡jsivm?:s;;v<>x?@v>CvB2pVc T O}OMoWpkdU2o7?rD?s;<q=<pJ_o]gnpol}li
kjricbgdfgcejb^mUNnTToURsRPvPNzKC}/%#$~$&|+4z42y/-z5D}Py^ZqGCrI~Wu]{\s\{aor|~ny}qrhi{mievhH|:-{00y6{4u.y/u4wRwxw}
ua~^tr||tv|irX~KrG~CvE|G|BwA~@vAmx~|q|jh|hf}gi|jl}ml|ol|g`|YR|ML}NQ~ST}VV|VV}VXZZZ[ZZZWSMF@=;::~9;>>DLQ|Zewoyt~rr
r|swvuvxu{}uttuww
x
xy|j}cen|zw|styrrwrsvsswppwmd{]T~J
C~AEzNZuerq}qtwz{yxx{|~qcXS{Zbvkstuuysp}nn~mn~lmmk}ml{v{}pkmosv
y{~|~~~~ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ0ÿ0ÿ1ÿ2ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ+ÿ.ÿ0ÿ9ÿAÿEÿOÿpÿÿÿ°ÿµÿµÿÿÿªÿ¯ÿ³ÿµÿ¹ÿ¸ÿ³ÿÿ ÿÿÿÿÿÿÿ{ÿzÿjÿbÿiÿ`ÿaÿcÿsÿtÿ|ÿÿÿÿÿ~ÿRÿbÿpÿxÿuÿXÿ,ÿ(ÿ+ÿ1ÿ7ÿ<ÿ@ÿAÿBÿ@ÿ>ÿ>ÿ<ÿ;ÿAÿAÿ<ÿ=ÿ;ÿAÿKÿHÿ9ÿZÿÿÿ}ÿ{ÿ{ÿÿÿÿÿEÿIÿVÿ_ÿxÿÿÿÿÿÿÿÿtÿqÿQÿDÿEÿWÿÿÿÿÿvÿÿJÿBÿBÿCÿDÿFÿDÿCÿ@ÿ=ÿ=ÿ9ÿ2ÿnÿ©ÿ¥ÿ¢ÿÿÿ~ÿÿ
ÿ|ÿyÿgÿ[ÿIÿAÿ7ÿ5ÿ4ÿ8ÿ?ÿ@ÿ;ÿ<ÿ:ÿCÿaÿhÿkÿqÿvÿÿ
ÿÿÿÿÿÿÿeÿ`ÿgÿhÿjÿjÿhÿdÿdÿ^ÿMÿ;ÿ=ÿLÿRÿPÿPÿQÿPÿMÿIÿ>ÿ*ÿ"ÿ"ÿ%ÿ'ÿ*ÿ0ÿ5ÿ6ÿ4ÿ2ÿ5ÿEÿQÿPÿRÿFÿ@ÿFÿHÿPÿ[ÿ\ÿ_ÿ[ÿbÿnÿhÿfÿcÿXÿQÿRÿYÿcÿiÿKÿ3ÿ0ÿ1ÿ.ÿ/ÿ2ÿ/ÿ,ÿ-ÿ8ÿoÿÿÿÿÿrÿvÿÿÿÿÿ
ÿxÿMÿ=ÿFÿBÿ?ÿCÿEÿLÿbÿsÿxÿmÿjÿgÿiÿgÿhÿjÿlÿnÿnÿoÿjÿgÿ`ÿYÿRÿOÿMÿNÿQÿTÿUÿVÿVÿVÿVÿVÿXÿZÿZÿ[ÿ\ÿ\ÿZÿYÿVÿSÿMÿFÿ@ÿ<ÿ:ÿ;ÿ;ÿ:ÿ<ÿ@ÿCÿHÿMÿTÿ\ÿgÿpÿzÿÿÿÿÿÿÿÿ|ÿwÿwÿwÿxÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ}ÿxÿtÿsÿsÿsÿrÿtÿtÿsÿqÿpÿmÿdÿ]ÿSÿGÿAÿAÿGÿPÿ^ÿjÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿnÿ`ÿUÿVÿ\ÿdÿmÿsÿuÿuÿsÿpÿnÿmÿlÿmÿlÿlÿkÿnÿgÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"!x""x!!x""x#~#x#~#x#~#y$~$y$}$y'}'y'}'z(}(z(}*{*}*{+}+|,|,|-|-}.|.}.|.~.|.~-|-},|,~,},,},,|+~+~+~*~)})~)})~)|*~*|)~)|)~)|*~*|*~*|)~){)~){(~(z(~(z(~({(~(y'~&z'~'z'~'z(~(z)~){(~(|(~({'~'{'~'|'~'|(~(|)~)})~))|))}))}))})){)*|**|**|*+|*+|+,|.,}--}..}/~/}0|1~1{1~1{1~1|2~3{3~2{4~4{3~3|4~4|4~4~4~43}33|32}21|10|00|00|0/|/.|..|..{..{..z.
.z.
.{..{..{..{..{..{..{.-{--{-,|,,}/
<yDUvt
u¥z³´v·¡nq¡«w¥©w©§z¨£~~~t|xqqgogeomnpqtos| ~SRvjuutRv'({+.}86:IDA~?=}<:{9=z:=x;?wGMxDFx{ww{yw
x|zGOXzztnry{a~FCuDinjk_DpA?s=?u@=u9:u7Rv`Bq
®i¢TL Nq¡hVi Z_QCi86r43t24t>Cs?<qAWoknitzf
eefihgfifigmpemkff\mB0q0>rLRsQRuSRwJ7z&"{"'{+,y36x59x:@zKR|OD~7{<}A|I|P{T}WwW|]s`{cqY}\t\H|=72:GWO6|/
/x21w/|.s.zIn_wgqxz{vy}{v|~tpwkmobqT>xCHE|EGuEPrP}Sr^|cvg}g|h}k|l|l}m}o~l}ga|]T{OM}NR}UV}VV}UWYYYZ\]\\ZURLFA>===<>BGLPW|`
ivrztqqr~ysvxtxyt|}tstvvvw
wzp|g|c}j}xx~tqysrwstutrvqlxjd{]QE
?~BHyT`rkxpquxyyy
y
x
{xm^VU{]fuottusyq
p}nn}mj~ik}jh|~vljlpsvz}~~}}}}"ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ/ÿ?ÿQÿoÿÿÿÿ®ÿ¦ÿ¥ÿ²ÿÿpÿ~ÿÿ¦ÿÿÿ¤ÿ¬ÿ©ÿ ÿÿÿ
ÿÿÿÿÿzÿsÿlÿjÿcÿkÿqÿuÿtÿ}ÿ
ÿÿÿ©ÿÿnÿJÿeÿjÿhÿLÿ*ÿ+ÿ&ÿ,ÿ.ÿ>ÿMÿKÿJÿBÿAÿAÿ?ÿ<ÿ;ÿ>ÿ;ÿ;ÿ=ÿ>ÿDÿHÿFÿFÿwÿÿÿÿ~ÿ{ÿ{ÿ}ÿÿÿuÿJÿRÿvÿÿ
ÿÿÿÿÿÿÿyÿrÿNÿFÿCÿKÿsÿÿÿrÿLÿEÿ@ÿ?ÿ=ÿ>ÿ=ÿ<ÿ:ÿ:ÿ5ÿGÿdÿXÿÿ©ÿÿÿÿ~ÿÿÿfÿ[ÿPÿMÿMÿCÿ6ÿ6ÿ4ÿ3ÿ1ÿ2ÿ6ÿ>ÿIÿHÿBÿEÿ`ÿoÿsÿxÿÿÿÿÿÿÿsÿqÿkÿkÿjÿkÿlÿpÿsÿpÿjÿXÿ9ÿ.ÿ/ÿ3ÿBÿOÿQÿRÿRÿPÿAÿ-ÿ%ÿ$ÿ'ÿ+ÿ-ÿ0ÿ;ÿ?ÿBÿAÿBÿFÿGÿHÿLÿBÿ8ÿ9ÿ>ÿHÿOÿQÿNÿVÿYÿYÿXÿOÿCÿ3ÿ,ÿ.ÿ1ÿ3ÿ3ÿ4ÿBÿOÿOÿ:ÿ7ÿ6ÿ1ÿ8ÿ;ÿ6ÿNÿ=ÿ<ÿSÿpÿuÿyÿzÿÿuÿbÿ]ÿkÿqÿqÿ
ÿmÿ>ÿLÿEÿ>ÿHÿHÿKÿMÿNÿQÿUÿVÿ_ÿdÿiÿlÿnÿnÿnÿkÿgÿbÿ_ÿVÿQÿOÿOÿSÿVÿVÿVÿVÿUÿWÿYÿYÿYÿZÿ\ÿ]ÿ]ÿ]ÿYÿWÿRÿLÿFÿAÿ@ÿ?ÿ>ÿ?ÿ@ÿBÿEÿJÿNÿRÿ[ÿbÿkÿtÿ{ÿÿÿ
ÿ
ÿÿÿ}ÿyÿvÿwÿwÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ~ÿÿÿÿÿÿ~ÿyÿsÿsÿrÿsÿtÿsÿsÿsÿrÿnÿhÿaÿZÿQÿEÿ?ÿBÿKÿXÿcÿnÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿwÿlÿ\ÿSÿWÿ_ÿhÿpÿtÿuÿsÿqÿpÿnÿlÿkÿmÿjÿiÿlÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"~"x#~#x##x"#x%~%x$~$x$~$y$~%y%}%y'}'y'}'z(}(z(}*{*}*{+}+|,|,|-|-}-|-~.|.~.|.~-|-},|,~-}-,},,|+~+~+~*~)})~)}*~*|*~*|*~*|)~)|*~*|)~)|)~){)~({(~(|(~(|'~'{'~'y'~&z(~(z'~'z'~'z'~({(~(|(~(|'~'|'~&|'~'|(~(|)~)})~)~)|))})*}**}**{**|**|**|+*|**|,-|--}..}/~/}0~0}1|0~0|1~1|1~3{2~4z5~5{6~6{5~4|4~4|6~6~5~44}43|32}22|21|11|00|0/|/.|..|..{..z..{..{-.{--{-.{..{..{.-{-,{,,{,-{--|-,|0
Cxepn¡©qp«lprm¥r®®s¬¬s§w
{ztq}qpiqfnorso{q{©®Gy_^v]Gw..{3.|-6}G:}>F~CB}@<|>={<<y<=xBKxLHxzrvzvu|{w
yl|Mc{~ynzd{k}}tt_EzDBtLcpp¢TqF¢EtA>u==u=;v:8t14tZjp¥kay\vm[eWbLChBAm74q33r4:pCKnSSmSEnGOnUXk\si
g|ezcqmclocpvcwqgiSn4-t00t5HtRTuQHx8-y*+z,.y2>wIMuNOtONwGEzF:}8:}>B}J{OO{T~S{I@~-+A
`z
£}¦¥{¥£{~wk]rWFl2=k@{5n?yYso{suy|zuj\sayq~qtJC{G<EKE|UcyXzVxUxStWxZo_zerk|kvm|m{g|_X{TR}QU}VU}VV}UVXYYZ\[[[XUOLFB}A@}@@}BD~HLR~V\{d
muu|rqqr{wsvwtv{t|tstvvvv
vyz~z~n|~i~}k~xyuszsswstwsrwqlxf_{XOE
>~DMxZerq}p
quxyyy
y
x
{~tg
Z~SWy_isrtuuszp
o}ll}kl}jj|wlkmosw{~}~}}}}}#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ)ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ0ÿJÿxÿÿÿÿÿÿÿÿ£ÿÿÿÿÿ©ÿ²ÿ¶ÿªÿÿÿÿÿÿÿÿÿÿsÿ{ÿxÿtÿvÿsÿiÿqÿwÿ|ÿÿÿÿÿ©ÿ°ÿ£ÿRÿUÿWÿVÿ@ÿ*ÿ+ÿAÿCÿ2ÿ0ÿ0ÿ3ÿ7ÿDÿAÿ@ÿ@ÿ=ÿ?ÿ<ÿ=ÿ=ÿ;ÿ;ÿ?ÿDÿJÿGÿ{ÿvÿjÿÿÿÿÿ|ÿzÿÿÿÿ`ÿRÿfÿmÿcÿ^ÿ^ÿ^ÿkÿÿÿÿ~ÿHÿ?ÿCÿAÿAÿBÿGÿHÿDÿBÿ?ÿ=ÿ>ÿ;ÿ9ÿ5ÿ2ÿ2ÿ5ÿTÿdÿÿ¥ÿÿÿ|ÿvÿiÿ_ÿUÿTÿLÿDÿ?ÿ8ÿ3ÿ4ÿ;ÿ?ÿGÿRÿVÿYÿ[ÿXÿYÿYÿMÿIÿGÿIÿOÿUÿ]ÿiÿzÿÿÿÿxÿiÿhÿrÿyÿ~ÿÿzÿmÿFÿ.ÿ,ÿ0ÿ3ÿ1ÿ7ÿIÿOÿIÿ<ÿ/ÿ.ÿ-ÿ.ÿ1ÿ2ÿGÿYÿ[ÿZÿ]ÿ^ÿYÿTÿOÿGÿ<ÿ7ÿ6ÿ<ÿAÿFÿJÿMÿNÿKÿ=ÿ2ÿ)ÿ7ÿXÿyÿÿ¦ÿ¶ÿ»ÿ»ÿ»ÿ»ÿºÿ»ÿºÿµÿ®ÿªÿÿhÿ/ÿ1ÿ5ÿ4ÿ0ÿ?ÿXÿkÿuÿ}ÿÿ{ÿwÿÿÿÿsÿ|ÿmÿbÿAÿJÿEÿHÿMÿNÿnÿuÿhÿbÿdÿ[ÿVÿVÿWÿ]ÿ]ÿdÿdÿdÿaÿ[ÿUÿTÿSÿUÿVÿUÿVÿVÿTÿUÿXÿYÿYÿZÿ[ÿ]ÿ\ÿ\ÿXÿUÿQÿLÿGÿCÿBÿAÿBÿBÿDÿFÿKÿOÿTÿXÿ_ÿfÿoÿvÿ|ÿÿÿÿÿÿ}ÿyÿvÿuÿwÿxÿ{ÿ|ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ~ÿ~ÿÿ~ÿ~ÿ~ÿÿzÿuÿqÿqÿrÿsÿsÿsÿsÿpÿlÿfÿ^ÿVÿMÿCÿ@ÿEÿQÿ]ÿgÿsÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿsÿfÿYÿTÿ[ÿcÿkÿrÿrÿtÿrÿoÿpÿmÿkÿjÿkÿkÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$~#w"~#w%~%w$~%w&~&w%~%w%~%x%~&x'~'y(~(y(~(z(~(z)}+z+}+z,},{,},|.}.~.}.~.}.~.}-~,},,},,~+~+~+~+~++~+}*}*}*}*}*~*}*~*}*~*|)~)|*~*}*~*{)~)z)~)y(~(z(~(z'~'z'~'z&&{&&{&&z&&z''z''z''z''{&&|((|(~(}(~(~)~)~)}))}))}**|**|*+|++|++|+,|,,|,-|-/}//}/~1}1}2}2{3}3{4}3{3}4{4}4{45{66|66}66}5~6~6~6~5}54}43|32|21|01|00{00|//}.-}/+|.-|-.|--|--|-,|,,|,,|,,{,,{,,{,,{,-|--}-+|0
[wohhklo §s¬q¨ory
{urwpvwrz{trxs}qup|
®¥X|NPwJ5x(*|7C|/-|00|07~CE~=A|><|;;{9;z@EwKHv|~usjtt}vwzwwZ{_d{[\{]|\}eywy~o?@zA@w?BvFDvB>u=;t98u40t0;rUbn|i|fpsbkXcUPhE9o97p<CoJOlTVjYXg[]f\`iZJmDDqKSp\im|hewa_ZuZ
[dp6p-0v26x62x8DxA1x,0x22v1<sWanahmqmp`WrPFw62|7@IJLH<3,0>Zz
¤»¾¾~¿¾~¾¾}¾½º|·´q°
ªe}9a(
0m2~4p?zMsd{tvw|x}y~|w|uvLH|HOSPZ~tkzjixe}`y]vYzYqZ|]p^|]uYzWzU{U}U{UUUUVWWXZ~Z[\\~[YUSM~I
E|D
D{EE|GJ|NRV}[byhpuu}rpq{swusvvsx{s~stvvvuuuvx}y{{{p}}}n~{vzvs|stwtsvsqvojxd^}UKCB}ITv_jqwqrwxx
x
wxx{{
re
WS[zbktrvtvqzo
l~ki~hk}xnlnprvz~}~~||}}&ÿ$ÿ#ÿ$ÿ%ÿ%ÿ$ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ1ÿ0ÿ0ÿ/ÿ0ÿ/ÿ/ÿ.ÿ/ÿ.ÿ8ÿ/ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ+ÿ2ÿgÿÿÿÿ ÿÿÿÿÿÿÿÿ£ÿÿÿ¦ÿ¤ÿ¥ÿ¡ÿ£ÿÿÿÿÿÿÿÿÿyÿyÿ|ÿÿ~ÿyÿwÿtÿvÿ|ÿ{ÿaÿ3ÿÿÿÿLÿPÿKÿEÿ/ÿ'ÿ(ÿ,ÿ.ÿ,ÿ,ÿ+ÿ.ÿ1ÿ6ÿ;ÿFÿAÿ?ÿCÿ=ÿ?ÿ<ÿ<ÿ;ÿ<ÿAÿFÿEÿyÿÿsÿmÿoÿÿÿÿ|ÿ{ÿ~ÿÿÿrÿSÿZÿRÿUÿWÿYÿfÿvÿÿÿÿÿUÿ?ÿ?ÿ=ÿ@ÿCÿCÿCÿCÿ=ÿ=ÿ8ÿ6ÿ4ÿ1ÿ0ÿ0ÿ=ÿRÿ_ÿyÿÿÿ{ÿpÿlÿjÿ]ÿJÿJÿAÿ4ÿ5ÿ7ÿ@ÿJÿNÿSÿTÿYÿZÿ[ÿ\ÿ^ÿ_ÿbÿbÿOÿEÿFÿLÿRÿ[ÿiÿ{ÿÿÿÿ{ÿbÿmÿÿÿÿÿÿ\ÿ-ÿ-ÿ1ÿ3ÿ7ÿ7ÿ5ÿ3ÿ5ÿ3ÿ+ÿ+ÿ1ÿ4ÿ4ÿ9ÿTÿ_ÿeÿmÿxÿ|ÿuÿeÿYÿJÿ>ÿ6ÿ4ÿ@ÿMÿOÿNÿHÿ6ÿ.ÿ,ÿ=ÿHÿWÿvÿÿ¦ÿ¹ÿ¾ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ·ÿ³ÿ°ÿ®ÿ¥ÿÿ8ÿ.ÿ4ÿ3ÿ4ÿ8ÿUÿhÿgÿ~ÿÿzÿ}ÿ~ÿÿÿÿ{ÿhÿGÿDÿNÿNÿNÿOÿyÿxÿkÿhÿjÿhÿgÿoÿjÿdÿ_ÿ]ÿ[ÿ\ÿYÿWÿXÿYÿWÿUÿVÿVÿUÿVÿXÿYÿZÿ[ÿ\ÿ]ÿ]ÿ\ÿZÿXÿUÿRÿNÿIÿGÿGÿGÿHÿHÿJÿNÿRÿVÿZÿ^ÿdÿlÿrÿwÿ}ÿÿÿÿÿ}ÿzÿuÿuÿvÿxÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ|ÿ}ÿ~ÿ~ÿ}ÿ}ÿ{ÿwÿsÿsÿtÿtÿsÿsÿqÿoÿjÿdÿ]ÿTÿKÿCÿCÿJÿVÿbÿkÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿpÿcÿYÿTÿ[ÿdÿmÿtÿuÿsÿpÿnÿmÿkÿiÿjÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(~&w$~%w%~%w%~%w&~&w&~&w'~'x'~'x(~(y)~)y)~)z*~+z*}*z+}+z,},z,},{,},|,},|-}-~-},~+}+,},~,},+}++~+*~*}*}*}*}*}*~*}*~*}*~*|)~)|)~)})~){(~(z)~)y(~(z(~(z'~'z'~'z&&z&&z&&z&&z''z''z''z''{&&|((|(~(}(~(~)~))}))}))}**|*+|++|+,|,,|,,|,-|--|-/}/0}0~0}2}2}2{3}3{3}3{3}4z4}4z55{56|55}66}6~6~5~5~5}55}53|32|21|10|//{..|..}./}MK|0-|--|-,|,,|,,|,+|++|++}++}++}++}+,|+,}*+}5hx{p ¢k¡kjk¢m
p §m¦¢mpv|y
xtv{s}|vyvvoktojwW;|]d~NM|PNy@+z')|+-|/;|1.|02{7J|D=}=>|@>z?;y;?w@Dvjtxssnutu|v|v
x^X|WT|T}[}jz~}x~x{M|>@yABxCGwH=v:8w43w1.w0<tR\oxltjqmhd]kKHo=8p75o?MlSYg[\baa``a`bcc]JhKNnSTpZdnujgwc _£ `hA/r/0v25x8:x51x--x.0u49rM^mephz
hnpYsB:v99zDQ~W
QB/~/
5CMc¤¹~¿¾~¾¾~¾¾~¾¾~½~»ºy¸µq±¬b¨ Or0V18iG5o>XtV}owu|{v~|u~s{cqnv>HGOPo~tmlhd
edbb_z[WtUVqW~XoX{VtVzVwU|UzW{X{Y}^|`}]~[|Z}Z|YXzSNzJIxH
HwJKxMQ{RV}\|`fxmutzqpq{vstusvwsy}s
stvvuttt
uw}z}v{|}p~}tzwu|sswssvsqvnixcZ}PI~DG{O[ucqp{oswxx
x
wwy{xm~b
V~V]xfntuwvsqzo
l|ml}ymkpruy~~|{y{||||}}'ÿ&ÿ$ÿ%ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ)ÿ,ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ6ÿfÿ{ÿÿ£ÿ£ÿÿÿÿÿÿÿ
ÿ¢ÿÿÿÿ§ÿÿÿÿÿÿÿÿÿÿÿÿzÿvÿuÿyÿyÿxÿrÿmÿeÿ\ÿWÿQÿJÿPÿNÿNÿMÿPÿKÿ<ÿ+ÿ'ÿ*ÿ)ÿ,ÿ0ÿ.ÿ+ÿ,ÿ3ÿ4ÿ4ÿ4ÿ5ÿ9ÿ>ÿ>ÿ@ÿ@ÿ?ÿ=ÿ;ÿ=ÿAÿ?ÿ_ÿÿvÿnÿlÿnÿ
ÿÿÿ~ÿ{ÿ}ÿÿÿpÿQÿPÿUÿ^ÿdÿqÿ|ÿxÿsÿlÿ}ÿÿkÿBÿ?ÿCÿBÿCÿFÿDÿ=ÿ:ÿ7ÿ5ÿ2ÿ1ÿ/ÿ0ÿ;ÿMÿZÿtÿzÿ~ÿxÿsÿhÿ\ÿYÿSÿFÿ>ÿ?ÿ:ÿ5ÿ;ÿPÿWÿYÿ_ÿdÿfÿqÿsÿlÿfÿ]ÿXÿWÿVÿLÿLÿQÿYÿaÿoÿÿÿ ÿÿÿÿÿÿÿÿaÿ1ÿ3ÿ0ÿ1ÿ4ÿ6ÿ6ÿ7ÿ4ÿ4ÿ2ÿ1ÿ0ÿ3ÿ6ÿIÿ\ÿeÿoÿwÿ|ÿÿÿÿlÿ:ÿ7ÿ:ÿ;ÿ<ÿ<ÿ>ÿ@ÿ=ÿ7ÿ0ÿ/ÿ7ÿ?ÿEÿfÿÿ¶ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ»ÿ»ÿºÿ¶ÿ³ÿÿ§ÿÿÿÿUÿ0ÿLÿWÿ@ÿ0ÿCÿBÿaÿYÿmÿÿ}ÿÿxÿFÿMÿ~ÿ[ÿ?ÿEÿKÿNÿVÿ`ÿ^ÿUÿRÿJÿIÿIÿJÿLÿMÿRÿQÿMÿKÿJÿOÿSÿWÿWÿUÿVÿTÿWÿYÿVÿTÿ[ÿdÿeÿeÿ^ÿYÿ[ÿ_ÿ]ÿVÿPÿKÿIÿJÿIÿKÿLÿOÿRÿUÿYÿ^ÿcÿhÿoÿvÿ|ÿÿÿÿÿ~ÿzÿtÿsÿuÿvÿxÿ{ÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿ~ÿ~ÿ~ÿÿ}ÿzÿxÿuÿtÿsÿsÿsÿsÿqÿnÿiÿcÿZÿPÿFÿFÿHÿPÿ\ÿeÿsÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿlÿ_ÿVÿXÿ_ÿgÿpÿuÿvÿsÿrÿpÿkÿnÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿtÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'~'x&~&x'~'w'~'w'}'x'}'x(}(y'}'x(}(y(}(y)~*z*~*z)}*z+}+z,},{,},{,},|,},|,},},},}+}+~+}+}*~*~*~,~+~+~*~*~*~*}*~*}*~*}*~*})~)})~)})~)|(~(|(~({)~){(~({(~(y''y&}&w&~&w&~&w&&x&&y%%z%%z&'z''{&&|''|(~(~(~()~))~))}))}*)})*|*+}++},,|,,|,-|--|-.}0~/}1}0}2}2}2{3}4z3}3z4}4{4}5{5~5z6~6{5~5|6~6}6}6}5}55}55}43}31}11{10{//{/.|-.|.-|..|--|--|,,|,,|,,|,+|+*|)*|**|**|**|**|*)}*)6
jzrljklmlghks{
zyxz{v}wupbugavPEyKQ}ON~OQ}LJ{<'{&(|)+~-*|)*z,+|/3{16{19{>>zA>z=?w>?vPvuosnlrrru|~u|xU|ASd}pyzr
e}Y`}xy}\C|DB{?DyH>x:7v43v10w/:tL`pn
lmanVToSFqBBr>8p<SkX[dah]nvXXy]wrcvgjbfoegmixidb^_nfD6p66t79u86v23x44x53tE`nflhu|dfob2v77w65x56y75y30z04{7Bu¬º½~¾¾~¿¾~½¿½¾¼|º¸yµ²l«¡U7~3MKWUTh19pTjtX~^v}}~w~~wzyswtME|NNeWSTRPUYVXWVQONMO}WU{TVsX|[o\z[pTzWqZ{\qbz^tZwZy`ud}dtf]tOLuKLtLMuQTwX\|`|fjwpvt}~rq|ryurttrwxr|rstvvutss
t
wy}w|~p~}t|y~u{vtwtsuqpvmgyaZ|PFEIzS^thvppsw
yx
xxwy|{t
j~]W|Yauirrvvvtqxl
m{{nlpsu
y|j~n{v
y{{{{{||
'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ/ÿ6ÿkÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿÿzÿtÿnÿiÿaÿjÿcÿKÿ9ÿCÿOÿOÿQÿOÿPÿOÿHÿ8ÿ'ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ+ÿ.ÿ5ÿ;ÿJÿGÿ9ÿ:ÿ@ÿ@ÿ=ÿ>ÿ<ÿ9ÿ;ÿBÿzÿÿwÿwÿuÿwÿÿÿÿÿ~ÿÿÿÿqÿXÿaÿhÿvÿ{ÿwÿnÿ\ÿGÿ>ÿHÿZÿeÿWÿEÿCÿEÿCÿ@ÿ@ÿ9ÿ7ÿ3ÿ1ÿ/ÿ0ÿ-ÿ;ÿIÿ`ÿÿÿÿÿcÿGÿSÿTÿVÿHÿDÿGÿCÿCÿHÿRÿXÿ_ÿhÿjÿnÿÿÿÿÿÿwÿfÿ^ÿeÿlÿgÿmÿrÿuÿtÿwÿÿÿÿÿÿÿ~ÿwÿpÿVÿ=ÿ:ÿ9ÿ8ÿ9ÿ8ÿ5ÿ4ÿ3ÿ2ÿ5ÿ8ÿ:ÿYÿtÿtÿuÿxÿÿÿÿÿÿbÿ6ÿ7ÿ5ÿ4ÿ4ÿ3ÿ0ÿ2ÿ0ÿ.ÿ.ÿ2ÿ6ÿ6ÿEÿqÿÿ´ÿ»ÿ»ÿ½ÿ¾ÿ¼ÿ¼ÿ¸ÿ´ÿ¶ÿºÿºÿ¹ÿ³ÿ®ÿ¤ÿÿÿÿÿÿZÿRÿLÿUÿRÿ1ÿ4ÿMÿOÿ=ÿhÿÿÿÿÿ|ÿ|ÿxÿnÿTÿKÿLÿLÿMÿNÿPÿMÿOÿNÿLÿDÿLÿXÿUÿMÿEÿCÿCÿEÿGÿVÿRÿQÿ[ÿYÿSÿYÿWÿcÿeÿeÿmÿkÿhÿhÿfÿmÿoÿhÿ^ÿQÿMÿLÿNÿNÿOÿRÿUÿXÿ\ÿbÿhÿnÿsÿzÿ~ÿÿÿÿ~ÿzÿvÿtÿsÿtÿwÿxÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ}ÿyÿuÿsÿrÿrÿpÿnÿoÿmÿgÿaÿZÿPÿIÿHÿMÿWÿ`ÿkÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿiÿ\ÿWÿ\ÿcÿkÿrÿwÿwÿqÿpÿlÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ|ÿdÿdÿtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(~(x(~(x(~(x(~(x'}'x(}(x(}(y(}(x)})y)})y)}*z*}*z*})z*},z,},{,},{+}*{+}+{+}+}*}*}*}*~*})}(~+~*~)~)~)~*~*~)~)})~)})~)})~)})~)})~)}(~(|(~(|'~'{'~'{'~'|'~'{&&z&}&x%~%x%~%x%%x%%y%%z%%z&&z&'{&&{&'{(~(~'~')~))~))}))}))})*|*+}++}+,|-,|,.|./|/.}/1}0~1}1}1}1{2}3z4}4z5}5{4}5{5~5z6~6{6~6|6~6}5}5}5}55}54}32|21|10{00{/.{..|--|--|--|--|-,|,,|,+|++|+*|**|**|**|**|**|**|**}0@>
hzrljlllgdfjq
y}}zyqyqpwmlxgcxTJzKK}MN}QN|JH|7'|(({()|)+|(*z.,{+,z7Pz=8z3<{:?z?=y<9x;lvusss~ttyYitxzxjW}D9}7:}Ka|TB{?CyCCyA8y41y/.y.<vKZnvjmRGoGWqXMqHCqBLoLVk[\ceuZ
VW{_`SfMbhyecb
taita|avscmhgaSn@<q;9r87t66u65u6:rUwl~f{a
dkc:r86u32w00z/-z.0{49}<J{Uox¤x¬³z¶±}§¨}®°u¯
¨cC,/XIVJGkWIn07nE4rK~twy}}y~uweppv>LP^c]}_`xVR{HZ~hhlke[SUgRGX_zZcwiav[gyml|c}`~Xzkly^SvOOuOPtPRuUVxZ^~b{gnwuzr}po}yqvursvrxxr|rstvvutsstw
{w~s~v}yuzsrurpsomujgy_Y|PI~JQy[etpyppsw
yxxwwyz|q
f~\Xz^fvnutvwurpx~~smqt
uz{d^{iwvuy{{{{{zz(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ/ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ0ÿaÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿwÿyÿwÿvÿrÿmÿeÿ^ÿUÿNÿLÿIÿKÿNÿNÿNÿKÿIÿ:ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ-ÿ-ÿ-ÿ,ÿ/ÿ3ÿ+ÿ0ÿVÿDÿBÿ=ÿ?ÿ@ÿ>ÿ;ÿ=ÿ\ÿÿÿÿÿÿÿÿÿÿÿ}ÿÿÿÿqÿrÿwÿwÿyÿtÿgÿTÿCÿ8ÿ5ÿ4ÿ8ÿLÿZÿOÿDÿAÿFÿDÿDÿ=ÿ5ÿ1ÿ/ÿ/ÿ.ÿ<ÿMÿeÿzÿÿÿsÿ4ÿ@ÿLÿNÿPÿLÿAÿ<ÿ?ÿBÿSÿXÿ]ÿdÿpÿ
ÿÿÿÿÿÿtÿuÿmÿ]ÿRÿbÿxÿ|ÿÿÿÿÿÿ{ÿnÿrÿtÿrÿlÿfÿcÿ[ÿYÿPÿDÿAÿ>ÿ=ÿ:ÿ9ÿ7ÿ7ÿ7ÿ9ÿ:ÿMÿvÿÿÿÿÿÿ
ÿÿÿgÿ<ÿ7ÿ5ÿ6ÿ3ÿ1ÿ/ÿ.ÿ-ÿ/ÿ3ÿ9ÿ;ÿCÿUÿ_ÿfÿkÿsÿ{ÿÿÿÿÿ}ÿwÿÿÿÿÿÿÿÿÿÿÿÿ~ÿZÿDÿDÿIÿKÿQÿPÿ<ÿ<ÿMÿmÿ}ÿwÿxÿ~ÿ~ÿlÿ_ÿÿÿCÿMÿYÿcÿcÿeÿkÿhÿYÿPÿ\ÿfÿlÿmÿmÿnÿlÿgÿaÿ`ÿXÿPÿJÿMÿ[ÿZÿ]ÿZÿVÿOÿVÿbÿeÿUÿHÿAÿ]ÿdÿWÿQÿQÿOÿPÿPÿPÿRÿVÿXÿZÿ^ÿdÿiÿoÿvÿ{ÿ~ÿÿÿÿ}ÿyÿvÿsÿuÿwÿxÿzÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿxÿuÿsÿqÿqÿqÿoÿmÿjÿgÿ_ÿXÿOÿIÿLÿSÿ]ÿgÿpÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿyÿpÿfÿ\ÿ\ÿbÿiÿpÿwÿvÿuÿqÿyÿÿÿÿÿÿÿÿÿÿ
ÿÿÿzÿdÿ^ÿ`ÿjÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(}(x(}(x(~(z(~(z(}(y(})y)})x)})x*|*x*|*y*}*y*}*z+}+{+}+{+}+}+}+}+}+}+}+}+|+}*|*}*}*~*}*~)~)~(~(~)~)~)~)~)~)})~)})~)~)~)~)~)})~)}(~(}(~'}&~&|&~(z'~'z&~&z&~&{&~&y%~%x%~%x$~$w%~%w%~%y%~%y%~%z%~%z&~&{&~'{'~'}&~'~'~('}((}()}))})*}*+}+,},,{-,{,-|..|.0}0~0}1}1}0}1}1|1}2{3}3{4~4{5~5z5}6{5~6{5~5|6~5}4}6~5}5~5|44|42|12|0/z/.z--{--{--|-,|,,|,+}++}++}+*|*+|+*|**|*+~,(~*)})*}**~*)~))~,Z{}rkhkmkedhnr{~}{xvztmzd]zREz>B|GM}MK|HH{>-{('|)*|((|)*|-.{,+z()z,.y:CzP;z?=z><z>Jywtsrr~s
vd~uxvpcO~>7}7
7|:E|V\|O?y>Bx?@y:4y0.x/:uKgokhl.6oQdpTHpB>p>AmE[iadb{[XZv¡xd{pc~^VX¤¢Y¢ \~^rmchggd_jZZmTOpIBq@>r=;s98t:4q[kwg{c
c|{ijDo66u43w/.z./{15|:?}Ph|}~zywwmav_byec~dlxtaJ|{<ncFY?`@7q<7rEZnBAob~vrx}uv|udsL`uOMzij}ggz`^t[_vhjxml{nn~kha^__WYZ~XU}W\~]a~jhB~0~8}Wo{WQvQPtQQsQSvVYz\`dzjpuw|poo}ypttqvxqy|ssstvvusssrv{u~sw}xu{ttvtqsomujez^V}MI|OVw`iru~npsyyyyvvyx{p
d~]]ydkssvtuowu}smqtvy~g_}_evnzrtz{{{zzz~}s(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ5ÿ6ÿ5ÿ5ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ/ÿ2ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ,ÿPÿmÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿwÿwÿtÿoÿeÿ_ÿUÿDÿ@ÿCÿJÿNÿIÿKÿHÿFÿ=ÿ-ÿ(ÿ'ÿ)ÿ*ÿ'ÿ(ÿ(ÿ(ÿ*ÿ,ÿ+ÿ+ÿ.ÿ-ÿ3ÿ+ÿ)ÿ.ÿ8ÿ<ÿ>ÿ?ÿ<ÿ:ÿ<ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿiÿnÿwÿuÿpÿ`ÿLÿBÿ8ÿ8ÿ<ÿDÿZÿfÿ_ÿTÿMÿDÿBÿ@ÿ?ÿ;ÿ5ÿ1ÿ/ÿ/ÿ;ÿHÿ[ÿ}ÿÿÿYÿ2ÿSÿrÿxÿ`ÿLÿEÿAÿ?ÿ@ÿAÿOÿfÿnÿwÿÿÿÿÿsÿÿ¥ÿ«ÿÿ¥ÿ¦ÿ¦ÿÿÿ®ÿ°ÿªÿ¥ÿÿÿÿ{ÿrÿiÿeÿ`ÿ]ÿZÿVÿRÿNÿKÿEÿAÿ=ÿ=ÿ>ÿ?ÿ?ÿ>ÿ;ÿZÿ
ÿÿ~ÿsÿwÿ~ÿÿ{ÿxÿnÿQÿ8ÿ7ÿ5ÿ3ÿ3ÿ0ÿ0ÿ1ÿ5ÿ8ÿ?ÿBÿVÿvÿÿÿÿÿÿ}ÿiÿZÿVÿRÿOÿMÿTÿ[ÿ^ÿ`ÿdÿ`ÿ]ÿ\ÿZÿbÿtÿYÿBÿ?ÿ=ÿBÿ4ÿ=ÿAÿKÿ7ÿKÿeÿgÿnÿzÿÿ
ÿhÿ&ÿbÿqÿSÿZÿ]ÿdÿkÿlÿfÿhÿcÿbÿhÿoÿpÿpÿpÿkÿgÿdÿ_ÿ_ÿ\ÿZÿXÿYÿYÿYÿZÿTÿTÿ\ÿ^ÿXÿ6ÿ(ÿ1ÿOÿaÿUÿRÿRÿPÿQÿQÿRÿTÿXÿZÿ\ÿ`ÿfÿkÿrÿxÿ}ÿÿÿÿÿ}ÿzÿvÿsÿuÿxÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿxÿtÿsÿsÿsÿpÿnÿlÿiÿeÿ^ÿVÿMÿLÿQÿXÿaÿkÿvÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿoÿdÿ^ÿ`ÿgÿnÿsÿwÿtÿtÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿÿiÿcÿcÿbÿfÿqÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)})x(}(x(}(x)})x)})x)})x)})x*}*x*|*y)|*y*}*z*}*z*}*z*}*z+}+|,},|+}+}*}*}*}*}*}*}+}+~)})~)~)~(~(~(~(~(~(~(~(}(~(}(~(~(~(~'~'}'~'}&~&}'~'}&~&|&~&z&~&z&~%z%~%y%~%x%~%x$~$x$~$y%~%y$~$y$~$y$~%y%~%y&~%{&~&{&~&|&~'}'~'}'}'(}((}()}))}*)}+,},,},,},-}..}.~/|/~/|/}0|1}/|1|1}2{2}2{4}4{5}5z5}5z6}5{4}4|4}4}4}3~3}3~3|32|01|//|/.{.-{--{-,{,,|,+|+*|*+}++}+*}**}**}**}**}**~))~)'}''}''~''~()+I}k~ski
jkie}flsv|
q~ty|sl}ha}SLzHG|F<}@I|GE{</{((|)*|)(|(*|*+{,+{-,z--z*,{/5{4=z<7y;Lywsqqrsv{ruum[K<~87~D_lrh|]WvOFu@Bv?8w10x-8uD_prmPlNwjjoSlJHmAAn>NkUef|
__ iµo²²k²¬[¥ K®N³®N¤OW}salegc^jYWkXTlMHoB@q@BrCBq@Cobiexwb|}cyvio[m@:r;;v<7z25{7<|@E}[x}
qj
c\VS}MGuFHlHL[XgJsoNLGgI>wG8y>EuDGq?HoW~atyv8uEtwIX}VQV~ck}b{]}lymq}tqmhd`][Z~YX~XY~ZW~Z]~\~VE}?L}WYyUQwPQvPRvRUwY[{_}chylttx|poo}zpxwqvzq{}ssstuuussstx|
ut~xzwsysstsosnlugdy\T|M
N|S[wcmqxp
qtwwwwvvy~w{me|`bwhosvutqyxpqvv
xkeebybhrvrw
{}{{zzzxm*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ+ÿ<ÿsÿÿÿÿÿÿÿzÿ~ÿÿÿÿÿÿÿzÿÿÿÿÿ~ÿnÿvÿÿÿÿÿÿ}ÿqÿqÿqÿqÿoÿcÿYÿQÿOÿMÿHÿFÿ@ÿAÿJÿEÿCÿ>ÿ0ÿ'ÿ(ÿ)ÿ*ÿ)ÿ(ÿ'ÿ+ÿ*ÿ*ÿ+ÿ,ÿ*ÿ)ÿ*ÿ)ÿ+ÿ+ÿ-ÿ.ÿ1ÿ7ÿ:ÿ@ÿKÿkÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿrÿtÿmÿ[ÿIÿCÿCÿCÿJÿWÿeÿoÿkÿ`ÿUÿPÿPÿIÿAÿAÿ@ÿ:ÿ5ÿ/ÿ2ÿCÿ[ÿqÿÿyÿNÿcÿÿÿÿzÿzÿeÿGÿ=ÿEÿ>ÿNÿVÿPÿ]ÿyÿÿÿ¥ÿ±ÿ³ÿµÿ³ÿ°ÿÿ«ÿ¤ÿÿÿªÿÿ¦ÿ¡ÿ¢ÿÿÿÿtÿnÿjÿdÿaÿ\ÿbÿpÿgÿ\ÿQÿHÿEÿDÿDÿDÿDÿDÿPÿ|ÿÿÿÿ{ÿzÿÿ~ÿwÿuÿiÿ_ÿ\ÿYÿ]ÿ\ÿ_ÿPÿ4ÿ8ÿ;ÿ>ÿAÿBÿWÿoÿÿ
ÿÿÿÿÿÿyÿwÿuÿpÿcÿcÿaÿ]ÿ\ÿ[ÿ^ÿaÿfÿmÿpÿfÿBÿAÿ=ÿ@ÿBÿCÿ;ÿLÿAÿIÿGÿ?ÿRÿgÿvÿÿpÿeÿÿÿÿÿPÿPÿWÿKÿJÿVÿOÿKÿWÿqÿrÿrÿtÿqÿnÿiÿeÿaÿ^ÿ\ÿ[ÿ[ÿYÿYÿ[ÿ\ÿ\ÿ\ÿ\ÿ\ÿXÿVÿZÿ[ÿXÿUÿUÿSÿQÿPÿPÿRÿRÿUÿXÿ\ÿ_ÿcÿiÿnÿsÿwÿ|ÿÿÿÿÿ|ÿyÿvÿwÿxÿ{ÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿzÿwÿsÿsÿqÿqÿoÿnÿkÿfÿbÿZÿSÿNÿPÿUÿ]ÿeÿqÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿ~ÿwÿmÿdÿcÿgÿiÿrÿwÿrÿ|ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿrÿhÿiÿfÿbÿbÿjÿxÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)})x*}*x*}*x(}*x*}*w*}(w)})z+}+z*|*z)|)z*}*z*}*{*}*|+}+|*}*{*}*|*~*|)~)|)~)}(~(}(~(}(~(}(~(}(~(}(~(~(~(~(~(~(~(~(~(~'~'~(~(}'~'}&~&|&~&|&~&|%~%|&~&{'~%z%~%y%~%y$~$y$~$y$~$y$~$y$~$y$~$y$~%z%~%z&~%{%~&{&~&|&~&}&~&}'~''~'~'~''~')~)(~*,~,+}+,},-|.~.|.~/|/}0|0|1|1}1}0|1}2}2}2{2}2z4}4z5}5{5}5{4}4|3}3}2}2}3}3}2|2~0|./|..|--|--|-,|++|+*|*)|))}))}))})(}'(}((}()}))}))~)'~''~''~'''&()~,2}k
ukhzhijg}i}psugdy~|
q
^~_^~`g}dQ~QS}UM|LN|NJ{FE{?1{''{))|))})(}+({),{*+{*+z,-z,,{04|7U|zzxrpnzorsx|~~{vxqsvsuw}x}}jYw[ZrUNsGFtGAv72w?Ntjxpl\iecftjL@k>JkULhRmdh¨±j³°l¬¨h££\¤¢R£KHHPsZmkehmfmjbsjacZgSKoHHpICoBNk}gc`}`wrciigotmzzvve{=6|8:{>B{Na}zzx{{z
s{fdtjgk`\a_fVgjOnZ\[[vX96:>M{KCvFDpO`py~|u[}rw~txTG~RK6~GM}e~l}p~r}ts~qoke_][[~ZZ~Z\~\[~\]~[~Z~\~ZW|WUySQwPPvNRvRUzY~]~_|cjwnrsv|ono~zpxxpy{q|~ssstuuu
s
s
stw}w
v~{y~vrxqqtqotnlvgcy\R{PT{Y_vhro|pruwwvvv
v
y~w{mf{egwmusv{w}~pruvxo~iiizdbueorzrw{{zyz{|ri)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ(ÿ*ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ1ÿ0ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ(ÿ)ÿ/ÿ3ÿNÿÿÿÿÿ
ÿÿ|ÿÿÿÿÿÿÿÿwÿiÿnÿnÿfÿtÿÿÿÿÿÿÿÿaÿOÿOÿPÿTÿVÿ_ÿ]ÿOÿMÿOÿNÿKÿJÿKÿHÿGÿEÿ?ÿ1ÿ'ÿ'ÿ)ÿ)ÿ*ÿ#ÿ*ÿ+ÿ*ÿ*ÿ'ÿ&ÿ(ÿ+ÿ+ÿ+ÿ*ÿ+ÿ,ÿ+ÿ+ÿ.ÿAÿnÿÿÿÿÿÿÿÿÿÿÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿRÿPÿRÿXÿRÿMÿGÿEÿJÿCÿ6ÿ4ÿJÿmÿÿwÿ{ÿÿÿÿÿÿÿ£ÿÿ
ÿhÿiÿZÿOÿ@ÿMÿfÿÿ«ÿ°ÿ°ÿ²ÿÿ¥ÿ ÿ ÿÿ¡ÿ¬ÿ¤ÿÿÿ ÿ£ÿ¤ÿ¤ÿ¢ÿÿÿiÿ[ÿdÿoÿÿvÿqÿnÿfÿ_ÿSÿJÿHÿKÿKÿGÿBÿ@ÿ]ÿÿÿÿÿÿÿxÿuÿxÿxÿqÿvÿÿÿÿÿsÿPÿ4ÿ6ÿ;ÿ?ÿ?ÿDÿSÿiÿyÿÿ}ÿqÿlÿqÿuÿsÿqÿmÿgÿcÿgÿfÿ_ÿ[ÿ_ÿcÿbÿgÿbÿ\ÿ`ÿsÿeÿBÿ7ÿ;ÿ=ÿ>ÿFÿJÿQÿGÿ;ÿNÿxÿ{ÿdÿ{ÿÿÿÿÿ\ÿIÿOÿ>ÿ+ÿ@ÿTÿ]ÿdÿqÿqÿrÿsÿsÿoÿlÿfÿaÿ]ÿ\ÿ\ÿ[ÿ[ÿ[ÿ[ÿZÿ[ÿ[ÿ\ÿ\ÿ\ÿ\ÿZÿXÿUÿUÿQÿPÿPÿPÿOÿPÿRÿUÿYÿ]ÿ_ÿcÿhÿlÿrÿvÿyÿ~ÿÿÿÿ~ÿ|ÿyÿyÿ{ÿ{ÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿzÿvÿsÿqÿqÿqÿoÿnÿlÿgÿbÿ[ÿRÿPÿTÿYÿ`ÿiÿsÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿ}ÿwÿmÿfÿgÿjÿqÿuÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿtÿjÿnÿlÿiÿeÿcÿgÿqÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*}*x)})x)})x*}+x+}*y*}*y+}+z+}+z*|*z)|){*}*z*}*|*}*|)})|)}){)})|)~)|)~)|)~)}(~(}'~'}(~(}(~((~((~(~(~(~(~(~(~(~(~(~'~'~&~&}&~&}&~&|&~&|&~&|&~%|%~%{%~%z%~%y%~%y$~$x$~$x$~$y$~$y#~$y$~$y$~%z%~%z%~%{%~%{%~%|&~&}%~%}'~''~'~'~''~'(~()~)*~*)}+,}-,|--|.~0|0}0|0|1|1}1}0|1}2}2}2{2}2{3}3{3}3{3}3{3}3|2}2|1}1}2}3}2|2~/|//|.-|--|--|,+|+*|**|*)|)(}('}''}&'}''~''~''~'&~&'~'&~&'~''~'''&(~,1z;Exk|p
jjk
koquqyv}~}[N|LT|XT}GWb[ONNK|JK{FC{=/{%'}(*,Im8~#(|,,|)){+,{,.{/.y/2zTz wtpnm~{nqqtzz~yxxy~
`xVXrXZq\UqJFtGBv:>saohcd¤¥g¤ hg^__@Oc¦j®°l¯m¢ha ¢WQ¤¥N¦¥M OY[<fJ`azW~ sWle`ZJiFHmLKnGAkDofbuz_
]
_}b~muw|a5}5:{<={@H}Uflkcgif
`{``w[Yr\]m[Zg[_a`_`JQk\g{`I:C|?7{ACwFRpGHiexryw
}xRJ}T=-G
A|05|eu}ss~qnjfa^~][~ZZ~Z[~[Z~[\~[[~\~[~Z{VSyRMwMNvNOvRUzZ~]~`|cexjqsuzp~ml~|mzzp|{q}~ssstuu
u
s
s
stw}vu}|y~u
swsqspptnlvg`yXT{PWz\ctluo~psvwwvvvvy|w{m
g{gnwtzu|pquvwv~l~l
m{liwffujurrw{{zyz{y~nj*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ,ÿ-ÿ,ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ1ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ)ÿ1ÿ8ÿEÿYÿrÿuÿ}ÿ
ÿÿÿÿÿ~ÿ{ÿzÿÿ|ÿÿÿ¡ÿÿÿÿÿÿÿÿÿÿÿpÿWÿOÿQÿ]ÿcÿZÿXÿUÿiÿiÿ_ÿLÿFÿLÿKÿJÿGÿCÿ:ÿ-ÿ$ÿ&ÿ'ÿ)ÿ)ÿ9ÿ=ÿ(ÿ&ÿ'ÿ(ÿ(ÿ)ÿ(ÿ*ÿ/ÿ0ÿ-ÿ0ÿ/ÿ-ÿ=ÿaÿ~ÿÿÿÿÿÿÿÿÿÿÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿrÿWÿ[ÿZÿ]ÿ_ÿ`ÿYÿMÿDÿDÿAÿ@ÿnÿÿÿÿÿÿÿÿ¤ÿªÿ¦ÿÿÿsÿ
ÿÿ ÿ¥ÿÿ`ÿlÿÿªÿ¬ÿ¬ÿ©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿYÿ<ÿAÿJÿrÿÿÿ}ÿpÿiÿbÿTÿJÿHÿFÿDÿEÿHÿAÿPÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿtÿgÿ>ÿ2ÿ5ÿ7ÿ<ÿ<ÿAÿFÿRÿ\ÿ]ÿ[ÿ^ÿ`ÿ\ÿVÿYÿYÿTÿOÿSÿTÿWÿXÿZÿ]ÿWÿEÿBÿLÿUÿ_ÿlÿaÿ9ÿ;ÿ6ÿ3ÿ1ÿ=ÿYÿ[ÿeÿWÿVÿpÿÿÿÿÿÿ
ÿcÿIÿJÿEÿ8ÿ=ÿ@ÿ4ÿ=ÿfÿtÿtÿtÿrÿnÿiÿfÿaÿ^ÿ]ÿ[ÿZÿZÿ[ÿ[ÿYÿ[ÿ[ÿ\ÿ]ÿ[ÿ[ÿZÿYÿUÿSÿPÿOÿOÿLÿOÿQÿRÿUÿZÿ^ÿaÿdÿgÿjÿpÿtÿxÿ}ÿÿÿÿÿ|ÿ{ÿ|ÿ|ÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿ|ÿxÿtÿsÿsÿqÿpÿpÿnÿiÿfÿ`ÿXÿRÿQÿWÿ\ÿdÿnÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿmÿiÿiÿmÿuÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿ|ÿlÿmÿoÿnÿmÿkÿhÿhÿnÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*|*x*|*x*|*x*|*x*|*y+|+z+|+z*|*z*|*{*|*{)|){*|*{*})|*}*|)})|)})|)~)|(~(|'~'~'~'~'~'~'~(~(~(~(~(~(~(~'~'~(~(}'~'}'~&}&~&|&~&{%~%|$~$}%~%}%~%}%~%}%~&|%~%|%~%{%~%|%~%|$~$|$~$|#~#z$~$z$~$z$~${$~${%~%{%~%{&~&~&~&~$~$}%~%}&~&~&~'~'~''~'(~()~))}++}+,},~-}.~.|.{.|0{1|1}1|1}1|1|1|3|2|2{1|1{2|3|2|1|2|2}3|3}0}0~0}00}0.}.-}--}-,{,,{+*}+*}*)}))}('~'&~%&}&&}&&~&%~%&~&%~%%}%&}&'~'(~&')~*+w7Fo[um{t|dvmqptxnulmlkolsvx¢¤z¡|~}{z^|WY}]a~ba~^fglp_[
SMHHB}7*{''{'(|(&}''}''}'&}'(|(*{+-{/,z5Lydxvtrqmj~|k}oqq
u{||zrudaqc\n[_o]VrJBr@?q^oheehlh[hq]¡¦U¨X}c¥k©¤ig_W TVY^
bWAiCJ`aWP{ qYjbeQGkBAjFHgD@aa_^\`
dmxvto|hH//|28{;>|@CJRTWXWUzT
SuPNrQ
SqT
WrYPo:+o<HrNOwSMy@Gu?9u4@uWJoQOgN[jp{sxyeK|CE@38}E^}pt}ts~qn~ie`]~\[~[\~\[~ZZ~[Z~Z~[~Z~ZWSP{NMyNNyOPySV}Z}^`{c
hwjosrwo|llm~|p{|r~r
rsstt
ss
r
rvy}~
vv|~x|usvrqsposmjue_zXS{RVx^esnxo
pruwv
uuuv
x|v{nkykow
{snrv
y~nmnzomvonvilrqyoty
zzzz
z}}pik*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ&ÿ(ÿ*ÿ*ÿ-ÿ5ÿEÿYÿnÿqÿaÿ`ÿ[ÿXÿ^ÿcÿdÿ\ÿSÿXÿ[ÿ\ÿ]ÿsÿÿ¡ÿÿÿÿÿÿÿÿÿÿ`ÿUÿWÿeÿiÿiÿdÿfÿdÿgÿhÿrÿzÿqÿmÿhÿeÿ]ÿKÿAÿ0ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ,ÿ/ÿ:ÿVÿhÿtÿÿÿÿÿÿÿÿÿÿ~ÿyÿyÿyÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿuÿjÿgÿfÿfÿdÿ`ÿ^ÿ\ÿZÿQÿFÿ=ÿ;ÿ[ÿÿÿÿÿÿÿÿÿÿpÿfÿaÿ\ÿeÿÿ£ÿÿÿÿÿÿÿÿ¢ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿgÿMÿNÿfÿ¦ÿ£ÿÿÿzÿoÿeÿUÿHÿDÿUÿtÿÿÿhÿjÿyÿÿÿÿÿÿÿÿÿÿÿyÿrÿkÿiÿfÿQÿ/ÿ,ÿ.ÿ/ÿ5ÿ8ÿ;ÿ=ÿBÿIÿMÿPÿQÿQÿPÿPÿQÿOÿNÿPÿRÿSÿQÿCÿ0ÿ0ÿ>ÿ?ÿ8ÿ;ÿ>ÿ:ÿ?ÿCÿCÿ=ÿ>ÿ=ÿAÿJÿGÿIÿXÿ[ÿJÿ^ÿkÿÿÿÿÿuÿXÿFÿIÿRÿDÿ<ÿNÿYÿlÿtÿtÿsÿqÿnÿiÿdÿ_ÿ]ÿ\ÿ[ÿ[ÿ[ÿ[ÿ[ÿZÿZÿ[ÿ\ÿ\ÿZÿXÿYÿUÿRÿOÿNÿMÿNÿNÿNÿOÿSÿVÿYÿ\ÿ`ÿcÿfÿhÿmÿrÿvÿ{ÿÿÿÿÿÿ}ÿ}ÿ~ÿ~ÿÿÿÿ
ÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿ}ÿyÿrÿrÿqÿqÿpÿoÿmÿiÿdÿ^ÿXÿUÿUÿXÿ^ÿgÿpÿxÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿzÿsÿnÿmÿlÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿqÿnÿqÿpÿoÿpÿoÿmÿjÿlÿtÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+|+x+|+x+|+x*|*x*|*y+|+z+|+z*|*z*|*{*|*{)|){)|){)})|(}(|(})|(}(|(~(|(~'|&~&{'~'{'~'~'~(~(~(~(~(~(~(~'~'~'~'(~(&~&~%~%|%~%}%~%~%~%~%~%~%~%~%~%}%~%|%~%|%~%{$~$|$~$}$~$}$~$|#~#z$~$z$~$z$~${$~${$~${$~${$~$|$~$|$~$|$~%|&~&|&~'|'~'~'~'~(~(~(~(~)}++}+,},~-}.~.|.~-|/~1|1}1|1}0|0|0|2|1|1}2|2}1|0|/|1|0|/}0|.}.}.~/}/.|--|-,},,}++~++~))}))})(}('}&%~%%~%$~$$~$$~$$~$$~$$~$%}%&}&%~'&&*.x4BnZrdzjgb}^yVxWYv\YvOCtKQuTZxm{}~}g|UR}[i~bdjhcdjtpumplw
xzgD~%}'&}*
'}('}&'}'(}'&})(|)({*+z.7yEVxftvsqnml|kmopqtwwxvuq~pxxnvpnb]pZQpH?q?}ogbe|mm\_p`_mcx`V¢¦WYzacd^s¡Y Z¢``¡_`|d`T^y¡ªZ£PSvk`]LhDKc]xU¡S~V ]]`d|xntntfay^V~4-~.)|*.{38}<B~FJMMM~NM{NOzKH{?3z,/x7
BqF7l?FlF@mKOkLIn>BsMIt@LmRTh_bktp
wxKAJ
Za
P}Zo}pq}ss~qn~je_]~[Z}ZZ}ZZ}ZZ}ZZ~ZZ~X~WT{PNyMLzLMzMN{RUX}\a{c
dwgjsquoz~lkl}oq~rrss
t
t
ss
r
rv
z}
wy~{v{sqvqpspnslhud]yXSzV\v`hqrynoruvvutu
v
xyrznkww{xprtv}uln{mnxppxpmzotx|x|}z~shgj+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ'ÿ%ÿ%ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ2ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ,ÿ1ÿ@ÿVÿuÿ|ÿuÿfÿ^ÿWÿYÿfÿdÿWÿQÿLÿEÿIÿVÿUÿ\ÿdÿxÿ{ÿ|ÿÿÿÿÿÿÿlÿZÿ]ÿ`ÿkÿgÿiÿkÿnÿlÿbÿhÿoÿpÿkÿoÿpÿsÿhÿmÿrÿÿ|ÿdÿ<ÿÿ)ÿ,ÿ,ÿ)ÿ(ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ-ÿ;ÿNÿZÿbÿxÿÿÿÿÿÿÿÿÿÿ|ÿ
ÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿÿÿÿÿÿÿÿ¡ÿÿÿÿÿÿÿ~ÿhÿ]ÿ\ÿYÿPÿHÿ=ÿuÿÿÿÿÿÿÿnÿZÿ`ÿpÿpÿkÿnÿpÿÿÿÿÿ¦ÿ¡ÿÿÿÿÿÿÿÿ~ÿsÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿpÿhÿÿ¥ÿÿÿÿÿyÿoÿcÿRÿDÿCÿHÿnÿÿÿÿyÿÿÿÿÿÿÿÿÿyÿsÿkÿfÿ^ÿ[ÿZÿVÿ@ÿ,ÿ+ÿ(ÿ'ÿ)ÿ)ÿ+ÿ/ÿ4ÿ7ÿ9ÿ<ÿ?ÿ?ÿ?ÿ?ÿ<ÿ8ÿ1ÿ,ÿ*ÿ+ÿ.ÿ3ÿ5ÿEÿUÿ>ÿ6ÿ;ÿ?ÿEÿIÿKÿ?ÿBÿQÿNÿJÿ<ÿLÿLÿNÿUÿVÿ_ÿhÿyÿÿÿrÿ<ÿ=ÿEÿRÿbÿ[ÿ`ÿoÿpÿqÿsÿsÿqÿnÿjÿeÿ_ÿ\ÿ[ÿZÿZÿZÿZÿZÿZÿZÿZÿ[ÿ\ÿYÿWÿUÿRÿOÿNÿJÿIÿIÿKÿLÿNÿRÿUÿYÿ]ÿ`ÿbÿdÿgÿiÿpÿtÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿzÿvÿrÿqÿqÿpÿpÿnÿlÿhÿdÿ\ÿXÿUÿWÿZÿaÿjÿrÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿwÿqÿkÿqÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿmÿqÿqÿrÿrÿtÿvÿvÿwÿxÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+|+y+|+y+|+x+|+y+|+y+|+z*|*z*|*z*}*{*}*{*}*{)}){(}(|(}(|(~(|)~)|(~(})~)}'~'}(~'}'~'~'~'~(~(~(~(~(~('~''~'~'~'~&~&}%~%}$~$~$~$~&~&~%~%~%~%~%~%~$~$|$~$|$~${$~${$~$}$~$}$~${#~#{$~$z$~$z#~#|$~$|$~$}$~$}%~%}$~$}$~$}$~%}%~&}&~&}&~&|'~'}'~(})~)|*}+|+}+}+}+~-}.~.{.}0{0}0|0}1|1}0|1|1|1|0}0|1}1|1|1|0|.|-|-}-|-}-|,-|.-|,+|++}+*}*)}++})(}('}'&}&'}&%~$#~#$~$$~$#~##~##~#$~$%}%%|%&|&~(+|1=qUrdzv_sgfUQw]q}t~c{]~]|QzIJz[bzcs|p~~}q{SP|Wl~umpmpnmmsmojrtmfsr~|y`7%}(+}*+|+*~(1}9-|)(|()z.;yMZxc
vspnnonklo}r}}srlHrXbqf¡wpq¥t¦¬rª¦nkwam^ZoSLpIjogc}fr[ksi|gyzdYQ¡£V\ b c yaop`|bc~ _\XYv uV¡RQ¢Uzq^j`fPCcT}S¢J¥sN
¥Y\`funnhasZWxQN~F0~)(}(
'}(
'|&'})*|))~(('&')++.}16x8=nEJbNDa>:f=<kHXnUHqI:r0NpMEjCRfdjj}{udNBBC}MV{`n}rrsrqmid~^\}[Z|ZZ|ZZ|ZZ|ZY}YW~UTQ}OM|JJ{IK|LO~S~VY{[`yceveksnroy~jklopqrtt
trss
qrvz{xz~yuzrpspprposkhud]yWUyZ]ubkpt|npruutt
st
v}xwqxozv~rtuv
yqr~v
z||||~~{vqnooomlfefl,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ$ÿ$ÿ&ÿ'ÿ'ÿ(ÿ-ÿ6ÿNÿlÿzÿuÿqÿxÿsÿnÿvÿwÿwÿuÿiÿbÿ\ÿGÿ/ÿ@ÿjÿoÿsÿ}ÿnÿÿÿÿÿÿ}ÿXÿGÿNÿYÿuÿqÿsÿÿuÿmÿkÿsÿ
ÿvÿgÿgÿmÿvÿ~ÿvÿfÿlÿyÿvÿ
ÿÿÿÿaÿ7ÿ$ÿ(ÿ+ÿ+ÿ+ÿ*ÿ0ÿ>ÿ0ÿ'ÿ)ÿ)ÿ)ÿ-ÿ;ÿNÿZÿiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿzÿvÿzÿÿÿ`ÿ5ÿOÿYÿfÿqÿxÿÿ¥ÿ°ÿ°ÿ±ÿ©ÿ¡ÿÿÿnÿcÿ^ÿ\ÿPÿIÿcÿÿÿÿÿÿÿvÿ]ÿuÿÿÿÿÿÿÿÿÿÿÿ ÿÿzÿxÿ~ÿÿ}ÿxÿtÿqÿÿÿÿÿmÿtÿÿÿ¢ÿªÿ¦ÿ¡ÿÿÿ~ÿÿÿÿÿÿ|ÿvÿqÿlÿdÿXÿEÿgÿ
ÿÿÿ
ÿ|ÿÿÿÿÿÿ
ÿÿwÿnÿgÿbÿ]ÿUÿOÿIÿCÿ:ÿ.ÿ(ÿ&ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ*ÿ*ÿ*ÿ*ÿ,ÿ.ÿ1ÿ6ÿ:ÿ=ÿCÿDÿLÿHÿGÿ<ÿ5ÿ9ÿNÿGÿ<ÿ;ÿ@ÿGÿ:ÿ3ÿAÿIÿHÿTÿdÿwÿÿvÿeÿ\ÿVÿkÿhÿQÿVÿ]ÿfÿqÿrÿsÿrÿqÿmÿiÿdÿ^ÿ[ÿZÿZÿZÿZÿZÿZÿZÿZÿZÿYÿYÿWÿUÿRÿOÿNÿKÿJÿJÿJÿKÿLÿOÿSÿVÿYÿ^ÿ_ÿbÿbÿdÿhÿlÿqÿxÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿuÿrÿpÿpÿoÿoÿmÿjÿfÿbÿ\ÿWÿUÿYÿ_ÿfÿmÿuÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿ{ÿvÿpÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ{ÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,|,z+|+z+|+y+|+z+|+{+|+|*|*|+|+|*}*}*}*})})})})}'}'|'}'|'~'|'~'|'~'}'~'}'~'}'~'}'~'}'~'}(~(~(~(~(~()~)(~('~'&~&&~&%~$%~&%~%%~%%~%%~%%~%~%~%~$~$}$~$}$~$|#~#|$~$}$~$}$~$|$~$|$~$|$~$|%~%{#~#{$~$|$~$|$~$|$~$|$~$|&~&|&~&{&~&|&~(|)~){)}*z,},|,},}-}-}.}.}.}.}.|.}/|/}.|/|0|0|0|0|/|/|/|/}.|.}-|,~+|+~,|,,|,,|,,|+*}))}))}((}('}'&}&%}%%}$#~#"~""~"~"~"~"~""~""~"#~#$~$%~%~%(z+5sFdhyxbsw`||ezzh{vjtjk_UnE<zJ~drzyu}q~}lM|O]~ipszxwrmqtqjg
nyz}jozuz
oxrZ.z+~'*~((}))|)+|++z.@yPYxpvrom}~kmmkjnzpwursyrt\GtJRqZ¨kn¦o±q®¯q©ltkd¦`lZ§TnN¢amiegt^ivc]Z
[
UWp[h¡tax¡k`t ^¤ª\¨_ZfP Ygk¡a\XTO TztXqp]nebXEagW
¡M£O~¢X _ewhk[SrPOvJBz9.}'$}&$~%
%~$%}&%~%''(')(''~)-}./y79t23q6
7n?
FiRDc56b4JgOJlY>o3+r1?nP[hc
be^jnjV|W~ir~d\~[`~lq}sqpmid~^[}ZZ|ZY|YZ|ZY|YY}YV~TQM~KJ}HH}JKMPS}VZz\
^x`bvdgskqov{jjknoqrtt
trss
r
tx|z
x}~~ysyrqspornlsieua\wXVw\`tfmot{npruutt
su
v{vp~zwqtvut
vspnjihhfg
hhhgfeceh,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ(ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ$ÿ$ÿ%ÿ%ÿ%ÿ*ÿ0ÿDÿbÿxÿwÿqÿvÿ|ÿ~ÿÿ|ÿzÿxÿtÿpÿhÿ\ÿPÿHÿQÿaÿjÿ}ÿ~ÿjÿwÿÿÿÿÿzÿWÿQÿWÿqÿyÿmÿrÿ{ÿqÿqÿqÿuÿyÿtÿwÿuÿÿoÿpÿ~ÿyÿtÿoÿ_ÿuÿrÿbÿmÿÿÿÿÿrÿNÿ3ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ4ÿGÿTÿ^ÿxÿÿ
ÿÿÿÿÿ{ÿpÿtÿxÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿvÿpÿmÿqÿzÿÿÿWÿ:ÿOÿ]ÿsÿÿÿÿ°ÿ¬ÿ®ÿ§ÿÿ]ÿIÿMÿNÿQÿNÿNÿ_ÿÿÿÿÿÿÿjÿPÿ\ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿ{ÿjÿkÿoÿxÿÿÿ§ÿ«ÿ§ÿ£ÿÿ{ÿSÿJÿJÿGÿYÿÿÿÿÿÿÿÿÿÿ}ÿsÿoÿnÿnÿoÿfÿPÿAÿ]ÿÿÿÿÿ
ÿ{ÿÿÿ
ÿÿtÿbÿGÿ:ÿ4ÿ3ÿ4ÿ2ÿ.ÿ(ÿ%ÿ%ÿ#ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ$ÿ%ÿ%ÿ%ÿ&ÿ%ÿ'ÿ(ÿ*ÿ,ÿ/ÿ0ÿ1ÿ5ÿ?ÿ5ÿ3ÿ/ÿ*ÿ+ÿ.ÿ2ÿ>ÿ^ÿRÿ?ÿ@ÿQÿJÿ<ÿQÿGÿ-ÿ+ÿ4ÿAÿZÿ_ÿRÿTÿIÿOÿtÿrÿÿÿvÿnÿSÿQÿ]ÿhÿpÿrÿqÿpÿmÿiÿdÿ^ÿ[ÿZÿXÿXÿWÿWÿXÿXÿYÿYÿXÿXÿUÿSÿPÿLÿJÿHÿHÿHÿIÿKÿMÿPÿSÿVÿZÿ\ÿ^ÿ`ÿaÿcÿfÿjÿpÿuÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿwÿsÿrÿqÿpÿoÿnÿlÿiÿeÿaÿ\ÿXÿXÿ\ÿ`ÿhÿoÿvÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿ
ÿÿ~ÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,|,z+|+z+}+y+}+z+|+{*|*|*|*}*|*}*}*|)})|)})}(}(}(~(|'~'|''|''|'~'}'~'}'~'}'~'}(}(}'}'}'~((~()})(}((}('}'&}&&}&&~&'~'&~&&~&&~&%~%%~%%~%%~%%~%~#~#}#~#}#~$~$~$~$~$}%~%}#~#|#~$|%~%|%~%|%~%{$~${$~${$~${$~$z&~&z&%{&&{(~({)~){(~*|,~,|,},}-}-}-{-}-{-}.|.}/|/}/{/}.{.}-{-|-{-~-z--z--|,+|+*|*+|+*}**}))}))}((}(&}&&}&&}$$~$~#~#~#~#}!~!~!~!~!~!~!~!"~""~"#~#$~%|%~${(/v>Wmsxfqtaz
|b|zd{zezrhjfi[NnIW{i{vz}p~i~KN}Pc~nnhikptuqfuxyposvkeX`plWt
jmldgw?~2'~))|)({*5zALxYcwt}r}n|vjnpkstlvrmv}jjlo{urrprpzt
uWQr`¡rm¤o®²n°©jCi=DnE®DoK©PoP¥[nlhiT:i=dcs{Y
Y{\xyZ|]h_aaiax ^¢TOZ~UiFHoOkjbZXZtr_sk_hk`oadHFeSl[x P{£rTn¡y\y~d}ckB4q0.v,*z((|'&}$#}$$~$%~%$~%&~&%$%'',/1~02{24v73u-(x&&|)*y.El_
PaXY^ZYbVIj,+o-9oQPjTLf\yk}}uyzSzQO{Xc~p~pq}pm~hc~]Z}XW|WW|WX|VX|XW|WU~QOKJGFFHJLOR|UY{[
^x_`uaerinntyiijnpqrtt
trrrruy~}yz~|~v
syrqronqmksheu_[wXXv^cqjrny~npsu
t
s
s
t
sv||y}st
vsoni
e
c
cdehilnoponkhhff,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ%ÿ'ÿ'ÿ,ÿ8ÿLÿkÿtÿrÿrÿwÿ{ÿ|ÿzÿzÿ|ÿyÿxÿqÿlÿbÿVÿNÿJÿ[ÿvÿÿÿxÿuÿÿ}ÿ}ÿÿyÿYÿGÿGÿSÿiÿnÿoÿkÿeÿkÿfÿjÿsÿmÿhÿlÿ~ÿÿrÿoÿsÿoÿhÿiÿgÿGÿjÿnÿTÿnÿÿÿqÿhÿfÿtÿ?ÿMÿDÿ)ÿ(ÿ,ÿ*ÿ)ÿ2ÿ<ÿLÿYÿfÿ
ÿÿÿzÿyÿyÿ{ÿuÿlÿjÿnÿnÿjÿkÿkÿnÿrÿÿÿÿÿÿ
ÿzÿqÿtÿrÿnÿvÿÿÿeÿ`ÿvÿÿÿªÿ¯ÿ¨ÿwÿiÿQÿBÿ=ÿ=ÿ@ÿPÿYÿZÿ[ÿÿÿÿÿÿ
ÿVÿ8ÿAÿSÿgÿwÿ{ÿvÿrÿpÿoÿpÿsÿpÿjÿlÿsÿ~ÿÿÿÿÿÿÿÿÿ}ÿTÿ=ÿ>ÿNÿ{ÿÿÿÿÿÿÿÿ~ÿcÿSÿ\ÿ`ÿbÿeÿfÿUÿHÿBÿFÿWÿsÿwÿqÿiÿ`ÿjÿpÿuÿuÿSÿ4ÿ0ÿ1ÿ/ÿ,ÿ*ÿ(ÿ(ÿ'ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ%ÿ%ÿ$ÿ&ÿ(ÿ+ÿ-ÿ.ÿ1ÿ2ÿ4ÿ6ÿ3ÿ2ÿ2ÿ0ÿ.ÿ(ÿ&ÿ%ÿ%ÿ'ÿ)ÿ+ÿ<ÿSÿXÿNÿVÿTÿ\ÿWÿHÿ?ÿ/ÿ2ÿ@ÿJÿOÿRÿXÿRÿ\ÿlÿsÿyÿÿÿvÿmÿYÿMÿWÿnÿrÿqÿqÿmÿhÿcÿ]ÿZÿXÿWÿWÿWÿWÿWÿWÿXÿWÿWÿWÿTÿPÿNÿIÿGÿFÿEÿEÿGÿIÿLÿOÿRÿUÿYÿ[ÿ]ÿ]ÿ_ÿaÿeÿiÿkÿqÿwÿ}ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿvÿsÿrÿqÿoÿnÿmÿkÿhÿcÿ^ÿ[ÿXÿZÿ`ÿeÿlÿtÿzÿ~ÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿÿ
ÿ
ÿÿÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+}+z+}+z+}+y+}+z*|*{*|*|*|*}*|*})})})})})}(}'}'})~)|(~(|('|''|(~(}'~'}'~'}'~'}'}'}'}(}(}()}))})(}((}((}('}'(}('~'&~&'}'&}&&}&&}%&~&%~%%~%%~%~$~$}$~$}$~%~%~%~&~&}&~&}%~%}%~%}%~%}&~&}$~$}%~%}%~%{%~%{%~%{&~&{%&{&'{(~)|)~*|*~+|+~+|-}-}-}-}.~.}.~.}-|-}/|/}-{-}-{-}-{-|-{-~.},,},,|+*|))|)(|()})(}((}('}&&}&%}%$}$%}$~#~#~"~"~#~#}!~!~!~!~!~!~!~!!~""~#"~"$%|(+u5Gkaseq
sev{h|yfyye{xeusfj`gUJoJc|zzu}z{|~nM}EL}]h~ii~jebdgjmluwrc
xoiGPw_E}ktdfr[hvhn|vZ:}++|+4z?KxW`vs~vqywmvsjljhkhjcdjifkinkqukwxo|quqrnntrxsqlzn¡m¢kw¢alw«Qp=§7r@¢Xqn pph`pljj`9l@afo^r[mj]ikapnbjkcw`¤VM¢}U{wadHlA=oMrjb]^yeM/n3AmQah_PiJKgUo`zrZlb][dcppkkJr,/w0/y+*z((|'&}&%}$%}%%}&%('()+02~12|15z74v.+x*){%&')+)+7yAFnQQcUM^Vg`CAh@JmMRkSMgXVm_
]yfk{toaKVi~rr~pmgc~]Y}WW|WV|VW|WW|WW|VS~OMHFDBCEHK~NR{UXzZ]w^^u`drhknpvi|ijnpqrtt
t
rrrsv
z~~y{z}urwqqromqmjshdu_XvZ_tagpoum|n
psut
s
r
s
tv|tsrlig
d
e
d
e
hlouy}~~}}xqlmlnn+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ&ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ$ÿ%ÿ'ÿ*ÿ3ÿCÿ[ÿnÿoÿoÿvÿ{ÿ{ÿwÿrÿuÿwÿvÿwÿuÿtÿgÿ]ÿUÿJÿIÿiÿÿÿÿsÿÿ|ÿzÿ|ÿ}ÿbÿIÿGÿPÿ`ÿaÿcÿfÿjÿfÿfÿhÿcÿUÿNÿVÿYÿXÿcÿgÿQÿ]ÿYÿWÿQÿVÿDÿ>ÿcÿoÿ]ÿÿÿÿdÿWÿgÿ]ÿhÿfÿYÿVÿIÿ9ÿ/ÿ3ÿ>ÿJÿVÿZÿ~ÿÿ}ÿzÿvÿuÿvÿrÿpÿoÿlÿiÿeÿcÿbÿaÿ^ÿ_ÿaÿgÿeÿcÿbÿyÿ}ÿqÿlÿnÿmÿlÿuÿÿ}ÿsÿÿÿÿRÿSÿ[ÿQÿ>ÿ:ÿJÿkÿÿÿ
ÿrÿcÿÿÿÿÿ{ÿÿpÿLÿQÿkÿvÿÿÿyÿnÿiÿfÿbÿeÿjÿiÿoÿyÿÿ¦ÿÿÿzÿvÿqÿqÿkÿMÿ=ÿ:ÿ<ÿKÿcÿuÿÿ
ÿÿÿÿÿpÿDÿ/ÿ,ÿ/ÿ?ÿcÿgÿ^ÿYÿ[ÿhÿÿÿÿsÿfÿ]ÿfÿmÿjÿ_ÿFÿ*ÿ-ÿ0ÿ-ÿ+ÿ*ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ"ÿ#ÿ%ÿ)ÿ)ÿ*ÿ*ÿ/ÿ4ÿ5ÿ6ÿ8ÿ>ÿFÿCÿDÿ<ÿ+ÿ'ÿ(ÿ&ÿ&ÿ&ÿ(ÿ)ÿ)ÿ)ÿ0ÿ7ÿ?ÿIÿYÿ]ÿUÿ]ÿcÿPÿ@ÿEÿLÿFÿMÿWÿkÿ\ÿSÿWÿSÿ^ÿkÿyÿKÿ@ÿ]ÿLÿfÿkÿfÿqÿoÿmÿgÿcÿ]ÿYÿWÿVÿVÿVÿVÿWÿWÿWÿWÿVÿUÿRÿPÿKÿFÿEÿBÿAÿBÿDÿGÿKÿNÿRÿUÿXÿZÿ\ÿ]ÿ^ÿ^ÿaÿfÿjÿoÿuÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿ
ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿyÿtÿpÿrÿqÿoÿmÿmÿjÿhÿcÿ^ÿWÿYÿ_ÿdÿiÿqÿwÿ}ÿÿ
ÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+}+{+}+{+}+{*}*{*|*{*|*})|)})|)})|)~)}(~'}'}'}'}(~(}'~(}((}((}(~(|(~(|(~(|(~(~'}(~(}(~(}(~)})~)})~(}(~(}(}(}(~(}((}'&~&(~('}''}'&}&&}&'~'%~%%~%&~&~&~&&~&&~&~&~&~&&~&&~%~%~%~%~%~%~%~%~&~&}%~%{%~%|&~&|&}&|&}&|&'{(({(~)|(~)|*~*|+~+|-}-}-}-}.|.}-|-}-{-}.{-}-|-}-{-}-{-}-|-},|+*|**|**|))|)(|((|((}((~(&~&&}&%}$#~##~""~"!~! ~ ~ ~ ~ ~ ~ ~ ~!~"~!~!~"~"#$~$'x0Bo\nfl
mbq{e{thqrdvxdxyewodf\gRDpB}n{
yxykxxyqVF|HQ|bh{jm|fbyUTyNDyKH|HE}FJ}E>887;BA
F^ZJ{pua~ttpznaT~KH~A;|>J{PVwyq}yntqmqqissgohhe_j[ZnWYoZanaWnHIqqurpotiftkwsuo|m[¨OnV«Nr?¦<rLvppoo_pljsNlcekezez]w]mgab]ebffeja£Y N£xSl§e]h¥^gH¡Bn=;nGZjgvgd
b{`f<,o,3oGiivniYXifaY
[qldmenU>u)*x,,z**z('|&%}%$}#$}$
#}"$~&%&,:BE}EDyGGyB
=y5
)|&&&&&()'*/}3:wDDkJ
Wbd`]ZVbF@l;
@mLWlXZm\>tB[{T28fQN]}irpmhb~\X}VT|TU|WW{WW{WU{TQ~NJEA@@AC~FJ|MQzT
XyYZw[\v]`schpmrlz|j~kn
oqrtt
trrrsw||{{}x|squpqqpmqljsgau]Zv[`rdjoqxm}nrsut
s
s
s
vzvnlhgdghkkntzz
xz{
|y{zrmoqvx+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ!ÿ"ÿ!ÿ!ÿ!ÿ"ÿ#ÿ$ÿ%ÿ,ÿ<ÿVÿqÿpÿhÿnÿtÿyÿtÿmÿmÿpÿrÿsÿwÿxÿvÿnÿbÿVÿMÿ=ÿ:ÿhÿÿÿrÿrÿwÿrÿwÿyÿkÿNÿCÿHÿUÿgÿiÿlÿvÿdÿWÿGÿDÿGÿAÿJÿPÿKÿKÿJÿJÿDÿGÿAÿ>ÿ;ÿ;ÿ@ÿFÿ?ÿOÿ]ÿ:ÿkÿ{ÿ
ÿÿÿÿ~ÿtÿcÿTÿMÿIÿEÿAÿ@ÿFÿJÿQÿrÿÿzÿvÿsÿrÿoÿnÿpÿrÿnÿiÿeÿ_ÿ[ÿYÿOÿNÿTÿ`ÿ^ÿRÿFÿ2ÿGÿjÿmÿnÿjÿhÿhÿnÿvÿÿ|ÿcÿ@ÿKÿNÿNÿEÿHÿoÿÿÿ¢ÿÿÿjÿ\ÿoÿ
ÿÿÿyÿ5ÿ1ÿLÿwÿÿÿÿÿwÿkÿdÿ^ÿ[ÿ^ÿcÿ\ÿfÿÿÿÿÿ|ÿoÿ\ÿaÿlÿ\ÿFÿBÿ@ÿDÿMÿWÿaÿkÿqÿwÿvÿuÿnÿUÿ8ÿ+ÿ-ÿ;ÿRÿhÿtÿ~ÿwÿdÿ^ÿdÿxÿÿÿÿÿ{ÿiÿ_ÿSÿ4ÿ&ÿ)ÿ-ÿ,ÿ*ÿ)ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ#ÿ$ÿ(ÿ/ÿ:ÿBÿDÿCÿ?ÿ:ÿ8ÿ7ÿ6ÿ0ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ*ÿ1ÿ6ÿ:ÿ=ÿ8ÿGÿQÿXÿ[ÿYÿVÿOÿIÿKÿCÿLÿYÿZÿRÿPÿFÿ6ÿQÿ\ÿMÿVÿjÿYÿJÿYÿrÿsÿpÿnÿhÿbÿ\ÿXÿVÿTÿTÿTÿSÿVÿVÿUÿUÿTÿSÿPÿLÿJÿEÿAÿ@ÿ?ÿ@ÿBÿEÿJÿMÿQÿTÿXÿYÿXÿZÿZÿ\ÿ^ÿaÿeÿjÿqÿwÿ}ÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿvÿsÿqÿqÿqÿpÿmÿlÿjÿgÿaÿ]ÿZÿ]ÿaÿfÿkÿsÿ{ÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ|ÿyÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,},{+}+{+}+{+}*{*|*{*|*}*|*})|)})|)~)}(~'}(})}(}'~'}(~(}''~))~)~)~(~(~)~)|(~(~(}(~(}(~)})~)})~)})~*}*~)}(~(}((})*}((}()}))|('|''|&'|''~''~''~'(~(~'~''~''~'~'~'~'~'~'~'~&~&~'~'~&~&~&~&~&~&}&~&{%~%|%~%|%%|''|''{(({(~({)~){*~+{+~+{+}+|,},|-|-}-|-}-{-}-{-}-|-}-{-},{,}+|+}+|**|*)|))|))|('|''|'&}&&~&&~%#~#$~#"~""~!"~"!~!!~!~ ~ ~!~!~!~!~!~!~!~!~##|#&x)7pOmfriekqfvsfligjkgoqgvwctkccViK?r;j|t{yrpxwbKH}Pa|g_z`hyWJxFDwICwHNwKLwKMyEC{B@|>?|>E@A
JD:<wvt}zrbU~MF}DC}CB{FLxgqwunsqmnlknoingib[kX[oSNqNCsLOrI9s,FsjjsfjshjtowszGr< BsI¡IsGmq¢r££r¡rsap]poCo+=l_Y{w^h_e\YhXZeSu]¡UTp¤[^P¤Yg\¡Ml;:oBIlRWi[agdfejkcaMi7,p7Ik\icnvb~vfjyc\
z]}ek\pJ,v%(y**{*(z'&|%$}%%}$
#}##}""~"!~&/~7:~<?~>~=~=~;8~1('''(('(&*1;=}79xBGqI[e^[^\GeFAlDKqKIrV`oR@qYRucn|^J\stpmgb~\W}UT|ST|TT{TT{TS{RN~KGC@??~@A}EI{LPyT
WxXYvZZvZ\s^bpgnltxj}~knoqrtt
trrr
ty||{{|vzrquppqpoqljsgau]Zu\`phnmu{l
nrst
t
s
r
ryxnkikkmmqv|}~|{yyvxyq|r
twz{y~|rnnt{~~
,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ(ÿ)ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ)ÿ*ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ#ÿ"ÿ#ÿ)ÿ0ÿFÿfÿtÿmÿkÿpÿtÿsÿmÿkÿhÿiÿjÿmÿpÿsÿuÿrÿkÿcÿVÿJÿ>ÿ6ÿfÿÿÿÿ~ÿqÿuÿxÿzÿ\ÿCÿHÿQÿeÿkÿ_ÿ]ÿ_ÿPÿDÿFÿIÿLÿGÿMÿQÿOÿMÿJÿJÿDÿBÿAÿ@ÿAÿ?ÿ@ÿBÿDÿCÿCÿFÿ@ÿnÿÿÿÿÿzÿkÿYÿNÿKÿEÿCÿBÿBÿAÿDÿGÿ_ÿ
ÿxÿvÿtÿqÿnÿjÿiÿkÿjÿgÿaÿ^ÿZÿVÿPÿGÿ>ÿ6ÿ3ÿ;ÿ=ÿ8ÿ6ÿ7ÿVÿmÿgÿgÿfÿfÿgÿoÿwÿqÿFÿ:ÿ7ÿ;ÿ_ÿÿ£ÿ¦ÿ¤ÿ¦ÿÿÿuÿ`ÿeÿyÿÿÿÿKÿ1ÿ@ÿÿÿÿÿzÿsÿbÿZÿXÿVÿVÿNÿTÿÿÿÿÿyÿjÿOÿNÿYÿPÿ;ÿ1ÿ9ÿBÿKÿQÿWÿYÿ\ÿ_ÿcÿfÿeÿ\ÿJÿ6ÿ0ÿ=ÿQÿcÿiÿkÿkÿuÿ|ÿ~ÿÿÿÿÿzÿ|ÿwÿdÿTÿ>ÿ*ÿ&ÿ'ÿ)ÿ)ÿ*ÿ(ÿ'ÿ&ÿ%ÿ$ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ*ÿ3ÿ:ÿ=ÿ?ÿAÿ>ÿ>ÿ;ÿ6ÿ3ÿ0ÿ+ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ&ÿ)ÿ-ÿ2ÿ3ÿ1ÿ9ÿ<ÿ:ÿDÿUÿ[ÿRÿWÿ\ÿDÿ?ÿDÿQÿTÿUÿ^ÿiÿ]ÿLÿbÿwÿxÿ~ÿgÿDÿXÿkÿtÿoÿmÿfÿbÿ\ÿWÿUÿSÿRÿSÿSÿTÿTÿTÿTÿSÿRÿNÿKÿEÿCÿ@ÿ?ÿ?ÿ@ÿAÿEÿIÿLÿPÿTÿVÿWÿXÿYÿYÿYÿYÿ\ÿ`ÿdÿkÿqÿxÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ{ÿuÿrÿqÿpÿqÿoÿnÿkÿhÿeÿ`ÿ\ÿ[ÿ^ÿcÿiÿmÿvÿ}ÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿxÿxÿwÿwÿxÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*|*|*|*|*}*{*}*}*}*|*}*}*|*})|(~)~))~)(~(}(~(})~)})~)})~)~)~)~))~))~)~)~)~)~)~)}*~*~*~*~*~*~+}+}*}*~,},,}+*|**|*)|))|))})(}((}((}('~''~')~))~))~)~)~)~(~(~'~'~(~(~'~'~'~'}(~(}&~&}'~'}'~'|&~&z&&|&&|&~&|'~'|''|))|)~*{*~*{)~*z,},{,},|,},|,|,|,|,|.|.}-|-},|,|,|,},|+~*|*~)})})}))})(}((}'&}&&~&&~&%~%%~$#~#"~""~!!~! ~ ~ !~!~ ~ } ~ }!~!}! }"$}#$y(
/sB^hqmdj
ofttglhifdkadkinitsbskbbUjH8s1U~vnsz~m[|HK~Sd|a\x]ZxLDxJOxNNvORvSRuKGwGE{FI|LA|<>@?CED
W
|yr|gUJ}GC|BA|B@|>@z\rysnopmoklhhlhela]mYTpPHr91v15v55v56t<itlitgctehtm}uX0v2Atsq¢¥r§¤t
tm\rvqrq`p7>iw`}[shbVRkUTnTMeq[
Vy¢n\^¢OeZ¡ajT5m4;nGNkTXg\^dabdc`gYFm9?mKZidgdfgelpghdbyesik^Tr?,u()z)(|))}'%}%$~##~#
#~#"~"!~#%}09|<~=|>|?{@|<|8}3~1/*''((((&&(-.1347:z<JqZZcJ
SfO
Cj=@oIHoLPjLZemwln
ixdEWmu~uni~c~[X}SR{QPzPSzTSzSR{QO}JEB>~=>}@B{FIzM
NxRUxVWwXXvWWtX]qbhnnulz|lm
oqss
srrr
ruz}zz{uyrqspppolqjfrc_t\]t_cpiqlx}loqts
s
r
q
u}|mifhmqrv~}}yvvvusvvqwyo{{morxyz|yqnrx~}{
*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ"ÿ(ÿ5ÿ%ÿ&ÿ-ÿ?ÿWÿrÿtÿlÿnÿtÿuÿnÿiÿeÿbÿ_ÿ\ÿ_ÿiÿnÿsÿwÿqÿjÿaÿTÿGÿ6ÿ-ÿJÿÿÿÿtÿrÿrÿvÿaÿOÿJÿLÿOÿ]ÿYÿSÿRÿOÿCÿFÿMÿOÿNÿKÿLÿPÿYÿ[ÿPÿHÿHÿIÿFÿHÿLÿPÿNÿBÿAÿ@ÿ@ÿCÿDÿAÿÿÿ
ÿ}ÿnÿ_ÿQÿIÿCÿAÿ>ÿ;ÿ=ÿ<ÿ9ÿ;ÿTÿ
ÿ}ÿqÿoÿpÿnÿhÿfÿgÿeÿcÿ_ÿ\ÿVÿUÿPÿDÿ5ÿ3ÿ3ÿ1ÿ1ÿ3ÿ3ÿ5ÿ6ÿMÿpÿlÿhÿfÿeÿcÿlÿtÿtÿ>ÿ,ÿOÿ~ÿÿ ÿ£ÿ¢ÿÿÿ}ÿcÿZÿwÿ`ÿvÿÿ|ÿrÿ:ÿ6ÿoÿÿÿsÿkÿXÿNÿOÿRÿRÿQÿWÿÿÿÿwÿmÿcÿXÿTÿVÿZÿUÿBÿ?ÿDÿKÿRÿWÿ[ÿ]ÿ_ÿ`ÿcÿaÿZÿRÿHÿEÿLÿRÿ\ÿbÿdÿgÿkÿpÿyÿÿÿÿ}ÿsÿnÿgÿ_ÿZÿUÿ@ÿ,ÿ*ÿ'ÿ(ÿ(ÿ)ÿ)ÿ'ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ!ÿ"ÿ"ÿ!ÿ#ÿ,ÿ7ÿ<ÿ=ÿ>ÿ?ÿ@ÿ?ÿ<ÿ8ÿ5ÿ3ÿ0ÿ+ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ+ÿ.ÿ1ÿ3ÿ5ÿ5ÿ5ÿ7ÿ;ÿCÿMÿOÿBÿFÿUÿZÿ>ÿAÿOÿ\ÿTÿVÿYÿ\ÿbÿMÿSÿ\ÿJÿRÿiÿvÿwÿpÿlÿeÿ_ÿWÿRÿPÿPÿQÿPÿQÿSÿSÿSÿRÿQÿNÿHÿDÿAÿ>ÿ=ÿ>ÿ@ÿAÿEÿIÿMÿOÿSÿUÿVÿWÿWÿWÿVÿVÿWÿ[ÿ_ÿfÿmÿrÿwÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿyÿtÿrÿqÿpÿpÿoÿlÿjÿfÿcÿ_ÿ\ÿ]ÿ`ÿfÿkÿrÿyÿ~ÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿxÿwÿtÿuÿvÿwÿwÿwÿxÿzÿ{ÿ{ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*}*z*}*z+}+z*}*{*}*|*}*})})}(}'~'~(~)~)~)~(})~)})~)})~)})~)~)~)~((~))~*~*+~+*~++~++~+,~,+}+~,},,},,},,|,,|,*|))|))})(}()}))}))~))~))~))~))~))~))~))~))~))~))~('~''~'}'~'}'~'|&~&z&&{&&{&~&{'~'{''z((z(~)z+~+z*}*|,},|,},|,},|,|,|,|,|,|,|,|,|,|+|-|-}+|+~*|)~)}))})(}((}('}&%}%%~%%~%$~$$~#%~%~"~"~"~!~ ~ ~~~ ~ ~!~!~ ~ } ~ }!~!} |"%z(,s>Vhoucn
qcsvfojieal]WmV]kiqgvvbrkb`SiF5t8[|p
rsl}\Q{FG|PTzLDxACxDGxMKxKJvLNv]\uOFvDBzA@|@R}VD~=A>D>>
i{vm~d[T}D;|88}87|79zB~r~rnllmjhodcp_XoVWpVRrL<t..x1/x./x02w75vdrvjat`ctgivs_t9^s|rsunuNRs^CraqvsqG7jr`xl_aGgGGnMNpNdh_wh_^OeRTjPLmSUnPSmVYj\^f__ebdfb`iRJkNWk]`hdlioliphf|ofiejb[mYSq=+u(&z''|(&}&%}%$~##~#
$~"!~!"~&1|;=|=}?{@{Az@z>|;{8~6}4-&$&&''%$&(,/0256777<zIFuFKrPGqC
FrN
MoUdgbZjTVvVOEIe~zqlf^X~RP|PQ{OQzQQzQR{QL}HC~@>~=>~@D|GJzM
OxSUxVVwVUvTUuVYq]cnjplv|mnpqss
s
rrr
rv
z}~|{~~xrwpqrqppnlqjerb^t]]sahontmzmpstssrtzpffhkty
~}}z
wwwvsuwpwwoxynyzm{|lmsyz{~~tpps{~{
z*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ"ÿ"ÿ'ÿ-ÿ<ÿVÿpÿuÿnÿnÿsÿwÿpÿiÿfÿbÿ\ÿTÿPÿPÿ^ÿkÿrÿxÿvÿoÿgÿ]ÿPÿDÿ5ÿ6ÿMÿwÿÿtÿmÿpÿsÿ^ÿLÿTÿEÿDÿLÿHÿEÿAÿ?ÿDÿEÿGÿFÿIÿIÿIÿIÿPÿVÿVÿOÿEÿEÿFÿ?ÿ>ÿ=ÿ;ÿ9ÿDÿ?ÿ?ÿ>ÿ=ÿAÿ@ÿJÿÿÿtÿlÿgÿdÿTÿAÿ;ÿ6ÿ7ÿ6ÿ4ÿ4ÿ6ÿ8ÿbÿ~ÿsÿmÿjÿgÿeÿcÿ_ÿ\ÿQÿLÿNÿMÿIÿEÿ6ÿ*ÿ*ÿ/ÿ.ÿ,ÿ.ÿ/ÿ0ÿ1ÿ8ÿOÿoÿjÿeÿdÿcÿaÿbÿfÿpÿ[ÿcÿ{ÿÿÿÿÿzÿmÿRÿFÿFÿCÿBÿWÿ~ÿuÿpÿUÿ:ÿvÿÿtÿkÿ\ÿBÿEÿGÿLÿKÿRÿkÿvÿuÿiÿ^ÿTÿSÿTÿVÿXÿCÿ<ÿPÿZÿYÿ[ÿZÿ]ÿ`ÿbÿcÿdÿfÿeÿbÿVÿPÿ^ÿgÿiÿgÿlÿxÿlÿ]ÿwÿÿÿ|ÿnÿgÿaÿ_ÿ]ÿVÿUÿRÿ?ÿ+ÿ(ÿ(ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ#ÿ$ÿ#ÿ"ÿ!ÿ!ÿ!ÿ)ÿ5ÿ<ÿ>ÿ>ÿ?ÿ@ÿAÿ@ÿ?ÿ=ÿ<ÿ8ÿ5ÿ+ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ%ÿ&ÿ*ÿ.ÿ1ÿ0ÿ3ÿ7ÿ9ÿ<ÿ;ÿ:ÿ9ÿ8ÿ8ÿ9ÿCÿMÿLÿLÿQÿ[ÿUÿXÿ_ÿaÿWÿEÿIÿ_ÿdÿKÿOÿqÿtÿmÿdÿ]ÿXÿSÿQÿPÿOÿQÿQÿQÿQÿQÿPÿOÿLÿHÿCÿ@ÿ>ÿ=ÿ?ÿAÿCÿFÿIÿNÿOÿSÿTÿUÿVÿVÿUÿTÿTÿUÿWÿ\ÿbÿkÿpÿwÿ|ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ}ÿwÿrÿpÿqÿqÿpÿnÿlÿjÿdÿaÿ^ÿ]ÿ^ÿbÿiÿqÿuÿ{ÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿwÿvÿvÿuÿvÿvÿwÿwÿwÿxÿxÿzÿ{ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*~*{*~*{*~*|*~*|)})}*})~)|)})~'~(~)~)~)~)~)~)~)~)~)~)~)~*~*~*~*~))**)~)*~*+~,~*~,~+~+|,~,~,},-}--}-.}.,|,,|-+|*+|+*}*+}+*}**}*)})*}*+}+*}**}*)}))~))~))~))~))~))~)'~''~''~'}'~'}''}''}'~'|(~(z))z((z))z**z*}+y+}+y+}+z-}-z,},{,},{,},{,},{+|+|*|)|*}*}*})})})~(}(~(}'~(}(~&}%%}%%~%%~%$~$$~#"~"}"~"}"~!}~!|~|~|} } ~ }!!} #{')u9Tiovcp
nasweskheamZTqMJpO]nioitsbmec[NlC7u5I]kfh~qS|EGzC={BEyGCxBDxEHxFGxIIvIKvQQuNFv@BxBCyED{>;|B=}=>?@:m{~rkfc
M}@Dz=4{11|23y5;urroiemeepb`rZQtKNuOKvE6x-*{*-{-.{//y.7w@iunitfbt`\v`juiirwruxluT>tFBtAKrX}qxjrbBnzgtfeW@kBHoJLoVdlgeh\TiSTlVVoE4p.8nVgh``g_cfededggggh\_fimflri{rm^`li|lhe_k]\mWQpPNr>+v%&z''|''|&%}%%~$$}$
!~"!~"&}1:}=~?{@|@{@{CzBy@{?{>};{4~,|$"""$~$"~"$%'*./15:===}71~.+}.6{HV{XP{RYuYiriLwIb}bZL}mu~kc\XXZ}RO{PP{PQ{QQ{NJ|FA~>=}>@|CC{FJzMPxRSxRUwWTvSSuUWp[bnjpmv|moqqrs
s
r
rruy}|{|
{~trwpprpppnlqjcs`]t]_qeinpul|mpsss
r
syuhfknrz~z
yxywswwrwvpwwowwnvxnyzm{loswy{yqqsy~{
zy)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿ ÿ ÿÿÿ ÿ ÿ ÿ ÿ!ÿ!ÿ#ÿ%ÿ+ÿ7ÿVÿpÿyÿoÿlÿqÿvÿvÿoÿgÿaÿ\ÿUÿMÿDÿEÿOÿ]ÿgÿnÿqÿsÿkÿbÿWÿJÿ>ÿ4ÿ2ÿ4ÿ`ÿ~ÿhÿgÿmÿoÿJÿFÿCÿ>ÿ:ÿ@ÿCÿGÿEÿDÿDÿEÿDÿGÿHÿHÿIÿIÿFÿKÿOÿQÿTÿXÿdÿnÿnÿqÿoÿkÿcÿUÿRÿUÿ_ÿPÿ7ÿ9ÿWÿxÿqÿjÿgÿ_ÿOÿBÿ@ÿ9ÿ3ÿ2ÿ1ÿ2ÿ3ÿ1ÿ2ÿHÿrÿjÿcÿbÿaÿ_ÿ^ÿYÿQÿLÿIÿMÿNÿMÿGÿ;ÿ.ÿ*ÿ.ÿ1ÿ/ÿ.ÿ/ÿ2ÿ3ÿ7ÿJÿoÿnÿiÿ_ÿ\ÿ[ÿ]ÿbÿmÿsÿwÿ{ÿ|ÿuÿkÿ\ÿAÿ?ÿBÿDÿMÿSÿ`ÿrÿnÿ_ÿ_ÿMÿuÿÿsÿeÿSÿAÿGÿIÿJÿMÿZÿ]ÿZÿXÿVÿTÿSÿUÿRÿ@ÿ0ÿ.ÿ4ÿCÿ_ÿjÿmÿnÿkÿgÿgÿfÿgÿiÿiÿeÿcÿkÿoÿrÿtÿzÿgÿ[ÿWÿ`ÿzÿ{ÿlÿcÿ]ÿYÿXÿYÿRÿPÿMÿJÿ;ÿ*ÿ&ÿ%ÿ&ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ!ÿ#ÿ-ÿ9ÿ<ÿ>ÿAÿAÿ@ÿ@ÿBÿ@ÿ@ÿ?ÿ>ÿ;ÿ5ÿ(ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ#ÿ$ÿ%ÿ%ÿ%ÿ(ÿ*ÿ.ÿ1ÿ3ÿ7ÿ9ÿ8ÿ6ÿ2ÿ,ÿ+ÿ+ÿ.ÿ<ÿRÿ`ÿZÿ[ÿ_ÿaÿlÿfÿaÿPÿIÿFÿSÿJÿfÿrÿnÿeÿ\ÿWÿ_ÿ^ÿ^ÿYÿOÿPÿPÿPÿPÿNÿKÿHÿDÿAÿ>ÿ=ÿ>ÿ@ÿCÿFÿIÿLÿNÿQÿQÿQÿQÿSÿTÿSÿQÿQÿTÿXÿ\ÿcÿkÿqÿxÿ~ÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿtÿqÿpÿpÿpÿpÿnÿkÿiÿcÿ`ÿ]ÿ]ÿ`ÿfÿlÿsÿvÿ|ÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿxÿyÿxÿxÿwÿwÿvÿwÿwÿwÿxÿxÿxÿzÿzÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(~(|)~)|)~)})~)}(~({(}(|)|)})~)~)~)~)~)~)~)~)~)~)~)~)~)~*~*}*~*}++~++~+~+~+~+~+~+~+~,~,~,~,~-~-}..}.-}--}--|-.|.,|,,|,+}++}++}+*}*+}++}+*}*,},,},+}**}*+}+*})*}*)~))~)(~((~((~(}'~'}''}''}'~'|(~(z)~)z*~*z)~){*~*{*}*{+}+{,},{,},{-}-|,},|,},{,},{*|*{+|*{*}*z)})|)}*~)})~)}&~%}%~&}%}$}$}#~#}%~%}$~"~!~"~#~#"~"!~!!~~ ~ } ~ } } }!!} !z#
'r5Qjnxdrndpucuselck]VpLAs=DqP]nfogrpbi^cWJi<4r11|f|kho|kEyE?w99x=BxEFwFEwEDxFIxECvFCwCLwiwtnxlvwmiUx_
qy}Y
7z?s~nke[M}?6z21y11z0.z/1x3Xrkco`^q]\sXRuMJwIJzMN}NG~9+~*-}/
-|/3y43xRrvmavWXvY\uamvzvvpjsbMs<@q?Bp]`pgboY¢ZqX Romkm`kNHoJMpLOpRRmTTmUSnQMq=3q03o@Sggqatxazvcokbilaljamray}drZjIZmPWmffkc^l[XnYTpMJqHDu5'x$&{&'|)(|'%}%%|$#}"
#} #})7z=>z=~@z@|@z@{AyAz@z@{?}<{3~&| ##"~#%~$$}#$}$%'(),-.*+*+*)+-7P\RZcdba}[\xi[~K[
s~oi_yc[w@Vx_}SO{OO{ML{HG|C@}==|>A{BD{G
LzOQxQQxRTwRQvPQuSXq\cnksmy}npqqr
ssrrr
u
z}|{|
~y{sptppqpopmjqhcsa]t^bpgnlsyl}mpssssvz|lfin
r{y{{rxxpxxpyyoxvowwowxnxwkwzj|mqwz
{}uprt|}zxx(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ(ÿ7ÿLÿkÿwÿmÿmÿsÿuÿvÿuÿqÿiÿaÿZÿOÿAÿ:ÿ=ÿDÿOÿ^ÿhÿmÿnÿlÿfÿ^ÿUÿHÿ<ÿ4ÿ3ÿ>ÿtÿvÿhÿgÿnÿcÿAÿCÿ@ÿ:ÿ9ÿ?ÿEÿCÿDÿEÿDÿCÿAÿEÿHÿKÿFÿBÿBÿHÿhÿÿ£ÿªÿªÿ¡ÿÿÿ|ÿwÿxÿ{ÿhÿWÿgÿÿ{ÿTÿ/ÿ`ÿoÿlÿeÿ[ÿKÿ=ÿ6ÿ2ÿ2ÿ3ÿ1ÿ/ÿ.ÿ/ÿ1ÿ2ÿ<ÿdÿcÿ_ÿ[ÿYÿXÿVÿSÿPÿLÿKÿKÿKÿKÿOÿMÿJÿBÿ6ÿ1ÿ-ÿ+ÿ-ÿ3ÿ1ÿ+ÿ/ÿZÿfÿ^ÿXÿUÿ]ÿ]ÿ_ÿfÿoÿwÿjÿaÿYÿCÿ=ÿ<ÿFÿaÿcÿbÿ`ÿ]ÿZÿPÿTÿdÿdÿ{ÿdÿUÿGÿHÿMÿPÿQÿRÿTÿUÿUÿUÿQÿOÿHÿ?ÿ4ÿ1ÿ1ÿ;ÿNÿ^ÿmÿwÿtÿrÿwÿxÿuÿrÿqÿqÿqÿpÿrÿsÿtÿuÿ]ÿGÿVÿOÿDÿQÿZÿ^ÿ_ÿ[ÿXÿUÿUÿRÿLÿHÿEÿ?ÿ2ÿ&ÿ$ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ"ÿ#ÿ(ÿ3ÿ<ÿ=ÿ>ÿ?ÿ@ÿ@ÿ@ÿ@ÿAÿAÿ@ÿ@ÿ?ÿ<ÿ2ÿ%ÿÿ ÿ#ÿ#ÿ"ÿ#ÿ&ÿ&ÿ#ÿ#ÿ$ÿ$ÿ&ÿ.ÿ)ÿ)ÿ&ÿ%ÿ&ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ,ÿ.ÿ3ÿFÿJÿ[ÿ_ÿ_ÿXÿ`ÿdÿZÿ_ÿYÿLÿVÿqÿrÿbÿZÿEÿ)ÿ4ÿNÿNÿLÿOÿOÿOÿMÿLÿHÿEÿBÿ?ÿ=ÿ=ÿ>ÿAÿBÿFÿIÿLÿOÿQÿQÿRÿSÿRÿQÿPÿPÿQÿSÿXÿ_ÿdÿlÿsÿyÿ}ÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿtÿpÿpÿpÿpÿoÿmÿiÿgÿbÿ`ÿ]ÿ^ÿbÿhÿnÿtÿyÿ~ÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿwÿyÿzÿyÿxÿxÿyÿxÿwÿwÿwÿwÿvÿwÿwÿwÿxÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)~)}*~*})~)})~)}(~(})~)})~)~)~)~)~)~)~)~*~)~)~)~)~)}*~*}*~*|*~*|+~+}+~+},~,|+~+|+~,~-~-~-~.-~./}/.}..}..}.-}.-},-},-}-,},,},,},,},-}-,},+}+,},,},+}++}++}++}*+}+*}**}*)})(}(~(~(~'~'}(~(}(}({)}){*}*{*}*{*}*y*}(z*}*z*}*z*}*z,},{,},{+},|,},|,},|,},|*}){(}){)}){*}*|(}'~&}&~&}%~%}%~%}%~%}%%~%}&~'}*~*|%~$}#~""~!"~"!~ "#} |!!z%(t0Ijjwdmhdntextesnif\oS~Cs7~6s?HqQ^lhofplag\eQ~Fl:4x6P~
rab
l}_AyBAw:6w?EvBBvBCvC@wCHyQJy?Hy^x¥x¥qmjjqrZb}xwn;Furk|eZO~@6y22{23{3/{.1y55w>^t`WrVUtTSwRNzLM|MK~L~LK{KH|</),/y-&x'4w_bv_[vX[u^dteqsn^sR=s;@p^_oUZnZ§\lW¬PoG¦PqQkp`PpCJrJMrNRoX\m`]mZNpE;r31q3EmYfdpv_uq_praqmbjlaoo`nn`lpgKOlUGoFQn[]m\ZnVUpTNpJFrB:v-#y#$|%&}''}%%}%
%}$
#}#
$|'2{9;{<>{?~?z@|AzA|A{A{@{@{?}:|/~#}!~ !""~"#~&#}!"~%)+))++**++++,*,//1DVmig
f_fyjX|RVn~gh`zN+{1}F}O{PNzN
NzL
K{GD|A>|=={>AzB
DzG
MzOPxPRxRQwPPvPOuRYr`gooum{~n
qqrs
srqqruz~}}}
~~yysosooqonplhqdaq`_qadnjpkv|kmqss
u
v~|mdfknu
|{syzpxyoxxowwoxxoxxmxxlxwkx{j~nuwy
|{rnqx~yxww(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ,ÿ,ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ(ÿ)ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ*ÿ0ÿ0ÿ&ÿ#ÿ"ÿ!ÿ"ÿ!ÿ"ÿ"ÿ!ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ#ÿ%ÿ&ÿ1ÿFÿdÿyÿoÿgÿkÿrÿxÿvÿvÿrÿjÿaÿWÿJÿ8ÿ0ÿ7ÿAÿJÿTÿ`ÿiÿpÿrÿmÿeÿ[ÿOÿDÿ8ÿ1ÿ<ÿaÿÿpÿeÿfÿnÿZÿ@ÿDÿ@ÿ9ÿ6ÿ<ÿCÿDÿAÿAÿCÿCÿBÿDÿEÿHÿEÿKÿdÿÿÿ ÿ¤ÿ¬ÿ§ÿÿÿÿÿÿ}ÿvÿtÿjÿ
ÿÿÿÿeÿ3ÿpÿjÿeÿ\ÿRÿCÿ2ÿ.ÿ,ÿ,ÿ*ÿ*ÿ*ÿ+ÿ,ÿ/ÿ0ÿ/ÿ;ÿVÿ\ÿWÿSÿPÿPÿOÿNÿMÿLÿMÿKÿMÿKÿJÿIÿHÿEÿ@ÿ5ÿ.ÿ1ÿ.ÿ)ÿ$ÿ)ÿ:ÿZÿ^ÿaÿbÿcÿaÿ]ÿ^ÿhÿhÿaÿHÿ:ÿBÿUÿZÿWÿ[ÿ`ÿaÿYÿRÿLÿIÿDÿGÿNÿXÿMÿFÿLÿQÿOÿOÿRÿUÿ[ÿaÿ_ÿVÿLÿ@ÿ9ÿ3ÿ0ÿ7ÿJÿ_ÿkÿoÿtÿvÿsÿnÿmÿlÿgÿeÿiÿjÿgÿgÿ_ÿ\ÿZÿGÿZÿNÿIÿHÿRÿ]ÿ^ÿ\ÿZÿVÿUÿRÿJÿFÿCÿ@ÿ5ÿ*ÿ$ÿ$ÿ$ÿ$ÿ'ÿ'ÿ&ÿ'ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ&ÿ0ÿ8ÿ;ÿ;ÿ<ÿ?ÿ@ÿ?ÿ@ÿAÿAÿAÿAÿ@ÿ@ÿ?ÿ:ÿ/ÿ#ÿ#ÿ%ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ%ÿ'ÿ(ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ.ÿ/ÿ3ÿ8ÿ@ÿTÿbÿhÿmÿoÿiÿdÿeÿUÿMÿWÿiÿjÿeÿ^ÿ\ÿXÿUÿOÿNÿMÿNÿNÿNÿLÿJÿFÿDÿAÿ>ÿ=ÿ=ÿ>ÿAÿBÿGÿJÿMÿNÿPÿPÿRÿRÿPÿPÿOÿOÿNÿSÿZÿ`ÿhÿpÿuÿ{ÿ~ÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿuÿqÿoÿoÿoÿoÿnÿlÿhÿdÿaÿ`ÿ`ÿbÿfÿlÿrÿxÿ~ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿyÿ{ÿyÿyÿxÿxÿxÿxÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(~(~(~(~)~)~(~(~)~)~)~)~)~*~*~*~*~*~)~)~+~*~+~+~*~*}+~+}+~+|,~,|,~,|+~+|,~,|,~,|,~,}.~.}-}-/}/~.}..}..}..}..}.-}-.}..}.,},.}.-}--}--}-,},,},,},,},,},,},,},,},+}+*}*+}+(}((}(~(~(~(~(})~)|)}){)}){)}){*}*{+}+y*}*z+}+z+}+z+}+z+}+{+},{,},|,},|,},|,}+|*}*{)}){)}){(}(|'}&}%}%}%}%}%}%}$~$}$~$}$~$}&~&})~(}%~#}"~"~!~!~"~"!~!!!}!!} }!"{$)u-@kaudskckrevxgxtioem\~Mq>~0t1:tDPpZbkjqhrigcXiLCp71|Eu
lgjq{VAyF>w99w?CvDCvBBvCAwCExDIyax£w¦¨uª¬rª¡nmmvypz||
g|
Eidluf`|WO|GG~FEEFE~FJ|L
WzZTxHLuRSvROwNMzLK|KK~K|KH{GD|DD>73|.*{'#z%>xYYvW[v\]u]^t]bsUIqRYnR§TmW«VnU¬SnM©JpG£@s4;tEAuDIuMLtLQpTWo\\pVIr<7s01r=Pn`lgptcvtcnkchgdficigde_h[NlURoGHpMZlaal^[oUSqMGrD@t=1w($z##|%%~%&}&&}&
&}$
"}"
+|59{:<{>>{?~?z@}BzB}A{A}@{@}?}:}/~#~#~$#""~!#~##}""~%((**++++***+,.148=BDGTfg
gmi
NL^j}j~d~`{ZTL|LNzMNzN
NzM
K{GC|@>|=>{?AzD
FzI
MzMPxPPxPPwPNvOOuUZrbioqxm}n
qqr
ssrqqsvz}~~~~|z
twpotooqompkgqecqbbqchnnukz~loqssw{qjiilrywyxpywoxxnxxnwxnxwnwwmwwlwxkz}jnuyz}uoqv{~yxww(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ%ÿ$ÿ"ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ#ÿ%ÿ(ÿ1ÿ?ÿ[ÿvÿsÿkÿkÿqÿvÿvÿvÿvÿoÿfÿ\ÿQÿBÿ4ÿ/ÿ5ÿ>ÿHÿUÿ^ÿfÿnÿqÿnÿgÿ_ÿUÿIÿ=ÿ6ÿ7ÿVÿ{ÿÿlÿfÿiÿqÿTÿ?ÿEÿ>ÿ9ÿ9ÿBÿEÿEÿDÿCÿCÿCÿBÿCÿEÿHÿYÿpÿÿÿÿ¤ÿ¬ÿ«ÿªÿ®ÿ£ÿ¡ÿÿÿÿrÿÿÿÿÿÿÿÿiÿQÿdÿ_ÿ]ÿ]ÿ\ÿYÿYÿVÿTÿUÿTÿSÿTÿTÿRÿWÿWÿYÿ[ÿSÿNÿMÿNÿKÿLÿKÿLÿKÿJÿIÿIÿJÿHÿEÿEÿBÿAÿ>ÿ:ÿ7ÿ3ÿ-ÿ(ÿ&ÿ'ÿ&ÿCÿWÿPÿPÿWÿ\ÿYÿWÿZÿ`ÿbÿUÿUÿWÿYÿUÿTÿRÿPÿKÿGÿBÿBÿ?ÿ4ÿ3ÿ>ÿHÿCÿEÿIÿJÿLÿOÿQÿUÿXÿWÿQÿFÿ=ÿ7ÿ2ÿ2ÿ>ÿNÿ]ÿhÿpÿxÿxÿqÿkÿiÿiÿfÿfÿfÿgÿhÿgÿXÿDÿ8ÿFÿMÿJÿHÿRÿ^ÿ`ÿaÿ\ÿXÿUÿPÿHÿDÿAÿ=ÿ9ÿ,ÿ%ÿ#ÿ"ÿ#ÿ$ÿ$ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ#ÿ#ÿ&ÿ0ÿ8ÿ;ÿ<ÿ>ÿ@ÿ?ÿ?ÿ?ÿ@ÿBÿBÿAÿAÿ@ÿ@ÿ=ÿ;ÿ/ÿ#ÿ ÿ ÿ"ÿ"ÿ!ÿ!ÿ#ÿ#ÿ#ÿ"ÿ"ÿ%ÿ'ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ/ÿ0ÿ6ÿ9ÿ?ÿ<ÿ=ÿ<ÿ?ÿDÿKÿQÿ_ÿqÿmÿIÿGÿXÿYÿ_ÿdÿeÿ[ÿSÿVÿQÿNÿNÿNÿNÿNÿMÿLÿHÿEÿ?ÿ=ÿ=ÿ>ÿ?ÿBÿFÿHÿKÿMÿMÿPÿPÿPÿPÿOÿOÿNÿOÿQÿVÿ\ÿbÿiÿqÿzÿ~ÿÿÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿuÿqÿnÿoÿoÿoÿoÿlÿjÿgÿeÿbÿcÿeÿfÿjÿqÿwÿ{ÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿyÿ|ÿyÿyÿzÿxÿxÿwÿwÿxÿxÿxÿwÿwÿvÿvÿwÿwÿxÿzÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'~'(~((((()~)~)~)~)~)~+~+~+~+~,~,~+~+~+~+~+~*|+~+|,~,},~,|,~,|,~,|-~-|-~-|.~.|-~-~/}//}//}/.}..}..}..}..}..}..}..}..}.-}--}-.}.-}--}-,},-}--}--}-+}+,},+}+*}*,},+}*~*}*~)~)})~)|*~*})~)|)}){*}*{*}*{*}*z*}*{*}*{+}+{+}+{+}+z+}+z,},{,},{+}*{*}*{)}){(}({'|'|'~'|&~%z%~%z%~%|$~$|$~$}$~$}$~$~$~$~&}$#}##~#~"~!~"~"!~!!!~""}#"|%)u1=lXscqjal
perrhsrlmdnY}NqC}5v/0w7BsMVnb
ijopgkeh[OlC7s5Be~lhfl{O@yC@w87wBGvECvCEuFCw@@xNkxut§tªªt¬¢r££q¤¢m}n{
lz`RYlXWvUTvOOxPPwOOxOO|P~PR|V[z[UxPIxIFxEEyF
E{EF|HH~H~HEA?>~:8{53y,'y&(y.PxZUvSUuTSuUYt]brWRoR¨NoPªNqK¥FsA¢=t?At7.v3=vAAwDFvKMtNRrWYqQFr:8u34u>KqWejlresldhhegfeffeghfichN6n@FpEIoX^n^\oXUpSKrEBs?;u5)z##|##}##}$%}%$}$#|##|)2|9={>?z@@z??z@~Az@~@z@}?{?}>}:~.~#!!""!~!"}""~""~$&+++)******,0466
668~9<~CF~EHUTFG\R]ye~fx[~W{\{S~MzNMxNNxMK{GD|?=|=?{@DzGHzKMyM
NyN
NxOPwOOwPQtV_qckorwn}n
pqs
srq~pqty|}||y|tpvoprpnpnkpigpeepcgohlmsxl}lort
v
{ykjk
lr{z~szqzxnyylyynxxowwmwwmwxlzykwwk{kntxz|spty{yxvv'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ#ÿ'ÿ-ÿ7ÿPÿoÿvÿjÿkÿnÿpÿrÿrÿqÿgÿ`ÿVÿLÿ?ÿ2ÿ,ÿ.ÿ2ÿ<ÿGÿRÿ[ÿfÿmÿoÿoÿiÿ`ÿXÿKÿ?ÿ2ÿ1ÿIÿqÿ}ÿÿlÿgÿjÿlÿLÿ@ÿCÿCÿ:ÿ8ÿ@ÿDÿEÿDÿEÿGÿGÿCÿ?ÿCÿ^ÿ}ÿÿÿÿÿÿÿ£ÿ®ÿ°ÿ®ÿ°ÿ®ÿ©ÿ«ÿÿtÿvÿÿ£ÿ¡ÿ£ÿÿÿ`ÿQÿUÿSÿRÿQÿQÿQÿQÿQÿRÿRÿQÿQÿPÿPÿQÿSÿTÿSÿVÿQÿMÿKÿGÿEÿCÿBÿ@ÿAÿ@ÿ?ÿCÿCÿBÿBÿ@ÿ>ÿ>ÿ:ÿ9ÿ6ÿ3ÿ0ÿ*ÿ%ÿ'ÿ*ÿ2ÿRÿ_ÿXÿPÿRÿUÿRÿPÿTÿaÿ[ÿDÿIÿCÿEÿFÿEÿBÿ>ÿ<ÿ>ÿ>ÿ5ÿ/ÿ,ÿ-ÿ:ÿ@ÿAÿEÿJÿLÿMÿNÿNÿPÿOÿCÿ9ÿ7ÿ4ÿ3ÿ:ÿHÿUÿ^ÿgÿlÿmÿlÿiÿhÿfÿfÿfÿeÿ_ÿXÿVÿLÿOÿUÿLÿCÿFÿOÿZÿ^ÿ\ÿZÿVÿSÿOÿHÿCÿ>ÿ<ÿ9ÿ3ÿ'ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ)ÿ3ÿ:ÿ=ÿ>ÿ?ÿ@ÿ@ÿ@ÿ?ÿ@ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ>ÿ:ÿ.ÿ#ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ$ÿ(ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ/ÿ3ÿ4ÿ4ÿ4ÿ5ÿ6ÿ9ÿ@ÿGÿIÿIÿIÿMÿCÿ@ÿAÿUÿhÿoÿnÿfÿbÿcÿgÿSÿLÿMÿMÿNÿMÿLÿJÿFÿCÿ@ÿ?ÿ?ÿ@ÿAÿDÿGÿHÿKÿMÿMÿNÿNÿOÿPÿRÿTÿTÿTÿWÿYÿ_ÿfÿkÿtÿzÿ}ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿ~ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿyÿtÿpÿoÿoÿoÿnÿnÿlÿjÿgÿeÿdÿeÿhÿiÿoÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsÿxÿ{ÿ{ÿyÿyÿyÿxÿxÿxÿxÿwÿwÿwÿwÿwÿwÿwÿxÿxÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ''((((())~)~*~*~+~+~+~+~+~+~,~,~,~,~,~,~+~-~-~-~-~-}-~-|-~-|-~-|.~.|.~.|.~.~/~//}/0}0/}/.}..}..}..}..}..}..}..}..}..}..}..}..}..}..}.-}--}--}-,},,},,},,},,},+}**}*)~)~*~*}*~*}*~*|*}*|+}+|*}*{*}*z+}+{*}*{+}+{+}+{+}+z+}+z,}+{*}*{*}*{*}*{)}){'}'{&|&|&~&|&~&}$~$}$~$|$~$|$~%}$~$}$~#}$~$~$}%#}""~"#~"~"~""~!!!~##}%(w*7mKjdyp`i
ldmphpmke\nQ}Ds9|1v+~+x.8w@KqValjphplge\kQGs;3{5Y{ylgkn}Q@yECw<7w?DvEEvFHwGBv?Nvo
urrr¡¯y¹º{»¸vª
§t|u}¢
qasMcUXpXVvWVwSRwSSxRP|R~RU{UUzUWzP
NzJHzF
FzEE{CB{BA|AA}A@~>>}<;|:6{3.z)(y+*x5Tw][vXSwQOvR[taJs;?s?CtB?u;8w87w3/w--x9DzIKzLNyPOwOQsO?t65v2.v5DsQWoY]kdiihkhkffgie\OgW8iLYpKGqNYp]\pZVqSQrKDt@<u:9w1&{##|##}$"}#$}$#}##|#"|)4|=?{??z?@zA@zAAz@@z@@{@?}<-~#! """~""}""~"!~"#~'')*+*+))++-102479669
=@ED?CFA}Kftg`s_}\wZ~U|N|MNzNMzLJ{FC|A?|?A{BEzHIzJLyLNyNPxRVwWZuZ]taaqemot{n~npqssrq~pqty|}|||wyrotnmpmnomjphgpdeofjnlpmw}lmpsux{}lklmpx~zuvx|l}zlyxmxxmxwnwwmwwmvuluxkxyk~kovz}xqrv{~zxvvv'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ$ÿ$ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ"ÿ!ÿ!ÿ"ÿ!ÿ"ÿ"ÿ#ÿ#ÿ'ÿ.ÿ7ÿEÿfÿyÿrÿkÿkÿnÿmÿlÿkÿcÿXÿKÿ?ÿ4ÿ,ÿ)ÿ)ÿ-ÿ3ÿ:ÿGÿPÿ[ÿfÿmÿqÿpÿkÿbÿXÿMÿAÿ5ÿ1ÿ6ÿ^ÿ{ÿyÿ~ÿmÿeÿiÿnÿ`ÿ?ÿCÿCÿ>ÿ8ÿ>ÿEÿEÿEÿFÿIÿHÿDÿHÿaÿwÿÿÿÿÿÿÿÿ¤ÿ±ÿ¹ÿ»ÿ¼ÿºÿ²ÿ¯ÿ©ÿªÿ¬ÿ®ÿÿ ÿ ÿ¥ÿÿÿ\ÿXÿXÿXÿVÿTÿTÿTÿPÿQÿQÿQÿPÿPÿQÿSÿUÿXÿYÿYÿTÿPÿMÿFÿFÿEÿEÿDÿDÿEÿDÿCÿCÿ@ÿ@ÿAÿ@ÿ>ÿ>ÿ=ÿ;ÿ;ÿ9ÿ5ÿ0ÿ-ÿ+ÿ,ÿ.ÿ7ÿUÿUÿWÿWÿRÿQÿPÿTÿZÿUÿBÿ;ÿ;ÿ>ÿ>ÿ=ÿ;ÿ8ÿ7ÿ5ÿ1ÿ/ÿ/ÿ2ÿ>ÿHÿLÿMÿMÿNÿPÿPÿOÿQÿUÿGÿ(ÿ/ÿ0ÿ.ÿ2ÿ<ÿEÿOÿSÿVÿ^ÿbÿbÿeÿgÿiÿhÿkÿiÿdÿ_ÿQÿQÿTÿOÿOÿWÿZÿ[ÿXÿWÿTÿOÿJÿDÿ@ÿ?ÿ;ÿ9ÿ5ÿ-ÿ%ÿ"ÿ"ÿ$ÿ#ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ)ÿ6ÿ>ÿAÿ@ÿAÿAÿAÿAÿAÿBÿAÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ>ÿ/ÿ#ÿ!ÿ ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ#ÿ#ÿ$ÿ$ÿ(ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ/ÿ1ÿ3ÿ5ÿ6ÿ5ÿ3ÿ6ÿ6ÿ8ÿ=ÿ?ÿ>ÿCÿCÿCÿ=ÿIÿ_ÿfÿfÿ\ÿZÿTÿUÿOÿLÿNÿNÿMÿLÿJÿFÿCÿAÿ?ÿ@ÿBÿCÿEÿHÿJÿLÿLÿLÿOÿOÿRÿUÿYÿZÿ_ÿaÿeÿgÿgÿlÿpÿwÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿtÿpÿnÿnÿmÿmÿnÿmÿjÿhÿfÿeÿfÿgÿjÿmÿtÿyÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿtÿzÿ|ÿ{ÿ{ÿzÿxÿxÿxÿxÿwÿwÿwÿwÿwÿvÿvÿvÿxÿxÿyÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(~()~)*~*)~))~*~*~*~*~*~*~+}+~+|+~-|-~-|-~-{--}--}.~.|.~.|.~.}-~-}/~.|.~.|.}.|.}0}/}/~/}/~.}..}/.}..}..}..}..}..}./}//}//}//}./}//}//}//}/-}./}..}..}.-},,},,},+}+,},,},+~+~+~+~+~+}+~+|*~*}*~*}*~*{*~*{*}+z,},|,},|+}+{*}*z*}+z+|+z+|+z*}*{)}){(~'{'~'|&~&}%~%}$$}%%}%%}%%}%%}$$}$~$|$~$}$}$~#}#~#~#}#~#}#~#"~"~"#}$'z+1s=Zhxrbj
jbqphphk]TmH}8s-|(y%~&z)-|5?wITq^flnoimeh]RnF~;t20{>gwvkbel}vKy?Dw@:w:EvDDxFGwFLv`turops£±yµ¶|º»{¶¹}»º~»º®~¦
v¢fj`ZVmRRtQSwSTxPPzN~M~M}RT|RT{UUzS
QzNKzH
FzED|CD|CC|CB}BB~A@~@>~==~=9|84{1.z..zAWyVQxPQxPOvMUvV;v5;v<=v:8w43w20y19yGM{NM}NN}NN|NQxSSvG=v)(w/5w;CuJMpV[n_bkdfigfhcajbUlVTrKPqY[r[YrUQtMEwA<w;:x60{'$|$$|$$}$#}#"}#"}""}"#|'2{>AzCC{B@z??z@BzAAzA~A{@~?|=3~%! "!~!!~!!!!!!!"""&))**++*+/1324468
;;:9==BG{MRt[}fob}at\{VzPzTQzNNzMJ}FB{@@{BBzCF{I
K{MLzMMxQUvZ\t`etkotppsptrw}popqrrrrorw|}}|{zwruonrmmomlokjogfofgnilmqwl{m
nprw|qkonnwyzwxo{}l}zlyxmxwmwwmwwlwwkwwkwwjxzilrx
{|trtw}}
yuutv(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ%ÿ(ÿ-ÿ8ÿMÿrÿxÿpÿmÿqÿtÿrÿiÿ_ÿSÿFÿ5ÿ)ÿ'ÿ%ÿ%ÿ&ÿ)ÿ0ÿ9ÿCÿNÿWÿbÿkÿpÿmÿhÿ`ÿYÿOÿ@ÿ2ÿ/ÿ0ÿCÿmÿxÿxÿÿmÿcÿcÿnÿzÿrÿ=ÿAÿ?ÿ:ÿ8ÿAÿCÿBÿEÿAÿGÿ_ÿvÿÿÿÿÿÿÿÿÿÿ ÿ¤ÿ¯ÿ®ÿ°ÿ¹ÿ¼ÿ¼ÿ¹ÿ³ÿ°ÿµÿ´ÿµÿ©ÿ©ÿ¡ÿÿwÿUÿTÿYÿeÿsÿyÿyÿyÿyÿuÿmÿ^ÿNÿLÿLÿNÿNÿOÿRÿRÿPÿNÿKÿHÿFÿEÿDÿEÿCÿCÿCÿCÿBÿBÿAÿ@ÿ@ÿ@ÿ?ÿ>ÿ=ÿ=ÿ:ÿ9ÿ8ÿ6ÿ3ÿ.ÿ.ÿ,ÿ@ÿSÿPÿOÿNÿNÿKÿKÿMÿQÿRÿ:ÿ6ÿ9ÿ:ÿ8ÿ6ÿ5ÿ2ÿ2ÿ0ÿ2ÿ=ÿKÿPÿOÿNÿNÿNÿNÿNÿNÿPÿUÿUÿTÿTÿ9ÿ2ÿ9ÿ:ÿ;ÿ=ÿAÿFÿRÿWÿZÿ[ÿ_ÿbÿ`ÿaÿ_ÿ\ÿ_ÿYÿSÿNÿKÿOÿXÿZÿYÿXÿSÿPÿJÿ?ÿ<ÿ9ÿ7ÿ5ÿ0ÿ*ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ&ÿ/ÿ;ÿ@ÿAÿCÿBÿ@ÿ?ÿ@ÿAÿAÿAÿBÿBÿAÿ@ÿ?ÿ<ÿ5ÿ)ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ$ÿ&ÿ(ÿ*ÿ*ÿ(ÿ(ÿ)ÿ*ÿ,ÿ/ÿ0ÿ1ÿ2ÿ4ÿ5ÿ4ÿ7ÿ7ÿ5ÿ5ÿ6ÿ9ÿ9ÿ;ÿBÿJÿSÿbÿlÿbÿZÿRÿPÿOÿSÿPÿMÿMÿJÿFÿCÿAÿCÿCÿCÿDÿHÿJÿLÿMÿLÿNÿOÿRÿUÿ[ÿaÿfÿkÿrÿvÿwÿxÿxÿxÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿyÿsÿoÿmÿmÿmÿmÿmÿlÿkÿiÿfÿgÿgÿgÿiÿmÿrÿxÿ|ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿyÿ{ÿ|ÿ|ÿ|ÿzÿyÿxÿxÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿwÿxÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)~))~)*~**~**~*~+~+~+~+~+~,},~-}-~-}-~.}-~.}.~.|.~.|.~.|.~.|.~.}.~.}/~.|/~0|/}/|0}0}/}/~/}/~/}/.}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}..}.-},~,},~,},},},}+~+~+~+~+~+}+~*|*~*|*~*|+~+{+~+{,},z,},|,},{+}+z*}+z+}+z+|+{+|*{*}*{*}*{)~'{&~&|&~&|%~%|$$}%%}%%}%%}%%}$$}$~$|$~$}#~#~#~##}##}##~##~"#${'+t4Gkivcnjaq
vfvnicVkF{5q'{$w$~$y%&z)1y:EtPZmd
lipkhf]lR~Gp:}.w-4{Us
xxlca
hw~|U:x@7x6?xACxDAxQuvsspmos¥¦w¬¹}¾¼³³²³±~¬¤y¨¦k`^bmt~~~}|{xxutk~`Y|XW{UR{RO{L
J|G
D|EF|FD|DD|BC}A@~??~@>~=<~;:|98{75z4.z-/y?OxLMyLJyIGxIPxL6w69x65x31x0/z5C{MR|RN}OM}ML|LMzP
SyW\vVFw;9w<;w=@uIPrVWqZZoZYm[ZnYUoPLsLStXZtVRvNLwC9z84z0,y(%|$$|$$|$$}$$}$$}##}#"}"#|$+{8@zAB{CBzAAzA@{@AzA>{??|<7~*!""!!~!!~!""""""##"~"$~&+*)**)****/2244010/57}<|@{EtJx[kfyjca{YhT}OuP}PN{LK|GC|AB|AE{FI{K
L{M
MzMNxQUv]atipsx|s}}s{zrz~popqrrrrrux
|}|~zytwomrmmplmomkojiohgohhnjnmtyl~mnptzvlknps|{yyry|m}{l{zlyxmxvmvwmwwlwwkwvkvvjy}jmsy|xqrvz~{vuutv)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ#ÿ"ÿ#ÿ%ÿ(ÿ0ÿ=ÿ^ÿyÿsÿiÿnÿvÿvÿqÿgÿZÿLÿ6ÿ(ÿ$ÿ#ÿ$ÿ$ÿ%ÿ&ÿ+ÿ3ÿ;ÿFÿQÿ[ÿeÿnÿnÿjÿaÿZÿMÿAÿ3ÿ-ÿ-ÿ8ÿ\ÿxÿwÿsÿ~ÿpÿaÿcÿhÿtÿÿuÿ@ÿ=ÿ:ÿ5ÿ<ÿ@ÿAÿCÿLÿfÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ¤ÿ²ÿ¶ÿ·ÿ´ÿ±ÿ¯ÿ¬ÿÿÿÿÿÿÿÿnÿtÿ{ÿzÿvÿwÿvÿwÿtÿsÿsÿsÿtÿqÿoÿmÿjÿhÿdÿbÿ]ÿYÿTÿRÿNÿLÿEÿDÿDÿBÿAÿBÿAÿ@ÿAÿAÿ@ÿ?ÿ=ÿ=ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ5ÿ4ÿ3ÿ0ÿ-ÿ-ÿ@ÿMÿGÿGÿFÿFÿFÿFÿKÿQÿFÿ6ÿ8ÿ6ÿ5ÿ3ÿ0ÿ/ÿ/ÿ8ÿIÿQÿSÿSÿQÿOÿMÿMÿKÿKÿLÿLÿPÿRÿYÿWÿXÿLÿ;ÿ6ÿ9ÿ<ÿ=ÿCÿJÿMÿRÿTÿVÿTÿTÿTÿUÿVÿQÿMÿMÿSÿXÿYÿVÿSÿOÿLÿIÿBÿ8ÿ1ÿ/ÿ+ÿ%ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ#ÿ"ÿ$ÿ"ÿ"ÿ#ÿ#ÿ&ÿ2ÿ=ÿ?ÿ@ÿAÿAÿCÿCÿAÿ?ÿ=ÿ@ÿBÿBÿ@ÿ?ÿ>ÿ8ÿ,ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ$ÿ$ÿ+ÿ)ÿ*ÿ+ÿ,ÿ*ÿ(ÿ(ÿ(ÿ+ÿ0ÿ/ÿ/ÿ0ÿ-ÿ*ÿ*ÿ1ÿ4ÿ0ÿ3ÿ8ÿ7ÿ9ÿ>ÿOÿWÿaÿWÿQÿZÿYÿQÿPÿMÿKÿIÿFÿCÿ@ÿ@ÿCÿEÿEÿHÿLÿMÿKÿMÿMÿLÿOÿTÿ[ÿcÿjÿsÿzÿÿÿÿÿ~ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ{ÿwÿrÿnÿmÿmÿmÿlÿlÿlÿkÿjÿhÿhÿgÿhÿhÿjÿoÿuÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿxÿzÿ{ÿ}ÿ{ÿzÿzÿxÿwÿwÿvÿvÿvÿvÿwÿwÿwÿwÿvÿvÿvÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)~))~)*~**~++~+~+~,~,~,~,~,~-~.~.~.~.|.|.|.|/~/{/~/{.~.{.~.{-~-|/~/|0~0|0~0|0~0|/~/|/}/|/}/|.}.}.}..}..}..}..}..}..}./}/-}-0}0/}//}/~/}/~/}/.}..}./}//}..}..}..}.-},~-}-~-}-|,},|+~+~*~,~+}+|+}+},},|,},|,},|,},|,},|,},|,},z+},z,},{,},{+}+{+}+{*}*{*}){*~)|&~&|&~&{%~%{$$|$$|$$|%%}$$}$$}$~$}#~#}#~#~#~##}#~#}#$~$~#~#~%)z,
6pNsgwlbjreuqik_lO~:p*|$w!|"{"#|$'|+3y=HuR^ndkijeh]RnH=u1,x5Hzk}
}sbcht
}ey59y68z?CzB[y|xurpmmr¤w£©|«¯³´}©£{{~q`tgituutrqroonnnllifb`__^\
\\
XUTQMJHEA~==~>=~==|<:}9
8{76z52y10z..y<KzHEyFCzBBxGRxA6w87w21x11y=L{TW}WS~QP~OK~KK}KM{QRxSNwTSvE<v=?y@CyHOwRUuTOtNNrLPtPPvPRuRPwNKxIDz>6|/.|+&|%$}$$~$$~$$}$$}$$}$$}$#}##|#$|+8|>
@{@@z@BzA>z>?{>?{=<};5~+"!!""!""""""!!###}#$}$'((*))(+./,+)*))*|-1|7<{=xCxGsNuWl[uaboyt[b~UgONzMKG}DB{@A{EHzHIzK
MzMMzMLxORvZbtkvr|rss~|qp
prrrqqrvz|{{yysotmkqkkolkokkoigofgohiomqmv{kmoru{pjkpt
zy|yqy{m|{k{ylxxlwvmvuluvkvvkvwjwxiz~inuy}vqsw}|xvvvvv)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ(ÿ+ÿ2ÿEÿkÿ|ÿoÿkÿpÿtÿtÿnÿcÿUÿBÿ-ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ'ÿ-ÿ5ÿ=ÿHÿRÿ]ÿfÿjÿjÿbÿXÿMÿBÿ7ÿ0ÿ5ÿGÿiÿ|ÿ~ÿ}ÿvÿ}ÿwÿbÿdÿfÿpÿÿÿ~ÿHÿ7ÿ7ÿ6ÿ=ÿAÿHÿjÿÿÿÿÿÿ£ÿ¤ÿÿÿÿÿÿÿÿÿ¢ÿ¦ÿÿ¬ÿ®ÿ±ÿÿ¦ÿÿÿÿÿÿÿÿÿÿkÿoÿoÿqÿqÿpÿmÿmÿkÿjÿkÿjÿiÿiÿhÿdÿbÿeÿ`ÿ_ÿ_ÿZÿYÿXÿXÿXÿWÿTÿQÿSÿRÿSÿRÿRÿPÿGÿ?ÿ=ÿ=ÿ;ÿ:ÿ:ÿ9ÿ8ÿ7ÿ7ÿ6ÿ4ÿ2ÿ2ÿ.ÿ.ÿ.ÿ=ÿDÿBÿAÿCÿBÿBÿDÿIÿTÿ>ÿ6ÿ6ÿ4ÿ0ÿ2ÿ4ÿBÿOÿSÿVÿWÿSÿSÿPÿOÿLÿJÿJÿLÿLÿMÿQÿSÿMÿNÿTÿSÿLÿBÿ?ÿ@ÿBÿDÿIÿMÿNÿMÿMÿJÿDÿDÿEÿGÿHÿFÿDÿDÿCÿEÿFÿCÿ@ÿ9ÿ1ÿ,ÿ+ÿ)ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ#ÿ%ÿ,ÿ6ÿ<ÿ?ÿ?ÿ?ÿ@ÿ?ÿ?ÿ?ÿ@ÿ>ÿ<ÿ:ÿ8ÿ3ÿ*ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ&ÿ'ÿ'ÿ'ÿ&ÿ*ÿ,ÿ0ÿ9ÿ<ÿ3ÿ(ÿ(ÿ'ÿ&ÿ'ÿ)ÿ1ÿ=ÿCÿEÿMÿPÿQÿUÿVÿYÿ_ÿpÿÿyÿ^ÿXÿNÿKÿLÿIÿAÿ@ÿBÿBÿCÿFÿGÿIÿKÿMÿNÿNÿNÿMÿNÿOÿUÿ^ÿgÿrÿ{ÿÿÿÿÿÿ~ÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿwÿrÿnÿlÿkÿkÿkÿlÿkÿkÿkÿiÿgÿfÿgÿhÿiÿmÿqÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿyÿzÿ|ÿ|ÿ|ÿ{ÿyÿxÿwÿvÿvÿvÿuÿuÿvÿvÿvÿvÿwÿwÿxÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)~))~)+~+,~,,~,~,~-~-~-~-~-~.~.|.~.|..}/0|/~/{/~/{/~/{/~/{0~0{0~0{0~0|0~0|0~0|/~/|0}0|/}/|.}.|/}/}/}/~.}.~.}.~.}.~.}.~.}.~0}0/}//}//}//}/~.}.~.}.~/}/~.}..}..}./}//}/.}..}-~-}-~,},~,},~,~,}-~+}+}+|+}+},},},},},},},},}-}-|,},|,}-{,},{,},{,},{+~+|+~+|*}*|)})|(~'|'~'|&~&{%~%{%%{%%{$${$$|$$}$$}$~$}!~#}$~$~$~$~$~$~$~$#~$$$~(.w<`ltbl
qcvyhqhjZ}Nn5|$t!}!y#}#{!#}%'{.5y<GuQ[qdimeamVJnA9m6?j]m~~zv{~{hbbl
~|t1y95{<@{Ov|zx¢¡u¤ qonqv¤¥{¤¡} ~{
x{t}xwu{eiekk{lmjkigghffda[V[\ZXZXTQPPOMMM
MN
QSRGA~<;|9:{89z8
8z65y51y/0x/2yFGyEEzBDyEIxRPw63w32x16zGQ{TT}U
U~RR~OM~KK}KK{KM|MPzON{OLzD6z2:{?AzEFyGHxFBxCCxCBy?CzFI|JH}FA}<0}+(}')z''|'&|&%|%$}%$}%'}'&|&&|$$|%%|%&|*0|7
;{>?|=?|@?{<8|4-~&#~!"#!"" """""""""""#&&%&'''(')4:/(&%'~'+|1;|B}E}JwN|QqSzSlWxXcYzjR~}]P^ogPHzGC@}@@{BEzF
IzK
LzLNzNMxNMvQWtcmrv}rs~s}}q~ppr
rrqqsx{}z
}ywtwqlskkokknkkokjohfoffogimmqlv{kkns}unmnqu}||tyyn||k}{kzxlwwluumuuluukvukvujuxizi
ntx|ursx~{wuuuuu)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ&ÿ)ÿ3ÿOÿyÿÿqÿpÿwÿ}ÿzÿpÿdÿTÿCÿ-ÿ#ÿ!ÿ"ÿ"ÿ#ÿ#ÿ$ÿ&ÿ(ÿ+ÿ3ÿ<ÿGÿRÿ\ÿbÿfÿdÿ`ÿUÿKÿDÿCÿLÿ`ÿ{ÿÿÿzÿwÿvÿzÿ|ÿiÿaÿ`ÿjÿ|ÿÿÿÿZÿ+ÿ2ÿ6ÿ>ÿUÿzÿÿÿÿ¥ÿ¦ÿ¥ÿ§ÿ¢ÿÿ ÿÿÿÿÿÿÿÿÿ¢ÿÿÿÿÿÿÿuÿiÿbÿaÿ\ÿnÿÿvÿ_ÿgÿgÿhÿhÿfÿeÿcÿbÿdÿeÿbÿ_ÿ_ÿ_ÿ]ÿZÿXÿZÿXÿVÿQÿOÿMÿIÿIÿIÿKÿKÿKÿLÿKÿKÿMÿNÿMÿGÿCÿ?ÿ<ÿ:ÿ9ÿ9ÿ8ÿ7ÿ5ÿ5ÿ5ÿ4ÿ1ÿ1ÿ0ÿ.ÿ9ÿHÿEÿBÿEÿDÿFÿFÿJÿOÿFÿ2ÿ2ÿ4ÿ3ÿ7ÿHÿRÿUÿTÿTÿSÿSÿSÿQÿOÿMÿKÿJÿKÿLÿKÿKÿMÿMÿKÿKÿJÿ?ÿ0ÿ%ÿ'ÿ,ÿ2ÿ9ÿ;ÿCÿGÿFÿDÿ@ÿ@ÿAÿ@ÿBÿFÿJÿIÿHÿFÿCÿ@ÿ6ÿ/ÿ*ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ'ÿ(ÿ'ÿ'ÿ'ÿ&ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ*ÿ-ÿ3ÿ8ÿ:ÿ=ÿ>ÿ;ÿ6ÿ1ÿ*ÿ$ÿ"ÿ"ÿ%ÿ$ÿ"ÿ#ÿ$ÿ%ÿ!ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ'ÿ(ÿ&ÿ'ÿ(ÿ&ÿ'ÿ(ÿ'ÿ&ÿ&ÿ%ÿ'ÿ*ÿ2ÿ6ÿ>ÿDÿGÿMÿNÿPÿSÿZÿcÿnÿÿaÿNÿ^ÿ\ÿIÿCÿAÿ?ÿ?ÿ@ÿBÿFÿHÿIÿKÿLÿLÿNÿNÿMÿNÿJÿOÿSÿ]ÿiÿqÿyÿÿÿÿ~ÿ}ÿ|ÿ}ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿsÿnÿmÿlÿkÿkÿkÿkÿkÿkÿjÿhÿfÿfÿfÿfÿhÿlÿqÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿvÿxÿyÿzÿzÿ{ÿyÿwÿvÿwÿuÿuÿuÿuÿuÿuÿuÿuÿvÿuÿuÿyÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*~**~*,~-,~,,~-~.~.~.~.|.~.~.~/~/~/~/~/~0~0{/}/{1}1{/}/{/}/{0~0{0~0{0~0|0~0|0~0|0~0|0~0|1~1|/}/|/}/}/}/}.}0}/}/~/}/~.}.~/}/~.}./}//}//}/.}..}..}..}./}//}//}//}/.}.~.}.~.}-~.}.~.}..}.,},~,},~,},~,},~,},|,},|-}-}-}-},},|-},|-}-}.}.}-|-|,|,|,},|+}+|*~*|)~)|(~({&~&{&~&{%~%{%~%|%~%|%%|$$|%~$|$~$|$~$}%~$}$~$~$~$~$~$%~%%%~%'|+;vfkw
qer
{gyik^kM|:o(|#v"}!{"}#}"#}&(}*1{:EwOZs`cpc`pVOlQ\cm}_ixxv|~}k`^dz
~zC+z2<}Tz}{§z©ªx©¥uq¡orvz{|
~{w
l|ghizaqk}e]bwfhfdb_dbb`^]
[Y
VVVTQOKIGGEFHHIJJJKMO~OH|C<{97z7
6z56y65w33w3/y0CyKG{FG{GCyFIxM?w11x2:|GR}TU~SSRS~QPNM~LL~LK}KJ}IG}FD}:
,}#$|$%|)-}6<|=>{<>zAA{BF~I
ECA:4|,(z('{*+y+*y()z('|'&}&&}&&|&'|&'}'&}$&}%$}##}#"}%+}.
2}31~-'~""~"#~$%""###"""""#"~$%%&~&)~+'&&$&&%%''&&''(()}),}.1{8~={EvJ|PmW{`bj|vOu\LY\Zj^pG@A~A?zAFyH
IyKKyKLxNMxKJwLPuWesmvq|~r~~t}zr~
qqsrrssvz||
z{xwstnkrkkpkjnjknjhnggoggmghmlqkv|ikov}}olnpt
{y~zryxmy{k{zlxvmvumtumsukuvkvvkuwjvxj}k
ou{|ssu{}xuttttt*ÿ*ÿ*ÿ*ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ!ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ)ÿ,ÿ?ÿqÿÿtÿrÿyÿÿÿuÿfÿYÿFÿ1ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ"ÿ#ÿ%ÿ'ÿ*ÿ1ÿ9ÿDÿOÿZÿ`ÿcÿdÿaÿZÿ]ÿgÿqÿ~ÿÿÿÿÿyÿ~ÿÿÿÿjÿ`ÿ\ÿ^ÿvÿÿÿÿÿzÿ6ÿ/ÿ5ÿOÿwÿÿÿ ÿ§ÿ©ÿ©ÿ¨ÿ§ÿ¢ÿÿÿÿÿÿÿ|ÿÿÿÿÿÿyÿuÿvÿsÿfÿaÿgÿkÿrÿ}ÿuÿ~ÿlÿVÿ_ÿcÿbÿ`ÿ\ÿ\ÿ_ÿ]ÿ\ÿ[ÿ\ÿXÿVÿSÿSÿSÿQÿPÿMÿKÿHÿFÿDÿAÿAÿBÿAÿAÿCÿDÿDÿDÿEÿGÿIÿIÿJÿGÿBÿ<ÿ8ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ3ÿ3ÿ2ÿ2ÿ7ÿKÿOÿGÿGÿGÿGÿJÿGÿHÿLÿ=ÿ0ÿ2ÿ8ÿFÿPÿTÿTÿUÿSÿRÿPÿOÿPÿNÿMÿLÿKÿKÿKÿKÿJÿIÿFÿEÿBÿ8ÿ*ÿ#ÿ#ÿ#ÿ%ÿ&ÿ'ÿ&ÿ)ÿ+ÿ/ÿ:ÿ<ÿ<ÿ@ÿDÿDÿEÿFÿBÿ5ÿ-ÿ*ÿ+ÿ-ÿ-ÿ-ÿ*ÿ+ÿ+ÿ*ÿ)ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ#ÿ"ÿ$ÿ$ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ$ÿ)ÿ,ÿ1ÿ5ÿ9ÿ7ÿ5ÿ.ÿ)ÿ'ÿ(ÿ(ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ*ÿ+ÿ*ÿ,ÿ/ÿ7ÿDÿMÿTÿXÿ[ÿ\ÿ[ÿ^ÿhÿiÿWÿPÿJÿCÿ?ÿ>ÿ?ÿAÿCÿFÿHÿIÿKÿKÿKÿKÿLÿMÿKÿJÿJÿOÿVÿbÿiÿtÿzÿ~ÿ~ÿ~ÿ}ÿzÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿvÿpÿlÿiÿkÿlÿlÿjÿjÿjÿiÿgÿfÿfÿfÿgÿiÿjÿnÿrÿwÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ}ÿyÿxÿxÿyÿ{ÿ{ÿ{ÿyÿvÿvÿuÿtÿsÿtÿuÿuÿvÿvÿvÿuÿuÿwÿyÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ*~*+~++~,~-~-~+~,~.~.~/~/}0~0|0~0}0~0|1~1{1~1|1}1{1}1{0}0{1}1{0~0{1~1{0~0|1~1|1~1|1~1|1~1|1~1|1}1|1}1}0}0}/}/}/}/~/}/~.}.~.}/~.}./}//}//}//}/.}..}./}/.}.0}0/}//}/.}..}..}--}--}..}-,},~,},~,},},},}-}-|-}-|-}-}.}.},},},}.}.}.}.}.}-}-|-}-|,},|+}*|*~*|)~(|(~({&~&{&~&{&~&{%%|%%|%%|$$|%$|$$}#~#}#~#}$~$~$~$~$~$$~$%%}%+{.@vonuthyjpk_~Qm=|+s$|#x#}#}"~#~"#{%({,0z8CxNXu^asc_p_
kgu}^
_m|~
}|m_[\t}
{ynvw~bx21|Dp
}¥|¨¨z¨¨w£sqrwtuwxugxel{q
j}`eZ{ppm]
qbyUqSWYYYZZVWVTSSNOPLLKIFDA?=<;<?@A@CEF~FE|CC{A
<z75z64y46y55y56y62y;K{IC{DCzHHyHIxN=x16xFP{TU}TT~US~PONN~MM~LK~KI~IG~E?~5%~##}$$}%&}''|(*|-2};BFDA;}2
-{-,y--x-,y-,y*+z*(z((|((}(&}&%|'%|%%}%%}%%}$#}##}#"}""}"
"}""~"#~#"~"#~"#""###""####%*18~AGzIHwD<v3*{$#%%&&&&'~')~*+}+)}+,{0;{EzL|JrO}PnV}]oc^tUNzJE~B}@?{@AzCFyH
HyJKyKMxMKxJIwJOuT]shrqy~rt}rqq
s
rrstw{||
z}wwsnsjiqjjpjjnjjnhgnffoggmilmptkx|k
mszsmnpr
w~}v{xqwylz{kzylxvmvumttmstkuvkvukuvjwzj~lpvzxssv||
vtsssss*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ'ÿ,ÿ/ÿ>ÿjÿÿuÿsÿzÿÿzÿeÿWÿIÿ3ÿ'ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ$ÿ'ÿ*ÿ-ÿ6ÿAÿLÿVÿ]ÿ`ÿbÿaÿ_ÿnÿ|ÿ
ÿÿÿÿÿÿxÿxÿ{ÿ}ÿ}ÿoÿ]ÿZÿ[ÿgÿfÿ_ÿiÿ{ÿÿ|ÿHÿ)ÿ>ÿdÿ}ÿÿÿÿ¤ÿ¦ÿ¨ÿ¦ÿ¢ÿÿÿÿÿuÿ|ÿvÿpÿrÿpÿoÿlÿeÿaÿhÿhÿdÿgÿpÿeÿNÿ^ÿIÿcÿ
ÿYÿMÿQÿSÿTÿUÿSÿUÿUÿSÿRÿRÿPÿNÿHÿIÿKÿJÿJÿGÿEÿBÿ@ÿ=ÿ;ÿ9ÿ8ÿ9ÿ9ÿ9ÿ;ÿ=ÿ<ÿ?ÿBÿCÿCÿEÿEÿBÿBÿ@ÿ<ÿ7ÿ6ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ3ÿ>ÿIÿEÿDÿCÿDÿEÿDÿFÿHÿTÿCÿ6ÿBÿOÿTÿUÿTÿSÿQÿQÿOÿOÿNÿNÿMÿLÿKÿKÿKÿIÿIÿGÿEÿ?ÿ2ÿ$ÿ%ÿ#ÿ$ÿ$ÿ%ÿ&ÿ'ÿ'ÿ(ÿ*ÿ)ÿ4ÿ@ÿBÿBÿ?ÿ7ÿ.ÿ+ÿ-ÿ.ÿ/ÿ,ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ(ÿ'ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ#ÿ!ÿ#ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ"ÿ%ÿ*ÿ4ÿ?ÿJÿSÿWÿWÿVÿRÿLÿAÿ3ÿ'ÿ$ÿ#ÿ%ÿ&ÿ&ÿ&ÿ&ÿ)ÿ)ÿ,ÿ/ÿ-ÿ,ÿ.ÿ3ÿ6ÿ;ÿ<ÿ?ÿHÿKÿPÿ]ÿlÿlÿgÿ_ÿYÿQÿNÿGÿCÿ?ÿ?ÿ@ÿAÿCÿFÿHÿHÿJÿKÿKÿKÿKÿJÿIÿHÿIÿLÿRÿ]ÿgÿpÿxÿ~ÿÿÿÿÿÿ
ÿÿÿÿ
ÿ
ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿzÿvÿpÿkÿjÿjÿhÿjÿjÿjÿjÿjÿhÿgÿfÿfÿgÿhÿjÿlÿpÿtÿxÿ|ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿzÿwÿwÿyÿzÿ{ÿzÿyÿxÿvÿvÿuÿtÿtÿsÿtÿuÿvÿvÿtÿvÿwÿxÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ**+++,~..~.~.~-~.~/~/}1~1|0~0|0~0|0~0{0~0{1}1{1}1{0~0{1~1{0~0{1~1{1~1|1~1|1~1{1~0{0~0{/~/{0~0|0~0}0~0}0~0}/}/}/}/}/}/}/}/}.}.}.}.}/}//}//}/.}..}..}..}..}..}./}/.}.-}-.}..}.,}--}-,},~,},~,},},},}-}-}-}-}-|-}.|.}-|-}.|.}.|.}.|.}-}-|-}-|,},|+}+|*~*|)~(|(~({(~({(~'|&~&|&~&{&~&{%$}%%}$$|%$}#~#|#~#|#~#}$~$}%~%~$~$~%%|'-z3>te
pu
skv|npamP~=p+|$w#}%|$}#~#~#~###&})-}6@zKVw\asdelbnd
]_nu
w|x{{~o~\YZf{cgvpzsxt|=y-Vs~ ¥{§¥y£vszr{wqqjuihyjezg
j}hqw|{rtTHpQWg~bgIOONKPMSRNLJHDEHEDDCA?<6544366779>B}BB}AA{A
@{@
<{96{34z65z65x44x68x=FzED{DE{ECxELwWGw@MySU}SS~QPOOOM~MM~LKKJJHF>1$$#~$%}%(}('}'(}*+~496
0+-{..x--x..z-.{-.}-*~*(|$#~&'}*'|&'|()|)*}'&}&$}$
$}#
#}"
"~!#}##}##~#
"~"
"~##~%$"!"!!""""
#(2@LVuaijhgcaZgPBq3*z$$%%&~&(}*0}5:{=A|=~8{L|\{l|v}u|v~v~sn{ga}ZQMHD}@?|AByD
GzIIzJ
JxJ
KxKIyIIwIJuP[sgrqzstrqq
srs
su{{||
~zxrvnjrjioijojjojiohhogfohjmjmlrvkz~jnx{omops{
z}syvnvylz{jzxkwulutlsrlrtjuvjvvjvviw~imrv|tqsv}{vsrrrrr*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ)ÿ/ÿ6ÿAÿaÿ~ÿuÿsÿuÿxÿkÿZÿJÿ5ÿ&ÿ"ÿ$ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ&ÿ)ÿ-ÿ5ÿ?ÿIÿSÿ\ÿaÿdÿgÿnÿzÿÿÿÿÿÿ}ÿkÿpÿwÿvÿxÿzÿrÿ`ÿ]ÿ^ÿdÿgÿuÿzÿ{ÿyÿ}ÿÿxÿ/ÿ>ÿhÿzÿÿÿÿÿ£ÿ¢ÿ¡ÿ ÿÿÿÿÿzÿvÿpÿgÿgÿfÿhÿgÿeÿgÿoÿxÿ|ÿxÿwÿoÿbÿ\ÿWÿgÿ]ÿCÿKÿJÿFÿFÿIÿJÿTÿUÿMÿGÿFÿDÿ@ÿ?ÿBÿAÿ?ÿ@ÿ?ÿ@ÿ>ÿ:ÿ6ÿ3ÿ3ÿ1ÿ0ÿ0ÿ3ÿ4ÿ4ÿ5ÿ9ÿ>ÿ@ÿ@ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ8ÿ4ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ4ÿ6ÿ7ÿ@ÿHÿFÿDÿDÿFÿEÿEÿHÿOÿXÿKÿHÿPÿUÿUÿRÿQÿPÿOÿOÿOÿMÿMÿLÿLÿLÿLÿKÿKÿIÿGÿ?ÿ3ÿ$ÿ$ÿ#ÿ$ÿ%ÿ%ÿ'ÿ'ÿ&ÿ'ÿ(ÿ*ÿ(ÿ'ÿ*ÿ'ÿ(ÿ,ÿ/ÿ.ÿ/ÿ/ÿ/ÿ.ÿ0ÿ.ÿ-ÿ/ÿ.ÿ/ÿ-ÿ'ÿ%ÿ#ÿ#ÿ&ÿ'ÿ*ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ&ÿ'ÿ'ÿ&ÿ%ÿ$ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ)ÿ5ÿ2ÿ%ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ$ÿ,ÿ8ÿDÿSÿ_ÿkÿtÿxÿtÿoÿhÿ\ÿNÿ?ÿ0ÿ*ÿ%ÿ%ÿ%ÿ'ÿ)ÿ.ÿ0ÿ/ÿ4ÿ<ÿHÿYÿ[ÿPÿuÿÿwÿsÿvÿvÿuÿtÿqÿmÿfÿ]ÿTÿLÿFÿBÿ?ÿAÿAÿCÿEÿGÿIÿIÿJÿJÿJÿKÿKÿIÿIÿHÿHÿJÿPÿ[ÿgÿrÿzÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿvÿpÿkÿiÿiÿiÿiÿiÿiÿjÿiÿiÿhÿhÿgÿgÿjÿkÿkÿoÿsÿxÿzÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿyÿvÿvÿxÿzÿzÿyÿxÿwÿuÿtÿsÿsÿrÿrÿtÿuÿvÿvÿvÿvÿwÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,*+--,~--~.~.~-~.~1~1}0~0|2~2{3~3{2~2z2~2{2~2{2~2{1~1{1~1{0~0{1~1{1~1|1~1|2~2{0~0{1~2{2~2{1~1|1~1}0~/}0~0}/}/}/}/}/}/}/}/}/}/}/}/}/}/~/}/~/}//}/.}..}..}..}..}--}-.}.-},-}-,},.}.~.}.~,},},},}-}-}-}-}-}-}-}-}-|-}-|-}-|-}.|.}.|.}.|.}-}-|-}-|-}-|*}*|)~)|)~(|(~({'~'{(~(|'~'|&~&{&~&{%%{%%{%%z%$|$~$|$~$|#~#}$~$}$~$~$~%~&'{+1y:Bt\
|pu
qmqqnl~Vo?|-r&|$y#~%|$}%~$~###$&).}2;zEQwZ
bphmgvZXfz_uO~mj~ip}ps~d
a`f|s~wxvuy
vya3~[r}{xurprgauaezhg{b^}fow{p~orv}xlxo`voe8D|GBAA?AEEDAC>;=<;<=:853211/./1~11~59}<=}>>|>>|=={=;{85z56z77z68z88z:<{DH{FG{EFxFFwHMvTNwHQ{US~QPOONM~MM~MLKKJKFA5&%$~%%}%&}'&}')})(}&%|$(|+-y//x..y.0{0/}/
01/'""#~%
'},*|((|('|''}''}'%}%
%}%
$}$
#}#"}"
"}"
"~"
"~#
#~#&~-*$"!!!!!""%/=K|Ziqv~c^zr]g\eM>o1)z'),04}<J}TM|DO{O}_|~y|xvv|uu~tq|mf|^V~MFB~?D|AByD
GzIIzIJxKIxIHyHGwGHuOYrgrp|qr
rqq
srstx{{{
{~xxtnujhqhioihohjojiohgogfoikmmpltyk|nt{rllnpv
}~x~{pxwmvxkzyjxwkvtlsslsslstjuujuujvyj|jmry{~ssuz~yurrrr
rr,ÿ*ÿ+ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ(ÿ.ÿ4ÿ;ÿFÿWÿwÿuÿqÿqÿoÿnÿSÿ7ÿ'ÿ%ÿ%ÿ#ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ$ÿ&ÿ)ÿ-ÿ3ÿ9ÿCÿPÿZÿcÿmÿtÿ{ÿÿÿÿ|ÿuÿbÿ<ÿGÿkÿpÿpÿsÿqÿwÿhÿdÿcÿfÿsÿ|ÿwÿvÿÿÿÿÿÿPÿCÿeÿtÿÿÿÿÿÿÿÿÿÿÿÿÿpÿ\ÿ`ÿaÿgÿhÿcÿ`ÿ]ÿcÿlÿyÿ}ÿuÿkÿlÿ{ÿ|ÿ|ÿÿLÿ9ÿBÿCÿ<ÿ;ÿ;ÿ<ÿ@ÿ>ÿ<ÿ<ÿ=ÿ;ÿ9ÿ9ÿ8ÿ7ÿ7ÿ8ÿ6ÿ4ÿ1ÿ/ÿ/ÿ.ÿ/ÿ-ÿ,ÿ-ÿ/ÿ/ÿ/ÿ2ÿ6ÿ9ÿ:ÿ;ÿ>ÿ>ÿ>ÿ=ÿ=ÿ=ÿ=ÿ;ÿ8ÿ7ÿ7ÿ7ÿ7ÿ7ÿ9ÿ9ÿ7ÿ:ÿ<ÿ=ÿDÿGÿFÿDÿDÿFÿEÿEÿHÿLÿPÿNÿHÿNÿPÿPÿOÿNÿMÿLÿMÿMÿMÿMÿLÿJÿKÿJÿIÿGÿCÿ9ÿ&ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ&ÿ%ÿ$ÿ%ÿ+ÿ.ÿ.ÿ/ÿ.ÿ.ÿ2ÿ2ÿ/ÿ0ÿ3ÿ5ÿ5ÿ/ÿ'ÿ ÿ ÿ!ÿ#ÿ'ÿ,ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ#ÿ#ÿ!ÿ"ÿ"ÿ#ÿ"ÿ#ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ&ÿ0ÿ?ÿLÿYÿjÿ{ÿÿÿÿÿzÿqÿfÿ\ÿMÿAÿ8ÿ4ÿ3ÿ6ÿAÿSÿaÿlÿkÿ[ÿ]ÿbÿgÿyÿ}ÿÿÿwÿtÿtÿuÿuÿsÿnÿiÿbÿWÿNÿEÿBÿBÿAÿBÿDÿFÿGÿIÿIÿIÿJÿKÿIÿIÿHÿHÿGÿGÿKÿRÿ]ÿiÿsÿ~ÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿvÿqÿlÿjÿhÿhÿiÿiÿhÿhÿjÿjÿiÿhÿgÿgÿgÿiÿkÿnÿqÿuÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿyÿxÿvÿwÿyÿzÿyÿxÿwÿvÿsÿsÿsÿsÿsÿsÿtÿuÿuÿuÿvÿwÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-~-}-~.}.~.}.~.}//|01|11|22|22}22}2~2|2~1|12{11{2~2z1~1z1~2{1~1{2~2{2~2{3~3{3~3{3~3z3~3z3~3{0~0{0~0}/~/}/}/|/}/|/}/}/{/}.~.|.~.}.~.}.~.}.}.}.}.~.}..}./}/-}-.}-,~,~,~,~,~,~,},~,},~-}-~-}-~-}-~-}-~-}-~-{-~-|-}-|-}-|-}.|.}.|.}-|-}.|/}.|.}.|.}-|-},},|+})|)~){)~){)~(z'~'z&&{''{'~'z&~&z%~%z%~%y%~%y%~%z%%|%%|"$z$~$|%|%|%%|'~+|05y<IpXmlxkmhkkmYn2{$r%|%y$|%%~%%%~%~%&&(~+~28zAMs\hku|]~|Z{w]p\j;~(s?~f}y~rq|tz~jdcb}mtx{
v
}w{zy|u}IRmy~}{wumv_\y`a{^ayd
c{gt}{}{pxkutzxntwbj-o9><86~7:~::~86~75~53~26~65~10~/-~,,~,(~*-~,,~/1467:~<<~<;|;<{=9{77z89{9<zDKxA>{:?|EG{GEzDEyEFxGIxOEwAKyNK|LM}LKK
KKKKJHGGE=+#%}%&~&'~')}*,})(&%~$%{,.z//z/1|11688:8}0& "'~-.,*&
)}(
)}''|''~'&}%
%}%&}$
$}$$}$#}##}##~##~##~#
#~#
""!!
"~"%~.=L|]mqe^}Yum\h^eTPmIFtHT{bfe}k}eezlsyvyyx|yzv{uwt~r~qk}cY|PH~CAA|DE{GHzH
IzIJyJJxJGxGFwGLuS^qito
oprrrrrsw{||
z
~yxrvnjsihphhohhohhohhoggoghnjklprkv{k~nwz~nnlmqx
|u}zpwwlyzlyzkvvkutlstlttmttkttktskvxi|jptzw~sru}{xusrqq
qs,ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ%ÿ&ÿ%ÿ%ÿ)ÿ-ÿ3ÿ3ÿ=ÿJÿYÿgÿrÿnÿgÿfÿiÿdÿ@ÿ$ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ(ÿ*ÿ.ÿ6ÿAÿNÿaÿtÿÿÿ|ÿyÿuÿmÿaÿJÿ4ÿ.ÿ6ÿYÿxÿsÿrÿyÿ{ÿlÿaÿaÿgÿiÿrÿÿÿÿyÿwÿwÿzÿzÿdÿKÿ]ÿpÿxÿyÿÿÿÿÿÿÿÿvÿlÿhÿdÿdÿgÿaÿYÿ\ÿbÿiÿnÿvÿÿ
ÿ|ÿrÿlÿlÿnÿqÿsÿvÿ>ÿ1ÿ4ÿ7ÿ5ÿ4ÿ5ÿ5ÿ5ÿ6ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ2ÿ3ÿ1ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ(ÿ'ÿ)ÿ)ÿ+ÿ,ÿ/ÿ0ÿ2ÿ3ÿ8ÿ9ÿ:ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ:ÿ8ÿ9ÿ<ÿ=ÿ?ÿYÿbÿHÿBÿ@ÿ>ÿBÿIÿIÿFÿFÿGÿFÿFÿHÿGÿHÿJÿBÿCÿHÿJÿKÿLÿKÿJÿJÿJÿJÿKÿKÿJÿHÿGÿGÿDÿ@ÿ5ÿ&ÿ%ÿ%ÿ&ÿ&ÿ(ÿ*ÿ*ÿ*ÿ+ÿ*ÿ'ÿ%ÿ%ÿ$ÿ%ÿ,ÿ.ÿ/ÿ/ÿ.ÿ0ÿ4ÿ8ÿ=ÿ>ÿ;ÿ=ÿ;ÿ3ÿ'ÿÿÿ ÿ"ÿ*ÿ0ÿ1ÿ2ÿ/ÿ-ÿ,ÿ+ÿ+ÿ)ÿ'ÿ'ÿ)ÿ)ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ'ÿ.ÿ<ÿKÿ^ÿnÿ}ÿÿÿÿÿÿÿyÿuÿpÿkÿfÿ`ÿWÿWÿXÿ]ÿfÿgÿ_ÿ[ÿSÿOÿ]ÿdÿRÿJÿkÿqÿmÿrÿvÿuÿuÿqÿmÿdÿ\ÿRÿJÿCÿBÿCÿDÿEÿGÿIÿIÿIÿIÿHÿHÿHÿHÿGÿGÿFÿGÿLÿSÿ]ÿjÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿzÿtÿqÿmÿkÿiÿhÿhÿhÿhÿjÿjÿjÿjÿhÿgÿgÿgÿiÿjÿmÿqÿtÿyÿ{ÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ|ÿyÿwÿvÿxÿzÿyÿwÿwÿvÿuÿtÿsÿsÿsÿsÿsÿsÿtÿtÿtÿvÿvÿzÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.~.}.~.}.~.}.~.}00{01{11|22|22}33}3~3|2~2|23{33{3~3z2~2z3~3{3~3{3~3{3~3{3~3{3~3{3~3|3~3|2~2|1~1|0~0|0~0|/}/|/}/|/}/|/{/|/}/|/}/}/}.}.}.}.}.}.}.~.}.~.}.~.}..}-,},,~,~,~,~,~,~,},~-}-~,},~-}-~-}-~-}-~-}-~-{-~-|-}-|-}-|-}.|.}.|.}-|-}.|/}/|/}.|.}-|-},},}+})}*~*|*~*|*~)|(~(|)~){(~'{(~(|(~'|&~%z&~&y%~%y%~%z%%{%%{%$y%%y&~%z%~%{*~/y16u=MnZ
fkonkhdjcfjV+n!|%x$|$|$~$~%%%}&'~((~){.6tFXkob[}
x\ofaUEe?6m9Txu~p
l|||~k
cbe}ivu
suusu{vy|zb~L`pprv||{yrjygizhh|ml{kmxryyxzy~yymwaVxKK~G}MK~.03~11~11~12~31~0.~./~/-~//~--~-+~+,~,'~$)~)'~(,,035~88~::}9:};:}::};:|=?{?ByCC|BA}DF}IL|IFzHGyDFxIJwH@x>FzHJ|LI~FG~IJKJHFEC@9)$}$&|$(|++}++}*(~%%|%&{+.z00z.1~:=@~B@y=:w4(z%-247}85~/-~.*&~&%}&'}'
%}%%}%
$}$$}#%}#%}$%~%#~#"~""~"##"""~#%~*4~G}Y~jr}e^\]~`{ziwmplgwY[yW\zf^zF@yF5v<}`vv||xw~s{s~n|to{g\zSJ}DCC|BE{G
HzIIzIIyIIxIHxHEwHMuT_qmxooprrrrsuy
{|{y}ywtotljrhhphioiioihohhoggoginknlrtkx{nr~{qmnmqu|~y~~szxpvulwxlxwlvulutmssmssmssksukuvky{jkpv}tstxzvsrqqqsu.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ,ÿ+ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ&ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ'ÿ+ÿ)ÿ'ÿ%ÿ&ÿ(ÿ+ÿ,ÿ/ÿ5ÿ>ÿLÿZÿfÿoÿqÿkÿgÿcÿfÿcÿAÿ"ÿ$ÿ#ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ)ÿ)ÿ+ÿ3ÿDÿTÿlÿ
ÿÿÿÿ{ÿtÿjÿ_ÿVÿPÿIÿDÿFÿRÿaÿbÿfÿ~ÿzÿnÿcÿaÿhÿkÿ~ÿÿÿ{ÿsÿsÿuÿvÿwÿ|ÿyÿOÿQÿgÿlÿjÿkÿoÿuÿsÿtÿkÿgÿjÿkÿlÿoÿtÿ|ÿ}ÿÿÿÿÿÿÿyÿqÿ^ÿMÿDÿ;ÿ1ÿ2ÿ2ÿ4ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ1ÿ1ÿ.ÿ/ÿ/ÿ/ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ(ÿ%ÿ&ÿ&ÿ'ÿ(ÿ*ÿ+ÿ.ÿ1ÿ3ÿ5ÿ7ÿ8ÿ7ÿ5ÿ6ÿ7ÿ7ÿ7ÿ9ÿ:ÿ:ÿ>ÿBÿDÿFÿHÿJÿFÿFÿFÿFÿHÿJÿLÿMÿKÿJÿIÿHÿKÿKÿJÿGÿ@ÿ@ÿDÿHÿJÿIÿDÿDÿGÿIÿJÿIÿHÿGÿEÿBÿ?ÿ9ÿ+ÿ$ÿ$ÿ%ÿ&ÿ(ÿ+ÿ,ÿ,ÿ,ÿ+ÿ)ÿ&ÿ%ÿ%ÿ%ÿ*ÿ.ÿ0ÿ0ÿ/ÿ6ÿ>ÿ@ÿDÿCÿAÿ?ÿ>ÿ3ÿ(ÿ ÿ!ÿ"ÿ'ÿ0ÿ7ÿ<ÿ<ÿ9ÿ9ÿ8ÿ5ÿ2ÿ.ÿ(ÿ(ÿ'ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ(ÿ2ÿGÿYÿjÿ|ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ{ÿuÿnÿ]ÿ\ÿcÿiÿeÿZÿPÿVÿOÿ?ÿKÿjÿ}ÿ~ÿvÿtÿhÿCÿ`ÿqÿhÿaÿUÿMÿFÿEÿEÿEÿGÿHÿHÿIÿJÿJÿIÿIÿGÿGÿFÿFÿEÿHÿMÿTÿ`ÿnÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿ|ÿwÿsÿmÿkÿiÿhÿhÿhÿhÿhÿiÿiÿhÿhÿhÿgÿgÿgÿiÿkÿnÿrÿuÿyÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ}ÿzÿxÿvÿuÿwÿxÿxÿwÿvÿuÿuÿtÿsÿsÿsÿsÿsÿsÿsÿuÿuÿvÿyÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-~-}.~.{/~/{/~-{/~0{1~1{1~1{2~2y2~2{2~2{22{22{23{33{3~3z2~2z3~3z3~3z3~3z4~2z2~2|3~3|3~3{3~3{3~3}2~2}1~1}1~1}1~0}0~0}/|.z/|/z/~/}/~/}.~.}-~-}.~.}.~.}.~.}.~.}.~.~-~+~,~,~,~,~,~,~,~,~,},~,},~,},~-}-~-}-~-}-~-|-}-|-}-|-}-|-}-|-}-|-}.|.}.|.}.|.}.|.~.|.~.|.~,},~,}+~+}+}+}+}*~*|*~*|)~)})~)}(~(})~(}'~&{%~%z%%y%%{&&z&'y*8wC7v'~&x'~&y)-w06r?
Ll[fgo
tgp
igg
bheZm/|$u#|#{$|&$~$&}'~(,y3=pO
cix
ed
cwled
`cY
WaRNgOXsa~dnz{w}ngfh|pvsykxnr{pr}w{}WAWbface~gw}na}lo}s{}zxx~kxWDx86y44}44~71*(~)/~.-~--~-,~,*~))~)*~**~**~('~''~'%~#$~&&~''*,/1~34~55333337~88~<B}FI{JI|JJ}HI}LL|PR|QPzOQzNKzHIzHCyAByEF{BB}CG~GHI
HF
@?
9,
%%%}'(}+-..-*~(%~##|)-z..z1:@EIyE
BvB=v1${#%+2:}=?y=:y86y42|+*)%$~%
$~##}#%~%#~"$~$%~%%~%$~%&~%&~''~()})(~')~,~2|E{V~gtyib`_^
fm|pqdktojxi[|]h{[5zAdxvx|w{rb{cp|ja{XN~FEE|GIzI
IyJJzJ
IzIGyGDxDFvGKtUaomxnoprrrstw|||
zv|xvrlsjhqhhphgogioihohhoggoijnlomsvlz}nvynnnou{|
u~{qyxowvmxxlwvlttmtsmrrmrsmssksujuujy~jnqw~|uuvyytsrrp
qv
y|-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ(ÿ-ÿFÿZÿLÿ0ÿ0ÿ+ÿ'ÿ)ÿ-ÿ2ÿ8ÿAÿNÿZÿeÿnÿrÿsÿlÿiÿcÿcÿfÿQÿ)ÿ#ÿ#ÿ%ÿ%ÿ$ÿ#ÿ(ÿ(ÿ.ÿ9ÿKÿ`ÿtÿÿÿÿÿÿÿyÿjÿcÿaÿaÿ`ÿ\ÿXÿVÿUÿYÿhÿuÿvÿsÿxÿrÿhÿfÿiÿoÿÿÿÿuÿnÿnÿnÿoÿpÿwÿyÿYÿ8ÿKÿWÿ\ÿ^ÿ]ÿZÿ[ÿnÿyÿjÿwÿyÿrÿ}ÿ
ÿÿÿÿÿÿÿÿoÿWÿ>ÿ9ÿ4ÿ4ÿ8ÿ?ÿ;ÿ6ÿ9ÿ7ÿ.ÿ'ÿ(ÿ*ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ'ÿ&ÿ%ÿ%ÿ%ÿ#ÿ$ÿ$ÿ#ÿ$ÿ&ÿ%ÿ'ÿ)ÿ,ÿ.ÿ/ÿ1ÿ0ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ4ÿ7ÿ8ÿ>ÿCÿIÿKÿLÿLÿLÿMÿOÿNÿNÿQÿTÿWÿXÿXÿXÿVÿRÿOÿKÿJÿIÿGÿBÿAÿAÿ?ÿ=ÿ?ÿBÿCÿEÿFÿGÿFÿDÿ@ÿ<ÿ5ÿ*ÿ&ÿ&ÿ'ÿ(ÿ)ÿ*ÿ,ÿ,ÿ.ÿ-ÿ*ÿ(ÿ'ÿ&ÿ%ÿ(ÿ-ÿ.ÿ0ÿ0ÿ<ÿCÿEÿIÿIÿEÿBÿ:ÿ+ÿ!ÿ ÿ"ÿ%ÿ0ÿ7ÿ;ÿ;ÿ;ÿ<ÿ;ÿ:ÿ7ÿ4ÿ1ÿ.ÿ,ÿ+ÿ'ÿ#ÿ%ÿ%ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ#ÿ#ÿ$ÿ$ÿ$ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ+ÿ,ÿ.ÿ1ÿ8ÿFÿYÿjÿzÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿyÿrÿhÿ^ÿOÿGÿ>ÿ4ÿ0ÿ0ÿ3ÿ1ÿ:ÿAÿMÿmÿyÿuÿvÿvÿsÿoÿkÿcÿ[ÿQÿIÿFÿFÿHÿJÿJÿJÿJÿJÿJÿJÿJÿHÿHÿFÿFÿEÿGÿKÿUÿaÿmÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ|ÿvÿpÿkÿiÿhÿhÿhÿhÿgÿgÿiÿiÿhÿhÿhÿgÿgÿiÿjÿlÿoÿsÿvÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿwÿvÿuÿvÿxÿxÿwÿvÿtÿtÿtÿsÿrÿrÿrÿrÿsÿsÿsÿuÿuÿwÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ--}//{//{00{1~1{1~1{1~1y3~3{3~3{3~3{33{33{33{33{3~3z3~3z4~4z4~4z3~3z3~3z4~4z3~3z3~3{3~3{3~3{2~2{4~4|1~0|0~0{0~0{0}0}0}0}.~.}.~.}.~.}.~.}.~.}.~.}.~.}.~.},~,~,~,~,~,~,~,~,~,~,~,~,},~,},~,},~+}+~,},|-}-|-|-}-|-}-|-}-|-}-|-}-|-}.|.}.|.}.|.}/|/~.|.~-|-~+}+~+}+~+}+}*}*}+~+|*~*|)~)})~)}(~(|(~)|(~(z(~({'~'{(~(z((z()y+?vUPu<;v-~)v*.u2:qEPj[dfmocr
lfhdgbehgCo#{"w%{&$~%+.x;Opi|hde
jxclXXg^ab`_`]
ZbWWke{}{yoz|sggf~mtzxyvvpxom|or~uz|\36KVVXXR\ntxrbk~}zzy}ixP@y98z86{58|89~;?5(~''~'*~,*~)(~('~%%~%$~&%~%
%~%$~$$~$$~$"~!$~%$&
(*
.,-///0111499}?E}GM|NO}OO~QT~UV}WY~Y]}ZY|XU{RNzJI{IG{DBz><{=A{BA}BCB>8/~*'}'&}')}**}+,},*~((~(%}'+{/0{3@BGJ{J
Hv@2w'} &5:<z=>w=9x3/|--~.--*(~&
$~$
#}!!~!#~"!~! ~ ~!"~$$~%(~))~*.}-},}.{0}4tE~Zmp~h}eeb``_}`uja`
WiOEq7.{(&#&{*6t:
@nN
Ymi
oqtttnfx^SyLH~G}II|JJzJJzJ
IzIIyHFxFEvFLtWaolxnoprrrsw{}}{
yu{vsolsjiqhhphgoghohhohhoghojkmnoktwn{rzrmopqx}
xs}|qxwotvmxwlvvltsmsrmrrmrrmrskstjuyj}kosz{~tuu{|wsrrr
rt
xx{n.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ2ÿIÿXÿKÿ:ÿ-ÿ*ÿ+ÿ/ÿ6ÿ>ÿGÿRÿ^ÿgÿkÿkÿfÿlÿmÿcÿ`ÿcÿiÿfÿ;ÿ"ÿ$ÿ$ÿ%ÿ)ÿ,ÿ5ÿIÿhÿÿ
ÿÿ
ÿÿ
ÿ|ÿoÿXÿJÿNÿPÿXÿ^ÿbÿbÿ`ÿ`ÿ\ÿZÿcÿ}ÿ~ÿ{ÿÿuÿjÿjÿhÿkÿmÿoÿuÿxÿrÿmÿnÿnÿrÿuÿwÿ^ÿ1ÿ.ÿ7ÿKÿQÿPÿQÿMÿNÿXÿ^ÿZÿRÿFÿBÿiÿ}ÿ{ÿuÿzÿvÿkÿPÿ;ÿ5ÿ6ÿ:ÿ9ÿ8ÿ7ÿ7ÿ6ÿ8ÿ;ÿ<ÿ;ÿ.ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ#ÿ#ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ#ÿ$ÿ%ÿ&ÿ)ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ4ÿ7ÿ;ÿ>ÿFÿJÿKÿNÿPÿQÿQÿSÿVÿWÿZÿ[ÿ\ÿYÿ[ÿ]ÿ\ÿZÿYÿUÿSÿOÿNÿNÿKÿIÿGÿDÿBÿ>ÿ>ÿ@ÿ@ÿAÿAÿ?ÿ<ÿ5ÿ/ÿ*ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ*ÿ+ÿ+ÿ+ÿ*ÿ(ÿ(ÿ(ÿ'ÿ'ÿ+ÿ.ÿ.ÿ4ÿBÿBÿGÿIÿHÿFÿ?ÿ-ÿ#ÿ ÿÿ ÿ)ÿ5ÿ;ÿ=ÿ?ÿ>ÿ7ÿ2ÿ.ÿ+ÿ)ÿ&ÿ$ÿ(ÿ,ÿ,ÿ*ÿ&ÿ$ÿ#ÿ"ÿ#ÿ"ÿ"ÿ!ÿ#ÿ"ÿ!ÿ!ÿ ÿ!ÿ!ÿ#ÿ$ÿ%ÿ'ÿ*ÿ)ÿ*ÿ,ÿ,ÿ0ÿ5ÿ;ÿ9ÿEÿSÿdÿ{ÿÿxÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿyÿsÿkÿfÿZÿRÿBÿ9ÿ.ÿ(ÿ(ÿ+ÿ0ÿ9ÿDÿFÿLÿOÿNÿQÿ\ÿkÿkÿiÿcÿWÿOÿJÿIÿIÿJÿLÿJÿJÿJÿJÿIÿIÿHÿGÿEÿEÿEÿFÿLÿWÿaÿnÿyÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿuÿmÿjÿhÿhÿgÿgÿgÿgÿgÿhÿhÿhÿhÿhÿgÿhÿjÿmÿoÿrÿuÿxÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿ
ÿÿÿ}ÿzÿxÿwÿuÿvÿxÿwÿvÿuÿtÿsÿsÿrÿrÿrÿrÿrÿrÿsÿsÿsÿuÿxÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿ//{//{00y00y22z22z11z33z33z33z33y33y33z33z44y44y55y55y3~3z3~3z4~4z4~4z4~4{4~4{33{44{4~3{2~0{1~1{0~0{0~0{0~0{.~.}.~.}.~.}.~.}.~.}-~-}-~-}-~-}-}-}-}-}+}+}+}+}+},},},}+}+},},}+}+},},},|,|,|,|,{,},{,},|,}-|-}-|-}-|-}.|.|.|.|.|/~/|/.}.-}-+}+},},},~,}*~*}+~+}*~*})~)|*~*|)~){(~(z)~)z'~'z)~+y*~*y,},y+,y-0v9LvT<t--s/3r:BmL
Ug`
hajgba`ck
hhb
becohc}/r|$y(+z/>r^~id
hwn]Bs5<rELqRXh\_`ad^bbfds}xx{nkjij~lrytpykk|lo|uuz_2|,.9
HKHNKJLBB?4~;d~xzzpbwN;x45y79z;;{:9}68}:;};6}(%}$$}%%~%$~$#~#"~""~"
"~"
"~"
"~"
"~"
"~"
!~!!~#$%
&&
(+---,--/.;~A=|BE|IL|OR|ST~UU}UX}\^~]^~^]|]\|XU|SS|TR|QP|IFzD@{=;|>=|<9}4.|*)|)(|(({((}*+~+*}**}*(|')|*.}4AFH
H|E
Bz7)| ~"#,6=>|<9z40{,*~('$$'(*(
$~##~#$~$"~!"}""}! ~!$~%'~(+~--/3}6z<|Qkh|nRjpFN{\eca`a`y`tlef\lREu73w22r49n?GhEDbGI]KW[ZYd]aoTIuHK~MIK|LI|JIzIHyFExDEwFLtW`pnxopqq
rr
vy
|~|
{xuzsrlirggqggoggnggnhhnhhmhimjmkoslxxov|~qnppu
y}ur}zpyvnvwlxwlutmtsmsrmqsmssmsrmstiw|imqx}ywwy{vsrrt
vxz|ok/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ1ÿ1ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ*ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ1ÿ1ÿ1ÿ/ÿGÿAÿ1ÿ1ÿ2ÿ8ÿ?ÿHÿPÿ[ÿcÿiÿjÿeÿ[ÿQÿZÿmÿgÿbÿaÿgÿqÿTÿ$ÿ#ÿ)ÿ,ÿ2ÿHÿlÿÿÿÿÿyÿiÿIÿ.ÿ)ÿ2ÿ4ÿ;ÿDÿJÿRÿXÿZÿ]ÿaÿdÿfÿhÿuÿÿÿÿÿqÿiÿgÿfÿhÿlÿmÿkÿiÿiÿkÿlÿoÿqÿvÿ_ÿ,ÿ)ÿ+ÿ.ÿ4ÿFÿHÿEÿGÿFÿ>ÿ6ÿ5ÿIÿFÿ5ÿ3ÿVÿdÿ]ÿHÿ<ÿ5ÿ5ÿ6ÿ8ÿ9ÿ;ÿ;ÿ:ÿ:ÿ9ÿ7ÿ9ÿ:ÿ;ÿ=ÿ-ÿ%ÿ%ÿ#ÿ$ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ#ÿ$ÿ&ÿ&ÿ%ÿ&ÿ(ÿ*ÿ+ÿ*ÿ*ÿ*ÿ,ÿ.ÿ4ÿ9ÿ;ÿ<ÿCÿHÿNÿOÿRÿRÿRÿSÿSÿTÿUÿWÿ\ÿ]ÿ]ÿ^ÿbÿbÿ`ÿ^ÿYÿVÿXÿYÿXÿXÿWÿWÿUÿMÿIÿCÿ=ÿ9ÿ7ÿ8ÿ7ÿ3ÿ0ÿ,ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ*ÿ,ÿ/ÿ5ÿAÿFÿFÿEÿCÿ=ÿ/ÿ#ÿÿ ÿ!ÿ%ÿ-ÿ6ÿ<ÿ?ÿ;ÿ7ÿ1ÿ.ÿ+ÿ)ÿ&ÿ%ÿ#ÿ#ÿ"ÿ#ÿ$ÿ&ÿ#ÿ"ÿ$ÿ)ÿ)ÿ%ÿ%ÿ#ÿ"ÿ#ÿ#ÿ#ÿ"ÿ#ÿ%ÿ'ÿ)ÿ*ÿ,ÿ0ÿ2ÿ2ÿ/ÿ1ÿ=ÿ\ÿuÿ~ÿzÿyÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿwÿpÿiÿcÿ\ÿPÿFÿAÿ:ÿ7ÿ6ÿ8ÿ:ÿ?ÿ?ÿAÿDÿFÿIÿNÿKÿNÿHÿJÿ^ÿeÿSÿUÿZÿHÿFÿJÿJÿJÿJÿJÿGÿFÿEÿDÿDÿHÿOÿXÿbÿoÿzÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ}ÿvÿrÿmÿiÿgÿfÿfÿfÿfÿgÿgÿgÿhÿhÿhÿhÿhÿjÿlÿpÿrÿtÿwÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿyÿwÿuÿvÿwÿxÿwÿuÿtÿtÿsÿsÿtÿtÿrÿrÿrÿrÿrÿsÿtÿwÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿÿÿÿÿÿÿ00{11{22{33{33z22z22y22y33y43y23y44y4~4z4~4y44y44y55y55y5~5y3~3y4~4y4~4y4~4z5~5z55{44{3~3{2~2{3~2{2~2{0~0{0~0{0~.}.~.}.~.}.~.}-~-},~.}.~.}-~-},},},},}+}+}+}+}+},}+},}+}+}+},}*},},},},|,|-|-|-|-|-|-|.|.|-|-|-|-}-|-}.|.~.|.~.|.~.|..}.~-}-~-}-}.}.}-}-~,}+~,~,},~+}*~*|*~*|)~){*~)z)~+z+~-z.~.y0~0y1~2x1}1x00w11w9Bt=5s7>pENkT]efjbjbcUKgHaik
eicdhnqj>|!s(*w6
Qryk}~lsWq9|&x(|+|,1{5<wAHqP
ThZ_cdlhr~uzv|s}hdbgmq{mk{ij}ko}opz^+y)+~**7FIEC>3,13~1*~1D|C8z43x24y7:z::{;;};7}8:~;<~6(|('|&%}&'}'%}$"}"#}#
"}"
!}!
!}!
!~!
~
~ ~! "#$%(***,,,.3~67};<}BG}LO}RQ~QQ}SV}VZ~Z\~[`~ac~a^}\]}^]|Y\|^W{SN{ID{<7{56{40{..{-,{*)z()|)*|*+|++}+,|+,|-1}7@DED~A7}+!""#}(.|4;}>}:4|/*~)&%#""!"$!%&)~))~&$~#"}$%}%%~&&~*+~+,~./-~.};uKXectYxwXs
|`{egebayb|e
e|xcridb\fV
OlB:l>:h=@dCE^GHYLNUUQOVZUJH]VS^_if\MrIJJ|KIyGEyCDwHQtXdpoyopqr
rtx{}~{yw{tvqrmhqgfpeepegnggnhgnhhmimmmpkqtlvq
zwqsru
y}~ztr}{oyxmyylyultsmsrmrrmrrmrqmqsmsuiv{imry|~vwwzyutssvy|}rmk0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ-ÿ-ÿ/ÿ0ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ>ÿJÿCÿ9ÿBÿLÿTÿ]ÿfÿkÿlÿjÿ`ÿUÿEÿ8ÿHÿiÿiÿdÿbÿiÿtÿaÿ*ÿ#ÿ+ÿ:ÿZÿ|ÿ}ÿxÿwÿcÿ7ÿ%ÿ'ÿ*ÿ)ÿ,ÿ,ÿ0ÿ5ÿ9ÿ@ÿEÿMÿSÿ[ÿfÿrÿxÿÿÿ
ÿyÿxÿsÿeÿ`ÿ`ÿaÿiÿpÿoÿfÿgÿkÿmÿnÿoÿrÿ]ÿ,ÿ(ÿ*ÿ-ÿ1ÿ2ÿ9ÿFÿAÿ@ÿ<ÿ6ÿ*ÿ*ÿ)ÿ(ÿ'ÿ(ÿ,ÿ/ÿ0ÿ2ÿ3ÿ5ÿ9ÿ8ÿ:ÿ:ÿ9ÿ:ÿ;ÿ;ÿ9ÿ7ÿ7ÿ9ÿ<ÿ;ÿ-ÿ(ÿ*ÿ+ÿ,ÿ(ÿ)ÿ)ÿ*ÿ)ÿ(ÿ'ÿ%ÿ#ÿ$ÿ$ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿ ÿÿ"ÿ#ÿ$ÿ%ÿ&ÿ)ÿ)ÿ)ÿ*ÿ,ÿ,ÿ,ÿ/ÿ3ÿ6ÿ8ÿ9ÿ<ÿAÿEÿJÿOÿRÿRÿPÿPÿTÿVÿXÿ[ÿ\ÿ\ÿ_ÿ^ÿ`ÿaÿbÿ`ÿ_ÿ_ÿ_ÿ\ÿ[ÿ[ÿ[ÿZÿWÿIÿNÿHÿ<ÿ7ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ-ÿ,ÿ+ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ-ÿ0ÿ;ÿ@ÿDÿEÿDÿ?ÿ5ÿ&ÿ ÿ!ÿ"ÿ$ÿ)ÿ.ÿ3ÿ9ÿ;ÿ9ÿ5ÿ0ÿ+ÿ'ÿ&ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ$ÿ%ÿ(ÿ*ÿ*ÿ*ÿ$ÿ%ÿ#ÿ#ÿ#ÿ$ÿ&ÿ(ÿ'ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ3ÿAÿPÿWÿBÿ=ÿ9ÿ<ÿ:ÿZÿlÿrÿ~ÿÿÿÿÿÿÿÿ|ÿuÿsÿ{ÿÿÿÿ~ÿxÿpÿkÿgÿ_ÿUÿNÿDÿBÿ@ÿ@ÿCÿFÿKÿKÿMÿLÿLÿQÿNÿHÿ?ÿKÿKÿLÿOÿXÿgÿlÿbÿIÿIÿKÿIÿHÿFÿDÿCÿEÿIÿPÿ\ÿeÿpÿyÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿuÿqÿmÿgÿfÿfÿeÿfÿfÿgÿgÿhÿfÿgÿiÿiÿiÿlÿnÿqÿrÿuÿvÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ~ÿ{ÿyÿxÿyÿyÿwÿtÿtÿsÿsÿrÿrÿqÿqÿqÿpÿqÿqÿsÿsÿuÿxÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ00{12{22z33z22y22y33y33y33y43y34z44z4~4x4~4x55w55w55x55x55y55y55x44x44z44z55{6~6|4~4|4~4|4~2|2~2|1~0{0~0{0}0}/}.}.}.}.}.}-~-}-~-}-~-|,~,|,|,},|,}+}+}+}+}+}+},}-}+}+}+}+},},}+},},|,|-|-|,|,|,|,|,|,|-|-|-|-|-|-~.|.}.|.}-|-}-|-}-}-}-}-}-}-~-}-~-}-~,}+~+~+~+~*|*}*}+}+|*~*{,~,y/~/x1~2x3~3y4~5y6~6y5~6y5~6x7~7w88uDLrDHoRYh`
fcmmai[dP>j05mV
plicgcjeqHk$~)r8Zq~{pxsr\z.u&z&{)}((~+,039{?FrKXmiwqy~uszuyd^\]cl}ph{gg~il|or{Z-z'*{-5;9RH=:92+
%'$&&{)-z.2y7@x=9z89{:9}98}66~8<~<7|-+z-/|,,|,
.|..|,)|%#|$#}#"}""~
~
~"~##~$&'())),,/37~31}39}>A}HM~PQ~OQ~SU~VX~Z]}][}[a}dc~bb}_^|_a|b\|RT|RG|;7z53|32{0-{.-z++z+,{,+{+,{,.{/0}03~;
@CCA;1$!!%'}(-|26}:93-)(&%$""$%#$%())~,-{(%~$~$$~&''()**)(+}/}8~EwTUq=5s*'}/B|X~kv}~nid}cukghqkxje~xaslceadYRfKCeBFaFFXIPSQSOWTNLITP
NSO
ROYeQV]d]
MyK~JJ{G
ExDEwJSs]fpqyooq
qs
wy}}|ywu~xssorkgpgeodfofgoggohgohinkmmprltvmxs|uttt
y|~~~xsq|oxxlzxlwtlsslsrlrqmqrmqqmqsmtskyjns{|wxx{}yvtrsw|tmmn2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ,ÿ,ÿ+ÿ+ÿ,ÿ-ÿ/ÿ0ÿ3ÿ3ÿ6ÿ7ÿ6ÿ6ÿ8ÿ8ÿ9ÿ9ÿ8ÿ8ÿ8ÿ:ÿ;ÿ;ÿ;ÿ;ÿ>ÿJÿUÿRÿTÿ\ÿcÿjÿrÿmÿcÿWÿJÿ8ÿ.ÿ,ÿ?ÿfÿmÿeÿbÿeÿlÿfÿ;ÿ$ÿ5ÿQÿwÿ|ÿvÿqÿ^ÿ6ÿ&ÿ'ÿ)ÿ)ÿ'ÿ(ÿ)ÿ*ÿ.ÿ3ÿ8ÿ>ÿGÿWÿlÿyÿuÿ|ÿÿÿÿzÿvÿhÿ^ÿ\ÿ[ÿ^ÿcÿhÿgÿgÿeÿgÿmÿpÿrÿZÿ+ÿ'ÿ*ÿ-ÿ5ÿ<ÿCÿcÿ[ÿBÿ7ÿ9ÿ3ÿ,ÿ,ÿ(ÿ%ÿ(ÿ(ÿ)ÿ*ÿ,ÿ.ÿ5ÿ<ÿ;ÿ5ÿ5ÿ7ÿ8ÿ8ÿ7ÿ8ÿ7ÿ6ÿ5ÿ7ÿ7ÿ:ÿ2ÿ+ÿ+ÿ-ÿ,ÿ+ÿ+ÿ,ÿ-ÿ.ÿ/ÿ,ÿ*ÿ)ÿ(ÿ&ÿ&ÿ$ÿ"ÿ!ÿ ÿÿÿ ÿ ÿÿÿÿÿ"ÿ#ÿ#ÿ$ÿ&ÿ'ÿ(ÿ'ÿ(ÿ(ÿ(ÿ)ÿ-ÿ1ÿ1ÿ0ÿ/ÿ0ÿ5ÿ:ÿ>ÿAÿGÿNÿRÿTÿPÿRÿSÿSÿUÿUÿWÿXÿXÿ[ÿ\ÿ]ÿaÿ_ÿ`ÿ_ÿ_ÿ[ÿ[ÿYÿYÿ[ÿVÿSÿNÿGÿ@ÿ6ÿ7ÿ4ÿ2ÿ1ÿ0ÿ-ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ1ÿ3ÿ3ÿ3ÿ;ÿ>ÿAÿAÿ?ÿ9ÿ/ÿ$ÿ!ÿ"ÿ(ÿ+ÿ,ÿ/ÿ1ÿ9ÿ<ÿEÿDÿ>ÿ3ÿ+ÿ'ÿ&ÿ$ÿ&ÿ&ÿ#ÿ$ÿ#ÿ&ÿ)ÿ)ÿ(ÿ*ÿ.ÿ.ÿ-ÿ,ÿ)ÿ%ÿ$ÿ%ÿ'ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ+ÿ2ÿ6ÿ4ÿ<ÿ1ÿ*ÿ'ÿ'ÿ*ÿ5ÿCÿZÿnÿ~ÿÿÿÿÿÿÿ|ÿqÿjÿeÿiÿqÿxÿÿÿÿ}ÿxÿsÿoÿkÿeÿYÿRÿLÿJÿIÿOÿOÿQÿUÿYÿ\ÿ\ÿVÿQÿWÿRÿKÿNÿTÿWÿZÿUÿIÿPÿXÿKÿDÿEÿEÿEÿDÿFÿNÿTÿ]ÿgÿrÿxÿÿÿ
ÿ
ÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿxÿsÿoÿkÿfÿfÿeÿdÿfÿfÿgÿgÿgÿhÿiÿiÿjÿlÿnÿpÿrÿuÿvÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿ~ÿ|ÿyÿxÿzÿuÿwÿtÿrÿsÿsÿrÿrÿqÿqÿqÿqÿqÿqÿrÿsÿtÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ33z33z33y33y13x44x33y33y22y44y45y55y4~4x3~3w45y55y55y55y55y66y77y55y55z55z55z6~6{6~6|5~5|4~4|3~2|2~2}1~1}1}1}/}.}.}.}-}-}-~-},~,},~,~-~-~-~-~,~,~,},~,},~+}+}+}+}+}+~+}+~,},}-}-},|,|-|-|-|-|-|-|-|-|-|-|-|-|-|-~.|.}.|.}-|-~-|-~.}.~-}-~.}.~.}.~-}-~,}+~,~,~,~,|-}-}-}.|1~2{2~2y5~5z9~;z:~:z;~;z;~<z;~;z<~<x=~=w=@vCFtOVoZ\kf
nfvncaShF~5n.,r3Mrp
iof
cjemk]/q*Dqj{oupme}Ap&z%y'{)'}&)})*}/48zAZtryvo~suszvyna][^eff~fcfj~lk{Z-z%&{,17Jg
[N8//,+~+
(~(
*|++{*,{.1z32z24{57}76}85}59~65~82}*
)|+,|,
+|+
,|,
+|,+|)(|''|&$~
~
}}~ ~""~#$&''''''((*~()},/}28|>A}KQ~QR~TS~RS~SS~TT~RU}XZ~\Y}[^}]]}dc|`Z}RQ}Q
M}I
8}87|2
1{1/z0-z--z--z--{//{34}54~8=>><6+##$~&)|/1}9M~`ylprk_rRAw3+|*&"""&|-6t46p88o83r0-w,*{++{*){))|)&|%*+'%%'''(2}C\|pw|ogc|dpch\`phroyjd}zbtp`kh_bbZcaS`_Kb_G^]F[
bFmh@Y[K^]JYWLP@W8OeV
erZG~F|FGwLUt`iqszpqrsuz|~}{
xvs}wrrnpjgqfeodfnffnghmhimijmmokrsltyov|~tsst
y}~xq~o}|mzylzvlvslqrlrrlrrmqpmpqmqrmsuj{jou|yvyy}}
wttsv{x~pmps3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ/ÿ2ÿ4ÿ7ÿ5ÿ8ÿ;ÿ>ÿ?ÿ=ÿ>ÿ>ÿ>ÿ@ÿ@ÿ@ÿAÿAÿAÿBÿBÿCÿDÿFÿJÿLÿSÿVÿYÿZÿjÿvÿnÿ_ÿOÿDÿ5ÿ/ÿ,ÿ-ÿ9ÿZÿtÿiÿeÿdÿjÿlÿZÿ.ÿ2ÿYÿwÿvÿpÿiÿOÿ*ÿ'ÿ)ÿ'ÿ(ÿ&ÿ)ÿ*ÿ)ÿ+ÿ/ÿ5ÿDÿ`ÿwÿsÿjÿnÿ~ÿÿzÿtÿoÿqÿfÿ^ÿ\ÿ\ÿ`ÿbÿfÿfÿcÿgÿmÿlÿmÿ\ÿ.ÿ&ÿ&ÿ+ÿ+ÿ0ÿMÿgÿYÿRÿQÿ9ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ,ÿ,ÿ-ÿ.ÿ-ÿ-ÿ-ÿ/ÿ/ÿ1ÿ4ÿ5ÿ8ÿ7ÿ5ÿ5ÿ6ÿ8ÿ7ÿ5ÿ@ÿPÿ+ÿ*ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ+ÿ,ÿ*ÿ(ÿ'ÿ&ÿ$ÿ"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ'ÿ'ÿ(ÿ(ÿ*ÿ/ÿ9ÿ8ÿ<ÿFÿLÿRÿSÿTÿPÿTÿXÿUÿTÿQÿOÿQÿOÿQÿSÿUÿVÿWÿ^ÿaÿbÿ_ÿbÿfÿcÿ]ÿUÿTÿRÿQÿMÿ:ÿ5ÿ;ÿ9ÿ4ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ0ÿ2ÿ5ÿ6ÿ8ÿ4ÿ4ÿ8ÿ9ÿ;ÿ6ÿ0ÿ'ÿ#ÿ#ÿ%ÿ%ÿ+ÿ6ÿFÿ[ÿsÿÿÿ
ÿÿÿzÿmÿ`ÿTÿBÿ.ÿ%ÿ$ÿ,ÿ9ÿAÿFÿGÿFÿEÿAÿCÿBÿ<ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ/ÿ-ÿ*ÿ'ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ(ÿ4ÿHÿ_ÿuÿÿÿÿÿÿÿÿ{ÿqÿaÿWÿYÿ`ÿjÿsÿyÿÿÿÿÿ|ÿwÿqÿjÿgÿhÿdÿdÿdÿaÿ`ÿaÿfÿkÿlÿlÿcÿ[ÿWÿXÿWÿ`ÿeÿfÿaÿaÿ[ÿLÿUÿkÿ\ÿJÿEÿFÿKÿOÿXÿbÿiÿsÿzÿ|ÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ{ÿwÿrÿnÿjÿfÿeÿeÿdÿfÿfÿfÿfÿfÿfÿiÿiÿjÿmÿoÿrÿsÿuÿ|ÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ~ÿ|ÿzÿyÿxÿvÿvÿsÿqÿrÿrÿrÿrÿrÿqÿpÿpÿqÿqÿrÿsÿvÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ22y22y22y33y44w46w55w44w33x44x44w44y55x55x55y44y66z66z66z66z55x66y66z66z66{66{7~6|6}5|4~4|4~4|21}11}1}1}0}/}.~.}-~-}-~-~,~,~,~,}-~-}-~-},~,}+~+|,~,|+},|,},|-},{+},{-}-}-}-},|,|+|,|.|.|-|-|-|-|-|-|-|-}.|.}.|.~-|-~-}--}-.}..}./~/.-}-},|*}+,~,}-~-|/~0{1~2{5~7{8~9{<}>|?}A|B}A|C}D|C}DzC}DxD~DxE~EwE~GvHKsQYn_^j\cgpkfZLj?3q/.t-0uAirplnd
fkmsmY-nGlnwuimYh5{&r({)}'{))|*,~-1;|Sqwxlxj}r|s|tvomyqm`
][}_acfgij~lm|_.z%*},.~1Pe
]WU~V:}+'}(
(((~)*}-.}/-{,,z-.{04{45|31}4433KxR}!//|.-z-
-z,
+|+*|++}*(}'&~%"~ ~ !~~~~~} !!~#$~$#~"&~&
&~(,~37~=EJOSW~SS}PR}OQ}NQ}OP}RT~RZ~`e}fg|hd|dZ|N>~QNY@~0;|=7{22{00y00z10z01{68{86|24~52~*
&~#
%~&(4J^zppjgggzjkTp1)u:EuMIqA?nEFjFEjA;n:8p::s:<q<:p86s3,x&!| """%'7}N}d}ws}jbb}ancdU
SnY
cpktl{e
b\zzYy
xTx
wLm
l@nm:uy6up8kl=qoEsrCik@jkHnoU]NfTOuGCMyUZsbirrxr{~sq
ty}~}{
zvs}ryuqpnqjgpgfpffnegngfmgikjkkmpjrvkvqzvqrtv{
~~}wqo~|nzylxvmvsmqqmqqmqrlrqlqplpqkrxkkou~wvyy{trtuxv|onquv2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ3ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ-ÿ,ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ,ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ3ÿ6ÿ8ÿ9ÿ<ÿ>ÿ?ÿAÿBÿDÿEÿFÿGÿGÿGÿGÿGÿGÿHÿHÿIÿIÿIÿKÿJÿNÿUÿ]ÿdÿgÿgÿaÿdÿgÿ[ÿLÿ?ÿ2ÿ/ÿ.ÿ-ÿ0ÿ4ÿTÿpÿkÿiÿdÿhÿrÿuÿJÿ<ÿaÿzÿwÿpÿcÿ=ÿ%ÿ'ÿ&ÿ'ÿ(ÿ)ÿ*ÿ+ÿ-ÿ0ÿAÿ`ÿxÿvÿkÿlÿxÿ~ÿxÿsÿmÿlÿoÿvÿdÿ]ÿ\ÿ[ÿaÿcÿeÿfÿfÿiÿkÿlÿ]ÿ/ÿ&ÿ*ÿ,ÿ-ÿ3ÿSÿgÿ[ÿYÿ[ÿXÿNÿ9ÿ*ÿ'ÿ&ÿ&ÿ'ÿ)ÿ(ÿ-ÿ0ÿ0ÿ/ÿ.ÿ+ÿ+ÿ-ÿ0ÿ1ÿ2ÿ3ÿ1ÿ1ÿ6ÿ9ÿ9ÿIÿeÿÿÿ6ÿ&ÿ.ÿ0ÿ0ÿ/ÿ1ÿ1ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ(ÿ'ÿ&ÿ%ÿ$ÿ$ÿ%ÿ%ÿ"ÿ!ÿ"ÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ ÿ!ÿ!ÿ ÿ!ÿ#ÿ"ÿ"ÿ'ÿ)ÿ-ÿ/ÿ7ÿ?ÿDÿFÿIÿNÿQÿLÿNÿPÿRÿPÿMÿJÿLÿNÿQÿRÿRÿSÿWÿ]ÿeÿfÿfÿhÿgÿdÿZÿUÿVÿKÿIÿZÿNÿ6ÿ7ÿ=ÿ:ÿ6ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ7ÿ8ÿ8ÿ6ÿ4ÿ2ÿ3ÿ0ÿ(ÿ%ÿ%ÿ'ÿ,ÿCÿaÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿwÿOÿ0ÿ:ÿBÿMÿBÿ>ÿ<ÿ@ÿLÿJÿIÿDÿ?ÿ9ÿ6ÿ7ÿ:ÿ;ÿ<ÿ<ÿ9ÿ9ÿ9ÿ8ÿ-ÿ$ÿ ÿÿ!ÿ$ÿ$ÿ$ÿ*ÿ?ÿSÿiÿ}ÿÿÿÿÿÿÿÿÿtÿdÿSÿKÿPÿZÿeÿoÿvÿ|ÿÿÿÿ
ÿÿÿÿ{ÿzÿwÿuÿqÿsÿxÿzÿyÿxÿtÿuÿfÿaÿdÿkÿjÿpÿqÿoÿnÿgÿ_ÿTÿWÿgÿbÿfÿTÿLÿTÿ\ÿeÿjÿsÿvÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿÿÿÿ~ÿ|ÿxÿtÿoÿmÿiÿfÿeÿeÿeÿfÿeÿgÿgÿfÿgÿiÿjÿlÿnÿqÿqÿtÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿzÿzÿyÿvÿtÿsÿqÿqÿqÿqÿqÿqÿqÿoÿoÿpÿpÿqÿrÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ33x33x44w44w44w45w44w55w55x44x44w55y55x55x55y55y44y45y66y66y45x56y77z66z56z66z6~6z6}6z5~4|4~4|3}3|2}2|1}1}0}/}.~.}-~-}-~-~-~-~-~-}-~-}-~-},~,},~,|,~,|,},|,},|,},{,},{-}-}-}-}-|-|,|,|-|-|,|,|-|-|-|-|-|-}-|-}-|-~-|-~-}--}-.}..}./~///}0~1|0~.|.~.|0~1{3~5{5~9{:~;{?~A{C}D|D}E|G}I|I}J|J}LzK}KxL~LxL~KwL~NuOSrY`ngmjpmed\eUKh?6o2/t//u/;t_
pphekd
jjt
kj@
UjwydsicF}(m'z'z){)(|)*-3Fzi{vrkvn|ztqmqkduav|k\]}\`|cdcch~jj|]0z'*{+-}3X~hZ~Y
Y|V
R{J>z,&|&'}''+2~3/|..{,,{-.|./}24>FPaxrcwn+{1}3z23z43|2
1|/
-|+(|(%}#
"}#
#~##~" ~ ~~~}~ ~ !~! ~!#~$&~)-~3:@DEFHH~GJ}LN~ON~NM~MP~OQ~U\}`f|ei|jg|d[|RM}JZ~e|XFv?~<y:}73|20{1
1{23{66{87|63~32}+'}'
)}AivliigefgilBn?FtLEuD~MrI}TmYUhOMcIBd==g>AhA>g>?i<2s($|#%%&'.B}Xm}u~kgcayebNBjHRr_iqpxjc]
Z~UyvOutIuqBtw<wk>fcG]ZM`^NbfRb[Wa\PZ=MAQcd]q^[}_yemurvszzq|q
v{~
|
ywsr}zpvsqnlqiepeepefnffnffmgikjnkosjstm{t~|ustvx
}
~~}wpo~|nyxlyumsrmpqmqqmpplpoloplppku{kkrx}wxy{zutvy{{q}mn
ty~|3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ4ÿ5ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ3ÿ3ÿ5ÿ4ÿ0ÿ0ÿ0ÿ2ÿ3ÿ3ÿ7ÿ8ÿ<ÿ>ÿ?ÿCÿEÿFÿEÿHÿJÿMÿNÿNÿNÿOÿPÿPÿOÿPÿPÿPÿOÿQÿRÿRÿUÿZÿaÿhÿnÿoÿsÿpÿbÿRÿCÿ>ÿ7ÿ5ÿ4ÿ3ÿ/ÿ/ÿ1ÿCÿjÿpÿfÿeÿeÿlÿyÿ^ÿNÿtÿ|ÿvÿoÿPÿ+ÿ&ÿ(ÿ*ÿ*ÿ(ÿ(ÿ*ÿ-ÿ2ÿJÿmÿyÿoÿlÿrÿÿxÿnÿlÿgÿbÿdÿoÿpÿbÿ`ÿ^ÿ^ÿaÿcÿaÿbÿeÿiÿhÿ\ÿ0ÿ'ÿ+ÿ,ÿ0ÿ6ÿ_ÿeÿ[ÿ]ÿ]ÿ[ÿPÿOÿLÿ@ÿ.ÿ(ÿ'ÿ&ÿ'ÿ(ÿ.ÿ2ÿ/ÿ-ÿ,ÿ,ÿ*ÿ+ÿ,ÿ,ÿ-ÿ7ÿBÿNÿYÿiÿxÿÿÿÿÿ[ÿÿ-ÿ4ÿ5ÿ6ÿ6ÿ3ÿ2ÿ1ÿ/ÿ-ÿ+ÿ*ÿ)ÿ(ÿ&ÿ#ÿ"ÿ"ÿ"ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ!ÿ#ÿ$ÿ$ÿ&ÿ(ÿ-ÿ2ÿ6ÿ9ÿ>ÿAÿBÿDÿEÿEÿIÿNÿKÿOÿPÿPÿOÿPÿMÿOÿSÿVÿ\ÿlÿ_ÿaÿoÿlÿjÿcÿ[ÿQÿLÿYÿeÿ_ÿUÿOÿIÿAÿ9ÿ8ÿ8ÿ5ÿ2ÿ2ÿ1ÿ2ÿ5ÿ6ÿ7ÿ7ÿ9ÿ8ÿ6ÿ6ÿ2ÿ,ÿ(ÿ9ÿbÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿRÿ<ÿFÿIÿJÿJÿKÿNÿSÿVÿXÿXÿ\ÿaÿaÿ^ÿUÿUÿQÿOÿNÿMÿGÿAÿ4ÿ&ÿ#ÿ#ÿ%ÿ&ÿ(ÿ(ÿ.ÿAÿWÿmÿÿÿÿÿÿÿÿÿÿuÿdÿIÿ9ÿ=ÿJÿWÿ`ÿkÿvÿ}ÿÿÿÿÿÿÿÿ}ÿ~ÿ~ÿtÿjÿaÿdÿaÿ^ÿ`ÿhÿ]ÿaÿYÿUÿWÿaÿfÿaÿWÿZÿaÿ
ÿÿAÿCÿWÿ_ÿ[ÿ`ÿdÿiÿoÿsÿvÿxÿyÿ|ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿyÿuÿrÿmÿkÿhÿeÿeÿeÿeÿfÿfÿeÿeÿfÿgÿiÿjÿnÿoÿsÿsÿtÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿyÿyÿwÿuÿsÿrÿpÿpÿpÿqÿqÿpÿpÿoÿoÿpÿpÿqÿvÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿ33w44w44v44v55w44w44w44w55w44w44w44w55w44w44y55y55x55x66y66y66z77z66z66z66z66y76z6~4z5~5{4~4{3~3|3~3|1}1}0}0}.}.}-}-}-~-},~,}-~-}-~,}-~-}-~-},~,},|,{,|,{,|,{,|,{,|,{,|,|-|-|.|.},|,},|-{,|,{-|-}-|-}.|.}.|.}.|.}-|-}-|--}-.~./~/..~01|45z7~7z6~6y7~7y9~9y>~@|A}B|F}G|K}M|M}O{O}Q{Q|S{U}U{T}T{T}TyS}SxS~SvUVs\cpi
qkqqdoec[PeC<n85s42w10x5Puqipeflf
rku
]jqgyscY~0j&z&w){*)}),.5Ryqvtm
muu~qxegxgiyii~th_^\~_`~`cdgh}Z0{&,}-1}:e}e^{`^z[OxMNxQDx-)|('%
'~/
/}+)}+*}*+~/8GWbn{vfXY6$s/{3~4z44z40z0-{-.{-,{)
&{$
!}!~
~~~~~~~~~ ~!"~"
"~#
%~)-.1~6:>@BC}EM}SR~QNMIHMQU~[e~jo|pl~mj}`S~NK~`cUsPPrNIv?8|7~43|33|44|78}:<}<<|9
4~9Z~yqoppljijj
^m>ItI}JvH{LvMzRnTZi^_ebcbd]`Z^_]Z`XTfE0s%#}#%%&&-A~Si|}x}m~he|bpbcE/m1>zJXzelrwkd`]
YVx
jZaZ\Y
^Z\WYWWY\^T^
_Yj
e_XiH,}L@Za_^ZtYfoxrstvzr|r
z}
{y~v~~q~}pzxptqqkkqgepdepeeodenegnhillnlpskssnwyppru
z|}~~woo~|mzxmvumqqmppmpomoomoomonlopjw}i
ms
z|wxy|~ytux}zt~oon
u~}{4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ1ÿ3ÿ4ÿ4ÿ6ÿ8ÿ;ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ=ÿ@ÿDÿDÿHÿJÿLÿNÿOÿQÿRÿSÿUÿWÿXÿYÿXÿYÿXÿXÿXÿXÿXÿXÿZÿ[ÿ`ÿfÿkÿpÿsÿqÿoÿeÿZÿQÿPÿKÿAÿ8ÿ4ÿ3ÿ3ÿ2ÿ0ÿ8ÿ^ÿqÿgÿdÿhÿnÿvÿuÿxÿÿÿxÿaÿ3ÿ(ÿ*ÿ(ÿ*ÿ*ÿ+ÿ,ÿ.ÿ<ÿZÿxÿsÿmÿlÿsÿ~ÿvÿjÿiÿiÿiÿiÿjÿpÿmÿaÿ]ÿ\ÿ^ÿcÿaÿbÿfÿhÿfÿYÿ0ÿ+ÿ.ÿ3ÿ4ÿ?ÿfÿdÿ^ÿ]ÿ^ÿZÿMÿJÿPÿTÿSÿCÿ,ÿ'ÿ&ÿ$ÿ$ÿ&ÿ*ÿ.ÿ*ÿ-ÿ3ÿ1ÿ7ÿAÿKÿWÿhÿpÿ|ÿÿÿÿÿÿÿÿxÿÿ(ÿ/ÿ2ÿ4ÿ4ÿ3ÿ1ÿ0ÿ/ÿ,ÿ-ÿ-ÿ+ÿ+ÿ)ÿ%ÿ#ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ"ÿ!ÿ#ÿ&ÿ(ÿ*ÿ,ÿ,ÿ-ÿ2ÿ7ÿ9ÿ<ÿ@ÿBÿGÿMÿPÿQÿIÿBÿHÿKÿNÿSÿYÿ_ÿ_ÿeÿjÿgÿlÿnÿiÿ\ÿTÿVÿMÿRÿdÿXÿSÿOÿPÿPÿMÿHÿAÿ8ÿ0ÿ/ÿ1ÿ3ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ<ÿ8ÿ=ÿYÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlÿFÿFÿIÿIÿLÿMÿOÿQÿSÿVÿYÿ\ÿ^ÿ_ÿ_ÿ\ÿ\ÿ[ÿ\ÿ[ÿXÿQÿBÿ,ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ&ÿ)ÿ7ÿMÿcÿxÿÿÿÿÿÿÿÿyÿnÿXÿ=ÿ,ÿ)ÿ/ÿ@ÿLÿYÿeÿtÿ~ÿÿÿÿÿÿÿÿÿÿÿyÿvÿtÿdÿeÿfÿeÿZÿZÿ^ÿjÿjÿgÿ_ÿLÿHÿNÿkÿ
ÿÿÿtÿSÿPÿLÿQÿYÿgÿnÿsÿuÿuÿwÿ|ÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿ~ÿ|ÿ|ÿ}ÿ}ÿ{ÿxÿtÿqÿkÿiÿeÿeÿdÿdÿdÿdÿdÿfÿfÿfÿgÿiÿlÿnÿpÿqÿtÿuÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ~ÿzÿyÿwÿtÿsÿqÿqÿpÿoÿoÿoÿoÿoÿoÿoÿoÿnÿnÿrÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿ
ÿÿÿ44u44u44v44v55w44w44w44w44w34w44w44w44w55w44w55w55x55x66y66y66z77z77z77z77z77x8AyC~;y6~6{4~4{4~4|3~2|3}1}0}0}0}/}.}.}.~.},~,}-~-|-~,|-~-|-~-|-~-|-|-}-|-}-|-},|,|-|-|-|-{.|.{-|-{-|-{-|-}-|-}.|.}.|.}.|.}-|-}-|--|-.|..}.-~-/~/13~55z8;w=AvABuB@u=~=x@~C{F}H{K}N{P}R|T}U{X}Y{[|[{[}\{]}\{\}Z{[}[z\~\v^_sbhonslvtfqgc]TbLQhUPnB5r21w1/v>etnhpfhkqyj~gyci}>e%x't*y(})})~*0xBdvyoslkvqukh~gi~ig~flc_]^b}bdgfg~V3}11}14}Gf{c]z__xYFvFNtSTuRCy+&|$#}#'6~=6}9?{EO|X
`zjxu
jaTN
U[|*y,{.1z0/z..{.-{,+{+
*{'
${"
}~~~~}}}}~~~"~"%~'
*++~+,}/3}7:|@J|KQ}QN~JO~LN~PS}W^~^^}ac}aa|ZV}YS}Uc^xQOqNNsNLsHEy>~72|.+|//|0
2|34}5O~s~wustvpswupnkrlHAsHIwJ|LwNzQvS|SrS~VnWYjWWf\^f^\fXUkE,v!! "$%$'4|Jb{w}}r~lf~ubiRf3
(s5
6z>DxO^so|ked`
^]zt^n
ecZWfVXe^d_jd^ed_R|LY[|{=,1}AofT[Vial|o{nwz}{
~z
w{t|{p{{pyxptoqkhqedpdcpcdodeneenghllojqritynwxqqr
uz|}~{ton|ylvvmusmqpmqpmpomonmnnmnllotj{inuzyvyz~~}xux|xqpqu
|{
x4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ8ÿDÿWÿNÿ9ÿ6ÿ5ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ2ÿ3ÿ5ÿ7ÿ9ÿ;ÿ?ÿDÿDÿEÿFÿEÿBÿAÿCÿEÿIÿKÿNÿQÿSÿTÿVÿXÿ[ÿ]ÿ^ÿ_ÿ`ÿ`ÿ`ÿ`ÿ_ÿ`ÿ`ÿ`ÿ`ÿ`ÿbÿdÿgÿkÿpÿsÿvÿtÿrÿkÿ`ÿWÿMÿHÿDÿHÿMÿKÿAÿ4ÿ1ÿ0ÿ1ÿGÿkÿkÿdÿcÿhÿpÿ}ÿ
ÿÿzÿpÿVÿ0ÿ(ÿ*ÿ(ÿ+ÿ-ÿ1ÿ7ÿJÿiÿuÿmÿkÿhÿoÿzÿoÿjÿhÿhÿiÿiÿeÿcÿeÿhÿaÿ`ÿ_ÿ^ÿbÿdÿeÿeÿfÿUÿ3ÿ1ÿ1ÿ1ÿ2ÿPÿgÿaÿ_ÿcÿ`ÿWÿEÿFÿKÿPÿQÿRÿPÿ?ÿ)ÿ#ÿ#ÿ#ÿ%ÿ1ÿDÿNÿDÿCÿMÿSÿZÿbÿiÿyÿÿÿÿÿÿÿÿÿ ÿÿ;ÿÿ/ÿ.ÿ0ÿ0ÿ/ÿ/ÿ.ÿ/ÿ.ÿ-ÿ+ÿ+ÿ*ÿ'ÿ$ÿ"ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ&ÿ&ÿ*ÿ,ÿ,ÿ-ÿ/ÿ3ÿ7ÿ?ÿDÿEÿKÿMÿNÿKÿIÿJÿMÿOÿQÿSÿWÿZÿ^ÿ^ÿaÿ`ÿbÿ^ÿYÿUÿSÿUÿcÿ]ÿQÿPÿNÿNÿNÿLÿHÿEÿDÿCÿ>ÿ8ÿ3ÿ0ÿ.ÿ,ÿ+ÿ/ÿ/ÿDÿiÿÿÿÿÿÿÿÿÿÿwÿUÿ=ÿAÿYÿxÿÿÿÿ
ÿÿÿÿuÿPÿDÿHÿIÿIÿIÿLÿMÿNÿOÿRÿSÿUÿRÿRÿTÿWÿYÿXÿVÿRÿPÿCÿ.ÿ!ÿ!ÿ ÿ"ÿ$ÿ%ÿ$ÿ&ÿ2ÿEÿ]ÿrÿÿÿÿÿÿÿ}ÿsÿfÿRÿ/ÿ&ÿ7ÿ;ÿ?ÿAÿMÿaÿnÿyÿÿ
ÿÿÿÿÿÿÿÿÿyÿsÿkÿcÿYÿSÿQÿPÿKÿOÿVÿXÿ^ÿ_ÿ^ÿ_ÿvÿÿÿÿÿÿÿÿÿÿÿÿ}ÿWÿUÿ[ÿTÿUÿ[ÿ`ÿcÿpÿ~ÿÿÿÿÿÿÿÿ~ÿ|ÿ{ÿzÿzÿ{ÿ|ÿyÿwÿsÿnÿjÿgÿdÿdÿdÿcÿcÿdÿdÿeÿeÿeÿgÿiÿmÿpÿsÿtÿuÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿyÿxÿvÿtÿrÿqÿqÿpÿoÿoÿoÿoÿnÿnÿnÿnÿnÿpÿuÿ|ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ4~4t4~4t34v55v44w44w44w44w44v44v55x55x44w33w44v55v66w55w66y77y66z77z77z66z66z66z7:yGNy<~5z5~5{4~4|4~3|3~1|/~/|0}0{.}.|.}-}-}-{.~.}-~.|.}.},},}-}-}.|.}-|-}-|-}-|-|.|.|-|-|-|.|.|.|.|.|.|.}.|.}.|.}-|-}-{-}-{-~-|-~-|--|-.~.~.~.~/~/~14}69{;>wBHsJMqMLsI~GvG}JzL}N|P}S~W}Y|[}^}_|b|b|c|d}d{d}dzc~c{d~dye}eyffvjnrqumvwgrlbcZcQJjCBoBFsIIuD:v10tPorf
cob
flr
~j~dykaW~Ci5|3q6}:rBJoY
opqjsgevn
umki~jj~he}ec~mda`_}bdd~d
e}S0|,*},2|[i{dayghwWItKMtOOtOOvL9x&$~%(0yAVoXKiHRnXaqgrl{f`TKL%c({.{3z20z//z.+z)*{**|(
&|# }~~~~~~~~~~~~!"~"$~&'}*-{/2{4:yAD|GP|TY}YZ~[]~^e~mvustp~kf~`_}ZP}Wf~`vSPnOOnMKrIFuD~ByB|C|={:~4{21{+2|\~~|y
xtmuK*}()=cvpnvoPAqIJuK~KvK}MuO}OuN|NtNKqK~PpSSnONnMKqC.v!!~""$~%%~(2{H]|r{~r~ld{o_b
Mc-)p+/w6?wMatluo|i
ccb
bysckfgYUjQPlR]k_]d\b[g}oM{:8 F¢O¥¤ZpWLN
SQJG@;}AL{Vl|~zy~vzyryzo{zoyvqsmqheqddoddoddodeneemgjkmqhstjupzrooovz~}|unn}xlwumspmopmppnpnnnnnnmmnokpui~kow}zwxy{vv
yz}tpq
rx~
wv4ÿ4ÿ4ÿ4ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ7ÿ:ÿ3ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ.ÿ.ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ3ÿ5ÿ7ÿ:ÿ<ÿ@ÿDÿIÿMÿOÿRÿRÿPÿMÿJÿLÿNÿQÿSÿUÿXÿZÿ_ÿaÿbÿeÿeÿgÿfÿfÿhÿhÿiÿiÿhÿhÿiÿiÿiÿiÿnÿqÿtÿuÿvÿuÿrÿlÿcÿZÿTÿMÿFÿBÿ@ÿ>ÿ=ÿDÿMÿNÿEÿ?ÿ>ÿ\ÿlÿdÿcÿcÿiÿsÿÿÿ}ÿyÿrÿfÿ\ÿUÿVÿ[ÿ`ÿdÿlÿrÿnÿiÿgÿdÿlÿxÿoÿiÿjÿiÿhÿfÿbÿdÿbÿmÿlÿfÿeÿcÿdÿfÿdÿdÿcÿRÿ0ÿ)ÿ*ÿ*ÿ<ÿgÿiÿdÿfÿeÿgÿOÿLÿOÿMÿNÿNÿMÿNÿNÿKÿ8ÿ(ÿ(ÿ.ÿ6ÿ:ÿKÿ\ÿZÿLÿQÿYÿ]ÿdÿmÿwÿ~ÿ
ÿÿÿÿÿÿÿÿÿhÿÿ(ÿ/ÿ/ÿ.ÿ/ÿ-ÿ+ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ'ÿ$ÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ"ÿ"ÿ#ÿ%ÿ%ÿ)ÿ,ÿ/ÿ1ÿ1ÿ2ÿFÿWÿdÿlÿqÿrÿvÿxÿzÿvÿrÿtÿtÿvÿtÿrÿtÿsÿsÿtÿrÿmÿaÿUÿXÿeÿaÿTÿOÿNÿNÿNÿLÿIÿFÿDÿBÿCÿBÿDÿFÿIÿAÿ0ÿ/ÿAÿoÿÿzÿ{ÿ~ÿÿÿ
ÿ}ÿaÿ7ÿ%ÿ(ÿ)ÿ%ÿ2ÿWÿ}ÿÿÿÿ
ÿÿÿsÿIÿDÿIÿJÿKÿKÿKÿMÿNÿNÿMÿMÿIÿFÿKÿLÿNÿNÿNÿOÿKÿIÿBÿ-ÿ!ÿ!ÿ"ÿ"ÿ$ÿ%ÿ%ÿ(ÿ3ÿIÿ_ÿtÿÿÿÿÿÿ~ÿwÿnÿ^ÿFÿ(ÿ+ÿ3ÿ/ÿ*ÿ3ÿMÿ_ÿiÿqÿxÿ|ÿÿÿÿÿÿÿÿÿ}ÿxÿpÿiÿeÿbÿ[ÿUÿ[ÿ\ÿ^ÿ\ÿ]ÿaÿgÿqÿÿÿÿÿÿ ÿÿÿ
ÿiÿSÿHÿVÿaÿaÿiÿnÿ\ÿMÿJÿHÿGÿGÿDÿBÿJÿfÿ|ÿÿÿ|ÿzÿyÿzÿ{ÿzÿyÿxÿuÿrÿmÿhÿdÿdÿdÿdÿdÿdÿdÿdÿeÿeÿeÿgÿjÿmÿqÿsÿuÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿzÿxÿwÿuÿsÿpÿoÿpÿoÿoÿoÿnÿmÿnÿnÿmÿnÿoÿpÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿ
ÿ
ÿÿÿ44v44v44v44v44v44v44w44w55x55x55x44x54w55w44x55x45y66y66y67y77z66z66z66z55z66z55z63z4~5{5~5|4~3{3~3{3~2|2~2|1}1~0}.~/}.}-}-|-}.}.}.|.}.}.}.}.|.|.|.{.|.|/|/|.|.}.|.}.|.|.|.|.|.|/|/|/|/}.|.}-|-}-|-}-}-}-}-~.|.~-|--|--~--~-0~036|8;z?CwFKsNRqVXqW~SsO}PwQ|T{W|Z|[}^~b}c~f}e~h}i~j}k|k}k{k~kzj~kyllxmnvrsrssmutepkcb]fTNnKDqC@v><x:=xCEwEOvghpddmfoky
|f|w_ysbppipriroipnmherf
bxk{pgf}ff}ec}cb~mv~j
feadc~d
b|Q,{**{,I{lgyfhvgfuKKrONsNOrNLtLKwH8|259}9=nSZgTOgSZk_iiq|d^VOGMCl*{,|,{*
*{*
'|'
&|&&|&
&|%
#}
~~~~~~~~~~~~ !"~!&}'*|/9{Na|hj}ji~jp~y
yv
ustsqooponp~r
r~ts~qm~`{UQtOOqNLrIGuE~CyB|BzE{JyQz`y`zPz[|{y~{w||yx~m{8&(%'
/~Nw~zqohrCDvIJyK}KxJ|KwN|MwM|LwF}FvJ}KsL}LqKJqIGt>.z! "~#$}$%~(3{K`|w}~s
m~{ftlb\
Gh.&t1){(:{P[uelqqwj{~h
h
ge{dsmdijckhcaZb^c^gkTpxD:?XB.w0;M]`nq~tfzVXychyimymhx^RyVgxy{wxysxzqzyoxuqpkqfdqbboddoddoddneemgkknqkssl{t~}rqoov|}{rm~m|ylvtmrqmpomoonomnllnlmmmojsyhkrx}yyyz{x
y}}u~rsuw
xtu4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ/ÿ2ÿ8ÿ;ÿ@ÿDÿHÿLÿQÿVÿYÿ[ÿ[ÿZÿUÿSÿTÿUÿZÿ\ÿ^ÿbÿeÿgÿjÿjÿlÿmÿmÿnÿnÿnÿnÿnÿmÿnÿpÿpÿrÿsÿuÿvÿvÿvÿtÿsÿoÿiÿbÿ]ÿUÿPÿMÿHÿEÿBÿ@ÿ=ÿ9ÿ8ÿ5ÿ6ÿ;ÿCÿRÿfÿeÿcÿeÿiÿsÿyÿuÿxÿvÿtÿtÿuÿxÿvÿsÿoÿlÿfÿbÿbÿeÿbÿiÿ}ÿpÿiÿjÿhÿjÿhÿiÿiÿjÿoÿ{ÿnÿiÿhÿcÿcÿeÿbÿ^ÿNÿ,ÿ*ÿ+ÿ/ÿXÿlÿhÿfÿjÿnÿaÿEÿLÿPÿPÿOÿPÿNÿLÿKÿIÿGÿFÿBÿ?ÿ<ÿ=ÿ;ÿ=ÿEÿ9ÿ<ÿKÿVÿ^ÿfÿnÿrÿ|ÿÿÿÿÿÿÿÿÿÿÿ,ÿ!ÿ&ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!ÿ#ÿ"ÿ'ÿ?ÿZÿfÿgÿdÿcÿdÿeÿgÿrÿxÿxÿuÿtÿvÿtÿsÿrÿoÿmÿkÿkÿlÿlÿnÿpÿpÿrÿvÿ{ÿ}ÿtÿ\ÿPÿLÿNÿNÿLÿHÿFÿEÿCÿBÿCÿDÿHÿSÿaÿmÿrÿzÿÿ~ÿ{ÿ}ÿ}ÿ}ÿ~ÿyÿ]ÿ,ÿ$ÿ%ÿ&ÿ$ÿ$ÿ/ÿNÿtÿÿÿÿÿÿÿZÿ9ÿDÿHÿLÿMÿMÿLÿMÿNÿMÿLÿKÿFÿHÿKÿLÿMÿMÿKÿJÿIÿGÿ>ÿ.ÿ#ÿÿ!ÿ"ÿ"ÿ"ÿ%ÿ(ÿ8ÿPÿfÿzÿÿÿÿ~ÿ~ÿ{ÿtÿiÿXÿ>ÿ+ÿ,ÿ0ÿ,ÿ.ÿEÿQÿZÿ^ÿgÿkÿqÿuÿzÿ|ÿ}ÿ~ÿÿÿÿÿ|ÿyÿuÿtÿsÿpÿnÿgÿeÿfÿhÿkÿqÿxÿÿÿÿÿÿÿ\ÿÿ2ÿDÿLÿ\ÿbÿaÿ]ÿnÿsÿmÿÿÿ£ÿ©ÿ¦ÿÿÿÿÿÿÿoÿgÿmÿvÿwÿxÿzÿyÿwÿxÿsÿoÿjÿeÿdÿbÿbÿdÿdÿdÿdÿdÿeÿfÿgÿiÿmÿoÿoÿrÿuÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ{ÿyÿvÿtÿrÿqÿpÿoÿoÿoÿoÿlÿlÿlÿlÿmÿmÿoÿsÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ55u44u44v33v33u44u44u44v44w45w55x44x44x44x44x55x55y77y55y56y66y66y66y66z66|66|66|55|5~5{5~4|2~2|4~4|3~3~2~1}1}1~0}0~0}/|/}.|.}/}.}.|.|.|/}/|/|/|/|/{/|/{/|/{/|/{/|/{.|.{.|.{.|.|/|/|/|/|.|.|.|.|/|-|-|-|.|.}-|-.|.-~--~--}-/}0146:~@D{H
LwQ
Xr\
^qa`qZ}XtY|Zy\|`}`|d~h}ij~lmn|oo{oowoovqqtrrtstqwwovuitqemffa\jWQpMJuHDvA?x;9y62x13v<Wtjcqfeoo
rkg
hggfhjkjjimfglifofesd`zfx
som~ii|fe~gh~h}~rhfd~ag}a\{N,z*)z<gzkhwilvmStBKsOPqQQqONqLItGCxB@|B>?u9;l54iB
QhZbhglcu[RNHEsP} u${'&|'
%}%
$|$
"}"
!}!
~~
~~~~~~~~~~~} }"7}[l~ica`acirzw~u
u~w
v~tsrp~n
n~lllkkloptzwZzLOvNLrK~GuE}CxC}CzC{JyTx`zkzt{{y}yy{{zxuxoJ|)&&%
&|&.}Nt~zs
pzOs7BzF~J|L|LzM{LyL|LwK}HwE{IxK{KvM|KsJHsFDw>.}# !!!%},
<|Tk|~|~tk}yer
gdS8n"'x18|=LyQZt\`qf
kmn
qlr
wly
|h~b~~b|wcx
wbu
q_q
qao
j`n
sTzKHPAi'7EQ[}]Q{Ubv}s³¼|»º¹x±¢u|~xptxvww}tz|{z
ztzyqvspnkqgcpbboddmdcncdmegmilknqjssms{spoqx~~
zqm}mzxlvtmrpmoomonnmlnllmlnlloit|ilt
z~}{zy{~zy{v~rquvz}tsu5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ4ÿ6ÿ:ÿ>ÿAÿHÿLÿPÿVÿ\ÿ`ÿcÿdÿaÿ^ÿ\ÿ]ÿ_ÿaÿbÿfÿhÿiÿlÿnÿpÿpÿqÿoÿoÿoÿoÿoÿqÿqÿrÿsÿuÿwÿxÿxÿvÿtÿsÿoÿkÿfÿaÿ\ÿVÿSÿPÿMÿJÿFÿDÿ@ÿ=ÿ:ÿ7ÿ4ÿ1ÿ1ÿ1ÿGÿjÿeÿaÿcÿiÿlÿXÿKÿRÿUÿTÿTÿNÿJÿKÿWÿhÿfÿaÿ_ÿ\ÿZÿdÿxÿpÿjÿiÿdÿgÿjÿjÿgÿdÿeÿxÿxÿkÿhÿfÿcÿhÿgÿ[ÿJÿ,ÿ'ÿ.ÿPÿnÿiÿhÿlÿoÿjÿEÿEÿLÿPÿSÿTÿRÿQÿOÿMÿIÿEÿCÿ>ÿ<ÿ@ÿEÿ>ÿ;ÿ9ÿ=ÿ9ÿ?ÿTÿVÿ[ÿ`ÿgÿmÿvÿ~ÿÿÿÿÿÿÿÿÿÿXÿÿ&ÿ&ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ4ÿZÿkÿjÿeÿ`ÿ`ÿbÿcÿcÿlÿvÿyÿuÿvÿuÿxÿxÿxÿxÿuÿsÿqÿpÿnÿnÿlÿlÿjÿjÿjÿkÿlÿnÿsÿzÿÿyÿ\ÿOÿNÿLÿKÿGÿEÿCÿBÿBÿEÿJÿRÿaÿnÿtÿÿÿ}ÿyÿwÿwÿsÿpÿ`ÿ9ÿ,ÿ,ÿ(ÿ&ÿ'ÿ(ÿ/ÿOÿsÿÿÿÿÿÿvÿIÿ7ÿBÿFÿJÿLÿLÿMÿLÿJÿJÿIÿCÿEÿIÿJÿIÿIÿIÿHÿIÿGÿEÿ?ÿ-ÿ$ÿ#ÿ#ÿ!ÿ"ÿ#ÿ%ÿ,ÿ@ÿXÿmÿÿÿÿÿÿÿzÿrÿdÿPÿ5ÿ"ÿ"ÿ&ÿ2ÿBÿMÿPÿVÿYÿ\ÿaÿeÿiÿkÿmÿqÿtÿxÿzÿ|ÿ|ÿ|ÿ{ÿ{ÿ|ÿ{ÿwÿtÿtÿtÿrÿpÿtÿxÿ~ÿÿÿÿÿÿÿ7ÿ1ÿ<ÿEÿSÿ_ÿVÿQÿMÿnÿ ÿ¶ÿ¾ÿºÿ¶ÿ·ÿ£ÿ«ÿ´ÿÿÿÿÿÿÿÿÿfÿXÿpÿ{ÿyÿvÿrÿmÿgÿcÿcÿbÿcÿcÿcÿcÿcÿcÿdÿeÿhÿjÿmÿoÿqÿsÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ}ÿzÿwÿuÿtÿrÿpÿoÿoÿoÿnÿmÿlÿlÿlÿlÿmÿmÿpÿuÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ55u44u44v44v44u33u33u44v44w55w44x44x44x44x44x44x66w66w55y56y77y66y66y66z66{66{66{55{5~3{4~4|4~4|4~4|4~4}3~2|1}1|0}0|0}0|0}/|/}/}/}.|/|/{/}/|/|/{/|/{/|/{/|/{/|/{/|/{/|/{/|/{/|/{.|/{/|/|/|.|/|/|.|/|/|.~.|..|.-|--~--~--}--}/1358~;@{EJwO
Tr[
aqejpiera|]x_|b{d}f|jl{jmvnntpnonnknniqrgtwhyzixwhvtfrneidi`\nWTsROvLHwFBv@~<x9~7x31y2:x^iua`rbkpX0r4;t:6u2/w7Nqhdn\ZqW
Sx]xrig~bd|hj}hi~lr}}m~fc}bj|o\zG'y(@xhkxijuppu]?tHNrSTsUSrQPqNIsEAv?={<>EB=v8@nARhdQdYabfk^u}WPKCC=X|&y({(%|&%|$
#}#
"}"
!~! ~
~~~~~~~~~~~"~Elo
e^
\`ddf~jx~zw~vw}xy}{z}y
x}v
u|rq|p
q}mk~kkiijlr{z[}PLwJ~FtE}CwD}CyD{IyTya{nyt{~}z|vs|st{qrzZ7}/~0,}('{'
2|Qu}y~sqp=v3A}HK~J~L}M}L|J~IzABzG}IyI}IxJ~IuHIvHFz>,}"#%~#$~%'}/C|[q|y~
qk}yep
afM2o !w%9yKPsTVnUXk\^lcemg
kml
sjvxey{b{
{c}~fzzcxxbuvay
|ZSP
X3p3<D~T[vLEqXq´½|½«z¨¤z¬¹w²t}rwny
lw|_vnwxwsvoqkgqecpbbobcmccnccmegmjmkoqiswnwxqppry~|
xpm{mxwlusmrpmoomoonmknklmlllnqivinu{|~zzy{~||y~tr
s
w{~x
ssw5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ:ÿ=ÿBÿFÿMÿSÿ[ÿaÿeÿjÿmÿjÿfÿcÿbÿdÿeÿhÿkÿkÿjÿmÿnÿnÿpÿnÿnÿnÿoÿpÿqÿsÿvÿxÿ{ÿ{ÿxÿwÿtÿsÿqÿmÿgÿdÿ`ÿ\ÿXÿVÿSÿPÿMÿJÿGÿDÿBÿ>ÿ:ÿ7ÿ4ÿ2ÿ1ÿ3ÿNÿjÿ`ÿ]ÿ`ÿgÿgÿ5ÿ'ÿ*ÿ+ÿ+ÿ,ÿ.ÿ9ÿRÿeÿ_ÿWÿVÿPÿMÿVÿxÿsÿhÿgÿgÿcÿeÿkÿeÿjÿkÿnÿÿtÿiÿfÿfÿmÿvÿ`ÿCÿ&ÿBÿdÿjÿhÿiÿlÿpÿqÿNÿBÿKÿPÿUÿVÿVÿTÿRÿOÿLÿGÿEÿCÿ?ÿ=ÿ9ÿ:ÿ@ÿJÿJÿ?ÿ9ÿ>ÿEÿ[ÿXÿPÿXÿ`ÿbÿjÿuÿ{ÿÿÿÿÿÿÿÿÿÿ-ÿ ÿ(ÿ)ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ/ÿ]ÿnÿkÿ_ÿ^ÿ`ÿcÿeÿgÿjÿqÿzÿwÿuÿvÿwÿxÿyÿ{ÿ}ÿ}ÿ}ÿ}ÿyÿwÿvÿtÿrÿqÿoÿnÿmÿkÿjÿfÿhÿgÿkÿsÿyÿÿqÿUÿLÿJÿFÿEÿCÿCÿCÿDÿJÿTÿbÿlÿrÿ|ÿzÿrÿpÿpÿqÿnÿnÿmÿaÿEÿ1ÿ/ÿ/ÿ+ÿ+ÿ5ÿUÿwÿ
ÿ~ÿ~ÿÿÿiÿ2ÿ#ÿ5ÿEÿJÿKÿNÿMÿLÿGÿ?ÿ=ÿCÿIÿKÿJÿJÿJÿIÿHÿIÿHÿFÿ<ÿ*ÿ"ÿ#ÿ#ÿ"ÿ%ÿ%ÿ&ÿ0ÿGÿ_ÿwÿÿÿ
ÿÿÿ}ÿwÿoÿ`ÿHÿ-ÿ#ÿ%ÿ,ÿBÿPÿTÿWÿWÿWÿYÿZÿZÿ]ÿ\ÿ`ÿdÿfÿkÿqÿtÿvÿxÿyÿyÿ{ÿ}ÿ}ÿ}ÿ}ÿ|ÿxÿzÿ}ÿ~ÿÿÿ
ÿ
ÿÿÿyÿ7ÿ7ÿ;ÿAÿMÿRÿJÿDÿoÿ¦ÿ·ÿ¾ÿ¶ÿÿ|ÿ ÿ´ÿºÿ¸ÿ¶ÿÿ~ÿÿÿÿÿÿÿÿuÿvÿuÿtÿoÿkÿgÿeÿcÿbÿaÿaÿbÿbÿbÿbÿcÿeÿgÿjÿmÿoÿsÿtÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿÿ}ÿzÿvÿtÿrÿrÿqÿpÿnÿnÿkÿkÿkÿkÿlÿlÿlÿnÿpÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ44u55u43u55u55u44u44u44v44w33w33w53x44x44x55w55w66x55x55x56x56x66x55y66y66z77z77y66y44z44{4~4{3~3z4}4{3}4{3~3{1~1{1}1{1}1{0}0|/{/z/|/{/|/{/|/z0|0|0|0|/|/|.|/{0|0{/|/{/|/{0|0|.|/|/|/{0|0}0|0~/|.~.}-}.}.~.}.~-}--~-.~..~.~/~1~3~4679<|AEyL
RtZ`nf
knnnoj}gvf|g{i~izijwikqlnipodoo`rs_wx^{
|\y
x]x
w`vscnihgen_\tZXuSQxOKxIFxB?x:8y62{2/x?cwc^safpjGt&}(y*})|+/|;Tse[oW
RsMNxTtwfg~hi|ii|fh|lp}~}m
k|jhzuzwpgvihxhlvpntphuCGuMQsUWsURsRNrIFsEAv>=x;8|5
9FI?u:9nBIjPPfV^afm[u~SLG@Dw%b"|({({(&|%
%}$!} !}~~~~~~~~}}""B~jnb^`cefjp}w{}wt}v
x}w{||~}}~|~}{zxzuvxwtzrq{o
j|g
feffmsz|iP|JGvF}CuE{CxGzLyUzbzr|xzyu~pm|np{o~o}p{om|aG|2-{,7|V~y~w~p~pd0v#$*;FHJH?53<C~I|KLzKKwJHwHEy8%~""""$~$&}3M{d{}u
ok}vem
]hC)t"*|:LzUWtZ]n\\i[ZhYTjUZjd
hmlqgttduwex~f
d
~a}}b~_
Y~X~_rGt9@C}KIuC Mo©s¶½}¶{Y©~²yªu~qvlv¡f£|curqzroskfrbbpaapabpbcncdmfhkkokqqjr~pyxoppry}}}~wpm~zmwtlsqlookoomnmmlkmklkljjmphxinv|{~yzx{}}ztrsvx
zqru|5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ4ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ8ÿ9ÿ<ÿAÿEÿKÿQÿXÿ^ÿdÿjÿpÿpÿmÿjÿgÿfÿhÿhÿiÿjÿlÿkÿnÿoÿqÿpÿqÿsÿwÿyÿ|ÿ}ÿÿ~ÿ{ÿyÿzÿyÿwÿrÿmÿjÿfÿcÿ_ÿ\ÿ[ÿXÿTÿRÿPÿMÿIÿFÿBÿ?ÿ;ÿ9ÿ6ÿ2ÿ3ÿ0ÿ4ÿ[ÿgÿ`ÿ_ÿbÿhÿWÿ(ÿ%ÿ)ÿ)ÿ,ÿ0ÿ<ÿVÿfÿZÿUÿQÿOÿPÿUÿrÿxÿhÿeÿeÿeÿbÿeÿkÿhÿiÿmÿzÿÿsÿkÿjÿiÿoÿ|ÿÿzÿxÿoÿlÿoÿpÿpÿqÿ\ÿAÿHÿNÿPÿSÿUÿTÿRÿOÿLÿGÿCÿ@ÿ>ÿ<ÿ:ÿ9ÿ5ÿ3ÿ2ÿ8ÿCÿGÿ>ÿ1ÿ5ÿ=ÿCÿFÿQÿ[ÿ^ÿeÿnÿuÿ{ÿÿÿÿÿÿÿÿÿkÿÿ%ÿ&ÿ&ÿ"ÿ"ÿ#ÿ"ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿÿ ÿÿÿ#ÿNÿnÿlÿ`ÿ_ÿeÿdÿdÿiÿrÿwÿzÿ|ÿwÿtÿsÿuÿxÿzÿ{ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿxÿtÿuÿxÿvÿtÿtÿtÿrÿpÿkÿiÿgÿeÿfÿhÿmÿsÿzÿxÿ`ÿKÿHÿDÿCÿDÿDÿHÿMÿXÿfÿrÿxÿwÿrÿqÿnÿmÿoÿnÿnÿqÿrÿoÿmÿkÿ`ÿTÿBÿ?ÿ\ÿzÿÿ|ÿ~ÿÿyÿ[ÿ(ÿ"ÿ"ÿ#ÿ!ÿ*ÿ8ÿAÿAÿ7ÿ/ÿ9ÿ@ÿBÿFÿGÿIÿKÿLÿJÿIÿHÿDÿ6ÿ%ÿ"ÿ"ÿ"ÿ"ÿ$ÿ$ÿ(ÿ:ÿQÿiÿ{ÿÿÿ
ÿÿÿÿxÿmÿZÿ?ÿ&ÿ%ÿ1ÿDÿRÿVÿYÿ[ÿ^ÿ_ÿ_ÿZÿYÿWÿTÿSÿYÿ]ÿeÿkÿmÿoÿoÿmÿpÿsÿxÿ|ÿ}ÿÿÿ}ÿÿÿÿÿÿÿ|ÿÿÿmÿVÿCÿFÿEÿIÿEÿ@ÿaÿÿ¨ÿ²ÿ·ÿ¸ÿªÿÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿ¦ÿÿÿrÿoÿnÿhÿfÿdÿbÿbÿbÿbÿaÿaÿcÿdÿdÿfÿgÿlÿnÿqÿrÿsÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿwÿuÿsÿqÿoÿoÿoÿnÿmÿlÿkÿkÿkÿlÿmÿkÿnÿrÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿ65t55t43u45u55u55u55u44v44w33w44w33x33x44x55w44w55w55w66x66x66x55x44y55y66z75z66y66y54y55z4~4z4~4y3}3{3}3{2~1{1~1{1}1{1}1{0}0|0{0z/|/{/|/{/|/z0|0|0|0|/|/|0|0{0|0{.|.{/|/{0|0|/|/|/|/{/|/}0|0~/|.~.}-}.}.~.}.~-}--~-.~.0~00~13~5689<~AE|I
QvU[qbinptornrj~gxgfwhiskljo
qevyay|c}
e
b_~^{z_wqemjlfbr_\v[XxURxOLxJGxC?x<9y73{21y3Lwi^t]aqc~as0z$y(})|+0{>XrdWnS
QrRTvXirfa}bc{aa}il}rp}q}uj|f
fzhqz{zyyuvontprsnNuDJvNOuRRtROtKJtEAu><v:7x53z31|/2@C:|37x<?qDTh\a_cnYu|QG
@<GXh~"z$}!}! ~ }~~~~~~"~#$}&
#}
}-Yokedh
hin~ru}sv|wu|st{uv|yz{yz{{y{rj{ii|hq{uqyprzvwzro}lh~filpswrSI|F}FyB{EyIzP{]zk{r|w{vo~nm|np{m~p}r{qo|je|i}o|ozk~oxr|~p}wqS$x!#$#"%#+*(/~:?~EGI~IJzEFxF?z1%~#$""$~%-~?V}m~~toiycl
Xh:&t*;|JUz[ZuZ]l__h]ZgWUiSVm[
`keihklhjkiowhz
|f|
|d}
b
__~_dmVsOLGIBvAlq¥q««y¨}
|¡z y¡~ph|d|b|^~b|sukyidsbbpbapbbpbcncemggkjnkqsksq
zwqrsu{}}{
uom}zmwslqolnnkmlmlkmkjmjjmkklnsg{ipx}{~{zxx~x~vqs
u
y
}v
qty}6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ0ÿ1ÿ3ÿ5ÿ6ÿ8ÿ:ÿ=ÿ?ÿCÿHÿNÿTÿZÿ`ÿgÿnÿrÿuÿsÿnÿjÿiÿgÿhÿhÿiÿnÿqÿvÿ|ÿÿÿÿ
ÿ
ÿÿÿÿÿÿ~ÿ{ÿzÿwÿqÿnÿkÿfÿbÿ_ÿ\ÿ[ÿXÿUÿRÿOÿLÿJÿGÿCÿ?ÿ<ÿ9ÿ7ÿ3ÿ2ÿ0ÿ0ÿ@ÿcÿ`ÿXÿ]ÿbÿeÿ@ÿ%ÿ(ÿ(ÿ*ÿ/ÿAÿ[ÿbÿVÿRÿQÿTÿVÿVÿdÿsÿgÿ`ÿcÿcÿcÿhÿmÿmÿoÿkÿgÿqÿsÿhÿ^ÿ_ÿcÿiÿmÿrÿxÿ{ÿxÿtÿtÿmÿ]ÿGÿIÿKÿMÿNÿOÿOÿOÿKÿKÿJÿEÿAÿ>ÿ;ÿ9ÿ6ÿ5ÿ3ÿ3ÿ2ÿ1ÿ/ÿ2ÿ<ÿ=ÿ4ÿ3ÿ7ÿAÿGÿRÿYÿYÿaÿeÿmÿuÿyÿÿÿÿ
ÿÿÿÿÿMÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ"ÿ%ÿ(ÿ+ÿ-ÿ%ÿ ÿ7ÿaÿmÿhÿdÿgÿiÿmÿmÿrÿxÿuÿnÿpÿrÿrÿqÿpÿoÿsÿvÿvÿwÿyÿzÿpÿhÿcÿeÿhÿmÿnÿoÿpÿnÿmÿkÿkÿrÿxÿwÿoÿiÿhÿjÿlÿmÿrÿtÿsÿdÿMÿHÿDÿCÿGÿKÿUÿcÿoÿtÿvÿtÿnÿmÿmÿoÿnÿkÿnÿtÿrÿpÿlÿjÿhÿiÿmÿrÿ{ÿÿÿ{ÿ~ÿ|ÿsÿKÿ$ÿ!ÿ$ÿ$ÿ"ÿ$ÿ$ÿ$ÿ'ÿ*ÿ+ÿ)ÿ.ÿ:ÿAÿEÿHÿIÿKÿMÿLÿEÿ<ÿ0ÿ&ÿ$ÿ$ÿ#ÿ#ÿ&ÿ'ÿ0ÿDÿYÿqÿ
ÿÿÿÿÿÿÿwÿhÿTÿ4ÿ%ÿ0ÿBÿLÿRÿYÿZÿ[ÿ^ÿ`ÿ`ÿ_ÿ\ÿWÿUÿSÿTÿYÿ^ÿaÿfÿhÿiÿkÿnÿpÿuÿqÿuÿzÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿnÿ[ÿZÿZÿTÿQÿHÿDÿqÿÿ¦ÿ¥ÿ£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿjÿeÿdÿaÿbÿbÿaÿbÿbÿbÿcÿcÿeÿgÿhÿkÿpÿpÿsÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿÿÿÿÿÿÿ}ÿzÿvÿsÿqÿnÿnÿmÿlÿlÿlÿkÿkÿjÿjÿkÿjÿlÿnÿtÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ5~5t6~6t55u66u64u55u55u44v44x55x44y33y33y34y55w44w55x55x55x55x66y66y66y66y66z66z66z66z66z55z5~4{4~4|3~3{3~3{3~3{2~2{1}1{0}0{1|1{1|1{/|/{/|/{/|0{/|/{0|0{/|/{0|0{0|0{/|/|/|/|/}/|/}/|/}/|/}/|/}/~/}.~.}-}.}.~.}.-}--~-.~./~/1~23~57~9:=?C~GLzS
Xt_fqlqpwuprotkiughoioft{d
hm
p
m
f}dzwfronlhsdbv^~[y[YyUSxOLxJGyC?y;8z62z1.y/4xSdtZWs[ctS|&x%}(|+2zC]raXoRQqVYuX`{ric~jj~nlkmkl~lc}gi{e`{]czinzquxzzun^vOMvNMwMNwNMvLKxJIuEBv?=y96y52{2/{..}.
/6A?}7>tG
NjWaaU]ZdlWt{ME?CRAp~|}~~ ~ !!!~! ~ ~~!%~(
/}4-~Aemhehjknv|wp{ikznrzpo{nozqt{tu{vk}ca~aagj~mn~qn}li|fj{u{|sl}l
jjlnpqmW~F~C{C{H|NxX|ezp|t}x|qnop~qn|j|m~rzro{lj|j|h}kyt~}ur|}p}rqC#z$$"##"%%',.~**~4=BGH~GH|E<~/%$$$$&~)
2}G^|u|~s
mgtcg
Sj1&u6D}KO|V[w\_p^]l_\hYVkTTlX\k`
cmacneinkpkpmguwe|c
a
`
bdpdpgc~ZVQ}Out¦u¥£y|
xnd^]¡ \[d|~fzbvcap`apbapacncemgillqjqukyr}ursrt
|~~~|rlm{xmuqlnmlmlllkljkmkjmhklkmjowhkt
y}~{}
}
xuuss
u
x|
}vrtx
|}9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ6ÿ6ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ9ÿ:ÿ=ÿ?ÿCÿGÿLÿPÿWÿ\ÿcÿiÿoÿrÿuÿtÿpÿmÿiÿfÿhÿlÿtÿxÿ|ÿÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿ{ÿvÿsÿpÿnÿlÿhÿdÿbÿ^ÿ[ÿZÿWÿTÿQÿNÿKÿIÿHÿCÿ?ÿ;ÿ8ÿ6ÿ2ÿ2ÿ0ÿ/ÿ1ÿCÿbÿ[ÿXÿZÿ`ÿ]ÿ2ÿ%ÿ+ÿ.ÿ0ÿ@ÿZÿ]ÿVÿSÿUÿWÿXÿYÿ`ÿsÿmÿdÿhÿpÿqÿqÿnÿnÿqÿmÿnÿkÿ_ÿ]ÿbÿiÿdÿfÿgÿkÿlÿnÿgÿaÿUÿQÿSÿSÿTÿQÿQÿOÿOÿMÿKÿIÿHÿGÿEÿBÿ?ÿ>ÿ;ÿ7ÿ6ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ,ÿ-ÿ7ÿFÿEÿ?ÿCÿHÿNÿiÿdÿUÿ_ÿgÿlÿqÿxÿ}ÿ~ÿÿÿÿÿÿÿ8ÿÿÿÿÿ ÿ ÿÿ ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿÿ ÿ!ÿ#ÿ"ÿ'ÿ/ÿ8ÿHÿZÿhÿhÿhÿkÿmÿmÿqÿwÿvÿlÿiÿgÿiÿlÿlÿmÿmÿoÿpÿqÿqÿtÿrÿ]ÿ[ÿ[ÿ[ÿ_ÿ`ÿeÿkÿoÿpÿnÿoÿqÿmÿjÿdÿiÿtÿwÿqÿoÿkÿkÿmÿlÿnÿnÿrÿeÿIÿDÿBÿIÿQÿ[ÿhÿqÿwÿuÿnÿnÿpÿpÿpÿmÿfÿiÿrÿrÿpÿnÿkÿlÿjÿiÿqÿ{ÿÿÿzÿ|ÿ{ÿkÿ;ÿ$ÿ&ÿ%ÿ$ÿ%ÿ%ÿ%ÿ#ÿ%ÿ'ÿ,ÿ1ÿ2ÿ+ÿ$ÿ(ÿ6ÿAÿDÿGÿFÿBÿ7ÿ+ÿ#ÿ$ÿ%ÿ$ÿ$ÿ&ÿ*ÿ6ÿMÿcÿzÿÿÿÿÿÿÿ|ÿrÿeÿLÿ.ÿ&ÿ7ÿEÿKÿOÿSÿYÿYÿ\ÿ^ÿ]ÿ]ÿ[ÿWÿUÿUÿUÿXÿ\ÿ^ÿaÿbÿdÿdÿcÿcÿiÿkÿjÿoÿrÿxÿ|ÿÿÿÿÿÿÿÿ|ÿrÿnÿkÿgÿeÿ`ÿ\ÿ\ÿ|ÿÿ¤ÿ¢ÿÿÿÿÿÿÿÿzÿÿÿÿÿÿÿÿÿÿ¢ÿÿÿÿÿ}ÿkÿcÿbÿbÿbÿaÿbÿaÿaÿcÿcÿeÿgÿiÿlÿqÿrÿtÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿ{ÿxÿuÿpÿnÿmÿlÿlÿlÿjÿjÿkÿkÿjÿkÿjÿjÿmÿqÿxÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ98v99v99v87v77u66u55u54v44w44w44x33x33w33w44w44w44x44x66x66x55y66y66y66y66z66z66z66z65{66{4~4{4~4|4~3{3~3{2~2{2~2{1}1{1}1{1|1{1|1{1|1{/|/{/|/{/|/{-|-{/|/{0|0{0|0{1|1}0|0}0}0|/}/|0}0|/}/|/}/}/}.}.}-.}..}.-}--~-.~..~/0~13~57~9;~>@BFJ}OTuY
_qe
imptlwsoplqggnm
whz}imu
zy~rzvkusiplnifuc~aw^~[yYVyROxMJxHFyC>y:8y63y30{//y5Yv`WtX\ta~Cw&|)z,0yA\s]WpVVqYYrY_ws
qijkmqljjl}nj|jd{XY{YR|=:|>B|FRySWxYYvYVuSOuOKvJGxFDzB@{?<{;9{74|0/|.-}---/8FKyFFpCSgrc^Y`YiiVszO}IDFT2r}! ~!!~"#~###"$$%$$""(~%
5~HMRZdjoot
x}wi{cezddyggygkznpzno{jR|QX~W[\_cfhlpsutnj~d
k}y
y~s
pllmlorpU~D|C{J|Qx]|mzu|x}r|no~pp}ni|b|h~qyspymm{mm|l|u~}uq|{py
hs7%~''%%'~(&'
*~./~41*$%-:DC@3*$$~$$~''},9|Pg}|zrlcynd_
Al)(x<E}JN|RVxY[t]]p\\l[[kZZk[]n_`oa`o`_obemedkhlkpxh{}edd|fwslspqk
g}fe||~
~sV}oujb_`^\yyid~acucapbapabncemgjlmpjruk}r}spont|~~~|
tnm{wmtplnmlkklkkljkmkhmijlkmiszhlt
z~~|
~~
xsqqr
t
x
z
utvz
{};ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ3ÿ5ÿ7ÿ9ÿ;ÿ>ÿ@ÿBÿDÿHÿLÿRÿVÿ\ÿaÿgÿmÿrÿvÿvÿsÿpÿlÿjÿnÿuÿyÿ{ÿ~ÿÿÿÿÿÿÿÿ|ÿzÿyÿuÿrÿoÿlÿjÿgÿeÿbÿ`ÿ^ÿ[ÿZÿWÿRÿOÿMÿJÿHÿFÿCÿ>ÿ:ÿ8ÿ6ÿ3ÿ3ÿ0ÿ.ÿ.ÿ1ÿIÿbÿ\ÿYÿ[ÿ`ÿTÿ(ÿ(ÿ+ÿ0ÿBÿ[ÿ]ÿWÿUÿUÿWÿWÿXÿ\ÿoÿvÿlÿnÿqÿuÿyÿqÿoÿqÿrÿkÿiÿkÿeÿ]ÿ\ÿZÿLÿ=ÿ=ÿ@ÿHÿNÿTÿXÿYÿ[ÿ[ÿYÿVÿSÿQÿOÿMÿIÿDÿCÿBÿAÿ>ÿ>ÿ=ÿ<ÿ:ÿ8ÿ5ÿ2ÿ0ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ5ÿ9ÿCÿLÿIÿBÿ2ÿFÿ^ÿgÿ]ÿ^ÿdÿjÿsÿyÿÿÿÿÿÿÿÿÿ2ÿÿ!ÿ ÿ"ÿ$ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ$ÿ%ÿ5ÿSÿlÿbÿNÿJÿWÿbÿlÿqÿxÿ{ÿoÿbÿaÿaÿcÿaÿaÿcÿcÿbÿgÿkÿlÿnÿaÿAÿEÿLÿRÿXÿYÿ]ÿbÿcÿdÿgÿmÿqÿuÿuÿwÿsÿmÿiÿiÿtÿ~ÿyÿqÿnÿkÿmÿmÿmÿpÿrÿcÿHÿEÿJÿUÿaÿrÿvÿxÿrÿoÿpÿpÿpÿnÿbÿ_ÿgÿqÿuÿpÿmÿkÿjÿhÿlÿrÿ|ÿÿ~ÿ}ÿ}ÿzÿeÿ1ÿ&ÿ&ÿ'ÿ%ÿ%ÿ&ÿ(ÿ(ÿ)ÿ)ÿ&ÿ%ÿ)ÿ2ÿ4ÿ,ÿ&ÿ%ÿ%ÿ.ÿ;ÿ>ÿ1ÿ&ÿ$ÿ%ÿ%ÿ(ÿ'ÿ(ÿ-ÿ?ÿVÿoÿÿÿÿÿÿÿÿyÿnÿZÿ8ÿ(ÿ0ÿ?ÿGÿJÿNÿQÿTÿYÿ\ÿ]ÿ[ÿ]ÿ]ÿ[ÿ[ÿ[ÿ[ÿ]ÿ^ÿ^ÿ_ÿ`ÿ_ÿ^ÿ]ÿ^ÿbÿcÿbÿcÿgÿjÿoÿvÿyÿ}ÿ}ÿ}ÿÿÿ{ÿuÿtÿtÿrÿsÿqÿoÿpÿÿÿÿÿÿÿÿÿÿÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿoÿ`ÿaÿbÿaÿbÿaÿaÿbÿcÿeÿgÿjÿmÿqÿsÿtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿ}ÿzÿuÿsÿpÿmÿmÿkÿkÿkÿkÿjÿkÿkÿiÿjÿiÿlÿnÿsÿ|ÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<;s::t::u::u9~8t8~8u7~7u6~6v44u44u44v33w34w44w33x33x44y55y66y55y55x55x55y66y66y55y6~6z6~6z5~5{4~4{4~4{4~4{3~3z3~3z2}2{2}2{1}1{1}1{0|0|0}0|0{0{/{/{/|/{.|.{.|.{/|/{/|/{/|/{0|0|/|/|0}/|/}/|/}/|/}.|.}.}.}.}-}-|.}..~.-~--~--~--~.0~12~47~9:}=?AEGKN~RWt]
bmj
piuwkvtlmliovgx{g{~r~|zz||{{yzxwvqtolrkiue~czb~_z\~YyWTxROyMJyEEyA=y97z73z21z-.z/9y]axYYu_~au6}&x+0xC[u^YqYYqZ[r[_wkwqn
mpy{xtw|oh{efz^WzTG|<>}BH}MRzVZx\[uZWtUStPLuHDyA@z><|<;|:8~76}43~11/
015;?;;JzM
FrABe]gZ_`XdkYryRNIL\/w"~&#~$$~%&##!$~$%~#*~Fhuqd
LAJ`t|y~g_|b^{_ayccycaxaeyik{T?}CJ}HOTWZaa`dintyzwtq
loy{r~mmmmkmmiP}IzL|Wwf|v{x|u}q|qr}qp|m~\}^zfpwtqymkyki{k{s}tq{}sxas,'}('
(('~(*})'}&'(
-00'(()~*
//
*)'~&(|%&|0F}\s~wplgyjhU1r'5{EK~JL|PRxTXxZ\u\\p\[o\]m]\l]`m`_p`]n^^m[Yo\`pelno
tjwyhz}i~xlrsmtumw
ustv|
} |¬¬}¢~z{xrh_
`¡bbaaxdsbz``raaobaocenhkknrkttm
s|~roqqv~}}
uo{lwvluqlnmllkkjjmjkmkkmkjmkojwjmw}}
{~~
}}x~qoss
u|
~
w
tuxy{~~=ÿ<ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ1ÿ2ÿ4ÿ7ÿ9ÿ:ÿ=ÿ?ÿAÿDÿEÿIÿLÿOÿSÿYÿ^ÿdÿkÿrÿvÿwÿwÿrÿpÿrÿsÿvÿwÿzÿ{ÿzÿ{ÿ{ÿzÿzÿzÿyÿwÿuÿrÿoÿlÿjÿhÿeÿbÿaÿ^ÿ[ÿYÿUÿRÿOÿMÿKÿHÿEÿBÿ?ÿ;ÿ9ÿ7ÿ6ÿ4ÿ1ÿ1ÿ.ÿ.ÿ/ÿ2ÿMÿaÿ[ÿYÿ[ÿbÿOÿ'ÿ+ÿ/ÿ=ÿXÿ`ÿ\ÿ\ÿ\ÿ^ÿ^ÿ`ÿaÿgÿwÿuÿoÿqÿwÿÿÿwÿvÿxÿrÿkÿhÿeÿUÿJÿOÿAÿ:ÿ=ÿ@ÿCÿKÿQÿUÿXÿYÿYÿYÿWÿVÿRÿPÿMÿJÿEÿCÿ@ÿ>ÿ=ÿ;ÿ:ÿ:ÿ:ÿ:ÿ8ÿ6ÿ5ÿ3ÿ2ÿ3ÿ3ÿ4ÿ<ÿ=ÿ;ÿ;ÿ=ÿ<ÿ>ÿSÿ]ÿZÿcÿiÿjÿcÿdÿdÿmÿsÿyÿ~ÿÿÿÿÿÿÿÿ+ÿÿ(ÿ'ÿ&ÿ%ÿ&ÿ&ÿ$ÿ#ÿ%ÿ$ÿ!ÿ(ÿOÿnÿxÿiÿ`ÿcÿ^ÿKÿ<ÿAÿTÿnÿtÿdÿYÿ[ÿ]ÿ^ÿbÿbÿbÿfÿeÿ_ÿ`ÿhÿdÿOÿ=ÿ?ÿIÿCÿGÿHÿNÿUÿVÿUÿWÿZÿ`ÿhÿoÿtÿyÿ|ÿ{ÿxÿuÿqÿlÿtÿ|ÿtÿlÿlÿlÿkÿjÿhÿhÿhÿXÿKÿRÿ[ÿmÿwÿvÿqÿpÿqÿrÿqÿpÿeÿWÿ[ÿfÿoÿsÿpÿlÿkÿkÿjÿmÿtÿ}ÿÿÿ|ÿ|ÿwÿYÿ)ÿ'ÿ(ÿ(ÿ)ÿ)ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ*ÿ-ÿ4ÿ.ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ3ÿKÿaÿtÿ
ÿÿÿÿÿÿÿxÿfÿLÿ-ÿ,ÿ<ÿGÿJÿKÿLÿNÿQÿRÿVÿXÿYÿZÿZÿ\ÿ\ÿ\ÿ\ÿ^ÿ^ÿ\ÿZÿ^ÿ]ÿZÿ]ÿ_ÿ_ÿ\ÿYÿZÿ\ÿ^ÿbÿiÿoÿsÿtÿwÿyÿwÿqÿnÿpÿuÿuÿuÿuÿvÿwÿÿÿÿ ÿ³ÿ¶ÿ®ÿ£ÿÿÿ{ÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿ|ÿrÿaÿbÿaÿaÿ`ÿ`ÿ`ÿbÿfÿhÿkÿnÿrÿtÿvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿÿÿ
ÿÿÿÿÿ{ÿwÿtÿqÿpÿmÿmÿlÿkÿjÿjÿjÿkÿkÿkÿkÿkÿmÿrÿwÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ==s>=t<<u<;u;~;v9~9v8~8v7~7v65v44v44w44x33y44y33x44x55y55y66y55y55x66x66y66y66y55y6~6y6~6y5~5z4~4z3~3z3~3z3~3z3~2z1~1{1~1{1~1z1}1z1}1{/|/{/}/{.}.{.|.{-|-{-|.{/|/{0|0{/|/{0|0|/|/|/}/|/}.|.}.~.}.~.}.}.}.}.}.~/}/~.}./}/.~-.~..~.0~12~46~8:}=?ACEGJ~NPyV[o_
fkjnktwgurcqratvexyoxxwyy}xx|wu{toznlzihzfb{_]{Z~WzSPyNLyIFyC?z>:z97z42z2/z.-z-.z=^y^YvY`va;x(-y=Xte`p_^n_cqi
iwg
s}}ut|
y}wwzunxj_xQJxPEz:=|@C|GN{QVxVWuVUsURrPMsIFvDAz@>|<:|::~::75445
79=<<;=6,3L^rfg_inUndTbjWqwW{TR[
g&!y)~')~'&%$#"~5qyrke_]XN?>~QR~VY}YY|\^{bbzeezeeyfWzD=|<B}HE}HF~MQ~OQT_ho
sstvy
y
wups}xolkkjh~if~[R|T~a{p~v}tp~rss}rm|^~V{Yzbnwrnyjjyji{m{t~ur}~quTt'(})(()
*~)(}('}()}')},
21)'(*+~*+~,,}0-|-/|;S}i}|rmi}fs
cjC+s/>}IK|KMyPPxQRxUWvXYtZZp\]o_^n]]n]]r\
\s]^rZWqXYp[[q`
ipppnqupqgshmtostv
xwwzx|®³«}y
zwoe
`
_
b¡
db`~lf}`_u``pa`oadnhmkprktwmv|~rooow~}}
tn}{lwslqnlmllkkkkkmkkmklmlkmmqjxiow}
}
}~~z}u~qoquy
ysuvxy{~~@ÿ@ÿ?ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ2ÿ4ÿ6ÿ8ÿ:ÿ=ÿ?ÿAÿCÿEÿIÿLÿMÿNÿRÿVÿ[ÿ`ÿgÿlÿpÿvÿxÿxÿtÿrÿtÿvÿvÿvÿwÿwÿuÿuÿvÿvÿuÿtÿsÿnÿmÿkÿgÿfÿeÿ`ÿ_ÿ]ÿXÿUÿQÿNÿLÿJÿGÿDÿBÿ?ÿ=ÿ;ÿ:ÿ8ÿ6ÿ4ÿ1ÿ/ÿ.ÿ-ÿ-ÿ-ÿ2ÿQÿ`ÿ[ÿZÿ^ÿbÿRÿ*ÿ,ÿ9ÿUÿdÿcÿdÿdÿeÿlÿpÿpÿiÿrÿ~ÿzÿ}ÿÿÿÿ|ÿvÿ|ÿuÿnÿfÿ`ÿUÿKÿ=ÿ:ÿ9ÿ<ÿ?ÿAÿFÿHÿMÿRÿSÿTÿSÿTÿSÿRÿPÿMÿJÿGÿEÿBÿ@ÿ>ÿ=ÿ;ÿ:ÿ;ÿ:ÿ7ÿ5ÿ4ÿ4ÿ4ÿ5ÿ6ÿ;ÿ>ÿ=ÿ<ÿ;ÿ;ÿ3ÿ/ÿ+ÿ*ÿ<ÿYÿfÿgÿjÿrÿtÿhÿcÿdÿkÿtÿzÿ~ÿÿÿÿÿÿÿ,ÿ"ÿ)ÿ)ÿ'ÿ'ÿ'ÿ#ÿÿ@ÿrÿvÿnÿjÿjÿkÿiÿjÿfÿZÿAÿ8ÿIÿMÿEÿOÿUÿVÿYÿ\ÿaÿcÿdÿeÿgÿ^ÿCÿ4ÿEÿ?ÿAÿQÿKÿEÿHÿHÿIÿLÿNÿSÿ]ÿfÿlÿrÿtÿtÿsÿtÿuÿxÿyÿxÿvÿxÿ}ÿ}ÿuÿmÿjÿlÿhÿfÿfÿcÿaÿWÿZÿiÿuÿwÿsÿpÿrÿsÿtÿpÿhÿVÿTÿXÿaÿlÿrÿnÿjÿkÿiÿjÿnÿuÿ~ÿÿ|ÿÿ}ÿrÿKÿ$ÿ'ÿ)ÿ)ÿ)ÿ(ÿ)ÿ(ÿ(ÿ*ÿ*ÿ*ÿ)ÿ*ÿ'ÿ)ÿ0ÿ3ÿ-ÿ'ÿ'ÿ(ÿ*ÿ*ÿ*ÿ*ÿ-ÿ0ÿ3ÿ3ÿ9ÿGÿ\ÿsÿ
ÿÿ
ÿÿÿÿÿ|ÿrÿ`ÿAÿ+ÿ3ÿBÿJÿKÿKÿMÿPÿPÿPÿPÿRÿTÿVÿYÿYÿYÿZÿ\ÿ_ÿ_ÿ_ÿ]ÿ\ÿ\ÿ[ÿYÿ_ÿ^ÿ[ÿTÿUÿYÿ_ÿ`ÿZÿbÿjÿjÿmÿoÿdÿZÿ`ÿgÿnÿsÿwÿyÿ|ÿÿ
ÿÿ ÿ¨ÿ©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿoÿ_ÿ`ÿ_ÿ_ÿ`ÿ_ÿcÿfÿkÿmÿpÿpÿuÿvÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ~ÿyÿuÿrÿpÿlÿlÿlÿkÿkÿkÿkÿkÿkÿkÿlÿlÿlÿoÿrÿyÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿA@rACsB~@t?~>t<~<u;~:u:~:w8~8w7~7w6~6w66v55x44y55y44w44w44x44x55x66x66w55w66x55x55x66y66y5}5y5~4z4~4z3~3z3~3z2~2z2~2z2~2z0~1z1~1z1~0z/}/z/|/z/|/|.|.|.|.{-|-{-|.{-|-{.|.{.|.{.|.{.|.{.}.{.}-}-~-~.~.~.~.~.~.~.}.~.}.~.}./}/.~.~.~.~.~.~.~0~2~45~8:~;~>~@BDFILM|QUwX]qdhllqguxbtr^tt_uuiusssszrt~rr~o~l~j~h}f~d|b_{[Y{VS{PLyJHyFCyB?{>:{87z64z1~/{-~,{,,{-Ay_]wYZv^bv=)w7Quffrikonqsut|p
t|{pwmuvsluj_uXHv78y9:{<={AE{KPwQPuPQsPOsNOsKFuB@y?>{=;};988~5
4323
7=>><<8~1.|..+0
JychfhqWqoVgfZms\x}\Y_k(!|'+(~'"~3suokkmmloo[}C8}<O~DJ}UX|Z[|\^{^[{L9{45|86}EM~IB~IJ~JLT\cgimprq
pp
uxz
zz}
}u
nh
jg
c~a
a~b\|`~p|z~x}q}qq~rs|lbzP}PzWx`jvpkwjlyjj|q}yv|q~pqCw&''(''(~)*}))|**|)(|*-~150'((~))}*
,}2
3{4
>{Rf}zyn
mj}
yfo
]l:+w9E}IM}OO{OPyOOwPRwRVxWYsZ[q^^p_`p]YsXXtW
]r[
TpS
UqY
erd
_qdephkq]Vu^fxm
txz{tps¡u ussojc^^
b
egfiydwaz`asaapdgnilkosjwznxzrqppx~||
u|mywltqlpokmkkjkmkjmjjnjjljkjnsi}
jqy|
~~~
}~}v~snort{
}
xuxyyy{~BÿAÿAÿ@ÿ?ÿ@ÿ?ÿ>ÿ>ÿ;ÿ;ÿ:ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ3ÿ4ÿ6ÿ8ÿ;ÿ=ÿ@ÿCÿEÿEÿGÿJÿLÿPÿSÿVÿZÿ]ÿcÿgÿlÿqÿuÿuÿqÿqÿqÿpÿpÿrÿrÿpÿpÿqÿpÿmÿmÿlÿjÿhÿeÿcÿaÿ^ÿZÿXÿVÿTÿQÿMÿKÿHÿFÿEÿBÿAÿ>ÿ<ÿ:ÿ7ÿ6ÿ4ÿ2ÿ0ÿ.ÿ,ÿ+ÿ+ÿ+ÿ-ÿ3ÿOÿ`ÿVÿUÿYÿ`ÿWÿ,ÿ2ÿJÿaÿkÿlÿmÿqÿvÿxÿvÿqÿqÿsÿÿÿmÿtÿlÿnÿvÿiÿeÿhÿ^ÿXÿWÿBÿ8ÿ:ÿ:ÿ9ÿ9ÿ:ÿ=ÿAÿHÿNÿPÿMÿKÿLÿNÿOÿOÿNÿLÿHÿCÿ?ÿ<ÿ;ÿ:ÿ8ÿ7ÿ7ÿ8ÿ8ÿ5ÿ4ÿ3ÿ2ÿ5ÿ:ÿ?ÿ@ÿ>ÿ<ÿ<ÿ7ÿ1ÿ/ÿ/ÿ0ÿ/ÿ.ÿ+ÿ5ÿSÿjÿjÿjÿuÿrÿhÿeÿjÿrÿ{ÿÿÿÿÿÿÿvÿÿ$ÿ(ÿ'ÿ(ÿ&ÿjÿwÿmÿhÿjÿpÿrÿtÿtÿxÿrÿZÿGÿ<ÿ7ÿGÿIÿEÿSÿWÿYÿZÿZÿXÿEÿ6ÿ5ÿ7ÿ:ÿ8ÿ1ÿ:ÿDÿBÿ@ÿBÿDÿHÿIÿQÿ]ÿdÿgÿfÿgÿhÿgÿiÿnÿnÿpÿrÿvÿxÿwÿvÿyÿzÿuÿoÿjÿhÿeÿaÿ_ÿ`ÿaÿ_ÿdÿsÿwÿvÿsÿqÿrÿrÿpÿiÿYÿNÿOÿUÿ`ÿiÿlÿiÿjÿlÿjÿjÿqÿ|ÿÿÿ|ÿÿÿmÿ<ÿ%ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ*ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ+ÿ+ÿ/ÿ4ÿ7ÿ1ÿ&ÿ'ÿ(ÿ)ÿ)ÿ,ÿ1ÿ8ÿ<ÿIÿ\ÿrÿÿÿÿ
ÿÿÿÿÿwÿjÿVÿ4ÿ,ÿ:ÿDÿIÿMÿOÿOÿPÿRÿQÿOÿNÿOÿPÿRÿUÿYÿZÿ]ÿ^ÿ_ÿaÿ`ÿ]ÿYÿWÿUÿUÿ\ÿ_ÿ]ÿYÿSÿOÿNÿaÿgÿgÿfÿgÿfÿZÿYÿaÿgÿrÿvÿ|ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ¢ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtÿfÿeÿdÿdÿeÿgÿiÿlÿmÿpÿsÿwÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿvÿsÿqÿmÿlÿlÿmÿlÿkÿiÿjÿjÿjÿjÿjÿjÿlÿoÿuÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿFFoEDqD~CtA~?t?~?t<~:u9~9v8~8w7~7w7~7w77v55x33w33w44w44w33x44x55x55x66w66w66x55x55w55y66y5}5y5~5z4~4z3~3z3~3z2~2z2~1z1~1z0~1z1~1{1~0z0}0z/|/z/|/{.|.{.|.{-|-{,|,{,|,{-|-{.|.{/|/}-|-}-}-{-}-}.~.~.~.~.~.~.~.~-}-~.}.~.}..}..~.~.~.~.~//~11~23~56~;;~>>@CEGINQ~TWyY
^rchknnfo
oamm^nndoopmmzmk~kj~h~g~e~b}a~^|[YzTRzQNzKI{HEzCAz>>{;8{66z31z/~.{,},{+~+{,.y<\yXSwT\w_Ax)=yTfyloys{}zvolgin{[bxqrxog[oOSrSBu::y;9{97{7;{CJyMKvHFsJKsMLsKEuB<y::{88}88}76}43~237>BB@><5|11{11|00/,'}@]rflcmpZmj[mv^y~_`f
Or#}(&Qzqkjpuw{}~`V~G;~3;}B;}?I|UZ|O5|)6{;9{=@|=;|;>}BD}GIQZchifabc`_eikoqstsrqqj
d`
_~^
`~_c|hw}xtturqo}dR{M~NzTy_fxhjymkyik|r~|~w}qpm@v2.~*+++
+~****~+,}--},-~-
08</&~()},.}.9|AS|g|y}ql
lj~uhdHp-+y:CGK|NOyPRxSRyONyNOzRSxX[v]_t__q^ZtV
TvQZt`cn_
ZmW
TlU
_kgijiej[_nh
npuzp~nlllmm
¤n¤
¢olia\]
cgh~h}olh}k{i
iwkmtnrqsupwszxqpppx~~|~s~{lwulrolllklkkjimkimiilijkjmjpwhks{}
~~}{}t~pnos
w
zwvyzyy{~HÿHÿFÿEÿCÿCÿBÿBÿAÿ?ÿ>ÿ>ÿ<ÿ<ÿ:ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ2ÿ3ÿ5ÿ7ÿ8ÿ:ÿ;ÿ=ÿ?ÿBÿEÿGÿHÿKÿOÿRÿVÿXÿYÿ]ÿbÿeÿgÿiÿkÿkÿkÿiÿiÿjÿjÿiÿiÿiÿhÿhÿfÿeÿdÿbÿ_ÿ^ÿ[ÿWÿUÿSÿPÿNÿLÿJÿHÿFÿCÿAÿ?ÿ=ÿ<ÿ8ÿ8ÿ6ÿ4ÿ2ÿ0ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ2ÿMÿ_ÿVÿQÿWÿ[ÿWÿ,ÿ0ÿ=ÿPÿ^ÿhÿoÿuÿzÿtÿmÿhÿ_ÿWÿQÿJÿZÿrÿ
ÿÿÿÿsÿfÿeÿ`ÿIÿ=ÿ9ÿ;ÿ9ÿ9ÿ7ÿ7ÿ8ÿ>ÿCÿIÿHÿEÿEÿFÿHÿIÿHÿGÿCÿ@ÿ<ÿ:ÿ8ÿ5ÿ5ÿ4ÿ5ÿ6ÿ5ÿ3ÿ3ÿ5ÿ7ÿ=ÿCÿDÿBÿBÿ=ÿ9ÿ4ÿ2ÿ2ÿ2ÿ0ÿ1ÿ0ÿ.ÿ.ÿ/ÿ,ÿ+ÿDÿcÿjÿmÿpÿqÿmÿpÿsÿvÿ~ÿÿ
ÿÿÿwÿ ÿ$ÿ"ÿBÿ{ÿqÿhÿjÿoÿvÿyÿ|ÿÿwÿ\ÿVÿVÿIÿ9ÿ2ÿ5ÿ7ÿ8ÿ7ÿ6ÿ;ÿ8ÿ,ÿ0ÿ5ÿ6ÿ7ÿ9ÿ:ÿ>ÿ>ÿ>ÿEÿHÿLÿQÿTÿZÿ[ÿaÿiÿjÿfÿaÿ^ÿcÿfÿ_ÿZÿ\ÿ_ÿaÿbÿfÿoÿqÿmÿlÿnÿqÿjÿdÿ`ÿ_ÿ^ÿ^ÿ]ÿaÿlÿvÿtÿqÿqÿrÿqÿoÿoÿ`ÿLÿKÿOÿUÿ]ÿcÿeÿkÿmÿjÿjÿnÿuÿ}ÿÿÿÿÿÿrÿRÿIÿEÿ<ÿ0ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ+ÿ.ÿ.ÿ,ÿ+ÿ-ÿ-ÿ.ÿ2ÿ:ÿ9ÿ2ÿ)ÿ-ÿ8ÿ>ÿ7ÿ=ÿMÿbÿtÿÿÿÿÿ
ÿÿÿÿÿuÿeÿJÿ/ÿ'ÿ0ÿ>ÿFÿJÿKÿNÿQÿPÿPÿOÿOÿNÿMÿNÿNÿOÿRÿXÿ]ÿ]ÿ^ÿ_ÿ^ÿ[ÿWÿTÿSÿWÿ_ÿfÿhÿdÿcÿbÿeÿjÿkÿlÿnÿjÿcÿiÿpÿvÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢ÿ£ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿpÿnÿmÿoÿoÿqÿrÿsÿwÿxÿyÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ{ÿwÿuÿrÿpÿmÿlÿlÿjÿiÿiÿhÿiÿiÿiÿiÿjÿjÿmÿpÿxÿÿÿÿÿÿÿÿ
ÿ
ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿMMoIHqG~FrD~BsC~As@~?v=~<w;~;v8~9w8~6w55y66y66y55y44y44y44x44x55x55x55x55x55y66y4~4y4~4y4~4z4~5z5~4z4~3z3~3z3~3z2~2y2~2y2~2y2~2y2}1y/}/z/}/z/|/y.|.z.|.z.|.y,|,y,|,{,|,{-|-|,|.|-|-}-|.}.}.|-}-|-}-|-}-}-}-~-}-~-~-.~.-~-.~..~.~.~.~.}.~.}.~0~02~36~68~;<>@BEHHMOR~VXxZ\p`bkd
efd
ddbchegpfezef}ec~ba}^[}ZX{URyPOzLJ{HH{EA{A>{;;{8~6|42|1.{+~+{+~+{+~+{,~-{,<z[YxRUx[}bwE|&{19|E
W~`ksoha
\|XU}I
H|`|x
q{nuuojNt<9y<:|87}65}8=z@Az@BvCCwCCvDAw?<y96z43|0/|/
1}4
6;
?BEECB=63|2
3{31|1
2}1
0320(3wRglmm^jtX}w]tvbzk
jt7 {4uujjnvw~u[T~UV}N>}42}35}65|43|27{88{::{99{CJ}NPT\_`
^ejhe`cghec_]YPP
`omjlq~kb}]
\}]^}\^|ov}qopqr~ql{RLyL|OzSzX~\welxlgxim{vz~r
lkylmc[rSD{4-++++
+++~,+~++~+,~.
4;=033:~>D|Z|n}s~mkmk{uij]k[WoLEqADvFHyNOzNO{NL}JD}@I}LT}ZZ|YXx\\y[
WxY^uflmoofpr_s
s]t
q]sr]qs_w
{e
hgfeg
j
mpmkgd`_ahi
jm}wsusytv|w}wx{wzy|y}~pppoqx~~~~}~{~r|ynwtkqpknlkljljjmiimihlhkkkljqyimv|~
~
~
~~~yropqt|{
wuxzzxxz~NÿNÿLÿKÿIÿHÿEÿDÿEÿCÿCÿAÿ?ÿ=ÿ<ÿ<ÿ:ÿ9ÿ9ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ2ÿ3ÿ4ÿ7ÿ7ÿ9ÿ:ÿ<ÿ>ÿ?ÿAÿCÿGÿJÿMÿQÿSÿUÿUÿVÿXÿ\ÿ\ÿ^ÿ]ÿ]ÿ^ÿ_ÿaÿcÿbÿaÿ`ÿ`ÿ_ÿ`ÿ^ÿ]ÿZÿWÿVÿTÿRÿOÿOÿKÿIÿGÿFÿCÿAÿ?ÿ>ÿ<ÿ;ÿ9ÿ8ÿ5ÿ2ÿ1ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ-ÿ,ÿ-ÿ1ÿGÿ_ÿWÿQÿZÿdÿ_ÿ0ÿ(ÿ0ÿ:ÿMÿXÿ[ÿcÿeÿdÿ^ÿXÿYÿZÿRÿJÿNÿlÿzÿtÿpÿoÿoÿrÿbÿQÿAÿ<ÿ5ÿ4ÿ4ÿ2ÿ1ÿ/ÿ1ÿ4ÿ5ÿ5ÿ4ÿ6ÿ7ÿ8ÿ;ÿ<ÿ>ÿ<ÿ:ÿ;ÿ9ÿ6ÿ4ÿ3ÿ3ÿ2ÿ1ÿ5ÿ8ÿ?ÿBÿEÿFÿFÿEÿBÿ>ÿ9ÿ3ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ5ÿ0ÿ/ÿ=ÿZÿiÿnÿnÿyÿ}ÿ{ÿuÿeÿRÿTÿ8ÿ'ÿ/ÿlÿtÿiÿhÿlÿoÿwÿ~ÿ|ÿgÿWÿWÿZÿ[ÿXÿRÿAÿ3ÿ4ÿ6ÿ8ÿ<ÿ3ÿ/ÿ1ÿ6ÿ6ÿ6ÿ:ÿ>ÿ?ÿBÿJÿMÿOÿTÿWÿWÿ[ÿ_ÿ_ÿbÿfÿgÿfÿeÿdÿeÿeÿhÿeÿaÿ[ÿYÿRÿEÿ@ÿQÿfÿjÿjÿkÿpÿiÿ`ÿ[ÿ[ÿ\ÿ\ÿ\ÿeÿyÿzÿpÿpÿqÿsÿqÿpÿbÿJÿHÿKÿNÿPÿUÿ\ÿeÿoÿlÿiÿkÿpÿ{ÿÿÿÿÿÿÿ
ÿÿyÿpÿdÿ[ÿIÿ6ÿ,ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ,ÿ/ÿ5ÿ>ÿ7ÿ3ÿ5ÿ=ÿBÿQÿiÿzÿÿÿÿÿÿÿÿÿÿ}ÿtÿjÿ\ÿ]ÿaÿfÿfÿeÿWÿJÿFÿGÿLÿLÿJÿKÿGÿAÿ.ÿ*ÿ>ÿBÿFÿVÿXÿXÿYÿ\ÿ`ÿ_ÿ]ÿ^ÿeÿmÿrÿsÿsÿtÿvÿyÿyÿxÿxÿwÿvÿxÿzÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿwÿyÿzÿzÿzÿ|ÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ~ÿ~ÿÿÿÿÿ~ÿ{ÿyÿwÿrÿqÿpÿnÿlÿkÿiÿiÿiÿiÿiÿiÿhÿhÿjÿkÿlÿqÿ{ÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿONnNMoK~JpH~FrE~DrC~Bu@~?w>~=v;~;x9~7x76y77y55y55y55y44y44x44x44x44x44x44x44x44x3~3x3~3x3~4y5~4y4~3z3~3z3~3z3~3z2~2z2~2z2~2{1~1{1}1y0}/z/}/z.|.y.|.y-|-y-|-y,|,y,|,{,|,{,|,|,|,|-|-},|-}-}-|-}-|-}-|-}-}-}-~-}-~-~-~-~-~-~-~-~-~.~.~.~.~.}-~.}-~0~01~44~57~89:;=?AFGKLOP{QQzSVuWXqXXpXZr[]x]\~]\\\~ZX}UR|RP{OLzJJ{GE{CA{A?{;9{76{4~3{2~0{,~,{,~,{*~*{,~,{,~,{,-z7Ux[SxU]yeO|&|,4@MSUYXTRVYXUR~]~lmxkfrhiobXrK8v-.z-+~*(~(
'~''~'
'|*-|14|8<|AC|DD}EG~GC~@AEFGGGFEA;4}0
0|00{1
5z57|66}65~5
633.0Iwbgjs{a|`xXk
$y+k}vl}nqorysc~W
\~\[}^\}UA}55|65|30}-/}4D}IF~FXdTQVZZY^
^bded```dcdc\WTM?:CUbggk~g
_|Z
Y|[]}]n}~wpprqq~sX{FEyK|NzQzW~]wejxlmzr|x}qicacdskh^Lt4-~,,,-,,-++~++~+
-1
6:;;B~LZ}rvljkji|
seh
`h]
blfckbbjYGlOMoG>r9=w?9y#/}3~.GX}Z\y_cvd
crfklrvew
y\yyVy
zT|
~T}|T}V
[
dhj
m
n
p
p
q
nmiea`c
f
hiighlqvvrmllmoy~~~~{~rzxnvrkppknlkkilihmfhmiiliikkmis}hmw}~
~|}}vqpqsv
}{wwyyywwz~PÿOÿNÿNÿMÿKÿJÿHÿGÿEÿDÿBÿ@ÿ>ÿ>ÿ=ÿ<ÿ;ÿ9ÿ8ÿ7ÿ6ÿ7ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ3ÿ3ÿ4ÿ5ÿ7ÿ8ÿ9ÿ:ÿ;ÿ=ÿ@ÿDÿEÿIÿJÿLÿNÿMÿOÿOÿPÿQÿRÿRÿRÿUÿWÿYÿZÿYÿXÿYÿXÿXÿWÿVÿTÿQÿOÿNÿMÿLÿJÿIÿHÿFÿDÿBÿ@ÿ>ÿ;ÿ9ÿ7ÿ7ÿ5ÿ3ÿ1ÿ/ÿ.ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ/ÿCÿ_ÿWÿSÿXÿcÿhÿ>ÿ*ÿ-ÿ3ÿ@ÿLÿPÿLÿLÿLÿMÿNÿRÿVÿYÿVÿTÿgÿkÿkÿhÿcÿbÿbÿ^ÿ[ÿJÿ+ÿ(ÿ'ÿ&ÿ%ÿ#ÿ#ÿ!ÿ!ÿ"ÿ"ÿ#ÿ$ÿ&ÿ)ÿ0ÿ9ÿEÿNÿQÿRÿPÿOÿOÿNÿMÿJÿIÿIÿHÿGÿGÿFÿCÿAÿ<ÿ7ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ5ÿ6ÿ7ÿ6ÿ6ÿ6ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ1ÿ0ÿ5ÿIÿ^ÿoÿwÿyÿyÿeÿ0ÿ*ÿiÿzÿmÿnÿmÿnÿrÿxÿwÿ\ÿXÿZÿ[ÿ\ÿ]ÿaÿ_ÿ_ÿGÿ6ÿ5ÿ5ÿ3ÿ1ÿ-ÿ.ÿ0ÿ4ÿFÿJÿIÿLÿeÿlÿSÿSÿSÿRÿSÿWÿ]ÿ]ÿaÿcÿbÿ_ÿ\ÿ\ÿ\ÿ]ÿ_ÿ^ÿZÿYÿQÿNÿCÿ:ÿ7ÿ=ÿOÿ`ÿgÿ`ÿiÿjÿ`ÿ\ÿ\ÿ\ÿ[ÿaÿxÿ~ÿuÿrÿpÿsÿqÿqÿlÿLÿBÿEÿIÿMÿQÿWÿ^ÿdÿjÿnÿuÿyÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿkÿ[ÿDÿ0ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ/ÿ4ÿ;ÿ;ÿ=ÿ@ÿNÿbÿvÿÿÿÿÿÿÿÿÿÿÿ{ÿpÿeÿ_ÿDÿFÿcÿiÿeÿaÿ^ÿSÿcÿmÿgÿ_ÿVÿLÿ=ÿ@ÿ;ÿ9ÿ<ÿ@ÿKÿYÿ_ÿaÿcÿeÿfÿhÿnÿrÿvÿyÿzÿ|ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿÿÿÿ~ÿ|ÿzÿwÿuÿrÿpÿpÿnÿlÿkÿiÿiÿhÿfÿfÿfÿhÿhÿiÿjÿoÿvÿÿÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTSmRPmNNoM}KpI}GqF}DtC}Bt>}=u=};v9}:w8~6w7~7y4~6y6~6y5~5y4~4y4~4y4~4y4~4w4~4w44x44x4~4x4~3x3~3w3~3w4~5x4~4x2~2y2~2y3~3z1~1z3~3y2~2y2~2y1~1y0~0{0~/{/~/{.}.{-}-{,},{,},{,},{,},|,},|,|+{+|+{+|+|+|+|,~,}-~-}-~-}-~-}-~-}-~-}-~-}-~-}-~-|-~-|-~-}.~.}.}.~.}.~.~.~0~1~3~3~5~5~7~78~:=@DDF~HI~KMM~MM~NN|OO|PS|TS}T~T}T~S|S~R}R~O|MM|KI|IG{GFzD~@|?~<|<:}75}42|1.{-~,{,},|-}-|,},|,},|,},|,~-|.5|TayVXz_~f|a|0+}/
6ALJFGHG}HM{VUySXi}o
pvcZp\dqaWvC'y%%|#
!}!"~"#~#$')~/:}K
U~TRPOOONJJIIGDDCA~=7}30|00{0
1{4
4|67|77{;8|95653560
.|07y?E{B5},j}xn~nrqpzz]X
Z~\_}aa|`a|O@{74{43{0/|--|26}>ABJNQWX
UTUZ]__ZZ\[]]YTQMGA<66=O\^V`~l
b|]
]|]\}l~uqqrsp_DzB}FyH|LyOzV~_xcgyr~|{t
~icbcbabtgkV=v//.....~.++,+-~.0~7
<|@E}R}jrhghii|pedcjP=l^glbege
hehhchlbolebOiDKpQQvY~avfgrknmr
shx{c^W~
R~
~OP
~S|
}Y~
_
lv{ z z¡
¢z£
¡x£¢x
s
pkedbcddefe
b
bb
f
hhhhhknw~~~~~~~}}y~{ryvntqkonkllkkilhgnfeneglgikjpgwipx~
}{}sqqsuy}zwyyyxvvz~UÿTÿSÿSÿPÿOÿOÿMÿKÿIÿFÿFÿCÿBÿ@ÿ>ÿ=ÿ;ÿ:ÿ8ÿ9ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ5ÿ5ÿ7ÿ7ÿ:ÿ<ÿ<ÿ>ÿAÿCÿDÿFÿFÿHÿJÿJÿLÿMÿLÿNÿNÿNÿNÿPÿQÿPÿQÿPÿPÿOÿNÿLÿLÿJÿJÿHÿFÿFÿEÿDÿCÿBÿ@ÿ?ÿ<ÿ:ÿ:ÿ8ÿ6ÿ4ÿ2ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ+ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ?ÿaÿ`ÿWÿXÿ_ÿgÿOÿ(ÿ*ÿ.ÿ6ÿBÿMÿJÿGÿIÿEÿDÿHÿKÿMÿTÿWÿ^ÿmÿqÿfÿ_ÿ`ÿdÿeÿ^ÿTÿ9ÿ!ÿ$ÿ"ÿ!ÿ ÿ"ÿ"ÿ"ÿ$ÿ'ÿ,ÿ1ÿ?ÿPÿ[ÿYÿXÿYÿVÿRÿUÿQÿNÿJÿGÿFÿFÿDÿAÿ@ÿ?ÿ<ÿ9ÿ4ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ4ÿ4ÿ6ÿ6ÿ8ÿ<ÿ>ÿ<ÿ9ÿ5ÿ5ÿ7ÿ7ÿ5ÿ4ÿ4ÿ1ÿ0ÿ/ÿ-ÿ.ÿ+ÿ1ÿeÿvÿmÿkÿoÿqÿpÿwÿ{ÿcÿYÿ[ÿ^ÿ^ÿdÿfÿfÿPÿ>ÿ8ÿ;ÿ8ÿ4ÿ4ÿ2ÿ2ÿ1ÿ0ÿ1ÿ3ÿ4ÿ7ÿ:ÿBÿKÿPÿXÿ[ÿYÿZÿYÿYÿ]ÿ]ÿ]ÿ]ÿZÿZÿ[ÿ\ÿ\ÿYÿTÿPÿMÿIÿ=ÿ8ÿ6ÿ5ÿ6ÿ=ÿKÿSÿTÿQÿYÿhÿeÿ[ÿ]ÿ]ÿaÿwÿÿ}ÿvÿtÿuÿtÿsÿqÿRÿ?ÿBÿFÿGÿKÿOÿVÿ_ÿhÿoÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿsÿbÿMÿ5ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ,ÿ+ÿ+ÿ-ÿ,ÿ2ÿ7ÿKÿ\ÿdÿtÿÿÿÿÿÿÿÿÿÿÿÿyÿlÿcÿbÿcÿZÿ^ÿeÿcÿcÿeÿfÿiÿiÿgÿgÿjÿoÿoÿpÿpÿoÿkÿdÿgÿmÿrÿsÿuÿuÿyÿzÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿ{ÿzÿxÿyÿ{ÿ|ÿ}ÿ
ÿÿÿ£ÿ¦ÿªÿ«ÿ«ÿ«ÿ«ÿ¬ÿÿ®ÿ°ÿÿªÿªÿ©ÿ£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ|ÿ~ÿ|ÿ|ÿ}ÿÿÿ}ÿ{ÿyÿvÿtÿqÿoÿmÿlÿlÿkÿiÿhÿfÿfÿeÿfÿgÿgÿiÿjÿqÿzÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXWmUSmRRoQ}OpM}KpH}IrE}Cs@}>t=};v;}9w9~7w7~7y6~5y5~5y4~4y4~4y3~3y3~3y3~3w3~3w33x22x3~3y3~3y4~4y2~2y2~2x1~1x1~1w1~1w0~0x1~1x0~1z2~0z1~1{0~0{1~1{/~/{.~.{.}-{-}-{,},{+}+{+}+{+}+|*}*|*~*|*~*|)})|*}*|+~+}*~*}+~+}*~+}+~+},~,},~,}-~-},~,}-~-}-~-}.~.}-}.}/}/}/}/~0}1~3~3~3~3~5~6~7~9~9;~=?~A~CE~EFHIIK~KL~MN~NM~MM~KK~J~H~GF~D}DB|CC|B@{A@z>~<z;~9z6~7{4~1{0~/|.~-}-~-},}+|-}-|-}-|,},|-},|+~+{--z4~P{eY|WX|`~g}={#,~/6CKJFD|CBzEFzHQ~Wer}jiuhbs]Xu\Sx2({$
!|! ~!"~%)~/=~S^^``
\[
[TQKFBAA?>7~78}8
2|10z0
1{2
3{3
4|78|;@{A=|88}755
4432
0-,-6lwl}jonnwzh\\~[
]}`a|W:{(.|7;{95{20{03{64{35:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:F~MQ}SVzdfz]^|[f}zwvtstvk~K=|B}E|J|QzU~\xkyy~{zp}j}j}iieb`ycm]mC2|.,//
0~0..
.+
+~-.~.4~Caw{skjjh
hugf`_haaibaicdid
ggg
eeehenrcrvdy~b
c}
|c|~d~cb`]\
Y~
|Y{xZwt^rshttyt~¦
¬~¯
±´´´¶·º»}»º|´³w²®s©¥p
p
o
k
i
k
~mzzoy{p{{m|}kig
d
acccccgqx~~z}}y~~y~zqxulrqknmkkjkiilhflfflfglgikjsi|kqy}}|~|}x}ruusu{|yxyyxwvvz~~YÿXÿWÿWÿUÿTÿSÿPÿNÿMÿIÿHÿEÿCÿAÿ?ÿ>ÿ<ÿ<ÿ:ÿ8ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ3ÿ6ÿ6ÿ8ÿ:ÿ:ÿ<ÿ=ÿ?ÿ?ÿAÿCÿDÿEÿGÿGÿIÿIÿIÿJÿJÿJÿLÿLÿIÿGÿFÿFÿDÿCÿDÿAÿ@ÿAÿ@ÿ@ÿ>ÿ=ÿ>ÿ<ÿ<ÿ9ÿ8ÿ6ÿ4ÿ5ÿ2ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ,ÿ+ÿ,ÿ,ÿ-ÿ/ÿ;ÿZÿaÿZÿUÿYÿeÿdÿ3ÿ&ÿ,ÿ/ÿ9ÿCÿKÿHÿEÿEÿCÿAÿAÿAÿHÿPÿZÿkÿuÿwÿpÿ^ÿPÿUÿ`ÿbÿJÿ(ÿ ÿ!ÿ!ÿ!ÿ!ÿ#ÿ%ÿ+ÿ6ÿNÿ`ÿbÿeÿdÿbÿ_ÿYÿOÿHÿAÿ8ÿ5ÿ0ÿ0ÿ2ÿ7ÿ7ÿ5ÿ6ÿ9ÿ8ÿ4ÿ2ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ5ÿ6ÿ7ÿ:ÿ>ÿ=ÿ:ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ4ÿ5ÿ3ÿ3ÿ,ÿ-ÿBÿmÿuÿmÿjÿmÿqÿqÿvÿyÿgÿaÿ\ÿ`ÿ^ÿaÿXÿ:ÿ)ÿ)ÿ-ÿ0ÿ7ÿ:ÿ9ÿ5ÿ2ÿ0ÿ2ÿ8ÿ7ÿ5ÿ5ÿ:ÿ?ÿHÿOÿRÿXÿ[ÿWÿOÿOÿVÿZÿaÿaÿ^ÿ\ÿZÿ[ÿ^ÿ]ÿ^ÿZÿXÿVÿWÿNÿBÿ<ÿ6ÿ5ÿ5ÿ<ÿFÿOÿUÿSÿRÿ^ÿgÿ^ÿZÿ\ÿmÿÿÿxÿvÿuÿsÿtÿsÿqÿvÿtÿSÿAÿGÿLÿMÿPÿ_ÿsÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿsÿfÿWÿ>ÿ1ÿ4ÿ9ÿ;ÿ;ÿ<ÿ8ÿ6ÿ2ÿ-ÿ*ÿ+ÿ0ÿ2ÿ2ÿCÿ_ÿyÿÿÿÿÿÿÿÿÿÿÿ
ÿ|ÿoÿbÿ_ÿbÿaÿaÿbÿbÿdÿeÿeÿeÿgÿgÿjÿkÿpÿrÿtÿxÿ}ÿÿÿÿÿÿÿ
ÿ
ÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿzÿxÿwÿtÿtÿpÿqÿoÿnÿnÿqÿuÿÿ«ÿ±ÿ²ÿ·ÿ¹ÿ¹ÿ¹ÿ»ÿ¼ÿ¾ÿ½ÿ½ÿ¼ÿ»ÿºÿ¹ÿ·ÿ°ÿ¬ÿ¡ÿÿÿÿÿÿ
ÿÿÿÿ~ÿ|ÿyÿwÿvÿuÿvÿvÿvÿwÿyÿ{ÿ~ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ}ÿ~ÿÿÿ|ÿyÿvÿsÿrÿnÿmÿlÿkÿkÿjÿiÿhÿfÿfÿhÿhÿhÿhÿiÿmÿtÿ}ÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[mZXmXXnT}QoO}NqL}IrG}DsB}@u?}=v<};w8~8y8~7y5~5y5~5y3~2y3~3y33y33y2~2x2~2x2~2x1~1y22{00{0~1y1~1y1~1y1~1y1~1z/~/z0~0{0~0{1~1{1~0{/~/z0~/z-~/{.~.{-~-z.}-z-}-z+}+z+}*z)})z)})|*}*|)}(|(}(|(~(|)~(|)}*|+}+|+~+|+~+|+~,|,~,|,},},~,}-~-}-~-}.~.}-~-}.}.}0}0}0}0~0}0~0}0}1}2}4~4{5~7{8~8}:~<{<}=}>}A}B~D}D~E~E~FG~GG~GF~EG}D}CC|C~A}A~A}?~=|=~={:~<z=~;z9~8|6~4|3~3{2~/{/}/{.}.{-}-|-}-|-}-|,},|-}-}-}-}-}-|-}-|-~3}Hg}`W{U\|g}_)z&
&}/5@HG~FD|A@|@B|IT}_nrzo`sPTr]^vW;y!z!"}"$~'.~D
]ccd
c\
N~D7,(#
!#
'(
+.1}5:|<9{43{34{76{79{;=z=@z:8z97|56~5
445}30~7b}~xm{gk~qsuw`Z\_a}bS|-&z(-{/3{64{77z41{8;{89|9>FOTUWWTPPRW^___\\^\[ZZ\\XK
?<89~DN|TUzV~Vy[dzaW|_
v~}yvtprrjj~wx|XEzIM{Vcyw~zyzszr{q}pnlhaa|qccSpEG|LP|PP{MHzC@8~12~1
4|Ed}|xnjlkgxfk`h_cjbciaajb
cif
ghh
jgo
sgs
ue{
bb
bbabcc
ef~eyvdspiohvf
impmynu²x³¸|¼¼}½½¾¾¾½~¼~»»{»»x¶°v§v
vto
o{
ytx
vysr~o
r}p
p{rryv
wtn{h~
dedd
e
ekor~~uy{xqwsmplkllkljliilihlfilihkhijluilt{}z}z}w}rssux{|yyzzxvvvy}[ÿ[ÿ[ÿ[ÿYÿYÿVÿTÿRÿNÿMÿJÿGÿEÿBÿ@ÿ@ÿ>ÿ=ÿ<ÿ9ÿ9ÿ8ÿ7ÿ5ÿ5ÿ5ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ'ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ4ÿ4ÿ4ÿ6ÿ8ÿ8ÿ9ÿ:ÿ=ÿ>ÿ=ÿ>ÿ?ÿAÿBÿBÿCÿDÿEÿEÿDÿDÿDÿCÿCÿBÿ@ÿ>ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ7ÿ7ÿ5ÿ2ÿ2ÿ2ÿ2ÿ1ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ-ÿ-ÿ3ÿSÿhÿZÿWÿYÿ^ÿiÿQÿ#ÿ%ÿ*ÿ,ÿ3ÿ>ÿFÿHÿDÿCÿBÿ@ÿ<ÿAÿIÿRÿfÿpÿpÿkÿ`ÿ[ÿ^ÿ]ÿ]ÿTÿ+ÿÿ!ÿ!ÿ"ÿ%ÿ*ÿ6ÿQÿdÿbÿcÿeÿZÿFÿ0ÿ,ÿ)ÿ%ÿ$ÿ"ÿ#ÿ#ÿ$ÿ$ÿ&ÿ(ÿ*ÿ/ÿ9ÿ;ÿ8ÿ4ÿ4ÿ4ÿ4ÿ7ÿ;ÿ=ÿ=ÿ?ÿBÿBÿEÿ@ÿ;ÿ:ÿ8ÿ6ÿ7ÿ6ÿ6ÿ7ÿ5ÿ0ÿKÿuÿ}ÿtÿkÿiÿmÿqÿvÿzÿuÿaÿ[ÿ_ÿZÿ_ÿgÿKÿ(ÿ(ÿ+ÿ.ÿ-ÿ1ÿ0ÿ3ÿ8ÿ:ÿ7ÿ<ÿFÿEÿ>ÿ;ÿ>ÿ>ÿGÿMÿVÿXÿTÿSÿSÿSÿPÿQÿSÿQÿUÿZÿ^ÿ^ÿ]ÿZÿZÿYÿYÿYÿXÿ^ÿdÿ`ÿOÿCÿBÿ>ÿ?ÿFÿRÿUÿUÿUÿVÿXÿaÿ`ÿ[ÿiÿÿÿ|ÿwÿuÿsÿuÿsÿjÿ`ÿbÿkÿuÿxÿXÿFÿKÿXÿoÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿzÿÿÿÿÿÿÿxÿsÿiÿdÿ_ÿbÿfÿfÿfÿbÿ]ÿVÿQÿJÿAÿ8ÿ4ÿ6ÿMÿlÿÿÿÿÿÿÿÿÿÿÿÿ|ÿrÿdÿbÿ`ÿbÿcÿbÿbÿ`ÿaÿbÿdÿgÿlÿpÿtÿwÿ{ÿ|ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿÿ}ÿ}ÿ}ÿ|ÿxÿsÿnÿjÿfÿfÿ_ÿhÿmÿpÿkÿoÿÿ¯ÿ´ÿ¸ÿ»ÿ¾ÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ½ÿ¾ÿ½ÿ¼ÿ»ÿ»ÿ»ÿºÿµÿ¬ÿ¦ÿÿÿzÿzÿ}ÿÿÿ~ÿzÿxÿvÿtÿrÿoÿmÿkÿhÿhÿjÿjÿlÿjÿoÿyÿÿ}ÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿ~ÿ~ÿÿÿÿzÿwÿuÿpÿnÿkÿjÿiÿiÿjÿiÿiÿiÿhÿfÿhÿhÿhÿhÿjÿpÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[n\\n[YnW~VoS}QpN}LqJ}HrF}Ds@}>v=}<w:~9y7~6y5~5y4~2y2~2y3~3y22y22y1~1x0~2x2~2x2~2y22y11y1~1y0~0y0~0y0~0y1~1z0~0z0~0{0~0{/~/{0~/{.~.|.~.|-~-|-~,|,~,{+}*{+}+{*}*{)})|(}(|)})|)})|(}(|)})|(~(|(~)|(})|)})|*~*{*~*{*~+{+~+{+},|,},|-~-|-~-|-~-}-~-}-}-}/}1}.}.|0}0|1}1}2}2}3~3}3~3}6~6}7~7{9}9|<}>|>~@}@~A~A~AA~AA~@~?~?~=}>}><|;~;}:~:}:~:|9~9{9~8z6~6z5~5|5~2|2~1{1~1{0}0|0}0|-}-|-}-|.}.|.}.|.}-|-}-|-}-|-|-|-|.}/~8}Wc~\XYcd{@!z%
)}+1;FIB@~@>|=@zEU}jsrxmktihuiexD!y""{#
'.
?^ied}bL{3*|+)}%#}!#""#&(
+~/
7}96|66|67{9={@B{CFzHGzB?z;8|76~6
5}43~I~y~rm|il~qsyud[^]a~aD}$)|++{-.z04z7:z=9{>J|F<}9=~ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDL}RUzU{VyV}_z`a|r
~|xwvtvtd]`gl}ss}VO{^}s|x{tzvyxyw}
t}qsottvyoe`_}zfxyl{}i|zhwrikfm\VsKF{K~[{qvomnm
|kxkgaagdbfacecefhigkneq
ucy
}b}c
bddfdb
a~zgvvnwvsv
uvs
m{h
b\_d}actgmn¡p«³x¸»}½½½¾~½¶~¾¿}¾½~½}½~½}¼´|ª
styyyyz
{wzxzurpk
gffijhdemn}w
{uw
wm|kigeefjlq{}{wttrnplllklkjljiliglhhlhikikjq|imu|
|{}ytrssuy~|
yy
|¡~¡~xtty}^ÿ^ÿ\ÿ\ÿ[ÿYÿWÿVÿTÿRÿOÿLÿJÿHÿEÿEÿCÿAÿ=ÿ<ÿ:ÿ9ÿ7ÿ6ÿ6ÿ5ÿ4ÿ2ÿ2ÿ2ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ>ÿ>ÿ>ÿ@ÿ?ÿ?ÿ?ÿ?ÿ?ÿ>ÿ>ÿ>ÿ<ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ?ÿ_ÿcÿXÿVÿYÿbÿaÿ.ÿ#ÿ'ÿ'ÿ+ÿ.ÿ7ÿCÿGÿFÿBÿBÿ>ÿ;ÿ<ÿDÿXÿvÿ}ÿsÿjÿgÿbÿnÿtÿ]ÿ/ÿ ÿ!ÿ$ÿ'ÿ1ÿHÿhÿmÿmÿlÿ]ÿ7ÿ*ÿ-ÿ)ÿ'ÿ%ÿ#ÿ"ÿ!ÿ#ÿ%ÿ#ÿ$ÿ&ÿ*ÿ-ÿ4ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ9ÿ=ÿ@ÿDÿFÿHÿHÿGÿCÿ@ÿ<ÿ9ÿ8ÿ5ÿ4ÿ4ÿ1ÿ:ÿsÿÿuÿoÿnÿqÿsÿtÿyÿyÿbÿ^ÿ`ÿ]ÿaÿgÿIÿ%ÿ%ÿ*ÿ/ÿ9ÿ:ÿ7ÿ8ÿ>ÿ>ÿ?ÿAÿ>ÿ=ÿFÿDÿ?ÿ=ÿ@ÿFÿMÿNÿLÿMÿLÿKÿNÿQÿMÿLÿNÿOÿOÿQÿRÿSÿVÿWÿWÿ[ÿ\ÿVÿTÿUÿWÿUÿNÿKÿKÿGÿEÿEÿHÿNÿUÿUÿVÿVÿ\ÿdÿjÿ}ÿ|ÿvÿuÿsÿuÿuÿuÿuÿaÿ]ÿbÿbÿeÿkÿsÿoÿ_ÿiÿ{ÿÿÿÿÿÿÿÿÿÿÿÿxÿkÿhÿmÿtÿrÿxÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿtÿlÿfÿ\ÿ\ÿ_ÿmÿ{ÿÿÿÿÿÿÿÿ
ÿ
ÿÿ}ÿtÿfÿ]ÿ^ÿ_ÿbÿdÿcÿfÿgÿkÿlÿqÿuÿwÿ{ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿwÿnÿjÿjÿkÿlÿjÿdÿ[ÿWÿTÿQÿRÿUÿSÿWÿ`ÿÿÿ¡ÿªÿ¶ÿ³ÿµÿ»ÿ¸ÿ¸ÿ¼ÿ¾ÿ¼ÿ½ÿ½ÿ¿ÿ½ÿ½ÿ½ÿ½ÿ¼ÿ¹ÿ¦ÿ}ÿiÿmÿqÿtÿuÿvÿvÿwÿvÿuÿrÿoÿnÿkÿiÿdÿiÿpÿoÿpÿkÿfÿlÿtÿsÿjÿeÿkÿpÿuÿvÿÿÿÿÿÿÿÿÿ
ÿ
ÿ
ÿ
ÿÿÿÿÿÿÿ}ÿwÿuÿrÿqÿlÿlÿkÿkÿjÿiÿiÿiÿgÿhÿiÿiÿiÿiÿkÿqÿ{ÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ£ÿ¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]]n\\o[YnX~WoT~RpP~NqL~IrG~DsB}?w<};x:}9z8~7z6~4y3~3y2~2y1~1y11y00y1~1y1~1y0~0z0~0z00z00z/~/y/|/y.}.y.}.y/}/z.}.z.}.{.}.{.~.|.~.|-~-|-~-|,|,{,|,z-}-{+|+{*}*{)}({(}(|)}(|&}'|(}(|&}&|'}'|&~&{'~({(}({(}({)}){)}){(}){*}*{*|*{+}+|+~+|,~,|,},|-}-|,}.|-}-|0}0}0}0}1}1}2}2}2}2{2}2{3~4{5~6{7~7|8~;|<}=|=}=|=~=|<~<}<~<}<~;}:~:{:~:{99}88}7~7|6~8|8~8|6~5|5~5|3~3|2~1|0~0|0|0|0|0|0}/|/}/|.|.{-|-{,|,|.|-|-}-|.}.|.}-~-~-~1Fd^UV]~h~Vx%&y'&~(+3?IGA~??{<:|C^q~jjwbawps{qMy#y#'}3
Nm
pqnzP-y-,|)$|""}!!~"#""$)~,0}4
5}6
6}7
8|:
=|@
D{HJ{HGzA>y=8z75|11|0^xt}qt~uz|~gY_]
b~g
U}&,|)-{4=zGEzHL{OIzGD~EIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELS|VWzU{Yyds}{w~rs~vv~vwr_[__agk~qt~tz}q|pztyv{r
~pyetchyolvm{n
faaded`b|wfollqxqolmnn{jo`kZ_i`^ibgdjncqsdv|d}ddccddfh~whz}e|{dsfmZZwXT|PMW~PGzQ^rjln£n¨tµ¹z»²~
¬~¾¾}¿¾}¾½~½}¼~½~·¢rYcilppqttttrqpnkgjonoppmqvm}\[v`ermzpkh
e
f
h
i
is~xyursolokjnlhmhimiimhhmhhkjoiu}hnw~z~z}vssssvz{
yy}£{¢zytty~^ÿ^ÿ]ÿ]ÿ[ÿYÿYÿWÿVÿSÿQÿOÿLÿJÿHÿDÿBÿ?ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ6ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ.ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ5ÿ6ÿ7ÿ7ÿ9ÿ:ÿ;ÿ<ÿ<ÿ=ÿ=ÿ=ÿ<ÿ<ÿ;ÿ;ÿ9ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ4ÿ4ÿ4ÿ6ÿ5ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ.ÿ3ÿPÿgÿYÿRÿXÿbÿeÿFÿ#ÿ%ÿ$ÿ#ÿ$ÿ(ÿ.ÿ;ÿEÿIÿFÿ@ÿ?ÿ>ÿ>ÿHÿcÿpÿnÿcÿeÿkÿhÿkÿfÿ8ÿ!ÿ$ÿ(ÿ1ÿPÿoÿpÿoÿkÿGÿ.ÿ,ÿ)ÿ&ÿ#ÿ"ÿ ÿ!ÿ!ÿ"ÿ#ÿ"ÿ!ÿ#ÿ(ÿ+ÿ-ÿ1ÿ4ÿ6ÿ7ÿ9ÿ:ÿ<ÿ>ÿ?ÿAÿEÿIÿJÿIÿAÿ=ÿ>ÿ>ÿ:ÿ5ÿ0ÿ/ÿ[ÿyÿxÿsÿqÿtÿxÿ|ÿ{ÿ~ÿpÿVÿ\ÿ^ÿ`ÿhÿ^ÿ,ÿ)ÿ,ÿ,ÿ0ÿ:ÿCÿDÿEÿOÿQÿSÿXÿSÿMÿQÿOÿCÿIÿNÿMÿGÿFÿGÿEÿDÿCÿCÿDÿIÿIÿHÿGÿMÿNÿTÿUÿTÿUÿVÿXÿZÿ^ÿ_ÿYÿTÿSÿQÿOÿOÿQÿOÿLÿHÿDÿHÿQÿVÿWÿXÿ[ÿfÿuÿzÿtÿrÿsÿsÿtÿuÿwÿrÿ_ÿ[ÿ\ÿ`ÿ`ÿaÿgÿoÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿdÿbÿdÿlÿjÿbÿqÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿÿ
ÿÿÿÿÿÿÿ
ÿÿÿÿÿ{ÿmÿ^ÿ[ÿ^ÿ^ÿ^ÿeÿiÿnÿrÿwÿzÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ{ÿyÿtÿqÿuÿyÿ|ÿvÿvÿlÿdÿ[ÿTÿ^ÿaÿFÿDÿlÿÿÿÿ§ÿ²ÿ·ÿ¸ÿ¸ÿ¹ÿ¾ÿ¦ÿwÿÿÿÿ»ÿ¾ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ½ÿ²ÿÿ]ÿRÿWÿ^ÿcÿgÿkÿlÿoÿqÿtÿsÿsÿrÿqÿqÿrÿoÿlÿoÿrÿoÿmÿkÿkÿlÿoÿoÿoÿaÿOÿRÿVÿ`ÿtÿÿÿÿÿ
ÿ
ÿÿ
ÿ
ÿ
ÿÿÿÿÿÿÿÿÿ}ÿxÿtÿpÿmÿlÿkÿiÿhÿhÿhÿiÿiÿiÿhÿhÿhÿkÿpÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^^l]]n[ZnY~WoT~SoQ~OqN~JrH~EsC}@v<};w:}9w7~6x6~4y4~3y1~0y1~1y00y00y/~/z.~.z.~.z.~0z/~/z/~/z.~.y.|.y.}.y.}.y.}.z-}-z-}-|,},|-~-~+~+~,~-~,~,~+~+}+~*{*}*{)|){)}){(}'{'})|'}'|(}'|'}'|&}&|&}&|&~&{'~'{'}'{'}'{(}(z(}(z(})z)})z({*{*}*|,},|+}+|,},|,},|-}-|-}.|1}1{1}1{2}2{1}1{3}3{3}3{3~3{5~5{5~6z7~9z8}:|:}:|:~:|8~:}:~:|8~8|8~7{7~7{6}6{6}6{6~6|5~4|4~4|4~3|3~3|3~2|2~2|2~2|1~1|1~1|0}0}0}/}/|/|/|.|/|/}.|-}.}.|-}-|.}-~-~-~-~/6[bRQWa|b9x"#z#
"~"',6BHGCBB?~PgbXZ~]g~kn{Z(z"%|-
Ffm~j
f|@+z,'|&#}!~
!~$%##"$~(+}-/}3
3}6
:|<
=|=
>{AF{IKzC?y?Az<2|5`}wrlstxz||u[Z]]d
g~:+}+,|-.|6=|AG{OT~XfbWICDMLIGEB@ABBCGI
GH
JN
SVWWYZZ]a]YUSRRSNMHDEM}UXzZ{[yfs|vq}sw|xv|uvq`[^]`dg~s{s~n~o~q~roptbtccxii{^ju{lbaddeeffegiklk
kwlhZpTXs^bshkoosi{gfddddee
~e}
zgzzmugu\anrm_ngYe^c_
qws~IZu¬s±´u¹
»{»½{¾¼}»µ}¬
¸y°³{¼¾~¾¾~¿¾º§uHEORX~\`dkoppprrqsv
vrrxtlgginrriRAM}Woytn
kjjggny~zwzrnsllpihohhnhhmhhmhikmqizipx{~y
~x}ussrsw|~zzz~ }ytty~^ÿ]ÿ]ÿ]ÿ[ÿZÿYÿWÿUÿTÿQÿNÿLÿIÿGÿEÿDÿBÿ=ÿ<ÿ:ÿ9ÿ7ÿ6ÿ6ÿ4ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ'ÿ(ÿ'ÿ'ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ-ÿ,ÿ,ÿ+ÿ-ÿ-ÿ=ÿbÿ\ÿTÿUÿZÿ_ÿWÿ-ÿÿ!ÿ"ÿ!ÿ#ÿ"ÿ(ÿ2ÿBÿJÿJÿHÿCÿBÿAÿXÿ_ÿOÿLÿOÿWÿ_ÿhÿkÿCÿÿ#ÿ)ÿ9ÿYÿhÿiÿbÿAÿ,ÿ*ÿ&ÿ$ÿ"ÿ ÿÿÿ!ÿ$ÿ$ÿ#ÿ!ÿ!ÿ#ÿ$ÿ'ÿ+ÿ-ÿ0ÿ3ÿ6ÿ9ÿ<ÿ=ÿ=ÿ>ÿAÿDÿHÿIÿGÿCÿAÿ?ÿ8ÿLÿpÿvÿmÿmÿlÿpÿvÿvÿyÿzÿsÿ_ÿYÿ]ÿ\ÿ^ÿeÿIÿ)ÿ-ÿ,ÿ1ÿ1ÿ0ÿ5ÿ9ÿ@ÿHÿOÿWÿaÿkÿhÿWÿJÿGÿJÿNÿNÿJÿEÿAÿ@ÿ>ÿ>ÿ?ÿ?ÿBÿDÿFÿHÿIÿLÿMÿOÿSÿVÿXÿYÿXÿZÿ[ÿ]ÿ^ÿ\ÿXÿXÿWÿWÿTÿOÿNÿJÿEÿDÿGÿQÿWÿYÿZÿeÿrÿvÿsÿvÿzÿyÿxÿuÿwÿrÿ_ÿXÿYÿ\ÿ_ÿdÿkÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿqÿbÿaÿcÿfÿgÿ]ÿhÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿvÿhÿ^ÿWÿUÿaÿjÿgÿhÿoÿvÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ}ÿyÿtÿoÿnÿjÿ]ÿSÿIÿeÿnÿnÿpÿuÿhÿUÿ\ÿjÿLÿoÿ¦ÿ´ÿ¸ÿ»ÿ½ÿ¿ÿ¿ÿ¾ÿ»ÿ´ÿ³ÿ·ÿºÿ»ÿ½ÿ¾ÿ¾ÿ¿ÿ¿ÿ½ÿ»ÿ°ÿÿdÿFÿKÿSÿQÿOÿQÿUÿZÿ^ÿbÿgÿjÿmÿpÿrÿqÿrÿrÿuÿyÿyÿrÿsÿwÿqÿpÿpÿoÿqÿwÿvÿsÿhÿKÿEÿYÿpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ|ÿyÿuÿpÿnÿlÿkÿhÿhÿhÿhÿhÿhÿhÿhÿiÿmÿrÿ{ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^^n^^n[ZnZXnVSnQOpM~JqJ}GrE}Bv@}>x;}9y6~6x6~4z3~3z1~1y1~1y/~/z.~-z-~-{-~-{.~.{.~.{-~-{.~.{,~,{-|-{.}.|-}-|-},},},}-},},},}+~+~+~+~+~++~+}*~*|*~)|*~*|)~)|)~)|(~'|'~&|&~&|&}&{%}%{%}%{%}%{%~%{$~%{'~&{'~'{(}(z'}&z'~'{)~){(}(z*}*|*}*}*}*}+},|,},{.}.|.}/|0}0|1}1|2}1|1}1{2|2{3|3{3|4z4~4z6~6z5~5{7~7{7~7{8~7}7~7}7}7{7}7{6~6}6~6}6~6{7~7{7}7{6}6{6}6|4}3|3}3|3}3|33|22|2}2}2}2}1~1~1~0~0}00}0/}/}.}.}-|-}-|-},},},},~,~,+-GdWRU[c{V,x!"z!~ "'0<M
RO
KG
Ka]QJKMYnc~.{%
1}Rd`V?,}&
"}""~"!!#%%#
#~""}$
(})
,|.2|44|9:|<>|?A{BAzB>y;H{_
t~qljiputvvq`Y~\]^f~_)~+/}03}25}88|8?~HXil_UMMPS
USME==<<<?CFHJLMMQSVYYYWX\[ZYXXTPNIECDMS}W[{en|np|ru}v
s}uvng\|X]_h}s~u~nkl~o~o
o
roavaaybeya~lu{leehijjecccegfde~vjlbn^_vgousspy}jfefeebb}{cw
ubt
sek
qjo
_jYpcs
mWklIsjVZXu[}Wusª¸wº¼}¾¾~¼¸±ª~µº|¼¾|¾½~¾¿»´rQKT TQMzNNzNSzV\}`diloop
ps
y~~vwwssmiprpoaBKp}}~zvsnlhj
r~{{xu|sovljrhhohgnggmhjjmsh~hq
z{~x}tsttsuzzwxz~}
{wttx|[ÿ[ÿ[ÿ[ÿ[ÿZÿYÿWÿVÿSÿQÿOÿMÿJÿHÿEÿDÿBÿ?ÿ=ÿ;ÿ9ÿ7ÿ7ÿ6ÿ4ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ%ÿ'ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ)ÿ)ÿ(ÿ(ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ7ÿ7ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ5ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ)ÿ*ÿ*ÿ*ÿ0ÿQÿfÿUÿPÿVÿ\ÿbÿ[ÿ-ÿÿÿ!ÿ ÿ ÿ ÿ&ÿ+ÿ9ÿHÿQÿTÿMÿIÿQÿeÿ\ÿNÿNÿPÿSÿeÿtÿKÿ!ÿ$ÿ/ÿNÿaÿYÿUÿKÿ;ÿ'ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ#ÿ%ÿ%ÿ$ÿ#ÿ#ÿ"ÿ$ÿ'ÿ)ÿ*ÿ,ÿ/ÿ1ÿ4ÿ6ÿ6ÿ8ÿ;ÿ;ÿ;ÿ<ÿ;ÿ7ÿIÿcÿrÿsÿmÿfÿgÿjÿoÿpÿuÿ{ÿwÿmÿaÿZÿYÿ\ÿ^ÿ`ÿdÿ@ÿ-ÿ,ÿ/ÿ0ÿ2ÿ5ÿ8ÿ9ÿ7ÿ3ÿ6ÿAÿRÿ\ÿUÿKÿHÿPÿRÿUÿUÿWÿZÿUÿLÿ@ÿ=ÿ;ÿ:ÿ:ÿ=ÿ?ÿDÿGÿLÿLÿMÿMÿOÿRÿVÿYÿYÿYÿXÿYÿZÿZÿXÿUÿUÿUÿSÿOÿLÿIÿDÿAÿBÿHÿOÿVÿXÿbÿjÿjÿjÿlÿpÿsÿqÿsÿrÿkÿeÿ_ÿYÿZÿ`ÿoÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoÿaÿaÿaÿeÿkÿiÿqÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿrÿnÿjÿhÿnÿyÿ~ÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿzÿwÿuÿvÿrÿrÿrÿoÿnÿpÿqÿrÿnÿoÿqÿmÿkÿhÿfÿYÿSÿ_ÿcÿ|ÿ©ÿ·ÿºÿ½ÿ½ÿ»ÿºÿ¶ÿ¯ÿ¥ÿ°ÿ¸ÿ»ÿ½ÿ¾ÿ¿ÿ»ÿ¶ÿ©ÿ~ÿUÿMÿRÿRÿQÿOÿLÿMÿNÿOÿOÿOÿQÿVÿ[ÿ_ÿcÿgÿkÿpÿqÿqÿrÿtÿ|ÿ|ÿwÿqÿsÿxÿqÿjÿ`ÿmÿvÿxÿwÿnÿ\ÿaÿyÿÿÿÿ}ÿ}ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿÿÿ}ÿ{ÿwÿtÿqÿmÿlÿhÿgÿgÿgÿgÿhÿhÿiÿmÿuÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZnZZnZYnYWnTSnQOpM~JqH}ErD}Bt?}=v;}:w9~7y6~4y3~2y2~0y0~0y/~/z.~-z-~-{-~-{-~-{-~-{-~-{,~,{,~,{,|,{,},|,},|,},},},}+}*}*}(}+~+~+~+~)~)(~(}(~(|(~'|'~'|(~(|(~(|'~&|&~&|%~%|$}$|$}$|$}$}$}$}$~$|#~$|&~&{&~&{&}&{&}&{'~'{(~({)})z)})|*}*{+}+{,},z,},{.}.{/}0{0}0{1}1{2}2{2}2z2|3y3|3y2|3z4~4z4~4z6~6{6~6{6~6{7~7z6~6z6}6{6}7{7}7{7}6{6}6|6}6|7~7|6~6|5~5|4~3|3}3{3}3{3|3{3|3{2}2|1}1|1~0|/~/|/}/}/}/}/}/}.}-}-}-}-}-},},}+}+~,},+}()5_bSPU\f|a2x x
}
"(*7CRTOJ[i[PKQZpk5| ,~Jc]TMG*"}""~"!"$%%&%#$~$
'},
+|**|./|/1|26{98{41{Ow|pmgdfimp~pwpmc\~YZ}]^`Q~1/~/2}25}8<};:|8<~=K~E=;EQWVXZ\
ZQF>;;;;=BFLNN
MP
PUZZYY[\YUQOOLKJHD
CACKRW|`g|gh|jm}om}pmdcb{\~Zzd~sxrlklmlnqsbv`byh}ryu}}s~mhgikmponjihgfe
d{fyxhsrjxkkgggfec||azy_ws]q
p]o
m[pp]kj]knZpqTmiRejXc
^ec_vu¥µwº¼|¾¼~º¹~µ
£y¬y¸»~¼¹±eJL SR¢QzQ¢QyPPyOOyOMyMQzTZ{^a~gipqr
tssusv{|tq}py}}y|}|{}~yvqm
i
os}w{w}r~pnxjithfqggngkkoxglv{wvtsttsv{}{x{z}wustx|\ÿ\ÿ\ÿ\ÿZÿYÿXÿVÿUÿTÿQÿOÿNÿKÿIÿFÿCÿ@ÿ?ÿ=ÿ;ÿ:ÿ9ÿ7ÿ6ÿ4ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ)ÿ)ÿ'ÿ)ÿ:ÿdÿ`ÿUÿSÿVÿ_ÿiÿdÿ4ÿÿ!ÿ"ÿ#ÿ#ÿ$ÿ%ÿ+ÿ3ÿ>ÿMÿSÿPÿSÿgÿnÿZÿNÿNÿSÿaÿtÿXÿ#ÿ(ÿ>ÿaÿfÿYÿMÿBÿ*ÿ"ÿ"ÿ"ÿ"ÿ!ÿ"ÿ$ÿ%ÿ&ÿ'ÿ%ÿ&ÿ%ÿ%ÿ(ÿ0ÿ/ÿ*ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ.ÿ,ÿNÿrÿqÿkÿhÿgÿkÿoÿoÿoÿpÿvÿiÿXÿeÿaÿ\ÿZÿZÿYÿUÿXÿ4ÿ.ÿ.ÿ3ÿ6ÿ6ÿ6ÿ7ÿ9ÿ;ÿ7ÿ8ÿ@ÿ?ÿBÿ;ÿ1ÿ9ÿEÿOÿRÿPÿRÿUÿSÿQÿJÿDÿ=ÿ9ÿ9ÿ9ÿ;ÿ=ÿBÿFÿLÿNÿPÿRÿTÿTÿUÿWÿYÿZÿ[ÿ\ÿ\ÿYÿRÿLÿKÿIÿIÿGÿHÿEÿDÿCÿAÿAÿFÿKÿTÿ]ÿdÿdÿeÿhÿkÿlÿkÿmÿjÿ_ÿ`ÿhÿdÿ`ÿhÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿbÿ_ÿdÿkÿvÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ~ÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿxÿuÿqÿmÿmÿrÿsÿpÿpÿqÿoÿnÿmÿlÿkÿlÿoÿnÿnÿnÿcÿYÿ`ÿeÿdÿ`ÿZÿUÿ`ÿÿ´ÿ¹ÿ»ÿºÿ¸ÿ¸ÿ¸ÿ´ÿ®ÿ°ÿ©ÿ²ÿºÿ¸ÿ¬ÿÿQÿLÿMÿRÿRÿRÿPÿOÿQÿPÿPÿNÿMÿMÿNÿLÿNÿQÿUÿYÿ^ÿcÿhÿlÿnÿrÿqÿpÿrÿvÿxÿÿÿÿ
ÿzÿÿÿÿÿÿÿyÿÿ
ÿÿzÿxÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿ}ÿ{ÿzÿwÿsÿoÿmÿjÿhÿgÿgÿgÿhÿkÿpÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿÿÿÿÿÿYYoYYoYXnWUnSRnPNpLKqIFsF~Dv?~=v=~:w8~7x6~4y3~3y2~0y0~0y/}.z-}-z-~,z,~,z,~,{,~,{-~-|,~,|-}-|+}+|,},|+}+|*}*|*}*}*}*~(}(~*~*~)~)~)~)~)~)~)~)~(~(~(~(|(~({'~&{$~&{%~%{%~%{%}%|%}%|$}$}$}$}$}$|"}#|%}%|%}%|&~&{'~'|&}&|(}({)~){)~){*}*|*}+|,},{,}-{-}/{/}0{0~0{0~0{1~2{3~3{2~3z4~4z4~4z5~6z6~6z6~6z6~6z5~5z5~7z6~6z6}6z6}6z7}6{6}6{6}6{7}7{6}6|6}6|5}5|5}4|3}3|3}3z3}3{3}3{2}2|2}2|1}1|0}0|/}/~.}.}/}.|.}.|-}-}-}-}-}-}+}+~,~++}('''BkbTRW_h|c8x
!z#$}$$~()*6FQ
TXliY
PTXhsA
)~3FOJ</%!! !~!"%&'
&$
(~&(~+/},(}'&{(&z$$y!
*zLr~nmhhlmnmpr[Kh
f~`
\ZY~Q
Y~L*~.1}69|97}88}:7}57}86}328DKM
KKMM
HEB>:88:>AELNQTW
XVVXZ\]\XPMJLJFEGD
EEDAFMW`}a
b|eh}i
i}k
f~Z]~gwh}jrp}olnppolm
mwdt_~fyp|z{|snkhhlp
pu}wupolhfcccbcghiji{vhogha\i_cehk]lpZqrXooYnlYlnXmlYU@_MXa_[eH@vYy°w¶¹{¶¯{µ·{«¯x±z¶¶ª
NOQ OzQ¢QwS RvRSwQPyNNyLOyONyNSyUX|\b~fimo}qt{|
}~
v}vy|zzz{z{z~wpl
prt~yzu~q~olzkjtihojlls|k
mu~}wutssssx}zx||}xrqs}|sw
|YÿYÿYÿYÿYÿXÿWÿUÿSÿRÿPÿNÿLÿKÿIÿFÿFÿDÿAÿ?ÿ<ÿ<ÿ8ÿ7ÿ6ÿ4ÿ3ÿ3ÿ2ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ.ÿ.ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ(ÿ%ÿ#ÿ)ÿHÿmÿaÿYÿWÿWÿ_ÿgÿgÿCÿÿ!ÿ$ÿ%ÿ&ÿ%ÿ%ÿ$ÿ'ÿ/ÿ;ÿJÿPÿ\ÿtÿeÿWÿUÿWÿ_ÿrÿjÿ2ÿ,ÿ,ÿ(ÿ)ÿ%ÿ#ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ#ÿ$ÿ'ÿ)ÿ.ÿ/ÿ,ÿ,ÿ(ÿ&ÿ+ÿ)ÿ(ÿ'ÿ%ÿ$ÿ"ÿ"ÿ$ÿ%ÿWÿtÿmÿoÿlÿlÿnÿoÿkÿmÿrÿrÿFÿ>ÿhÿlÿeÿ\ÿXÿZÿPÿXÿWÿ0ÿ+ÿ1ÿ5ÿ9ÿ:ÿ<ÿ<ÿ;ÿ>ÿ;ÿ5ÿ5ÿ4ÿ6ÿ3ÿ.ÿ3ÿ9ÿDÿIÿHÿHÿFÿFÿEÿDÿBÿAÿ>ÿ:ÿ8ÿ8ÿ:ÿ<ÿ?ÿDÿGÿLÿOÿSÿXÿYÿVÿVÿXÿZÿ[ÿ\ÿXÿVÿPÿJÿKÿKÿHÿGÿFÿFÿFÿGÿGÿGÿFÿCÿIÿPÿXÿ]ÿ`ÿdÿgÿiÿjÿjÿcÿXÿ[ÿcÿiÿnÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿyÿdÿ_ÿfÿtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿuÿhÿXÿLÿIÿOÿRÿTÿ\ÿ`ÿbÿcÿfÿmÿpÿoÿoÿnÿnÿjÿeÿNÿ=ÿRÿ_ÿ^ÿVÿ>ÿ3ÿNÿpÿ¥ÿ´ÿ¶ÿ¯ÿÿÿ°ÿÿ¦ÿ²ÿ°ÿ±ÿ ÿÿoÿMÿOÿTÿ]ÿWÿSÿTÿXÿWÿUÿRÿQÿQÿPÿOÿOÿOÿOÿOÿPÿQÿUÿXÿ[ÿ`ÿhÿqÿ}ÿÿÿÿÿÿ¡ÿ¢ÿÿÿÿÿÿÿÿ
ÿÿÿÿÿwÿuÿyÿzÿzÿzÿzÿ{ÿ|ÿ~ÿÿÿÿ
ÿ
ÿÿÿÿÿÿ~ÿ~ÿ}ÿxÿvÿtÿpÿnÿkÿiÿhÿiÿlÿtÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿ}ÿÿÿÿÿÿWWoXXoWWnUTnTSnPNpMKqHEsE~Av?~=v<~;w9~8x5~3y2~2y2~0y/~/y-},z-}-z-~,z+~*z+~+{+~+{+~+{+~+{*}*{*}*{)})|)})|)}*})}))})~)})~(~(~(~(~'~'~'~(~(~(~'~'~'~&|&~&{%~%{%~%{$~${$~${$}$|%}%|$}$|$}$|$}$|%}%|%}%|%}%|&~&{'~'|(}(|(}({(~({)~){)}*{+}+{,},z,}-z-}/z/}/z/}/z0}0z1}2z2}2z4~4z4~5z5~5z6~6z6~6z6~6z6~6z6~6z6~5z5~5z5~5z5~5z5}6z5}6z6}5{5}6{6}6{6}6{6}6{4}3{3}3{3}3y3}3z2}2z2}1{/}1{1}1|/}/|0}0~.}.~.}.}.}.},},},},}+}+},},~+},*},,}(%%
*KmbXVW]d}iKz##y$${$$}"! &2?FkwcX
WUev
P|#'{'%}#"~! !!! ~!##%.
9@
<~:
/~(+}+)}&%{#"z#!|=spklonmposk:
+`
k}id}\
[|UK{[A|)/|49|::{=@}@=}86}54|2-|149@DDA=?ABAB?<:98;=BEJLQVVT
TV
XYZVTOJKLKHH
HH
JKJHDDIMV^ce~ef~he}WX}d{m~tsnopqpmiii}ho_~jwv|x}rmi
hin
qtv|utsqokebaaacdglopzmq[Nr\as\]r^^jZZeXYd`edjmbmm`e
WaG=gGJiKNlB?vM}^w¯²zRz¤w}u³·{ª
v\MN|R¢XvQTuYav]TwUVvV TvQ PvRRvPNuOPqR]qn~sz¢£¢
£
~~x~vx{xz~z{{z|~~w
nrvt~t}{vwu|tro{lluknmyjpx
}~ussqssvz|
y{|}vtt}srxptyWÿWÿWÿWÿUÿUÿTÿTÿRÿQÿPÿNÿLÿJÿHÿEÿDÿAÿ?ÿ=ÿ<ÿ;ÿ9ÿ8ÿ6ÿ5ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ-ÿ,ÿ,ÿ-ÿ-ÿ+ÿ*ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ/ÿ1ÿ1ÿ0ÿ0ÿ1ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ)ÿ&ÿ#ÿ(ÿ/ÿMÿmÿ`ÿVÿUÿXÿ^ÿfÿlÿ[ÿ&ÿ"ÿ$ÿ%ÿ#ÿ"ÿ ÿÿÿ!ÿ)ÿ3ÿHÿlÿqÿbÿWÿVÿZÿhÿpÿ2ÿ&ÿ#ÿ$ÿ#ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ$ÿ"ÿ(ÿ.ÿ3ÿ7ÿ8ÿ2ÿ/ÿ+ÿ+ÿ-ÿ*ÿ'ÿ%ÿ#ÿ"ÿ#ÿ-ÿhÿnÿkÿjÿjÿmÿpÿlÿlÿtÿaÿ.ÿ5ÿ_ÿkÿiÿgÿcÿ`ÿbÿJÿ[ÿQÿ+ÿ+ÿ<ÿ>ÿ;ÿ:ÿ=ÿ?ÿDÿ>ÿ7ÿ5ÿ5ÿ9ÿ8ÿ3ÿ0ÿ2ÿ5ÿ8ÿ<ÿ@ÿ=ÿ:ÿ7ÿ:ÿ>ÿ>ÿ>ÿ?ÿ>ÿ=ÿ:ÿ9ÿ7ÿ:ÿ<ÿ?ÿDÿIÿMÿOÿSÿSÿQÿQÿSÿUÿVÿWÿTÿRÿNÿJÿKÿKÿKÿKÿIÿGÿJÿMÿMÿLÿIÿFÿAÿCÿIÿOÿWÿ\ÿaÿcÿfÿfÿcÿYÿZÿeÿpÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿhÿ_ÿlÿyÿ
ÿÿÿÿÿÿÿÿÿ~ÿ|ÿxÿyÿ~ÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿ~ÿwÿoÿdÿ[ÿmÿmÿ[ÿRÿWÿVÿYÿ`ÿ\ÿXÿXÿYÿ^ÿdÿfÿfÿcÿ\ÿUÿFÿ5ÿ1ÿGÿQÿOÿRÿZÿ]ÿzÿ¢ÿªÿÿ_ÿÿÿsÿÿ²ÿµÿ¨ÿvÿVÿIÿIÿLÿOÿOÿOÿSÿVÿVÿRÿOÿTÿUÿTÿUÿTÿRÿQÿQÿRÿQÿNÿMÿaÿ~ÿÿ¥ÿ«ÿ°ÿ¯ÿªÿ§ÿ¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿwÿxÿxÿzÿzÿ{ÿ{ÿ{ÿ{ÿ|ÿ}ÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ|ÿzÿyÿwÿtÿrÿoÿmÿnÿqÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿoÿxÿÿÿÿÿÿVVpVVpTToTRoQOnNLpJHqGEtC}AvA}>v<~:w8~7x5~4x3~3y2~0x/~/x-},y+}+y+}+y*}*y*~*{*~*{*~*{*~*{)~)|)~){)~)})~)}(~((~((~(~(~(~'''''~''~''~'~'~'}&~&|&~%|%%|$$}$~#|#~#{"~"{$~#{$}$}$}$}$}$|%}%|%}%}&}'}'}'}'}'}(}(z(}(z(})z)})y*~*y+~+y,}-y-}.y-}.x0}0x0}0x2}2x2}2z3}4z3~3z5~5z5~6z6~6z6~6y6~6y6~6z6~6z5~5z5~5z5~5z6~6z6~6z6~6z6}6y6}6y6}6z6}6{6}6z4}4z3}3{2|2{1}1{1}1{1}1z0}0|0}/|0}.|/}/}.}-}.}.~-},~+}+~+}+~+}+})}))}*,}--}+
)}%$'-MobUTW]d~l_z4!x$#y"!} !%*Epn
`YY`r}U#{%"|""}! ~ ~ ~"$~(-/
27
6~1,~-.}/,|)%{$"{"Xvmggllm
uk
O~3
C~ah~j
j~hd{^Lz\[y5.{:@|?@|@B~B>~83~5:@B~<4~37~9976567::<><<::78;>CIOQQQONOQTUS
QLLKKLLK
KMNNNLGDABFOT\acec~\[~i|v~sqrt
spnihg~jnbpy{|x}rkg|cwqgospy|ss~s~tsqomjedbbacehm
qrxssssvrqtigujhtiircWoPQpW[mZVlTUlUOoCCnTSqVYz]~geyt¤wtr
£r¯¯z¦|WJ}H¡IvM¤LtL¢RtU¡QtO PuP RtU WtW UtQRuT OoTfk¦r²¹z¼º¸°©¥
¡
zw{v}{~zz~z~{~{{z~y|z~t}y}~|~vu}}r||vyw|rqpzpuq}nt
yz}tuttttx|||y}|ysrr{rqnwo
ty
UÿUÿTÿTÿSÿSÿSÿPÿPÿNÿLÿKÿIÿGÿEÿDÿBÿAÿ@ÿ=ÿ<ÿ;ÿ7ÿ5ÿ7ÿ4ÿ3ÿ3ÿ1ÿ/ÿ/ÿ/ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ(ÿ)ÿ+ÿ+ÿ+ÿ*ÿ(ÿ&ÿ(ÿ+ÿ.ÿ/ÿPÿnÿdÿXÿTÿWÿZÿ^ÿfÿfÿCÿ"ÿÿ"ÿ ÿÿÿÿ ÿ ÿÿ!ÿGÿwÿmÿ^ÿVÿZÿeÿqÿ:ÿ!ÿ#ÿ#ÿ"ÿ!ÿ ÿ ÿ ÿ ÿ ÿ#ÿ&ÿ-ÿ2ÿ1ÿ1ÿ1ÿ,ÿ+ÿ,ÿ-ÿ1ÿ2ÿ.ÿ*ÿ'ÿ$ÿ#ÿ<ÿsÿjÿjÿgÿiÿkÿpÿtÿ_ÿ<ÿ;ÿZÿeÿgÿjÿfÿgÿgÿaÿMÿVÿ_ÿFÿ2ÿ9ÿEÿ?ÿDÿCÿEÿCÿ<ÿ;ÿ7ÿ5ÿ6ÿ>ÿFÿEÿAÿ9ÿ5ÿ5ÿ5ÿ6ÿ4ÿ3ÿ3ÿ2ÿ5ÿ9ÿ9ÿ;ÿ;ÿ<ÿ<ÿ;ÿ:ÿ9ÿ9ÿ<ÿAÿEÿJÿPÿRÿTÿRÿMÿLÿMÿOÿPÿQÿQÿMÿLÿKÿKÿKÿLÿLÿNÿNÿOÿOÿOÿOÿMÿGÿDÿAÿ@ÿBÿGÿKÿRÿYÿaÿeÿcÿ[ÿ^ÿoÿ|ÿÿÿÿÿÿÿ
ÿ
ÿÿÿ
ÿÿÿÿÿÿÿÿÿ|ÿjÿcÿqÿ~ÿÿÿÿÿÿÿÿ~ÿyÿpÿgÿgÿmÿsÿvÿ{ÿ~ÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ~ÿvÿrÿpÿpÿpÿqÿsÿtÿqÿoÿcÿ]ÿaÿSÿEÿEÿPÿWÿTÿNÿHÿIÿNÿPÿMÿKÿQÿQÿPÿTÿSÿfÿaÿ`ÿÿÿ|ÿÿ¥ÿ¨ÿ©ÿ¬ÿªÿÿmÿLÿHÿHÿMÿNÿMÿMÿNÿPÿPÿSÿTÿTÿSÿRÿSÿTÿRÿQÿQÿPÿUÿnÿÿªÿ·ÿ»ÿ½ÿ¿ÿ¼ÿºÿµÿÿ¨ÿÿÿÿÿÿ ÿ¤ÿ£ÿÿÿÿ¡ÿ ÿÿÿ|ÿuÿ{ÿ{ÿzÿzÿ{ÿ{ÿyÿyÿzÿyÿ}ÿÿÿ{ÿ{ÿ|ÿ}ÿÿ~ÿ~ÿ}ÿ}ÿ}ÿ}ÿ{ÿzÿwÿtÿrÿrÿxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxÿnÿmÿxÿÿÿÿÿ
ÿTTpRRpSQpPOpONnLIpHFqDCtA~?v>~<v;~:w8~6x6~5x3~2y1~0y0~/y-},y,}+y+}+z*})z*~*{*~*{)~){)~){(~({(~(~(~(~(~(~(~((~((~((~('~'~'~'~'~'~&~&~%~%~%~%}%~%}$~$|$~$|$~%}$~#|#~#{#~#z#~#z#}#}$}$}$}$|%}%|$}%}%}&}'~'}'}'}'}(|)})|)})z)}*y*~+y+~,y,}-y.}-y.}/x0}1x0}1x2}2x3}3w4}5w5~5x4~5x5~5y5~5y7~7y6~6y6~6z6~6z5~6z6~6z5~5z6~6z6~6z6~6z5}5z5}5z5}5z5}5{4}4{4}4{3}3{2|2{1}1{1}1{1}0z0}/|.}.|-}-|/}.-},-}-,}++}+~*}*~*}*})}((}**}+-|,
)|'+|,.0/H
kc
VSTX]_f{Q/v
!x
|~!#Uzg][^j
`}%"|# }"
!~ !~! ~#(~*,}*
+}+
)~,,~-4~61|,(|%2}lnffji}jp~]
<~H
g~f
a~a
b}cc{cW|RV|I6{5=|;={C?}?=~97~53~2:BDB<54~32~30~312589;=>=;:<@DJMPSVURPONNMLKKKLLMNN
PQPOOMHF
CA@@CHRZaa`f~xzqstuus|yq{n
he{jjess~uoie~vcl`l_drk
ouswv|u~
tsomkfdcbdegkmoq}|szwttqwqqvpqtqnr\9q48s9<vDyItN{MsK|KsK~MuI~JwFMvRRxPll{juq q§¨r©¨yUFGzH¤JuJ¦ItL¤LtO¢KtK¡PuO¡MuM¡OvP PtQ OnXwlt¸»|½¾~¾¾~¾¼´
¤
¤ ¦¥vn
vrz|{{z~zzzyxy{|~zwwy
}~}|}y}}v}}u||uyx}t|r
zvsu
zy}stuuuv|~|
||{vss}suonnznuz
RÿRÿQÿQÿPÿOÿOÿNÿMÿMÿKÿHÿFÿEÿCÿBÿAÿ?ÿ=ÿ<ÿ;ÿ9ÿ7ÿ6ÿ6ÿ5ÿ3ÿ2ÿ1ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ%ÿ%ÿ&ÿ'ÿ'ÿ&ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ0ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ*ÿ)ÿ)ÿ)ÿ*ÿ+ÿ-ÿ,ÿ)ÿ)ÿ,ÿ,ÿ-ÿ0ÿ1ÿ3ÿEÿgÿjÿ]ÿTÿRÿSÿYÿ^ÿdÿbÿFÿ$ÿÿÿÿÿ!ÿ!ÿ!ÿ"ÿ'ÿfÿxÿbÿ[ÿ]ÿ`ÿnÿJÿÿÿ"ÿ"ÿ!ÿ ÿ ÿ ÿ!ÿ&ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ-ÿ-ÿ.ÿ1ÿ2ÿ0ÿ,ÿ,ÿ/ÿcÿpÿjÿeÿdÿhÿkÿnÿ_ÿMÿ`ÿjÿhÿdÿaÿaÿcÿbÿ[ÿ_ÿ[ÿJÿ?ÿ4ÿ7ÿ8ÿ9ÿ9ÿ;ÿ<ÿ:ÿ9ÿ7ÿ5ÿ6ÿ5ÿ1ÿ0ÿ8ÿAÿBÿBÿ=ÿ6ÿ3ÿ1ÿ0ÿ/ÿ/ÿ0ÿ1ÿ2ÿ2ÿ3ÿ7ÿ:ÿ;ÿ=ÿ>ÿ>ÿ=ÿ?ÿDÿHÿKÿMÿPÿRÿTÿSÿSÿQÿPÿNÿMÿKÿKÿKÿKÿKÿLÿLÿNÿPÿPÿQÿTÿSÿPÿOÿLÿIÿFÿCÿBÿ@ÿ>ÿ?ÿ@ÿGÿQÿUÿ[ÿbÿmÿ|ÿÿÿÿÿÿÿÿÿÿÿÿ}ÿvÿqÿuÿÿÿ
ÿÿÿ{ÿjÿiÿvÿ
ÿÿÿÿÿÿÿ~ÿ}ÿxÿmÿaÿ]ÿ`ÿeÿiÿmÿqÿvÿ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ|ÿ{ÿzÿwÿvÿtÿpÿqÿqÿpÿqÿnÿMÿ)ÿ5ÿ6ÿ8ÿ8ÿ<ÿCÿDÿJÿPÿSÿOÿKÿHÿKÿMÿVÿZÿZÿVÿ^ÿ_ÿlÿÿÿÿÿÿ¨ÿªÿ§ÿ¤ÿwÿDÿHÿIÿIÿKÿJÿIÿLÿLÿMÿJÿJÿLÿKÿJÿLÿMÿLÿMÿMÿTÿsÿÿ¯ÿ¸ÿ¼ÿ¼ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿ¹ÿ³ÿÿ¤ÿ ÿÿÿÿÿÿÿÿÿ¥ÿ¥ÿ¡ÿÿÿ|ÿzÿ}ÿ{ÿzÿ}ÿ|ÿzÿzÿxÿxÿ{ÿ}ÿÿvÿsÿuÿwÿzÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ|ÿ|ÿ{ÿyÿwÿtÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿqÿoÿnÿ{ÿÿÿÿÿ
ÿQQpPPpOOoMMoMKnHFrECsBAuA?w;;w9~8w6~6w5~4w3~2w0~/x/~/z-~,z-~+z,~,z*~*z*~*z)~){)~)}(~(}(~(~(~((~(~(~(~(~((~('~''~'(~(~'~'~&~&~&~&~%%%%%%~$$|$~$}$~$}#~#|#~#|#~#|#~#|#}#|$}$|$~$|%~%|%~%}$~%}'}'}'}'}'|(})|){)}*z*}*y+}+y,},y-},y-}.w/~0y1~1y23x22x34x55x55x55x55x44x55y77y66z66z6~6y6~6y5~5y5~5y5~5z5~5|5~5{5~5{5~5{4~4{3~3{3~3{3}3|1}1|1~1{0~0{/}/{/}/{.}.|.}-|,},},}+~,},~+}+~*}*}*}*})})~)})~(|))|-/|0
.|+*|+/|3477Bcn^S
QSU]c}j[y1x z | $$3lrb^[am0}| "|!
"~"
$(*,*},+}++~./~01~2/|,.}`o~ghgi}j
p}hd~jk~he}``}ac|b]zVH{82z05z76{8>|@<|84~36~41~07AFDB:4~30~.0}13~45~4679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTa
r{tsyxuzsnkuvphfyjkjwq
qlg}dzvekdn_^t_ewh
mxsyv
uqokj
g
e
d
efh|}l~~qqrr~}rzyuwvuuuqtupvknR/p.:w<{>{CwG{KyGxG|HvO}PvQ~XvXVs\`paerid}mzpo§©s¦ zmA{FJxI¥LuK¥JtI¥JtI£JuJ¢JuJ¢JtJ¢ItK¡JoS¡lj®q·¼{½¾~¾¾~¾¾~¾¾~¾¹µ®¥£¡£v£
jiz~u}{~{{{yzx}xz|~sp|tuyww~|{|y~~w~}x}{zyv{zu
w{z~uvvvux|~}~|~{vssuurooln{k
s
xOÿOÿNÿNÿMÿMÿLÿLÿLÿJÿGÿFÿDÿBÿ@ÿ@ÿ@ÿ=ÿ;ÿ:ÿ:ÿ7ÿ6ÿ6ÿ4ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ0ÿ0ÿ1ÿ1ÿ3ÿ2ÿ2ÿ2ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ-ÿ/ÿ2ÿ1ÿ/ÿ-ÿ.ÿ1ÿ4ÿ7ÿ:ÿ=ÿ>ÿ>ÿUÿjÿeÿXÿSÿRÿVÿXÿ_ÿiÿhÿNÿ%ÿÿ ÿÿ ÿ%ÿ(ÿ(ÿ;ÿqÿrÿ]ÿ_ÿaÿkÿbÿ ÿÿÿ"ÿ!ÿ!ÿ!ÿ$ÿ(ÿ*ÿ,ÿ,ÿ.ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ1ÿ1ÿ0ÿ,ÿUÿrÿfÿcÿdÿhÿkÿpÿnÿjÿhÿgÿeÿfÿdÿaÿaÿbÿcÿdÿfÿYÿ5ÿ1ÿ3ÿ3ÿ3ÿ4ÿ5ÿ9ÿAÿGÿCÿ9ÿ4ÿ4ÿ5ÿ1ÿ1ÿ0ÿ4ÿ>ÿEÿFÿAÿ:ÿ5ÿ1ÿ/ÿ.ÿ0ÿ1ÿ3ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ<ÿ>ÿBÿBÿCÿEÿFÿJÿJÿLÿLÿLÿNÿOÿQÿSÿOÿMÿLÿIÿIÿJÿJÿLÿMÿPÿRÿTÿTÿUÿSÿOÿMÿJÿHÿFÿDÿCÿAÿAÿAÿ@ÿ>ÿ?ÿBÿOÿ[ÿpÿÿÿÿÿ
ÿÿÿÿÿÿ~ÿzÿqÿhÿlÿtÿÿÿÿÿÿyÿmÿlÿxÿ
ÿÿÿÿÿÿ|ÿ{ÿ|ÿvÿkÿbÿ_ÿ\ÿ[ÿ_ÿ`ÿfÿmÿuÿ~ÿÿÿÿÿÿÿÿÿÿ
ÿÿ
ÿ
ÿÿÿ
ÿ
ÿÿÿ{ÿvÿqÿpÿqÿoÿuÿ|ÿ~ÿÿ
ÿÿÿ~ÿ|ÿ{ÿ|ÿ{ÿ{ÿ{ÿyÿxÿyÿ{ÿwÿgÿJÿ@ÿAÿFÿPÿ[ÿ^ÿXÿKÿMÿXÿ\ÿ]ÿ^ÿ\ÿZÿaÿdÿeÿfÿkÿkÿcÿ{ÿÿÿÿ§ÿ§ÿ¥ÿÿcÿCÿHÿJÿIÿJÿKÿJÿIÿHÿHÿIÿIÿJÿJÿHÿHÿIÿIÿOÿfÿÿªÿ·ÿºÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿºÿ¶ÿ«ÿ§ÿ£ÿ¡ÿÿÿÿÿÿÿÿ¢ÿ¢ÿÿÿÿ{ÿ{ÿ|ÿ{ÿ{ÿ{ÿzÿxÿxÿyÿ{ÿ}ÿnÿlÿnÿqÿtÿtÿxÿyÿ{ÿ|ÿ}ÿ|ÿ|ÿ|ÿ{ÿzÿyÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿuÿoÿoÿnÿ}ÿÿÿÿÿÿLLpLKpKKpIIpJGqDBqCAs@>u>;u:8u7~7w5~5w4~2w1~0w0~0w/~.x.~.y,~+y,~,z*~*z*~*z)~){)~)|(~(|(~(}(~(~(~(}(~(})~)(~('~''~''~''~'&~&'~&&&&%%%%%|$~$}$~$}#~#|#~#|#~#|#~#|$}$|$}$|$~$|%~%|%~&}'~'}(}(}'}'}(}){*}*z*}*z*}+y+},y,}-y-~-y/~/w0~0w1~2w43x33x34x45x55x55x66x55x55y55y55y55y6~6y6~6y5~5y5~5y5~5y5~5z5~5{5~4{4~4{3~3{3~3{3~3{2}2|1}1|1}0{/}/{/}.{.}-{-},|-}-|*}*}+}+~*}*~)}(~(}('}''}'~(}(~(~()~-1|44|2/|23|6:|<B|EA}>I}dm|\S~Q
QUZ~fm{a9zz"
%{',~,Htjc``p}F{!{
"|!
%&+-/~20~..~00~02~21}Lr~hbce~jp}pi}gd~dd}dd}ce|fd{jb{E1{25z53z48|:DX]9333~21~03:EKF;53.~,/}13~56~7879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKI
GD
CB@@@A@@?FSiz{
x~y}x|xujwciwvrljyopmyt
~rm~j||dzugf
eqfevd^w^azitzw
r
piii
f
eezivmnbaveeys{w|}uuu|}srmh~g~ykphrdeyh~i{idvW[s`at``tbcpfgmjkmohtblsp§©r¤zSC{F¢KwK¥ItJ¥ItH¥FtG£GuG¢HuJ¢GuG¢HqI¢]k£r²¹{¼½}¾¾~¾¾~¾¾~¾¾~¾¾}½¸³
¥
qg
zmyz{z{zyxx}yy{~pjylltqrytv}y|~x|zs||u|z{}|x
y|v~twuuvy~y{z|vssss|tqnokn|mv{KÿKÿKÿIÿJÿJÿHÿHÿGÿHÿFÿDÿBÿAÿ?ÿ=ÿ<ÿ:ÿ:ÿ8ÿ7ÿ7ÿ6ÿ6ÿ3ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ.ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ-ÿ1ÿ6ÿ7ÿ5ÿ2ÿ4ÿ:ÿ<ÿ>ÿBÿFÿJÿBÿCÿ@ÿ?ÿVÿkÿbÿWÿQÿPÿSÿ[ÿ_ÿgÿnÿ^ÿ4ÿÿ$ÿ+ÿ,ÿ-ÿ.ÿOÿxÿjÿ_ÿ_ÿ`ÿlÿ4ÿÿÿ ÿ"ÿ$ÿ&ÿ&ÿ)ÿ-ÿ1ÿ2ÿ3ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ1ÿIÿsÿmÿfÿfÿfÿiÿnÿpÿhÿeÿdÿdÿdÿdÿdÿeÿeÿgÿdÿdÿYÿCÿ7ÿ5ÿ3ÿ4ÿ4ÿ3ÿ7ÿ;ÿ?ÿCÿLÿNÿ7ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ2ÿ8ÿCÿHÿFÿ=ÿ8ÿ3ÿ0ÿ.ÿ.ÿ/ÿ0ÿ1ÿ5ÿ6ÿ6ÿ6ÿ9ÿ;ÿ<ÿ@ÿBÿDÿFÿFÿGÿFÿGÿHÿHÿIÿMÿPÿPÿQÿNÿLÿKÿIÿHÿHÿHÿHÿJÿNÿPÿSÿTÿPÿMÿKÿIÿGÿDÿCÿAÿ?ÿ@ÿ@ÿCÿDÿCÿAÿAÿHÿYÿjÿtÿ|ÿÿÿ~ÿ}ÿ|ÿ~ÿ|ÿwÿnÿbÿbÿkÿwÿÿÿÿÿÿ{ÿoÿmÿyÿÿÿÿÿ}ÿzÿ|ÿ|ÿyÿuÿlÿhÿjÿhÿeÿ`ÿ^ÿ^ÿiÿvÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ}ÿwÿoÿeÿWÿNÿQÿXÿmÿvÿyÿ{ÿ}ÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿ}ÿwÿqÿsÿsÿqÿnÿjÿdÿWÿ_ÿaÿ`ÿ_ÿ_ÿ`ÿeÿeÿgÿhÿlÿoÿgÿcÿgÿ{ÿÿÿ¥ÿ¥ÿ£ÿÿJÿEÿKÿJÿIÿHÿFÿHÿGÿGÿGÿGÿGÿGÿGÿIÿHÿFÿLÿnÿÿ¯ÿ¸ÿ¼ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ»ÿ´ÿ¬ÿ¦ÿ¡ÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿ{ÿyÿzÿyÿyÿzÿzÿyÿyÿzÿ{ÿÿqÿjÿgÿgÿnÿqÿpÿsÿvÿxÿzÿ|ÿzÿ{ÿ{ÿ}ÿ{ÿ~ÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿtÿnÿnÿoÿ}ÿÿÿÿÿÿIIpIIpHHqGEqDDpCBrB@q?>t<~;t9~8u8~6x6~5w4~3x0~0x0}/v/}-w.}-x-},x,~+{*~){)~){*~*|)~)|(~(}(~(}(~(}'~'})~)})~)})~)~)~)~)~)~(~('~''~'&~&%~%%~%%%%%|$$}$$}$~$~$~$~$~$|%~%|%~%{$~${%~%|&~&|%~&}'~(})~)}*~*})~){*~,{+~+{,~,{.~.z.~.z/~/y/~0w1~1x2~2x3~3w4~4w44v64v56w66w66w66y66y66y66x66x44y55y5~4z5~5z6~4{4~4{4~4{3~3{4~2|2~2|2~2|2~2|1~1{0~0{/~/|.~.|.~-},~,}+~+}*~)})~)})~)}'}'~&}&~'~'~(~(~'~&%~%&'~)+1~6
:}97z6>z?BzHLxMJyICz?<|Pb~eZ~R
RUX\_inzR1v))y))})SsbZ`j[{ "z$${&*|**}.02454~57}75~=i~khhkk
l|qg}de}gh~hc{dezih{^G~@B~33|22|27|FD}@73212~11|11}46~6:@C<;6.~./~/1~23~4569;<=ACDEFFGEGHILNNMMKIHHGGILNPROLJIGEDC@@@BDDDDEIS`ktxz{z{|vvqwh^y`jzwvon}opn{s~qm|{h{{f|{lskvjjxheub
cxn~xtqmjiig
gfztlf^qM
C|DVms}wzx|~x}~uronidb
tlppummxcXu\_t__r`arbdrfgneckkgqdiuxs ¤s zG?{G¢IwK¥IuF¦FuF¥FtF¤FtF¤HuE£GrF FlZ l¨·y¼¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~½¹²
¬§¤
sizkyyyxxxxzz~z{zqgzehphmsooxu{}|}}y{{x{z{~}zz||uvwuvv~xwz~|xusqr}ryrmnmjnmw~}xGÿGÿGÿGÿEÿEÿEÿDÿCÿCÿAÿ@ÿ@ÿ?ÿ=ÿ<ÿ:ÿ9ÿ8ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ3ÿ0ÿ0ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ,ÿ+ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ4ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ&ÿ'ÿ(ÿ*ÿ/ÿ7ÿ<ÿ=ÿ;ÿ8ÿ<ÿBÿHÿJÿLÿLÿJÿIÿEÿDÿ?ÿ=ÿGÿXÿjÿeÿ[ÿXÿXÿXÿYÿ[ÿbÿhÿfÿRÿ*ÿ"ÿ(ÿ)ÿ(ÿZÿtÿdÿ]ÿbÿlÿLÿÿ&ÿ$ÿ&ÿ*ÿ*ÿ+ÿ-ÿ0ÿ3ÿ7ÿ8ÿ:ÿ;ÿ9ÿ8ÿ7ÿfÿjÿgÿiÿkÿjÿoÿpÿhÿcÿfÿgÿgÿgÿhÿfÿgÿiÿlÿWÿAÿ?ÿBÿ7ÿ3ÿ5ÿ3ÿ3ÿ3ÿ7ÿCÿKÿEÿ7ÿ3ÿ1ÿ/ÿ/ÿ/ÿ0ÿ1ÿ2ÿ4ÿ6ÿ9ÿ9ÿ7ÿ9ÿ9ÿ9ÿ5ÿ1ÿ.ÿ.ÿ/ÿ.ÿ/ÿ0ÿ2ÿ5ÿ7ÿ8ÿ9ÿ;ÿ<ÿ>ÿAÿBÿCÿEÿEÿEÿEÿEÿDÿGÿGÿHÿHÿIÿIÿHÿHÿFÿFÿGÿGÿGÿJÿOÿQÿRÿOÿLÿJÿHÿFÿEÿDÿCÿ@ÿ@ÿ@ÿ@ÿCÿEÿEÿEÿEÿGÿHÿPÿ[ÿdÿmÿtÿyÿ{ÿwÿqÿfÿbÿ^ÿ_ÿkÿyÿÿÿÿÿ
ÿ{ÿoÿoÿ{ÿÿÿÿ|ÿ{ÿ{ÿ{ÿ{ÿ}ÿ}ÿyÿpÿoÿlÿjÿiÿfÿfÿqÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿ}ÿzÿtÿgÿZÿTÿKÿHÿPÿcÿtÿvÿvÿyÿ{ÿ}ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿpÿoÿnÿjÿ[ÿ_ÿ`ÿaÿ`ÿ`ÿcÿaÿcÿeÿfÿgÿcÿ^ÿeÿfÿhÿnÿqÿÿÿÿÿÿÿFÿ@ÿFÿGÿHÿGÿHÿEÿEÿEÿEÿEÿFÿEÿEÿDÿFÿOÿtÿÿ²ÿºÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¹ÿ²ÿ¬ÿ§ÿ£ÿÿÿÿÿÿ
ÿÿ
ÿÿÿÿÿ{ÿyÿyÿyÿyÿyÿyÿyÿyÿwÿyÿ}ÿrÿgÿeÿgÿgÿjÿnÿnÿsÿvÿyÿzÿ|ÿ{ÿ{ÿ{ÿzÿÿ
ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿxÿpÿmÿlÿsÿÿÿÿÿÿÿEEqEEqDDqBAq@@rA@s><t;:t:~8t7~6t7~4u5~4t2~2v1~0v0}/v.}.w.~-x,~,x,~,y+~*y*~*z*~*{*~*|)~)})~)})~)})~)}*~*}*~*}*~*})~)})~)}(~(~'~'~(~(~&~&~&~&~%~%~&&~%%~%%}$$}%~%|%~%|%~%|%~%|%~%}%~%}&~&}'~'}'~'}(~)})~)}*~*}*~*{*~,{,~,{+~,{.~/z.~/z/~1y1~1w1~1x1~2x33x44x34x44x55x55x55w55y66y55y55x43x45y55y5~3z3~4z4~4z3~3z3~3{3~3{2~3|3~3|1~1|1~1|1~1|/~/|.~.|.~.|-~,}+~+}*~*})~(})~(}(~'}'}'~&}%~'~%~&~&~&~&%~%%~%}&~*19?}B
Bz==yFKyKLwJJwHExB?y?A|;I~`jd
YYYWY]`}kgzA
&z&&|(bvdbdj:{!z(+|*,}--}14}56|88|?j~kg~ii~gl~tk{df{gh{hizilzl_{H:|BE|>3|15|30|16~A[~R520}/.}.0|13}48~<:~966642~-,~--~-/~04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CD
GG
FF
IG
MV_gosqgS}X_~bn}{wrp|qqp{s~~p|{lyxhyyf}}ptzonznkxi
lyv
xuo~jjllll~kyslhap[WwQQzZh{suywxvz}urrpkfe
wlpltjbt]`sabtbbsacsdfsgdo^
Yod
frem|r}zwuxy@ByF£EvD¦HtG¦GtE¥CtD¤FtD¥DtC¤EoJ¢dj¬r¸º}½¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾½~¼¶²¬
§¢|q
g}j{{uzz~yxxw~vwz}sd{dgqefpkltos{wx{~{{{{z}|{{x}suvuvw~y{|xtqqqq{ryuqtws~xy}qnBÿBÿBÿBÿBÿBÿAÿAÿ@ÿ@ÿ@ÿ?ÿ<ÿ;ÿ:ÿ:ÿ9ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ,ÿ/ÿ/ÿ.ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ4ÿ4ÿ4ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ3ÿ3ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ(ÿ+ÿ/ÿ:ÿBÿGÿHÿCÿ=ÿFÿNÿOÿPÿNÿJÿJÿEÿBÿ?ÿ=ÿ<ÿ:ÿ6ÿ9ÿMÿhÿiÿ]ÿXÿVÿTÿUÿYÿ]ÿcÿiÿVÿ2ÿ!ÿ$ÿ,ÿ`ÿvÿcÿcÿbÿgÿ1ÿ"ÿ*ÿ,ÿ0ÿ3ÿ2ÿ1ÿ3ÿ7ÿ8ÿ<ÿ8ÿ=ÿiÿqÿjÿhÿjÿhÿiÿoÿlÿfÿgÿjÿjÿgÿjÿnÿnÿ[ÿBÿ9ÿ:ÿ?ÿDÿIÿ;ÿ3ÿ4ÿ4ÿ2ÿ0ÿ1ÿ6ÿ?ÿPÿIÿ3ÿ0ÿ/ÿ/ÿ/ÿ/ÿ2ÿ7ÿ6ÿ7ÿ6ÿ9ÿ<ÿ<ÿ9ÿ8ÿ5ÿ3ÿ/ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ1ÿ5ÿ9ÿ:ÿ;ÿ=ÿ=ÿ>ÿ@ÿ@ÿ@ÿ@ÿ>ÿ?ÿAÿ@ÿ?ÿ?ÿ=ÿ>ÿ>ÿ@ÿ?ÿAÿAÿCÿDÿDÿDÿDÿEÿHÿJÿJÿIÿHÿFÿDÿEÿEÿCÿAÿ@ÿ?ÿ@ÿCÿDÿEÿFÿGÿFÿHÿGÿGÿJÿOÿXÿ`ÿfÿcÿVÿIÿOÿ^ÿeÿnÿ|ÿÿÿÿÿ
ÿ|ÿpÿqÿzÿ}ÿ~ÿ}ÿyÿyÿzÿzÿzÿ|ÿÿÿwÿtÿqÿoÿnÿlÿnÿyÿÿÿÿÿÿÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿ~ÿ{ÿxÿrÿiÿeÿaÿ[ÿWÿWÿ]ÿiÿsÿuÿwÿwÿzÿ}ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿxÿpÿgÿcÿ`ÿ`ÿaÿbÿcÿcÿcÿcÿdÿfÿfÿfÿaÿ]ÿ]ÿhÿhÿhÿkÿrÿuÿÿÿÿÿuÿ<ÿBÿEÿFÿIÿGÿFÿGÿDÿCÿDÿEÿCÿCÿBÿEÿWÿÿ¥ÿ´ÿ»ÿ½ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿºÿ¶ÿ²ÿ®ÿ©ÿ¢ÿÿÿÿÿÿÿÿ~ÿ}ÿ{ÿ~ÿ
ÿ~ÿ{ÿ{ÿzÿzÿ{ÿzÿxÿwÿvÿwÿ}ÿwÿgÿeÿfÿgÿeÿjÿlÿoÿpÿsÿuÿyÿ{ÿ{ÿ}ÿ|ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@@rAAr@@q??r=>s><s::t99t8~7s4~4t3~4u3~3u22w11w0~/w.~.x++y,+y,+z+)z)~)z*~*{*~*|*~*|+~+|*~*|*~*}+~+}+~+|+~+|*~*}*~*~*~*~(~'~(~(~'~&~&~&}&~&}&~&}&~&}&~&}%~%}%~&|&~&|%%{%%{%~%}&~&}&~&}&~&}'~'~'~)~*}*}*}*}*~*{+~+{,~-|-~-z.~/{//{00y11y20x22x24y44y55z66z66z66z55y55x55y55y55x55x45x54x33y33y3~3y3~3y3~3z2~2z2~1{2~2{1~1{1~0{0}0z.}.{-}-{-}-{,~+{*~*|)~)})~(}(}(}'}&}&~%}%~%~&~%~$~$~$~$$~%$}&~&~*~.~8E}LPyIDxGRxTVvRMwIDwA>x<<y99|2.8Ve
bWUS
VX\^gzgKx+
|'craeh|]'y*,{27{=CzKLzI@yI
j}smjm~i
i}pn}fhziizknxgZyL;z9:z=?{@;{64|22|00}46}7;71/1}1/|03}7:97:@A=:5~3/}++|,-|--|-/}2669;;<?=;;:;=><;;~=>~>?~?@~AC~CBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWf~s~vqqymrpwr{}o{zlyyhyzg|s~wq~q
ozo
r{}zv
rz{mz~~mm
po~|o}}o}|nuqkmimdao`crbissvtwwx{|tssolk
ylpepbbsc
dqc
ctccucctfdrccr]^sijsjmyrs
tzxry>AyE¢DuE¦CsC¦EtD¦DuD¤EtD¤BrD¥Kjm¢l®¶xº½}¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~º¸µ®«
z~rzym~~n{zwzz}yxxw}vuz|z}h|ehsgdqejrnnxqt}vy|}}|}|}}xuvvwwx~|xxxw
us
tyxyztmigk@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ?ÿ?ÿ<ÿ<ÿ;ÿ:ÿ9ÿ9ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ5ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ4ÿ5ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ-ÿ,ÿ+ÿ+ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ&ÿ&ÿ$ÿ$ÿ%ÿ&ÿ$ÿ#ÿ$ÿ$ÿ&ÿ)ÿ-ÿ8ÿEÿOÿTÿRÿMÿKÿSÿXÿVÿSÿOÿKÿGÿCÿ?ÿ;ÿ:ÿ7ÿ3ÿ2ÿ/ÿ.ÿ,ÿ=ÿXÿgÿaÿYÿTÿSÿVÿXÿ[ÿ`ÿgÿbÿBÿ&ÿ$ÿ^ÿoÿaÿeÿeÿVÿ:ÿ8ÿ5ÿ=ÿ@ÿIÿLÿQÿXÿcÿrÿrÿlÿmÿlÿjÿkÿpÿpÿjÿiÿkÿlÿoÿjÿSÿEÿFÿGÿHÿCÿAÿBÿAÿ>ÿ8ÿ4ÿ4ÿ2ÿ0ÿ/ÿ/ÿ3ÿ6ÿ7ÿ6ÿ3ÿ.ÿ-ÿ.ÿ1ÿ1ÿ1ÿ4ÿ9ÿ=ÿ;ÿ7ÿ;ÿBÿDÿ@ÿ=ÿ7ÿ1ÿ/ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ/ÿ1ÿ4ÿ6ÿ7ÿ8ÿ:ÿ;ÿ=ÿ:ÿ8ÿ8ÿ9ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ:ÿ:ÿ:ÿ=ÿ=ÿ=ÿ>ÿ?ÿAÿBÿCÿCÿDÿDÿEÿDÿCÿAÿ@ÿ@ÿ@ÿAÿAÿ?ÿ?ÿ@ÿ@ÿAÿAÿBÿBÿBÿBÿDÿCÿDÿDÿCÿDÿEÿFÿDÿEÿEÿSÿhÿuÿÿÿÿÿ
ÿÿtÿmÿqÿxÿxÿzÿzÿzÿyÿyÿyÿzÿ{ÿ|ÿÿÿxÿuÿrÿqÿqÿwÿÿÿÿÿ
ÿÿ{ÿzÿyÿ{ÿ~ÿÿÿÿÿÿ~ÿyÿwÿxÿzÿyÿzÿwÿxÿpÿnÿlÿmÿmÿlÿhÿmÿtÿwÿwÿxÿ{ÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿuÿnÿjÿfÿbÿfÿeÿdÿdÿcÿcÿcÿcÿcÿdÿbÿ`ÿ_ÿfÿlÿoÿlÿjÿpÿxÿyÿÿÿÿoÿCÿBÿGÿDÿEÿDÿAÿCÿDÿDÿDÿEÿDÿBÿCÿWÿ
ÿ¦ÿ³ÿ·ÿ¼ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ½ÿ¹ÿ¸ÿ²ÿ«ÿ ÿÿÿÿÿÿÿÿÿ
ÿ~ÿvÿuÿyÿyÿyÿzÿzÿyÿxÿwÿwÿuÿvÿyÿzÿgÿeÿeÿdÿeÿfÿiÿkÿlÿnÿrÿvÿyÿ}ÿ}ÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??q??q>>q==r>~;s;~;s97t88t7~6t7~6u4~3u3~2v11w00w.~-w.~.x+}+y+}+y*~+y+}+y*~*x*~*y*~*z*~*z+~+z*~*z*~*z,~,z,~,{,~,{,~,{+~*{*~,{+~({*~*{(~({&~&}'~'}'~'}'~'}'~'}'~'}'~'|(~(|''}&%}&~'~'~'~(~(~)~)~)~)~)~*~*~*}+~,},~,|.~.|.~.}.~.|/~0{/0{00z10z02y22y23y44y55z55z66z66z55z66y55y66y55x44x44x44x33y33y3~3z2~1z2~2z2~2z1~1{0~1{0~0{0~/{.}.z-}-{,},{+}+{+~*{)~)|(~(}(~'}'}'}&}&}&~%}$~$~$~&~$~#~#~##~#$}#~$~&}+~6A}LVyZVxQSxXYuXTsNKsHDt?<v83x10{./|,,~Dah^V
TTTX_}ch|cE|,^}i]}chzP+x7<xETz]d|jo~s
jjl~m
m~pp|ll|mm|gP{<BzMQyOHxILxIAz:5{43|2.|-0}55~54~0/},+}25}33~89989@DCB<~51}--|-,|,.|//|13}44~68~898777~;:~:9~89}89~::~9;~=<~=@ABDABBB@?@@>>????@@AAAAABBBCCCBBBETivxr{roirovqvwnxxkxygzufuvsyvt}r
x~~zu{xsx~xs|st
rxrppqtwpuwlvujsrjsujtqjlkktwoxxp{q
q
qq
p}ulsplkfoc
eqf
ftfducctbcu`[u]csgksqnworuy{wpyFCxC¢EtE¥EsB§AsB§CsD¥ErF¥CnJ¤ji¬rµ»z¼¾}¿¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾½»¹³
¨£¢ ¡zrvmuwnxytzzzyw~ww~vxzz}}h{eeqcfpdgqhkunrzvy~}{~}
}|}}xuvvvv{~|xwy}}vtx~w~pjdbce
j
n<ÿ<ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ<ÿ9ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ6ÿ5ÿ6ÿ5ÿ3ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ)ÿ0ÿ:ÿFÿQÿYÿXÿQÿQÿXÿYÿXÿXÿSÿOÿLÿHÿDÿ?ÿ;ÿ7ÿ5ÿ2ÿ/ÿ.ÿ-ÿ/ÿ*ÿ1ÿHÿeÿiÿYÿSÿSÿVÿXÿYÿ\ÿdÿiÿbÿVÿjÿqÿcÿdÿbÿJÿ8ÿKÿ_ÿeÿgÿgÿnÿtÿlÿhÿjÿlÿmÿpÿpÿkÿmÿlÿ]ÿ=ÿ-ÿ5ÿIÿ[ÿ]ÿTÿOÿMÿEÿGÿGÿ=ÿ7ÿ4ÿ2ÿ3ÿ0ÿ/ÿ,ÿ0ÿ:ÿ=ÿ6ÿ2ÿ1ÿ.ÿ-ÿ-ÿ0ÿ3ÿ6ÿ6ÿ8ÿ:ÿ:ÿ8ÿ9ÿ>ÿAÿEÿBÿ@ÿ9ÿ3ÿ/ÿ-ÿ-ÿ,ÿ,ÿ-ÿ.ÿ0ÿ2ÿ2ÿ2ÿ4ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ7ÿ8ÿ9ÿ8ÿ9ÿ:ÿ9ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ;ÿ;ÿ=ÿ?ÿ@ÿAÿBÿBÿBÿBÿ?ÿ>ÿ?ÿ?ÿ>ÿ>ÿ>ÿ>ÿ>ÿ>ÿ?ÿ@ÿ@ÿAÿAÿAÿ?ÿ?ÿ?ÿ@ÿAÿAÿBÿ@ÿBÿAÿJÿ[ÿkÿyÿ{ÿ{ÿ|ÿ}ÿyÿrÿkÿiÿpÿtÿtÿvÿwÿwÿwÿyÿxÿrÿgÿlÿÿÿÿzÿxÿvÿsÿyÿÿÿÿÿÿÿ|ÿyÿyÿyÿ|ÿÿÿ
ÿÿÿÿxÿoÿiÿmÿmÿoÿpÿsÿvÿvÿuÿuÿwÿvÿuÿtÿpÿmÿrÿwÿzÿ{ÿÿÿ
ÿ
ÿÿÿÿÿÿÿyÿtÿpÿnÿiÿjÿgÿfÿfÿeÿcÿcÿcÿcÿaÿ]ÿZÿ]ÿcÿgÿmÿuÿuÿuÿoÿmÿpÿÿÿpÿLÿDÿDÿDÿEÿDÿAÿCÿCÿCÿDÿDÿEÿFÿVÿÿ£ÿ²ÿ¸ÿ»ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿ»ÿ¸ÿ¶ÿµÿ±ÿªÿ¦ÿ£ÿÿÿÿÿzÿuÿwÿxÿyÿzÿzÿyÿwÿwÿwÿwÿwÿzÿ}ÿhÿeÿdÿcÿdÿeÿgÿhÿiÿmÿqÿtÿzÿ|ÿ{ÿ}ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<<r;;r;~;s:~:s9~9r8~8r7~6t7~7t6~6t5~5t2~1v1~1v0~0w.~.w-~,w+~+w*}*y*}*y*~*x*~*x*~*w*~*w*~*x+~+x*~*y+~+z-~-y,~,y-~-z-~-z-~-y,~,{,~+z*~*z*~*z)~){(~(|(~'|'~'}(~(}((~((~((}((}(~(}'~'~(~(~(~()~)~)~)~)~)}*~+,~,+~,-~-}-~-}.}.{/}/y/~/z00z01z11z02x22x2~3x4~4x44z55z56z66z55z55z55z55z55y44y44y44y3~3y3~3y1~1z1~1{0~0{0~0{0~0|0~/|/~/|/~.|-~-|,~-|,~+|+~*|*~*{)~)|(~(}'~'}'~&}&~%}$~#~$~$~#~#"~#"~""~"#~#~#~$~&-6CM|STzPNzUZv[[tYTqMIrGBr?<v73w2/y+,},,~+3Ui_
ZW
WWYY
Z~bj|qu|tp{onzlizge}b
lrjhg~jn~qr~n
j|\>|"
){3Gx]_xZRxMBy8E{E;{52{20{/.|/7}HL}<31/~/.}.3}78~9;86~7<~BFCA<7~1/}-,|,-|/1|32}44|35|55|56}66~66~87|77|79}98}87}78}8;}=>}?@~@?~??~>?~>=~<=~===>?@AAA@@>>>@@?BIWcrzwvzzyvqju^bulqpsunuvjwwgwqdcWpn}yx{}|z~u}~ztz}{t|~u~
u}t|qsbs_dwghtlnopsmuxhxvgtsfttgtujz~mm
m
pn{mwxlusnoirgfvedtcbub_s^Zt^fumtwwv~u~p
lxivvpxQGwF¡FuF¥DsC¨BsC§DsE¥EqA¥Jkd¢kª´u¹
½}½½¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾}¾¾~¾¾~»°«¦~wzouwlyzqyyuywzww}vwyz}{lccuddodfqhjumrws
{|z|}~
}}zvuuuux~{xuw{v~nkligfebb
e
im:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ:ÿ:ÿ9ÿ9ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ!ÿ#ÿ$ÿ)ÿ3ÿ>ÿGÿLÿIÿFÿIÿOÿYÿ\ÿ^ÿ]ÿXÿQÿNÿLÿGÿCÿ?ÿ:ÿ7ÿ4ÿ/ÿ+ÿ*ÿ)ÿ+ÿ(ÿ*ÿ+ÿ<ÿYÿeÿ_ÿUÿZÿUÿVÿWÿZÿaÿeÿfÿjÿjÿjÿgÿeÿfÿbÿ`ÿjÿsÿpÿiÿhÿkÿnÿtÿwÿoÿ\ÿ8ÿ'ÿ-ÿ6ÿ/ÿIÿfÿgÿ`ÿ]ÿWÿOÿ7ÿ6ÿJÿCÿ8ÿ5ÿ1ÿ/ÿ/ÿ/ÿ.ÿ.ÿ2ÿ:ÿ9ÿ4ÿ2ÿ1ÿ.ÿ0ÿ1ÿ1ÿ1ÿ5ÿ6ÿ7ÿ;ÿ8ÿ5ÿ7ÿ=ÿBÿEÿEÿCÿ=ÿ8ÿ2ÿ.ÿ+ÿ,ÿ,ÿ-ÿ/ÿ0ÿ2ÿ4ÿ5ÿ4ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ;ÿ<ÿ=ÿ=ÿ?ÿ?ÿ>ÿ=ÿ=ÿ=ÿ<ÿ<ÿ=ÿ=ÿ=ÿ=ÿ>ÿ?ÿAÿAÿAÿAÿ?ÿ?ÿ>ÿ?ÿ>ÿ>ÿ?ÿAÿIÿTÿ_ÿmÿwÿvÿtÿvÿuÿpÿeÿXÿOÿUÿaÿfÿkÿoÿqÿtÿuÿyÿwÿnÿ[ÿ;ÿ2ÿmÿÿÿÿ}ÿ{ÿ~ÿÿÿÿÿÿÿ}ÿzÿzÿ|ÿÿÿÿÿÿÿÿÿxÿiÿYÿYÿ]ÿ`ÿdÿfÿgÿlÿrÿvÿwÿwÿvÿvÿuÿuÿtÿwÿzÿ|ÿÿÿÿÿÿ
ÿ
ÿÿÿ~ÿzÿyÿzÿuÿrÿoÿmÿgÿdÿdÿcÿbÿ_ÿ_ÿ^ÿ]ÿaÿjÿsÿyÿwÿvÿuÿpÿmÿkÿoÿÿ~ÿYÿDÿGÿFÿEÿBÿBÿBÿCÿDÿEÿEÿAÿNÿsÿÿ¬ÿ¶ÿ¹ÿ¼ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿµÿ¬ÿ ÿÿxÿnÿpÿsÿuÿwÿwÿyÿyÿyÿyÿwÿwÿwÿvÿwÿzÿ}ÿkÿcÿcÿbÿdÿdÿfÿhÿjÿmÿqÿtÿzÿ|ÿ|ÿ~ÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ{ÿ99u::u:~9t:~:t8~8t8~8t6~6t6~6t4~4t4~4t2~0t1~1t/~.u.~-u-~,u+~+u*}*v*}*v)~)w)~*w*~*w+~+w+~,w,~+w+~+w,~,w-~-x.~.x.~.y.~.y.~.x-~,z+~,z+~*z+~+{)~)|*~*|)~)|)~)})~(})'|((|((|((~*~*})~)})~)~(~(~(~(~)~*~+~+}+~+,~,-~-.~.}-~-}.~.|/~/z0~0z00z00z11z01z22z22y33y33z45z55z66z66y55y55z44z55y33y34y44y2~2y2~2y1~1z1~1{0~.{/~/{0~0|/~/|-~-|.~.|.~-|,~+|+~*|*~)|)~){(~(|'~'}&~&}&~%}%~$}#~"~#~#~"~""~"!~!!~!!~!~"~"~#'09C{EAz?BzMWwZ_t_^qWUqPKqGCr?9v0(y'(|'&|&&'#*C[_Y
WSRUW~Y\|`b{cczdc|cl~wqiglp}t
y}h
?}!
#}(({9Nyeixdcwc[wL4x2A{94{20{//{.,|-/|1/~011/~15}41}25}79~88~8=}EGFD@;~50},+|+.|./{13|44{43{33{33|34|44|56|67|76|67|75}55}57|78|8:};>~>=~<<~<<~<<~==~<?~@@@AA@@>~<=~=A~ES_isvr|r
q{l
d|UB>FPX\bxgmrstjthfYVsL~f~}zwu~~~u}}u~t~t|r}oqm_QuSU~Z]{`fwjrpttkuugwvevyfyzg}gillj~|j|zkuumpkphfudav_]v[\weixps}ussynjtigrr|xcGwFFvF£DtB¥CsC§DsC§DqB¦Rj~¢ n¶zº½~½¾¿¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~½½¾¾~¾¾½µ
¥scdei{n
ssxyqzzsyxywv{wwxy~{n~cbvbdrfesgjwloyu{||}}}
~}wtuuuwz~zxyzxqponoqr
s
rr
tyuxmdz9ÿ9ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ6ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ+ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ(ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ0ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ#ÿ'ÿ-ÿ8ÿBÿGÿDÿ>ÿBÿHÿPÿWÿ]ÿ`ÿ_ÿ^ÿ\ÿZÿTÿNÿKÿEÿ?ÿ.ÿ'ÿ&ÿ&ÿ%ÿ(ÿ(ÿ(ÿ'ÿ&ÿ$ÿ$ÿ(ÿ@ÿ]ÿbÿTÿOÿTÿWÿWÿZÿ^ÿ_ÿaÿcÿeÿdÿlÿuÿsÿpÿkÿoÿrÿvÿxÿeÿ;ÿ%ÿ&ÿ(ÿ&ÿ'ÿ=ÿ]ÿiÿiÿeÿcÿ_ÿUÿIÿ,ÿ+ÿ8ÿ6ÿ3ÿ1ÿ0ÿ/ÿ.ÿ-ÿ*ÿ+ÿ,ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ2ÿ6ÿ7ÿ2ÿ2ÿ3ÿ6ÿ:ÿ:ÿ8ÿ9ÿ>ÿBÿDÿEÿEÿAÿ<ÿ6ÿ1ÿ,ÿ+ÿ+ÿ.ÿ.ÿ.ÿ0ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ4ÿ4ÿ4ÿ4ÿ4ÿ4ÿ5ÿ6ÿ4ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ6ÿ7ÿ7ÿ:ÿ;ÿ<ÿ=ÿ=ÿ9ÿ:ÿ:ÿ;ÿ;ÿ;ÿ<ÿ=ÿ<ÿ?ÿ@ÿ?ÿ@ÿ?ÿ?ÿCÿ@ÿ>ÿ=ÿ=ÿ?ÿGÿRÿZÿgÿrÿtÿqÿtÿrÿkÿcÿUÿ>ÿ0ÿ2ÿ9ÿ?ÿDÿIÿQÿZÿ`ÿfÿmÿnÿhÿ_ÿeÿhÿrÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿzÿmÿVÿMÿOÿPÿTÿWÿ]ÿdÿkÿpÿqÿsÿtÿtÿvÿwÿxÿyÿzÿ|ÿ}ÿ}ÿ~ÿÿÿÿÿÿÿÿÿ}ÿ|ÿyÿtÿtÿnÿjÿgÿcÿ`ÿ^ÿ^ÿ\ÿaÿjÿpÿpÿpÿqÿqÿqÿmÿiÿhÿfÿiÿrÿoÿPÿLÿJÿJÿEÿBÿBÿBÿBÿAÿBÿEÿUÿ
ÿ£ÿ°ÿ·ÿ»ÿ½ÿ½ÿ¾ÿ¿ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ½ÿ¼ÿ¼ÿ¼ÿ¾ÿ½ÿ¼ÿ¹ÿ©ÿÿhÿYÿ_ÿdÿeÿhÿhÿkÿqÿxÿyÿyÿyÿyÿwÿvÿvÿvÿyÿ~ÿsÿfÿcÿcÿgÿiÿiÿjÿlÿnÿqÿvÿ|ÿ}ÿ~ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿzÿtÿlÿdÿYÿQÿPÿ8~8t9~9t9}9t7}7t8~8t8~7t5~5t5~5t3~2s2~2s2~0u/~.u.~.u.~-u-~,t+~+t+}+v+}+v*~*w*~+w+~+w+~+w+~,w-~-w-~-w.~.w.~.v.~.w.~.w.~.w.~.w-~-w.~.y-~+y,~,{,~+|+~+|+~*|)~)}*~*}*~*|)~)|*~*|*~*|+~)}*~*}*}+~*}(~*}+~+},~+~++~+,~,-~-.~.}-~-}.~.}/~/{0~0{00{/1z11z12z22z22z33z22z33z33y45y44y55y55y33y44z33z45z32z2~2z2~2z0~0{1~1{/~.{/~.{/~.{.~.{.~-{,},{,}+{+}*{*})z)}({(~(|(~&|&~&|%~%}&~%$~##~""~"!~!~!~!~!~!~!~!~!~ }!~"}#&|+4A|FFxDGvIOuUZs_brcaq_\qWSsM>v.){)%}$$~$&~(*|((~($~*G]]UY]`{ac{ch|h
m~splkmqs
w}kNz4,y+&v'*wC^yibyWRxYSxJ-y(7z60{00{/-{-*|,0|/1}11~21~38~97~57|:<}><};;~@C~EE~B?~;5}0,{+,{,-{.0{11{23{32z20z02{23{34{44{44|44|45|54|23{46|79};<};9}78}88~8:~;<|>=|=?}?B}BB~A?~<>~CP[fquss
n|g_~R
@
2//0369@FOW_yb`o[^uV]t~{x
u
uuw}v}trmhylnVJ{HJLR[g{nnsopnrtjwycxx`{}`~~d~fgil~j{xkvrnnjpd`r^[u]ewhjzmpq{o
nxliugdudfukWwTPwI CuB£AuB¦BsB¦AoE¦Xj¤o°¶zº¼~¼¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¾½¼»ºº½½~»·
\PW]
`d
ghiyjrswxqxxvvvywxv|uyq|p~rsyssyst{t}w|}}~~|vvvuuwz~{y|w~prtwwwz|~wpg`
XN
GDHN8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7ÿ8ÿ8ÿ7ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ+ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ0ÿ0ÿ0ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ)ÿ&ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ#ÿ&ÿ+ÿ4ÿ?ÿHÿJÿJÿKÿNÿPÿSÿZÿ_ÿeÿeÿfÿfÿbÿ^ÿXÿMÿ4ÿ)ÿ+ÿ(ÿ&ÿ$ÿ%ÿ'ÿ&ÿ&ÿ+ÿ-ÿ.ÿ.ÿ+ÿ&ÿ%ÿ*ÿ:ÿKÿWÿgÿlÿjÿkÿmÿnÿkÿuÿrÿmÿmÿoÿsÿuÿwÿlÿSÿIÿ5ÿ2ÿ<ÿ=ÿ1ÿ2ÿLÿcÿdÿZÿSÿLÿMÿMÿGÿ0ÿ%ÿ6ÿ5ÿ/ÿ0ÿ0ÿ.ÿ-ÿ-ÿ-ÿ/ÿ2ÿ0ÿ1ÿ2ÿ1ÿ2ÿ3ÿ6ÿ9ÿ=ÿ;ÿ9ÿ8ÿ8ÿ;ÿ<ÿ=ÿ=ÿ>ÿ@ÿ?ÿ@ÿBÿ@ÿ?ÿ=ÿ9ÿ2ÿ.ÿ,ÿ,ÿ,ÿ-ÿ.ÿ0ÿ1ÿ1ÿ2ÿ3ÿ3ÿ2ÿ2ÿ1ÿ0ÿ1ÿ1ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ2ÿ3ÿ3ÿ5ÿ6ÿ8ÿ:ÿ;ÿ9ÿ8ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ9ÿ:ÿ;ÿ;ÿ;ÿ<ÿ=ÿ@ÿAÿBÿAÿ?ÿ=ÿ@ÿNÿYÿcÿoÿpÿqÿsÿoÿhÿ\ÿNÿ<ÿ.ÿ-ÿ,ÿ-ÿ/ÿ0ÿ0ÿ0ÿ4ÿ7ÿ:ÿCÿIÿNÿPÿWÿYÿRÿZÿiÿÿÿÿ
ÿ
ÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿoÿ[ÿHÿDÿEÿHÿQÿcÿiÿkÿkÿmÿpÿrÿxÿyÿwÿwÿyÿ{ÿ{ÿ{ÿ|ÿ}ÿ}ÿ~ÿ~ÿÿÿÿÿÿ}ÿ{ÿyÿuÿrÿnÿjÿeÿ[ÿPÿTÿ\ÿeÿfÿgÿnÿoÿnÿnÿlÿiÿgÿdÿdÿbÿ`ÿXÿRÿPÿFÿDÿBÿBÿAÿAÿAÿBÿDÿXÿÿ¥ÿ±ÿ·ÿ¹ÿ¼ÿ¾ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ¸ÿ·ÿ»ÿ»ÿ½ÿ¹ÿ±ÿÿ\ÿEÿPÿYÿ\ÿ^ÿbÿfÿgÿiÿhÿlÿtÿxÿyÿyÿzÿzÿ|ÿÿÿ
ÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyÿsÿgÿ_ÿVÿNÿEÿEÿFÿJÿLÿPÿSÿ8~8t7~7t7}7s7}7s7~6t5~5t5~5t5~5t3~2t2~2t2~0u0~/u/~/t.~-t-~,t,~,t,},u+}+u+~+u*~,u,~,u,~,u,~,u.~.u-~-v/~/v.~.w.~.x0~0x/~/x/~/x.~.x.~.x.~.x-~-z.~.{,~,{,~,{+~+{+~*{+~+|+~+|*~*|*~*|,~,|+~+}+~+|,~,}*~*~,~,~,~,+~-,~,-~-.~.}-~-}.~.}/~/{0~0{00{00z01z11z12z22z22z22z34z33y33y33y33y33y33y33z33z32z0.z0~1z1~1z0~0{0~/{.~.{.~.{.~.{-~-{,~,{+}+{+}*{*}){)}(z&}&{'~'|&~&|%~%|$~$}$~#}#~"}"~!}!~!} ~ ~!~!~ ~ ~!~!~~ } ~!}"%~+3=|EIxLOvQQtT[t`fskjrfdrc[tH1x,/{,(|)){)'{)*z,,{-/{.0|11~28~AQX`cfrroort}ut|eQyPOx;;wHOwJOw[`xXUzRKyKOyF1y$3z50{0/{-.{-1|68|55}32~44~69?>=;|;<|@@|BA~><~?B~@@~=;}4/{--{--{/1{12{22{22z22z22{22{24{43{21{12{23{32{12{11{56|77|56}77}77}78}88}89}9:}:<}=?}@=}@H}Wan
qqrog\P
>0.-,--//011248<BHIKPXj~|y|~
yustuwvsmgfso\H|BCJ[cf}fhxmrpyydwwbyzbzzazzb{|c~~e~hi{kzwmusoolqe_sah|d~_d{movonvlhufbuaat_[vNNvHFvC¢BuB¥BtB¦>pD¦Yk¦q°·z¹»}½½~¾¾~¾¾~¾¾~¾¾~¾¾~¾¾~¼»¹¶µ·~º»¹FEOV\^`dd
gii{owt{{t|q
nkjkhiii
nstsvvuuy{~{{z~tsuwz
{}x~u
qncVLFBA@CHMMPT7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ8ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ5ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ)ÿ*ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ-ÿ-ÿ,ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ1ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿÿ ÿ ÿ!ÿ#ÿ(ÿ.ÿ6ÿ>ÿEÿIÿKÿPÿSÿTÿWÿ_ÿdÿjÿnÿlÿiÿeÿbÿVÿ>ÿ.ÿ-ÿ,ÿ*ÿ*ÿ*ÿ-ÿ-ÿ(ÿ*ÿ*ÿ,ÿ,ÿ,ÿ*ÿ*ÿ-ÿ.ÿ0ÿ1ÿ1ÿ0ÿ.ÿ.ÿ(ÿ(ÿdÿrÿlÿlÿkÿhÿiÿmÿ`ÿIÿLÿNÿPÿDÿCÿNÿRÿQÿYÿ^ÿZÿYÿ]ÿ[ÿUÿUÿTÿRÿ<ÿ'ÿ/ÿ6ÿ8ÿ3ÿ0ÿ.ÿ-ÿ-ÿ1ÿ7ÿ=ÿ<ÿ:ÿ6ÿ3ÿ3ÿ3ÿ5ÿ;ÿ@ÿAÿAÿCÿAÿ?ÿ@ÿBÿBÿAÿ<ÿ:ÿ<ÿ>ÿ=ÿ=ÿ<ÿ:ÿ5ÿ1ÿ.ÿ-ÿ-ÿ-ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ3ÿ3ÿ2ÿ1ÿ/ÿ/ÿ1ÿ1ÿ2ÿ4ÿ4ÿ3ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ:ÿ:ÿ<ÿ=ÿ>ÿ?ÿ>ÿFÿPÿ]ÿkÿrÿnÿoÿlÿfÿ\ÿNÿ=ÿ0ÿ.ÿ,ÿ,ÿ,ÿ,ÿ-ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ0ÿ1ÿ3ÿ5ÿ9ÿ;ÿ>ÿAÿKÿ`ÿfÿ`ÿdÿhÿkÿpÿqÿsÿvÿzÿ}ÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿcÿGÿ?ÿHÿWÿcÿfÿdÿeÿkÿpÿzÿyÿvÿwÿyÿzÿ{ÿyÿxÿxÿxÿzÿ{ÿ|ÿ}ÿ~ÿ}ÿ|ÿÿÿ|ÿzÿvÿsÿrÿoÿoÿpÿlÿkÿnÿkÿkÿoÿoÿoÿlÿjÿhÿfÿbÿaÿbÿ`ÿ\ÿKÿLÿOÿGÿCÿBÿBÿBÿBÿAÿBÿVÿÿ£ÿ®ÿ¶ÿ¹ÿ¼ÿ½ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¾ÿ¼ÿºÿ¸ÿ³ÿ¯ÿµÿ»ÿ»ÿ·ÿ§ÿpÿHÿLÿQÿYÿ\ÿ^ÿbÿdÿgÿjÿjÿjÿmÿsÿ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿlÿdÿ`ÿZÿOÿEÿ@ÿ<ÿ=ÿ>ÿAÿ@ÿCÿHÿLÿNÿOÿRÿ7~7t7~7t7}7s7}7s6}6r5}5s4~4t3~3t3~2u2~2u2~0u1~0u0~0u/~.u.~.v-~,v-~-u-~-u-~-t.~.t.~.s..t..u..u..u11w11w00w0~0v0~0v0~0w0~0w-~-x.~.x-~.y0~.y.~.z-~-z.~.{-~-{-~-{,~,{,~,|,~,|,~,|-~-|,~,},~,},,},,~-}-~,}.~.}.~.}.~-}.~.}.~0~0~0~0|0~0|00{0.{11{11z00y22y22y11{11{34{54{33z22z22z22z22z11{1~1z1~1z0}.{0}0{-~-{-~-{-~-|-~-z,~,{+~,{+~+{*~*{*}*{))|(~({(~({&~%|&~%|%~%|$~$|#~#~!~ ~!~!} ~ } ~ } ~ } ~ }~} ~ ~ ~"}$*08@|EHzKPwSVwZbwfktonsies^Ot0*z))|)*|*-|1,z,,{,-{-*|%#|$'},0|01{6.}Z
vjjko~lhz\UxJIvJIwEKxROyX]za`yaazZXz_^yZKz/){4A}@5}.-|0:|AC}B<}64~469;?CDE~HD}BA}BC}<7}89}99|:8|84z0-z-,z.0z01z11z12z21z12z22z22{21{00{00{02{21{1/{/0{10{01{12{24{44{46{66{68|98|9;|===@~JVdnl
n
je
[
M;3/,+,,-~-.~./~/0~0//00221236@EEIMSU[|a
czho{tyyzx
tql
hehyoeIzGVd}ea{cyi|ssy}yjv~vhy~ygzycwt`qv`wza|}f}}g~g}}h{yivsmrnrno{o}nm{l
mxnjugguebtaaub]vOKwSPwD BvD¤CsB§CqD§Rnw¡q®¶zº¼}½¼~½½½¾¾¾}¾¾}¾¿¿¼»»µ«´¸·®
o_cdddgjjkmnnyryq}l
ifebaa__b
gm
pqrtuvx~}|}}vuvv
x
v~kaUME>98:;<>@@
DFILLLz7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ0ÿ1ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ-ÿ.ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ1ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ,ÿ+ÿ*ÿ*ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿ ÿ ÿ!ÿ$ÿ'ÿ,ÿ4ÿ=ÿCÿFÿGÿHÿKÿQÿUÿZÿaÿfÿhÿlÿkÿiÿeÿZÿBÿ-ÿ(ÿ*ÿ+ÿ+ÿ*ÿ*ÿ(ÿ(ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ'ÿ#ÿ"ÿ%ÿ%ÿ)ÿ,ÿ2ÿ;ÿZÿuÿlÿiÿkÿjÿkÿeÿ[ÿMÿLÿEÿGÿEÿ9ÿ5ÿJÿPÿQÿWÿ`ÿ_ÿ[ÿ_ÿ_ÿZÿZÿ[ÿ_ÿVÿSÿ:ÿ+ÿ/ÿDÿMÿ9ÿ/ÿ/ÿ3ÿ@ÿDÿEÿEÿ@ÿ9ÿ9ÿ:ÿ8ÿ8ÿ<ÿ>ÿ?ÿBÿDÿHÿIÿDÿ?ÿ?ÿ:ÿ:ÿ7ÿ5ÿ5ÿ5ÿ5ÿ8ÿ9ÿ9ÿ8ÿ3ÿ.ÿ-ÿ,ÿ.ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ1ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ6ÿ6ÿ8ÿ7ÿ9ÿ8ÿ9ÿ9ÿ:ÿAÿNÿYÿhÿmÿmÿjÿeÿ\ÿNÿ>ÿ5ÿ2ÿ0ÿ.ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ1ÿ2ÿ2ÿ0ÿ/ÿ0ÿ4ÿ4ÿ6ÿ8ÿ<ÿ@ÿGÿHÿNÿVÿ\ÿdÿiÿoÿuÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿÿ|ÿhÿSÿXÿ_ÿdÿbÿdÿoÿwÿxÿwÿvÿvÿyÿyÿwÿvÿpÿnÿnÿoÿtÿwÿzÿ{ÿ}ÿ}ÿÿ~ÿ~ÿ~ÿ}ÿ|ÿzÿyÿvÿtÿrÿqÿqÿpÿnÿnÿmÿlÿjÿfÿdÿbÿaÿaÿ`ÿaÿ`ÿVÿMÿMÿPÿHÿCÿCÿCÿCÿBÿDÿKÿoÿÿ¬ÿ¶ÿ¹ÿ¼ÿ½ÿ¼ÿ¼ÿ¼ÿ¼ÿ½ÿ½ÿ½ÿ½ÿ¼ÿ¼ÿ»ÿ»ÿ¸ÿ·ÿ±ÿÿ¦ÿ¦ÿ«ÿ¯ÿ®ÿ¤ÿÿÿxÿzÿwÿwÿvÿvÿwÿwÿwÿvÿyÿyÿ}ÿ}ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿ}ÿxÿnÿgÿZÿOÿEÿ>ÿ;ÿ:ÿ7ÿ8ÿ7ÿ9ÿ<ÿ=ÿ?ÿ@ÿAÿDÿFÿGÿJÿJÿJÿ7~7t7~7t7}7s6}6s6}6r5}5s4~4t3~3t3~2u2~2u3~1u0~/u0~0u/~.u.~.t-~-t-~-t.~.t-~-s-~-s-~-s-/t..t//u00t00v00v00v0~0v1~1v1~1w0~0w0~0x0~0x0~0y/~/y0~0z/~/z/~/{.~.{-~-{-~-{-~-|,~,|,~,|,~,|-~-}-~-}..}..~.}./}-.}.~-}-~.}.~/}/~0~0~/~/|/~/|/~/{//{//{00z//y00{00{11{22{21{12{12z22z22{11{10{00|0~0{0~0{0~0|0~0|/~/|.~.|-~-|-~-{,~+{+~+{*~*{)~){)}){)(|'~'|'~'|'~&|%~$|$~#|"~"|!~!~"~"~!~!~ ~ ~ ~ }~}~} ~ } ~ }!~#)/6>E|FFzFIwNUxY`wehujitigtW7v.-y,-|)*|*)|)(z(({(*{++|)(|''|%&~*(~Ipjjfgec}PA{KMyIDvB@w@FxNXybdz\Zzb_yWQxR\yYOzD:{36{=2|./}08}?E}FB};>~@:889:;?HK}F?}:7|56{54{46{7;{;8z52z/-z-/z/0z01z11z12z22z2/z//{/0{00{0/{//{/0{0/{/.{/0{01{11{12{22{21{23{46|67|77|75~9CP^hjh
f
]P?642~1.~,,~,,~,.~/.~..~.//00110222000/0//0026<CIOU\bjn|rvux{m}~jhhmqetabtf}nnv~wkx}vju}unu}tormkhgjgjimsiwygz|g~f~}f~~g|yhwukvsrtrxp}p}o{miyfdudat__t_at[KuIJvLIvE£CsD¥@oA£Glmn©²x¹ºº¼¼ºº¹¸µ³±
°®«~¨¦~£¡~
|¤£|
{x
xw
v
tooi
f
edccb`]\_`cgmpsuuxz{w
w{{vqiaZMC=<==
=<:989<???C
DDDDDD7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ3ÿ1ÿ0ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ1ÿ0ÿ2ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿÿÿÿÿ ÿ ÿ ÿ ÿ!ÿ&ÿ)ÿ/ÿ6ÿ>ÿDÿGÿGÿHÿGÿKÿPÿXÿ]ÿ`ÿdÿdÿeÿeÿbÿIÿ-ÿ+ÿ+ÿ)ÿ+ÿ'ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ)ÿ)ÿ'ÿ&ÿ&ÿ'ÿ>ÿhÿfÿgÿfÿgÿdÿcÿNÿ@ÿDÿMÿMÿLÿ>ÿ<ÿJÿNÿIÿWÿ_ÿcÿ_ÿ_ÿ`ÿdÿ`ÿTÿTÿZÿ]ÿaÿOÿGÿBÿ;ÿ4ÿ8ÿ8ÿ7ÿ9ÿ1ÿ1ÿ4ÿ<ÿCÿCÿBÿBÿ?ÿ:ÿ9ÿ9ÿ7ÿ7ÿ8ÿ;ÿFÿLÿMÿEÿ=ÿ7ÿ4ÿ4ÿ3ÿ5ÿ5ÿ6ÿ7ÿ:ÿ<ÿ>ÿ<ÿ8ÿ3ÿ0ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ3ÿ4ÿ4ÿ4ÿ4ÿ4ÿ8ÿDÿQÿ^ÿgÿgÿdÿ_ÿUÿCÿ5ÿ4ÿ4ÿ1ÿ0ÿ/ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ1ÿ0ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ.ÿ-ÿ.ÿ.ÿ,ÿ.ÿ.ÿ/ÿ3ÿ7ÿ<ÿ@ÿGÿMÿSÿYÿ^ÿdÿhÿlÿpÿsÿxÿÿ
ÿÿÿÿsÿgÿhÿqÿyÿ|ÿ{ÿxÿwÿwÿvÿqÿlÿgÿdÿ\ÿTÿVÿ`ÿgÿmÿrÿwÿxÿzÿ}ÿ~ÿ}ÿ~ÿ}ÿÿ|ÿ}ÿ{ÿyÿxÿvÿuÿtÿsÿrÿqÿoÿmÿjÿfÿbÿaÿ_ÿ_ÿ_ÿ`ÿ]ÿPÿHÿJÿIÿHÿDÿAÿCÿCÿGÿQÿjÿÿ£ÿÿ°ÿ±ÿ±ÿ±ÿ±ÿÿ¬ÿªÿ©ÿ§ÿ¤ÿ¤ÿ¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ}ÿyÿpÿjÿcÿZÿOÿGÿBÿ;ÿ9ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ<ÿ<ÿ:ÿ9ÿ:ÿ=ÿ?ÿAÿ@ÿCÿDÿDÿCÿCÿAÿAÿ8~8s7~7s6}6t7}7t6}6t6}6t5}5t3}3t3~2u2~2u2~2u1~0u1~1t0~/t/~/s/~/s-~-s-~-s-~-s.~.s..t..u..u//v//v00v00v00v11v11v0~0v0~0v0~0v0~0v/~/x/~/x//y//y/~/y.~.z-~-{-~-{-~-{,~,{-~-{-~-{,~+{-~-|.~.}.~.}.}.~.}/~/}//}/~.}.~.}.~.~.}.~.}.~.{.~.z.~.{.~.{//z//z00z00z11{22{22z11z21{1~1{0~0z0~0{/~/{0~0{0~/|/~/|.~.{-~-{.~.{.~.|-}-|-},|+~+{*~*{*~*{*~)})~)|(~'|'~'}(~&}%~${$~#{$~#|#~"|"~"~!~!~!~!~ ~ ~ ~ }~}~|~~ ~ #~%)/6?F|HH{HHxKOyTYy[^x_bv`Ww9'y*)|'(|((|((|)(|((|()|++|+*|(){,$|Cl~`
bbdce}P;|B
<|:KzG
>w=EyDSzdaz]]yaby_cx^ayb`ybTzHD{HV|J>~BG}9,}05};?}BG~B:}76~6336AJ}MG}B:{53{35{56{79{>?|A>|:5z1.z./z/0z01z12z35z51z0/{//{/0{0.{./{//{//|//|/0|00|00|00|00|00|00|01|12|22|23}8C~Q^cb`XJ;44~30}//}/-},,~,,~,,~,-~-/~/0~111111121000/.-----..--.13;?DHLRW|^dtippuwqwrqlmosxmwwjyvms~prj~as]XsMHqQZt]drmrnrwj}~g~~e~~d}~d}|h{ymwvrutvus|p~lhzf
aw_`t`_s_[tNLtJEoE¡HpPVo^jovv ¤¦¦¦}¤
£} |zyyzy
xv
u
t
t
sqolh
g
feddcceffbaaadgkntw~
|{zv}rnia\ULF>;:9:89;<<==<<;:<>>BBA
BC@@@?7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ*ÿ+ÿ*ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ)ÿ/ÿ4ÿ<ÿBÿGÿHÿIÿIÿIÿPÿUÿVÿWÿZÿYÿZÿWÿFÿ-ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ+ÿ+ÿ,ÿ,ÿ,ÿ)ÿ&ÿ&ÿ=ÿmÿdÿ[ÿ[ÿaÿfÿhÿSÿ>ÿ@ÿ;ÿ7ÿ6ÿ@ÿFÿ=ÿ5ÿ5ÿ6ÿPÿcÿcÿcÿdÿdÿdÿaÿ_ÿ`ÿdÿeÿbÿcÿXÿLÿDÿGÿJÿAÿ;ÿ@ÿAÿ:ÿ1ÿ.ÿ0ÿ2ÿ8ÿAÿDÿEÿ?ÿ6ÿ3ÿ4ÿ0ÿ1ÿ2ÿ:ÿBÿEÿFÿBÿ;ÿ7ÿ4ÿ4ÿ5ÿ5ÿ7ÿ9ÿ;ÿ=ÿ?ÿAÿAÿ>ÿ:ÿ5ÿ1ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ4ÿ4ÿ2ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ6ÿAÿMÿZÿaÿ]ÿZÿRÿEÿ9ÿ1ÿ2ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ1ÿ1ÿ2ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ.ÿ.ÿ/ÿ0ÿ0ÿ3ÿ6ÿ:ÿ>ÿDÿIÿMÿSÿVÿ[ÿ^ÿ^ÿ^ÿaÿfÿjÿkÿlÿlÿjÿgÿdÿ]ÿUÿQÿMÿMÿOÿIÿEÿQÿ^ÿfÿjÿmÿpÿuÿ{ÿÿÿÿ|ÿ|ÿ|ÿ|ÿ}ÿ}ÿ{ÿyÿyÿvÿvÿtÿtÿqÿmÿjÿhÿeÿcÿaÿaÿ`ÿ`ÿ`ÿTÿLÿNÿQÿXÿ_ÿkÿsÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿwÿmÿfÿdÿaÿYÿQÿKÿHÿAÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ:ÿ;ÿ<ÿ<ÿ;ÿ<ÿ<ÿ=ÿ=ÿ<ÿ<ÿ:ÿ;ÿ<ÿ=ÿ?ÿ?ÿBÿBÿBÿBÿ@ÿ@ÿ?ÿ@ÿ7~7r6~6r7}8s7}7s6}6t5}5t4}4t2}2t1~1u2~2u3~3u1~0u/~/t/~/t/~/t/~/t-~-s.~.s.~.s/~/s..t//t//u..v00v00v00v11v11w11w1~1w1~1w1~1x1~1x1~1y1~1y00y//y//y..z.~.{-~-{-~-{-~-{-~-{-~-{-~-{-~-|.~.}.~.}/}/~/}/~/}//}/~.}.~.}.~.}.}.~.}.~.}.~.|.~.|.~.|//{//{/0{//{00{00{22{11{11|0~0|0~0{0~0|/~/|0~0|/}.|.}.|.~-{.~.{-~-|,~,|++|++|+~+}*~*})~({*~(}'~'|'~'|&~&|%~$|#~#{#~"{#~"{"~!{!~!~!~ ~ ~ ~ ~ ~~}~}~|~~!~"%~()-
3;A|DF{HJxLOxRSxUUxSMx?4y)'{''{')|)(|((|()|))|)+|+-}++|)*~0ll`_adfV8zADx<2x39y@DyBKzEIz_bzaf{fb{c_{cd|dgzegzuKz@>{?=}>@}A9{0/{.7{EE{D@{82{1.|-/}29}@B}B?{;6{54{33|68|:<|?B|B?{:6{20z11z00z12z34z45z22{00{//{..{..{/.{..|..|.-|-.|./|//|//|//|/.|..|..|..|./}3>}IW[VSLA7~0/}//|..}.-}-,~,,~,,~-.~..~/1~000001111100/.-.-,---..0..//0/00/1568<>
ACEHM
PTV~VXUSNECCFKF?DR[a{flwovr}jg||d}|d|}f|{iyxnwwrsqwomzj|g}ezbcwacub\s_kvq{wz~|yyx
zy
y
z
z
zz{{
x
y
w
vtrpl
iidc
acefhhgfddde
fh
lwlu_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<=
=?BCC
B@BD7ÿ7ÿ6ÿ6ÿ7ÿ8ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ3ÿ1ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ(ÿ(ÿ*ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ#ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ&ÿ)ÿ,ÿ0ÿ4ÿ<ÿAÿDÿFÿGÿJÿLÿNÿPÿRÿRÿSÿOÿGÿ7ÿ-ÿ'ÿ'ÿ'ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ,ÿ+ÿ(ÿ,ÿeÿkÿhÿhÿfÿcÿgÿWÿ7ÿAÿHÿGÿ8ÿ-ÿ/ÿ7ÿ:ÿ=ÿ>ÿDÿ?ÿPÿeÿeÿgÿjÿeÿcÿdÿdÿbÿ_ÿaÿeÿbÿfÿdÿGÿBÿBÿDÿEÿEÿEÿEÿ>ÿ3ÿ/ÿ.ÿ2ÿ7ÿ9ÿAÿEÿ>ÿ3ÿ/ÿ.ÿ-ÿ.ÿ/ÿ2ÿ9ÿ>ÿAÿ?ÿ9ÿ7ÿ6ÿ6ÿ4ÿ5ÿ5ÿ7ÿ8ÿ;ÿ>ÿ@ÿ@ÿ@ÿ>ÿ:ÿ6ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ1ÿ<ÿFÿSÿVÿRÿNÿIÿ@ÿ6ÿ-ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ/ÿ/ÿ.ÿ0ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ/ÿ0ÿ.ÿ.ÿ/ÿ1ÿ0ÿ4ÿ6ÿ8ÿ=ÿ>ÿ?ÿBÿAÿAÿ>ÿ;ÿ;ÿ;ÿ>ÿBÿBÿ>ÿ?ÿEÿNÿSÿZÿbÿeÿnÿuÿ}ÿÿÿ~ÿ}ÿ}ÿ|ÿ}ÿ~ÿ|ÿ{ÿyÿxÿyÿyÿvÿtÿqÿoÿnÿkÿjÿgÿiÿlÿrÿvÿxÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿzÿrÿiÿ^ÿTÿJÿCÿ?ÿ@ÿ@ÿ?ÿBÿAÿ=ÿ<ÿ<ÿ;ÿ<ÿ<ÿ<ÿ<ÿ;ÿ=ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ<ÿ;ÿ<ÿ<ÿ=ÿ=ÿ@ÿCÿDÿDÿEÿEÿFÿFÿ6~6s7~7s6~8s6~6s7}7s5}5s5}4s3}3s2~2t2~2t2~1u0~/t.~.s/~/s//t//t/~0s/~/s/~/r.~.s.~.r/~/s//t//t/~/v0~0v0~0w1~1w11w11w11w11w11x11x11y11y/~/z.~/z/~/{/~.{.~.{-~-{,~,|-~-|-~-z-~-z-}-z-}-|-~-}-~.}00~//~/}/~/}/~.}.}.}.}..}.~.}.~.~-~-~-~-}-~-|.~.|.~.|/~/|/~/|/~/|/~/|//{/0{00}0}0|0~0|0~0|/~/|/~/|/}.|.}.|.~-{,~,{,~,|+~+|+~+}+~+}*~*~)~(~)~)}(~(}''}&&}&%}%%}$~#|#~"|"~!|"~"|!~!~!~!~ ~ ~~~~ } } |~~~!}$&}+
.~27~?B{HIzIJyLNxOPxQQwMHy:,z'&{&&{'(|)(|(({(*|*)|)*|))|*,}-]legdbfV,}<FyRQtJ=t33v7;w55{:R{fq{nm|e^}ab~c_}]a|f[zROzKKzJHzJI|HB{80|/1z25z@G{D:|1-|--}--~16}<;};7{55{54z45{68{=={>?|@=|97{32{10{11{24z45{54{44{44{10{..{.-{-.|..}--|--|--|-.}./}/.}.-~-.~.-}--}-,}09}BM}UP~LF?5~-,},,},,},,},,},+}+,~-.~..~//~/0~00~00~01~11~10~//~/.~.....////0000000000........./0/001111367:;;==AG
KPW_~hruy~nh}|d}~d}|f{{i{zjvvltsoqornoyt}z|}{~zyxxxxyx
x
{
}
~
}yvusplie}a`
c
gghjjigfcc
c~{ftmkiav\PB@@?B@>
<<
<==;;;;;;<;<:<=???@B
EF
FG
G
H
G
7ÿ7ÿ7ÿ7ÿ6ÿ8ÿ6ÿ6ÿ7ÿ7ÿ6ÿ5ÿ5ÿ4ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ!ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿÿÿÿÿ ÿ ÿÿÿ!ÿ"ÿ#ÿ&ÿ(ÿ,ÿ0ÿ3ÿ9ÿAÿFÿIÿIÿIÿJÿLÿNÿOÿPÿPÿNÿIÿDÿ4ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ)ÿ)ÿ*ÿ(ÿ(ÿ(ÿ(ÿ)ÿ-ÿ[ÿnÿdÿgÿeÿ`ÿhÿ[ÿ-ÿ2ÿ>ÿGÿTÿYÿXÿQÿFÿ8ÿ:ÿ<ÿ9ÿ8ÿ?ÿ^ÿlÿtÿrÿlÿhÿjÿeÿ^ÿ`ÿaÿaÿfÿfÿ\ÿSÿRÿOÿLÿNÿOÿNÿLÿLÿHÿ?ÿ3ÿ/ÿ2ÿ3ÿ7ÿ=ÿCÿFÿ?ÿ4ÿ/ÿ-ÿ-ÿ-ÿ,ÿ-ÿ/ÿ4ÿ7ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ4ÿ6ÿ7ÿ9ÿ<ÿ=ÿ>ÿ@ÿAÿ?ÿ<ÿ9ÿ6ÿ2ÿ1ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ5ÿ5ÿ5ÿ5ÿ7ÿ7ÿ5ÿ2ÿ2ÿ/ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ7ÿAÿJÿRÿOÿJÿFÿ?ÿ5ÿ-ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ1ÿ3ÿ3ÿ6ÿ6ÿ7ÿ7ÿ8ÿ;ÿ<ÿ?ÿEÿNÿYÿcÿnÿwÿ}ÿÿÿ~ÿÿÿÿ~ÿ}ÿ|ÿzÿyÿ{ÿ{ÿzÿyÿwÿuÿvÿwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿrÿrÿzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿxÿsÿoÿhÿ`ÿQÿBÿ=ÿ<ÿ>ÿ<ÿ;ÿ;ÿ<ÿ<ÿ<ÿ<ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ=ÿ>ÿ?ÿ?ÿ@ÿBÿBÿBÿCÿEÿGÿGÿGÿHÿHÿHÿGÿ8~8t7~7t7~7t6~6t6}6u5}5u4}4u3}2u2~2v2~2v2~1u1~0t0~0t/~/t/0t00t//s//s/~/r.~.s/~/u/~/u..v..w/~/w/~/w0~0w0~1w11x11x11x11x0~/x.~/x1~1y0~0y/~/y/~.y-~-y.~,y-~-{,~,{,~,|-~-|-~-|,~,|,,|,,}.~.}.~.}/~/~/~/~/}/~/}/~.}.}.}.}.~.}.~.}-~-}.~.}.~-|-~.{.~.{.~.{.~.|.~.|/~/|/~.|//}00}00}0}0|0~0|0~0|0}0|/}/|/}.|.}.|.~-|-~-|,~,|+~+|,~,}+~*}*~)~'~'~'~'}(~(}'&}&%}%~%}$~$}#~"|"~!|"~!|!~!| ~ ~"~"~ ~ ~~~~~ ~ } ~ } ~#%}()},1~5<~CI}JI|HIzJKyNMyLLyE?y.'y&%{&'{((|('|'){()|((|(({)*}(Qmcedac]./7=F{PWvXYtM7t29w:?{<\|tr|rp~cY~a^~[`}fh{e]zWSzQQzRQzPN{LJyC9z33z/1{8=zCA|81|-*{*+}*,}.1}35|44{56z78{87{9<{>@|A>|=:{75{20{/1{21z34{55{68{96|43|10|0.|-.}..},,|,,|,+|+,},-}--}--},,},-~--~-,},5}@F}QN~IE~@7}.(}()}))})*}**}*,},-~-.~.-~-.~./~//~/0~00~01~11~00~0/~/////////00000000000/...-..--...//~//~/0001222344556;IWcmzxoig}g~hzyixzh{{h{{k|lnoqvvxxy
{}|uolhffkmpqs{th}nz|z~~ z~ w wurp
n|
zk{{j}i
e
eimoomkifcced}
weshm]M|@?<;:
:;
;<<;;:;;;>@@AABCDFFFHGGG
FF
F6ÿ6ÿ7ÿ7ÿ9ÿ9ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ.ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ)ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ!ÿ"ÿ!ÿ!ÿ!ÿ ÿ ÿ!ÿ!ÿ ÿ ÿÿÿ ÿÿÿÿ ÿ!ÿ#ÿ%ÿ(ÿ+ÿ+ÿ.ÿ3ÿ7ÿ>ÿEÿJÿJÿHÿEÿDÿFÿGÿIÿKÿJÿGÿCÿ;ÿ+ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ'ÿ'ÿ)ÿ(ÿ'ÿ(ÿ(ÿ(ÿ)ÿ+ÿ%ÿOÿiÿbÿdÿeÿ`ÿeÿaÿ0ÿ,ÿ3ÿ8ÿ=ÿGÿNÿUÿYÿXÿPÿ4ÿ/ÿ9ÿAÿEÿHÿlÿtÿtÿtÿoÿ`ÿXÿ`ÿ[ÿZÿ_ÿcÿhÿgÿ`ÿYÿVÿTÿTÿQÿPÿPÿQÿPÿLÿGÿAÿ:ÿ5ÿ3ÿ1ÿ2ÿ7ÿ=ÿ@ÿ:ÿ2ÿ,ÿ*ÿ*ÿ*ÿ)ÿ*ÿ*ÿ-ÿ0ÿ1ÿ2ÿ3ÿ5ÿ7ÿ8ÿ8ÿ8ÿ7ÿ7ÿ9ÿ<ÿ>ÿ?ÿ?ÿ>ÿ=ÿ:ÿ7ÿ5ÿ2ÿ1ÿ1ÿ2ÿ1ÿ3ÿ3ÿ4ÿ6ÿ7ÿ8ÿ7ÿ6ÿ5ÿ4ÿ2ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ1ÿ<ÿEÿNÿMÿHÿEÿ@ÿ9ÿ4ÿ*ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ+ÿ,ÿ+ÿ+ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ:ÿGÿVÿbÿlÿvÿ}ÿÿÿ}ÿÿÿÿ~ÿ~ÿ{ÿvÿsÿvÿzÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿwÿpÿiÿcÿ[ÿUÿPÿMÿHÿHÿMÿQÿ`ÿ]ÿ_ÿdÿnÿsÿqÿlÿgÿsÿ
ÿÿÿ£ÿ¢ÿ¡ÿ¡ÿ¡ÿ¡ÿÿÿÿÿÿÿÿ}ÿyÿtÿrÿsÿsÿxÿ{ÿ~ÿÿÿÿÿÿ
ÿ
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ~ÿ~ÿÿÿÿÿÿyÿqÿjÿ]ÿLÿ@ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ<ÿ<ÿ<ÿ;ÿ<ÿ>ÿ>ÿAÿCÿCÿDÿDÿEÿFÿGÿGÿGÿGÿGÿFÿFÿEÿEÿEÿEÿ7~7s7~7s6~6t6~6t5}5u5}5u4}4u3}2u2}2u22u1~0u0~0t00t00t//t0/t//s//s/~/r-~/t/~/u/}/v/~/w.~.w.~.w.~/w0~0w0~0w11x11y11z00z0~/z/~/z.~.y.~.y.~.z.~-z--z--z--{,,{,,|,,|+,|,,|,,|,,}-~-|.~.~/}/~/}/~0~0/~//~/}.~.}.~.},~.|.~.}.~.}-~-|-~-|.~.|.~.|.~.}.~.}.~.},~-}.~.}/~/}/~/|/~/|0~0|0~0|/~/{/~/{/}.}.}.~.~-~-~-~,~,~+~+~+~+}*~(}(~(}'~'}'~'}%~%{&~&{%~%{$~${$~#{#~"|"~!|!~!|!~!| ~ { ~ { ~ {~|~{~} %~'*}+
,}04}9@~FI}IH{EByBFyGGzFEy?5y)&y&&z''z((|((|''|(({)*{**|*
T~m`b~ea~`c1*566}=DzIPwWXuT>u==v><zKl|ps}tU{V`}ca}be|ee{gcx]YxXWxSPyPQyQMzLFx>8y1/{13{8<|<3|,+|*)|))|*,|/1z35z89z:9{:7{77{8:|>@|AA{@={:6{42{01{23|34|56}75}35}42}21}10}..}--|-,|,+}++}++}+,},+~+,~,+~+*~*)~).~8>~FK~GD~A:~5.}''}('}'&}((}()}))})(})+~+-~-.~/.~./~00~10~00~12~21~11~1/~/////////00//11//..--.......//~/1~/00/1112344442249CS
`k{syphji}|iwqjo
tlx
}lg
gj
mo
ssw|~woh`XS
NLFCDDDE~M]{XRzOTzZ_ygny|~ yxxyyw|tyoiyddveiumsow|j~jlmqqook}~j~f~}c~~ddb~zergpZF><;
;<<><>>?ABCEFGGFFFFFFFFEEECCD6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ4ÿ4ÿ3ÿ2ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ"ÿ$ÿ'ÿ,ÿ-ÿ/ÿ3ÿ9ÿ<ÿ@ÿFÿIÿIÿGÿBÿ?ÿ?ÿBÿEÿFÿEÿAÿ=ÿ/ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ+ÿ.ÿ_ÿkÿaÿaÿbÿ_ÿdÿdÿ5ÿ&ÿ2ÿ9ÿ;ÿ>ÿ>ÿBÿJÿOÿPÿPÿIÿ>ÿ;ÿ<ÿAÿCÿTÿmÿqÿrÿnÿeÿ[ÿYÿcÿaÿeÿkÿmÿkÿhÿeÿ^ÿ]ÿ[ÿYÿVÿRÿPÿSÿRÿOÿMÿJÿEÿ=ÿ3ÿ0ÿ/ÿ2ÿ4ÿ9ÿ9ÿ3ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ+ÿ.ÿ0ÿ1ÿ4ÿ8ÿ9ÿ:ÿ;ÿ<ÿ9ÿ7ÿ7ÿ7ÿ9ÿ=ÿ@ÿBÿBÿAÿ=ÿ:ÿ:ÿ6ÿ5ÿ2ÿ0ÿ0ÿ1ÿ3ÿ3ÿ4ÿ5ÿ6ÿ5ÿ5ÿ5ÿ5ÿ5ÿ4ÿ3ÿ2ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ,ÿ2ÿ:ÿ@ÿHÿHÿCÿ@ÿ<ÿ6ÿ/ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ*ÿ*ÿ+ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ/ÿ1ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ1ÿ1ÿ2ÿ3ÿ1ÿ1ÿ1ÿ1ÿ1ÿ1ÿ2ÿ3ÿ5ÿ5ÿ5ÿ5ÿ2ÿ2ÿ2ÿ4ÿ7ÿCÿQÿ^ÿiÿpÿzÿÿÿÿÿÿ}ÿ|ÿzÿvÿpÿoÿqÿuÿyÿ}ÿÿÿÿÿÿÿÿÿÿÿ}ÿ}ÿ}ÿ~ÿ{ÿrÿiÿ\ÿTÿLÿIÿIÿKÿLÿOÿNÿLÿJÿHÿHÿIÿKÿQÿQÿOÿKÿIÿKÿTÿ_ÿoÿÿÿÿÿÿÿÿÿÿÿÿ{ÿsÿmÿeÿ`ÿ\ÿYÿVÿVÿXÿ[ÿbÿeÿiÿpÿsÿvÿ{ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿ~ÿ~ÿ|ÿ{ÿ{ÿ|ÿ}ÿ}ÿ|ÿ|ÿ~ÿ~ÿÿÿÿ|ÿwÿoÿdÿWÿFÿ=ÿ=ÿ=ÿ@ÿAÿAÿ?ÿ@ÿ@ÿAÿCÿFÿGÿFÿFÿGÿGÿGÿGÿFÿFÿFÿFÿFÿFÿEÿEÿEÿFÿCÿCÿ6~6u6~6u6~6u5~5u4}4u5}5u4}4u3}2u2}1u00u0~0u/~/t//t00t0~0t1~0t/~/s/~/s/~/t.~-u-~-v.}.v.~.w.~.x.~.x.~.x.~.y/~/y//x//y//z//z.~.{-~-{-~-z-~-z-~-z,~,z,+z++z++{++{**|,,|++|,,|,,|,,}-~-|.~.~.}.~/}/~/~//~//~/~.~.~.~.}-~,|-~.}.~.}-~-|-~-|.~.|.~.|.~.}.~.}-~-}-~/}.~.}.~/}/~/|/~/|/~/|/~/|/~/|.~.|/}.}.}.~-~,~,~,~,~,~+~+~)~)})~(}'~'}'~'}'~'}&~&{%~%{%~${$~#{#~#{#~"|"~!|!~ |!~!|~{ ~ {~{~|~~~ "$%~)
,}.2}7;}@C~HH}GF{A>y>ByCEzCBy:,y'%z%&z&'z'({(({)'{)({*)|3
e~l^`|_]}`j?"/4:~<@|B@zCHwJJu?8u;Au@<xZq{qs{om{i]{eg{hl{ppzidxa^x^\xWSyPQyPOzNKzGC{:3y00y54z73z.+{*){)({(*{,-z04z57z89{:8{67{77|:=|AB{B>{<;{:7{52{11|12|24}56}77}97}5
5}3
4}4
2}2
1|33}30}/.},,},-}--~-+~+*~*+~))~)*~-6~<D~JA~?:~51~*'~&'}''}%%}''}&%}&'}'(|**|**~++~+.~/.~.0~01~11~12~21~11~0//....////00..00-----------04455322012456766333347?N
Ze{ovr|li~{hzxiu
nlk
llo
uhx
{e||ez
zgy
xhtslqnqmlug^~VNJJIIKMLNNJGGHJLKHFG}KN{Zm|{}|
{
}yzvqjb
[UMGDBEGOU
Y^}_dteiplqpt
yoxyq}rr~}p|{p{{pzzn{{j|}g||d{|d}cb|tfkbrUI@
AABCCDEEFFFGGGGFGHHGGFFFFFFGG6ÿ6ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ4ÿ4ÿ5ÿ5ÿ5ÿ4ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ#ÿ#ÿ"ÿ#ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿ ÿ!ÿ#ÿ%ÿ(ÿ+ÿ/ÿ3ÿ8ÿ<ÿBÿEÿGÿIÿHÿFÿCÿAÿ>ÿ>ÿAÿBÿDÿBÿ?ÿ7ÿ)ÿ'ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ+ÿ'ÿ:ÿkÿiÿ^ÿ_ÿ_ÿ]ÿbÿoÿGÿ ÿ-ÿ2ÿ3ÿ8ÿ:ÿDÿGÿKÿFÿEÿHÿGÿ=ÿ>ÿQÿdÿ[ÿDÿOÿjÿlÿmÿgÿjÿiÿeÿgÿkÿmÿpÿqÿnÿiÿeÿfÿ`ÿ\ÿZÿWÿTÿQÿOÿMÿLÿLÿKÿGÿFÿ>ÿ7ÿ1ÿ1ÿ4ÿ3ÿ6ÿ3ÿ.ÿ-ÿ+ÿ)ÿ)ÿ(ÿ(ÿ)ÿ*ÿ+ÿ-ÿ1ÿ3ÿ3ÿ7ÿ9ÿ:ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ:ÿ=ÿ>ÿ>ÿ>ÿ<ÿ<ÿ;ÿ9ÿ7ÿ5ÿ2ÿ1ÿ1ÿ1ÿ2ÿ3ÿ4ÿ3ÿ5ÿ5ÿ7ÿ9ÿ:ÿ7ÿ6ÿ7ÿ5ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ(ÿ+ÿ1ÿ9ÿ@ÿFÿCÿ>ÿ:ÿ5ÿ3ÿ.ÿ(ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ&ÿ'ÿ)ÿ)ÿ)ÿ*ÿ)ÿ*ÿ,ÿ,ÿ-ÿ.ÿ.ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ1ÿ5ÿ6ÿ8ÿ8ÿ4ÿ3ÿ2ÿ0ÿ2ÿ3ÿ5ÿ6ÿ7ÿ7ÿ8ÿ8ÿ3ÿ3ÿ3ÿ3ÿ2ÿ4ÿ6ÿ?ÿJÿYÿcÿmÿuÿxÿ~ÿÿÿ}ÿ{ÿyÿvÿrÿmÿjÿkÿoÿrÿvÿ{ÿ|ÿ|ÿzÿxÿwÿuÿsÿqÿnÿiÿiÿdÿaÿ\ÿXÿRÿPÿNÿKÿKÿKÿKÿJÿIÿFÿCÿCÿDÿFÿEÿCÿCÿBÿ@ÿAÿDÿOÿZÿ[ÿYÿYÿYÿYÿYÿVÿQÿNÿFÿAÿ=ÿ9ÿ8ÿ7ÿ7ÿ9ÿ?ÿEÿJÿRÿVÿ\ÿ_ÿ_ÿ_ÿaÿcÿeÿiÿmÿpÿqÿvÿzÿ}ÿÿÿ~ÿ|ÿ{ÿzÿzÿzÿzÿ{ÿ{ÿ{ÿ|ÿ|ÿ|ÿ{ÿ{ÿ|ÿ~ÿÿÿÿyÿsÿkÿaÿWÿKÿCÿDÿEÿEÿEÿDÿDÿEÿEÿEÿEÿEÿEÿFÿFÿEÿFÿFÿFÿFÿFÿFÿFÿEÿEÿGÿFÿGÿEÿ6~6v5~4v5~5u6~6u4}4v3}3v3~3u2~2u1~0v2~2v1~1v0~0u0~/t/~/s/~/s/~/s0~0t0~.t.~.t.~.v.~.x-~.w/~/y.~.z-~-z-~-y/~.z.~.z.}.{.}.{.~.{-~-{-~-|,~,|-~-{-~-{--{,,{+~*z*~*z*~*{*~*{))z*+z,,{,,{-~-}-~-}+},}-}-}.}.~/}/~0}0.}..}..}..~.}.~.-~-|,~-|-~-|-~-|-~-}-~-}-~-}.~.}.~.}.~.}.~.}.~.}.~.|.~.|/~/|/~.|..}//}/.~--~-~-~-~-~+~+~+~+})~)|)~(|'~'|&~&|&~&{&~&{$${%%{$#|$$|#"|!!| || |||} ~#$~(
+~-18<DGJL~LJ|GBz?A{BC{EFzB=z2&z('z&&z&'{''{'(z')z))|Gog~`[{^a~bjU~!
.{:<z==z;E|GXzOLwJHuB=vEOvWWvXdyjh|gi|ia|bi}qr|ojychyhdv_]vZVwTQxLIzKIzFD{A:z61z46z75z/,{,+{+){)){)*{,-z01{59{88z85z55|69|:;|<>|=={=;{97{54|30}02~22}25}689979~95~44}21}0
1}10}0/}//}.+~+-~**~**~*)~)(~)-~5;~@C>;73/,)%$&~&%}%%}$%}%%}%%~&'~''~'(~)*}*,}--}-.}/0}12}32~22~20~00~0.~0/~//.--..--,,,,,,**--/1468534422345896666521134=IV
`zi
rqv}kf~
zgy
vjr
lmjfkkojtxf|{c}{fzzfwugsqjmine_r[
XvT
Q{MLJG
EB@?=@>><<;;==???
??><<9:::8
9:
:9:<BHMS
X[^w`
^p]
_m^brehvlpyuxy}~u|~yqyypxxozzn{yj{{g|{f||f~e
|cy
tildxZM
FFDFFEECCCCCEEEEDEEFGFHHGFHF4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ3ÿ3ÿ3ÿ2ÿ3ÿ3ÿ1ÿ1ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ*ÿ+ÿ+ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿ ÿ ÿÿÿÿÿÿÿÿ ÿ ÿ"ÿ%ÿ*ÿ,ÿ.ÿ2ÿ9ÿAÿFÿKÿPÿOÿLÿJÿGÿDÿBÿAÿBÿEÿFÿIÿFÿ<ÿ,ÿ(ÿ'ÿ&ÿ&ÿ&ÿ&ÿ(ÿ(ÿ'ÿ)ÿ%ÿ(ÿ'ÿ)ÿVÿlÿcÿ_ÿ\ÿbÿhÿgÿlÿSÿÿ2ÿJÿLÿLÿNÿSÿUÿJÿGÿJÿNÿKÿIÿAÿ<ÿDÿ@ÿBÿGÿQÿ^ÿaÿeÿgÿhÿjÿiÿ_ÿgÿjÿnÿpÿlÿbÿeÿjÿgÿbÿaÿcÿ^ÿWÿTÿPÿLÿHÿGÿFÿEÿDÿBÿ=ÿ8ÿ3ÿ4ÿ6ÿ7ÿ5ÿ/ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ0ÿ2ÿ6ÿ9ÿ9ÿ9ÿ5ÿ5ÿ3ÿ4ÿ4ÿ5ÿ7ÿ:ÿ=ÿ>ÿ>ÿ>ÿ=ÿ;ÿ9ÿ7ÿ5ÿ3ÿ2ÿ1ÿ1ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ5ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ4ÿ2ÿ1ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ)ÿ-ÿ6ÿ;ÿ@ÿ>ÿ:ÿ6ÿ4ÿ1ÿ.ÿ*ÿ&ÿ$ÿ&ÿ&ÿ$ÿ$ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ+ÿ+ÿ,ÿ,ÿ.ÿ.ÿ/ÿ0ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ+ÿ,ÿ,ÿ,ÿ-ÿ/ÿ1ÿ1ÿ3ÿ4ÿ4ÿ1ÿ2ÿ3ÿ4ÿ3ÿ4ÿ4ÿ5ÿ6ÿ8ÿ6ÿ6ÿ6ÿ6ÿ6ÿ4ÿ1ÿ1ÿ3ÿ4ÿ4ÿ<ÿFÿRÿ[ÿeÿoÿvÿ{ÿ}ÿÿ}ÿyÿwÿtÿrÿkÿdÿgÿjÿlÿqÿuÿyÿ{ÿ{ÿ|ÿ|ÿ{ÿyÿxÿwÿtÿrÿoÿkÿgÿcÿ`ÿ\ÿWÿVÿTÿQÿNÿJÿFÿAÿ<ÿ:ÿ8ÿ8ÿ8ÿ8ÿ7ÿ6ÿ7ÿ7ÿ:ÿ:ÿ:ÿ:ÿ9ÿ;ÿ;ÿ;ÿ<ÿ=ÿ>ÿ>ÿ<ÿ9ÿ;ÿ;ÿ9ÿ9ÿ;ÿ?ÿEÿJÿOÿSÿ[ÿ^ÿ`ÿ_ÿ`ÿ_ÿ\ÿZÿYÿ[ÿ`ÿeÿjÿqÿvÿzÿ{ÿ{ÿyÿyÿxÿxÿzÿzÿ|ÿ{ÿyÿ{ÿ{ÿ|ÿ|ÿ|ÿ|ÿ}ÿ~ÿÿ}ÿyÿtÿmÿcÿ[ÿNÿEÿFÿFÿEÿDÿCÿDÿCÿBÿBÿBÿCÿCÿCÿCÿDÿEÿFÿGÿGÿIÿIÿHÿHÿFÿGÿFÿ5~4u2~2u4~3u2~2u3}2u2}2u1~1u1~1u0~0v0~0v/~/v/~/u/~/t/~/s.~.s.~.s.~.t/~/t.~.t-~-v-~-w+~+x,~-z-~,z+~-{,~,z,~,y,~,y-}+z*}*z*~+z+~+z,~,|+~+|,~,|,~,|++{++{+~+z*~*z)~){)~){)){)*{++|++|+~+}+~+},},},},}.}.~-}-~-}-.}..}..}..~.~.~.}-~-|-~-|-~-|-~-|-~-}-~-}-~-}-~,}-~-}-~-}-~-}.~.}.~.~/~/~-~-|.~.|.~0}-~-}-}-}-}-}-~-},~,}+~*}*~*}*~*|(~'{'~'|&~&|&~&{&~&{&&|%$|##}##}""|""| |||||}!"~%(~)-~/4;AHMPPLI|FEzB@{BB{DJ|G8{**z'$z$&z('{''|&'|$7~fmc{]_zch{hlZ&+CK|JN|MO{TS{KIyNGx@9vNaqVZrWRv]d|il}kk|qn|ln|po{b_yegygewbbw_WwSPwLHzCBzAC|B?{:3z02z54z.,{,+{++{+,{,+{**{+-z/3y57z86z52{11{36|88|;<{=>{>>{>;|95{53|12|33|10~24~42~57~75}42}2
0}01}12}3
4}3
1~/-~-*~**~))~))~((~(1~8;=:6
64
0.
+)%~%
%~%%~%%~%&~&&~&&~&&~&'~'(}()}**}*,},-}.0}10~00~01~11~11~/0~0////.-,,,+**+*+++-.0111112333223455566766644559
ANYb
kzq
xp~
j}
xiu
rjnimefqg
jpm
qnvyf{{c{zczzcx
xdv
teplhighf
cia
]mX
SrM
I{FDA>::677767789
9<<==<<=<:9889<@FJPUZ|]`r``m][mUStTZ}]
dhp}v{y{ztzxpyyny{l{{j|}i|}h{{f|}f}
|gxtmkbx[
O
EDEDCCBAAACCCDEFFHHJJIHFFD3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ,ÿ,ÿ+ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ-ÿ-ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ#ÿ&ÿ)ÿ)ÿ,ÿ.ÿ2ÿ8ÿ>ÿDÿGÿJÿKÿKÿIÿFÿEÿBÿCÿBÿCÿBÿFÿBÿ0ÿ(ÿ+ÿ'ÿ&ÿ'ÿ&ÿ'ÿ'ÿ'ÿ(ÿ*ÿ%ÿ@ÿpÿmÿbÿbÿ`ÿfÿfÿhÿoÿLÿ#ÿ*ÿ0ÿEÿQÿJÿGÿFÿ@ÿBÿLÿQÿFÿKÿJÿ?ÿ<ÿFÿTÿ[ÿdÿWÿVÿcÿiÿnÿoÿnÿlÿoÿpÿkÿlÿpÿeÿWÿ_ÿjÿjÿhÿiÿcÿaÿ_ÿXÿRÿRÿMÿIÿDÿAÿ>ÿ>ÿ=ÿ<ÿ9ÿ3ÿ/ÿ0ÿ1ÿ1ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ+ÿ,ÿ,ÿ/ÿ.ÿ3ÿ5ÿ6ÿ5ÿ5ÿ3ÿ1ÿ/ÿ0ÿ5ÿ7ÿ7ÿ8ÿ9ÿ;ÿ<ÿ>ÿ@ÿ@ÿ?ÿ=ÿ:ÿ7ÿ6ÿ3ÿ3ÿ4ÿ3ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ4ÿ4ÿ4ÿ5ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ3ÿ2ÿ0ÿ-ÿ-ÿ,ÿ*ÿ*ÿ)ÿ)ÿ)ÿ(ÿ&ÿ&ÿ'ÿ*ÿ3ÿ8ÿ:ÿ<ÿ8ÿ6ÿ5ÿ4ÿ3ÿ0ÿ,ÿ*ÿ(ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ)ÿ*ÿ*ÿ,ÿ,ÿ-ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ1ÿ2ÿ2ÿ1ÿ2ÿ3ÿ3ÿ3ÿ5ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ3ÿ3ÿ4ÿ4ÿ3ÿ7ÿ?ÿKÿTÿ]ÿeÿmÿtÿyÿ~ÿ|ÿvÿrÿpÿnÿlÿgÿbÿeÿgÿjÿoÿuÿxÿ{ÿ{ÿzÿzÿzÿzÿyÿyÿwÿtÿrÿnÿlÿkÿkÿiÿhÿdÿ`ÿ[ÿXÿUÿPÿMÿHÿFÿAÿ;ÿ8ÿ7ÿ7ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ:ÿ:ÿ8ÿ8ÿ7ÿ7ÿ7ÿ9ÿ<ÿBÿGÿLÿQÿWÿ[ÿ]ÿ_ÿ_ÿ]ÿ[ÿWÿQÿMÿMÿTÿYÿ`ÿgÿpÿtÿwÿzÿzÿwÿwÿxÿyÿyÿzÿzÿyÿ{ÿ|ÿ|ÿ|ÿ{ÿzÿyÿ{ÿ}ÿ}ÿxÿrÿiÿbÿYÿNÿEÿDÿCÿCÿCÿAÿBÿAÿAÿCÿCÿDÿEÿFÿFÿJÿKÿJÿJÿJÿIÿGÿFÿEÿDÿ1}1t3}2t2}2v2}2u1}1t1}1t1~/t.~/t/~.v.~.v.~.v.~.u.~-s-~-s-~-r-~-r,~.s,~,u+~+v*~,v+~+w+~,y+~+z+~*{*~*|*~*|+~+|*~*|+~+}+~+}**},,}*)}**}**}++}**|**|(*|**|**}*+}*~)}*~*}*~*}+~+},~,~,~,~,~,~+~+~-~--~--~-~.~.~.~.~.~.~-~-~.~.~-~-}-~-}-~-}-~-}-~-{-~-{-~-|,~,|-~-}-~-}-~-}.~.}.~.}.~.}.~-~.~.~-~,}-~-}-~-}-~-|-~-},~,~+~+~+~+~)~)}(~(|'~'}&~&|&~&|&~&|&&|%$|$${##|#~"|"~"| ~ |~|}|}|~{~~ ~#$}'
*},-~.26;@BFIJI}GG|DA{AA{@?~8-}*,|('{%'z'&{('{'<~pkdydewgjylh~=+/3|?ZzLCyFGz<PzRNyKIwGFvLQt_fq`Xw_g|mo~on|pq|opzofx_ewjhygiwffwa]uXRwNGxB=y;:{;:{74z11z0-y,+{+*{*){*,{+-{-,{*,{./z01z34{54z20z03{75{89}::}<>}A@~?<|:8{69}75}31}//}./}/1}2
2}44}4
4}3
3}3
5~54~31~/
.~-
,~,*~)(~*)~'%~''~-2499856720-+'%%%%$$$$~$%~%%~%&~$%}%'}(*}*,},.}..}..}..}.-}--}/0~000.~.0~..-,,+++++~+*~*++,,,,----01111233444433332232126
:B
NWb}jqqx
{fz
ufr
mgm
kofbrbeok
qktvfz
xfy
zcy
xbxvdttfoldmmamlcjiegghb^oZVwQLE?
986
656777789::998866569>BFMSX\x^^p^]nYUrNIzHJS
[ckqwyz{qxulvvkwxnwwnyzl{}i}zg{{g{
zhvpni`xX
PFDCB@@ABCDDDGHHJL~KJ~HG~EC~A1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ.ÿ/ÿ/ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ+ÿ)ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ+ÿ+ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ*ÿ*ÿ)ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ.ÿ,ÿ-ÿ.ÿ-ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿ!ÿ$ÿ%ÿ)ÿ*ÿ,ÿ-ÿ.ÿ1ÿ6ÿ;ÿ?ÿCÿEÿGÿJÿJÿIÿHÿEÿBÿBÿAÿ@ÿ9ÿ.ÿ*ÿ+ÿ+ÿ(ÿ'ÿ(ÿ)ÿ(ÿ(ÿ(ÿ*ÿ>ÿkÿmÿfÿcÿfÿkÿmÿoÿgÿ5ÿ&ÿ.ÿ1ÿ0ÿ=ÿXÿXÿKÿNÿKÿJÿLÿMÿRÿSÿNÿDÿFÿFÿPÿQÿhÿiÿeÿaÿfÿlÿtÿoÿlÿkÿnÿnÿkÿnÿlÿ`ÿbÿdÿfÿfÿeÿfÿgÿhÿfÿ_ÿZÿVÿRÿJÿBÿ=ÿ;ÿ:ÿ8ÿ6ÿ6ÿ:ÿ;ÿ8ÿ3ÿ/ÿ-ÿ,ÿ*ÿ(ÿ(ÿ)ÿ*ÿ*ÿ,ÿ-ÿ.ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ4ÿ6ÿ4ÿ2ÿ2ÿ2ÿ3ÿ5ÿ6ÿ8ÿ9ÿ9ÿ9ÿ<ÿ?ÿ?ÿ>ÿ=ÿ>ÿ<ÿ:ÿ;ÿ9ÿ7ÿ5ÿ3ÿ2ÿ2ÿ1ÿ/ÿ0ÿ1ÿ3ÿ2ÿ3ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ4ÿ4ÿ3ÿ2ÿ1ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ'ÿ&ÿ&ÿ)ÿ,ÿ1ÿ5ÿ:ÿ:ÿ9ÿ9ÿ7ÿ6ÿ6ÿ1ÿ/ÿ,ÿ'ÿ$ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ&ÿ&ÿ'ÿ(ÿ*ÿ*ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ/ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ.ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ1ÿ0ÿ/ÿ0ÿ2ÿ3ÿ5ÿ>ÿJÿVÿ^ÿgÿoÿtÿyÿyÿvÿrÿpÿmÿlÿhÿcÿaÿeÿjÿlÿpÿtÿwÿxÿyÿyÿxÿvÿsÿqÿoÿmÿjÿiÿiÿjÿjÿkÿjÿmÿmÿkÿiÿeÿ`ÿZÿSÿKÿDÿ<ÿ8ÿ7ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ7ÿ6ÿ8ÿ>ÿDÿIÿNÿTÿZÿ\ÿ\ÿ^ÿ^ÿ]ÿUÿQÿJÿFÿBÿGÿOÿXÿaÿiÿoÿtÿxÿxÿxÿvÿtÿtÿuÿvÿvÿuÿuÿyÿ{ÿ{ÿzÿxÿyÿyÿzÿxÿvÿqÿiÿbÿYÿPÿEÿCÿAÿBÿBÿCÿDÿDÿEÿGÿHÿIÿJÿIÿKÿKÿJÿJÿHÿFÿCÿAÿBÿ0}0w0}0w0}0v/}/u0}/v/}/v/~/v0~.v-~-v-~-v+~,u,~,v,~*t,~,t,~,s,~,s,~+t*~*v)~)w*~)w(~)w)~)y)~)z)~*{)~)|*~)|*~*}*~*}+~+~*~*~))}))}((}'(}((}('}((}))})(|((|((}((})~)}*~*}*~*}*~*}*~*~+~+~+~+~+~+~,~,,~,,~,~,~,~,~,~-~-~-~-~-~-~.~.}-~-},~,}-~-}-~-|-~-|,~,|+~+|+~+}*~*}+~+}+~+}+~+}+~+}+~+~+~-~-~--~-,~,}+~*|,~,{+~,},~,~*~*~*~*|(~(|''|''{'%{''{&&z&&z%%{$#|$~#|"~"| ~ |~|||~{~ ~"~%(}),}+.}14~7>BEFH~IK}JI|HD{DC{?0{*)z*(|&(|*&{)(|*)dpgyeexjj{oW*(0|/4zDV{\L{KHzILyKByIRyKEx?IvY_qYXp[`vjp|sk}jj{elynlvg_vaewgcy_axfhxe_u]YuUNuGBx=:z87z9=zD?z51y-+z)(z(({(+{,-{-/{/.{.-z-.z//{33{45{52{11{36{77{9;|<<}<?|??{?>|><|98|66|53|33|34}44}4
5~4
2~3
0~00~00~.-~..~-,~+*~))~)(~'&~'(,27::;;9841.*$#####"~""~"#~#$~%$}&(})*},,},,}-.}..}.-}-,},,}--~-.~./~./~/.~..~.-~-,~,,,++,~,,~,,,,,----..//01000000//11/.////15;FO\c
kur
yh{
wet
phmimheqaatgjqo
rku
wfwtaq
q`nkdihifdkddjhjfijdljegdj_YrQH~>98
7665566775544445555569?ELQWY[w^
_p]ZpSMwIC@
I
PX
`gntxyynwukrrlstprrqrupy{kyyhyzh|
xjvpmj
du\
SJAACDFEHJJJJIIKJIFFCA?/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ.ÿ/ÿ.ÿ/ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ*ÿ,ÿ,ÿ+ÿ+ÿ)ÿ)ÿ(ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ'ÿ(ÿ(ÿ(ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ(ÿ(ÿ(ÿ(ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ,ÿ,ÿ+ÿ*ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ$ÿ#ÿ$ÿ#ÿ"ÿ"ÿ ÿ ÿÿÿÿÿÿÿÿÿÿ ÿ"ÿ&ÿ)ÿ*ÿ,ÿ.ÿ1ÿ4ÿ8ÿ>ÿCÿEÿFÿIÿJÿKÿKÿJÿJÿIÿFÿFÿFÿ9ÿ+ÿ+ÿ*ÿ*ÿ)ÿ'ÿ&ÿ&ÿ'ÿ(ÿ(ÿ#ÿTÿoÿjÿiÿjÿnÿpÿoÿOÿ,ÿ4ÿ3ÿ.ÿ-ÿ4ÿCÿJÿNÿOÿQÿMÿSÿSÿSÿOÿLÿKÿDÿBÿGÿGÿ[ÿ^ÿUÿKÿ\ÿbÿiÿqÿoÿmÿmÿjÿgÿmÿoÿmÿiÿfÿdÿbÿaÿ`ÿ`ÿ_ÿcÿfÿbÿ^ÿZÿYÿVÿPÿKÿDÿ?ÿ<ÿ<ÿ=ÿ<ÿ<ÿBÿ=ÿ5ÿ3ÿ/ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ+ÿ,ÿ/ÿ/ÿ.ÿ/ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ2ÿ4ÿ6ÿ6ÿ3ÿ2ÿ1ÿ1ÿ3ÿ5ÿ6ÿ8ÿ9ÿ;ÿ;ÿ=ÿ?ÿ?ÿ@ÿ@ÿ@ÿ@ÿ?ÿ?ÿ=ÿ<ÿ;ÿ:ÿ9ÿ9ÿ8ÿ8ÿ8ÿ8ÿ7ÿ5ÿ5ÿ4ÿ1ÿ0ÿ.ÿ.ÿ.ÿ.ÿ,ÿ.ÿ-ÿ.ÿ.ÿ-ÿ,ÿ+ÿ*ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ&ÿ'ÿ(ÿ-ÿ1ÿ4ÿ7ÿ:ÿ;ÿ9ÿ7ÿ6ÿ3ÿ0ÿ,ÿ(ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ$ÿ%ÿ'ÿ&ÿ'ÿ(ÿ*ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ/ÿ.ÿ.ÿ0ÿ0ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ1ÿ2ÿ8ÿDÿNÿXÿ`ÿhÿpÿuÿzÿyÿwÿoÿlÿjÿgÿeÿbÿ`ÿaÿgÿnÿqÿsÿsÿuÿqÿoÿmÿiÿeÿdÿbÿ`ÿ[ÿ\ÿ^ÿ`ÿcÿfÿhÿiÿhÿdÿ_ÿYÿPÿGÿ>ÿ9ÿ8ÿ8ÿ6ÿ6ÿ5ÿ5ÿ6ÿ6ÿ7ÿ7ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ5ÿ6ÿ8ÿ>ÿCÿJÿNÿTÿZÿ\ÿ]ÿ_ÿ]ÿ[ÿWÿPÿJÿEÿDÿDÿJÿQÿZÿbÿhÿpÿsÿwÿxÿvÿtÿrÿpÿqÿqÿqÿrÿrÿuÿzÿ{ÿzÿyÿyÿzÿ{ÿzÿwÿsÿoÿgÿ_ÿXÿMÿFÿEÿCÿGÿHÿIÿJÿJÿJÿJÿIÿIÿIÿIÿIÿFÿDÿAÿ=ÿ<ÿ.}.w.}.w-}-x.}.x.}-w-},w-~.w-~-w,,w))w*~*v*~*v+~(v(~(v(~(u(~(u''t''u''v''w''x'(x((z''{''}((~(())*~*~(~(|(~(})~)}(~(}(~(}'~&|&~&|&&}&&}&&|&&|''}&'}))}**}**}++})~)~)~)~)~)~+~+~+~+~+~+~+~,,~*+~+~,~-~,~,~,~,~+~*}+~+}*~,|,~+|,~,},~,}*~*}*~*}+~+|+~+}*~*~+~+~+~+~+~+~*~*~*~*~*~*~+~*~+~,}+~+}+~+|+~+|*~*}*~*|*~*})~)|)){('{&~'{(~(z''y%%x&&y%%z$~$z!~!{ ~ } ~ }||~}~ $~(+~,/|13~7;AGLMNL~JK|JL|KJ|JC{.&{)'{%'{''{((z'&}Mqhzciwo
p{i?"30}0/|14}=GRZ^X|VX{YUzMIxB6w<HsbcpZYohqtmrxrq{mnzmiyijvkgvdav_bw`_w^]x\UySUySQxMIwC@x@Cx?8y:9z56z1(y&'{'({(({)){*.{/0{0.{,+|++|,,}-0}31}01|/0|25|78}8:};=}??}?@}@A}AB|>@|@@}?>}>=};;}85~42~/-~,
-~-
-~,-~.,~,+~**~))~)(~'&~&&%&+/2
468844~1.,'"####~##~#$~$$|$%}&'}(+|+,},-}-.}.,~,+~+,},-}-.}..}..}./}//|/.|.,~,,~,-~--~--~-.~.-}--}-----..//000011//..//..--....017?IS\
ewm
shwwdvpfjhkhdq_^s^fql
nlppfpnelgibam^[sWTvSSuX[r`ena_qZWvME~=98
666556677556655666~66~69~<AGMSW
Z|\_v]\qYStNH{F
EHMT]ek~putwxmxvksppmntopyqstx{mzyiyyi{zjw
tlq
jtc[~UMIHIHI~JI~GIGHGFC@=;9.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ+ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ)ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ,ÿ+ÿ*ÿ*ÿ+ÿ,ÿ-ÿ,ÿ,ÿ+ÿ,ÿ*ÿ*ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ"ÿ"ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿ"ÿ%ÿ)ÿ,ÿ/ÿ1ÿ4ÿ7ÿ;ÿ=ÿBÿHÿNÿRÿQÿOÿMÿLÿKÿKÿMÿLÿHÿ7ÿ*ÿ&ÿ&ÿ(ÿ'ÿ&ÿ'ÿ&ÿ&ÿ(ÿ$ÿNÿtÿiÿeÿjÿjÿlÿeÿ2ÿ#ÿ1ÿ2ÿ0ÿ.ÿ3ÿ0ÿ6ÿEÿIÿOÿWÿYÿVÿVÿVÿWÿVÿMÿDÿ;ÿ0ÿ8ÿFÿ^ÿRÿUÿ[ÿaÿkÿjÿrÿuÿqÿmÿpÿrÿnÿjÿiÿgÿgÿdÿbÿ`ÿ]ÿ^ÿ^ÿYÿUÿTÿNÿMÿPÿRÿNÿLÿMÿFÿ?ÿ?ÿAÿ<ÿ7ÿ6ÿ3ÿ5ÿ5ÿ1ÿ+ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ-ÿ.ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ+ÿ,ÿ-ÿ.ÿ0ÿ0ÿ/ÿ0ÿ0ÿ3ÿ5ÿ7ÿ7ÿ8ÿ:ÿ:ÿ;ÿ<ÿ>ÿ@ÿ@ÿ@ÿ@ÿAÿAÿBÿBÿBÿAÿAÿAÿ?ÿ=ÿ;ÿ8ÿ7ÿ5ÿ3ÿ1ÿ/ÿ/ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ+ÿ+ÿ+ÿ*ÿ*ÿ)ÿ)ÿ)ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ$ÿ&ÿ(ÿ+ÿ.ÿ0ÿ2ÿ5ÿ4ÿ4ÿ2ÿ/ÿ-ÿ)ÿ%ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ'ÿ(ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ,ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ/ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ-ÿ-ÿ.ÿ.ÿ/ÿ/ÿ0ÿ1ÿ2ÿ2ÿ;ÿEÿPÿ[ÿaÿiÿnÿvÿyÿuÿpÿmÿjÿiÿeÿ_ÿZÿ]ÿbÿhÿkÿnÿqÿrÿpÿlÿfÿbÿ^ÿYÿUÿQÿNÿLÿOÿOÿSÿXÿYÿWÿUÿPÿJÿBÿ9ÿ7ÿ8ÿ6ÿ6ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:ÿ:ÿ;ÿAÿFÿKÿQÿVÿYÿ[ÿ\ÿ]ÿ^ÿZÿUÿQÿKÿGÿFÿHÿMÿRÿXÿaÿhÿlÿpÿuÿwÿyÿyÿwÿrÿmÿmÿoÿoÿnÿoÿrÿvÿ{ÿzÿzÿzÿ{ÿ|ÿ|ÿyÿxÿtÿoÿlÿcÿ\ÿRÿKÿIÿIÿIÿIÿHÿHÿGÿEÿEÿDÿCÿ?ÿ=ÿ;ÿ7ÿ6ÿ.}.x.}.x,}+x-},x,},w-}-w,~,w,~,w+}*w*}*w*~(v'~'v(~&w&~&w&~&w'~'w''v&&w''y''z''{&&{%%{''|''}((~(())(~()~)~(~(~(~'~'~'}'~'}%~%|%~%|%%}%%}%%}%%}&~&}&~&}&&}''}('}''}'~(~)~)~)~)~*~)~*~+~+~+~*~++~++~++~,+~+~+~+~*~*}*~*})~)~+~+~+~*}+~+}*~*}*~*}*~*|*~*}*~*)~))~)~*~*~*~*~*~*~*~*~*~*~*~*}*~*}+~+~+~+~*~*}*~*|*~*|)~){))z)(z(~(z((y((x'%w%%x%%y$~$z"~"{!~!| ~ |||~} ~$&~)
-~03~7;>@CHMPQO~NN|LL~MK}?-{)({('{*({'&|%&}TvjyefxjdzgC#~.2|10|.-~/9HORXYY^d|ZTyMGw>1u<HqZZnTWn^hrjmvqqyswyvqyiavafvd_vZYu^_wVPyNI{HJ{NM{MMyIDwB@w<8y54z77z4-y(%{&'{(){)){),{/.{.-{..|.+|)(}('}))}*-|-.|01|45{66{89{9;{<=|>?|?A}=>}?@~@B~BA}=:}:8~66~42~10~//~-,~+*~*+~*)~)(~('~&%~%$#$$$%),
.1234/~.+&#"$
#~#$~#%%&~&&}''}(*|**}*+}++}+,|,,|,-}--}-.}-.}..}..}./~/.~-,~,,~+-~--~--~--~--.-------.///00011001100/.00//00230149AJV]zf
knr
xgwqepjfecn]ZvZ^xcgslmmonhl
gjb[oVSvPIGGHJ
MM
KFC
=88:9887788888888:<<}<;~;;~;<~;?CHMRXZ
Zw\^p[WpRJxIGILQUZagmyqtrxylxvlrmoonvpqzrswv{p{{j{|h}g
{gx
slo
hvaVNI
HGGDDCDA?;:752,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ,ÿ*ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ*ÿ)ÿ)ÿ)ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ+ÿ*ÿ*ÿ*ÿ+ÿ+ÿ*ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ%ÿ$ÿ$ÿ"ÿ"ÿ!ÿ!ÿ ÿ ÿÿÿÿÿÿÿ ÿ$ÿ(ÿ*ÿ-ÿ0ÿ3ÿ7ÿ?ÿCÿDÿEÿHÿIÿJÿLÿMÿOÿMÿMÿLÿJÿBÿ2ÿ'ÿ'ÿ'ÿ'ÿ(ÿ*ÿ)ÿ%ÿ%ÿ$ÿSÿtÿkÿaÿfÿhÿiÿiÿMÿ&ÿ.ÿ1ÿ0ÿ.ÿ.ÿ-ÿ+ÿ/ÿ>ÿMÿWÿPÿUÿVÿYÿ_ÿ_ÿ]ÿ[ÿTÿHÿ>ÿ7ÿCÿQÿ^ÿ^ÿUÿWÿcÿ_ÿ[ÿhÿtÿsÿtÿwÿyÿzÿqÿfÿfÿkÿiÿ`ÿ[ÿZÿ[ÿ\ÿZÿRÿKÿIÿHÿJÿJÿJÿKÿJÿJÿFÿDÿAÿ<ÿ8ÿ7ÿ5ÿ6ÿ5ÿ4ÿ-ÿ(ÿ%ÿ&ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ+ÿ-ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ*ÿ'ÿ'ÿ&ÿ$ÿ%ÿ(ÿ*ÿ+ÿ-ÿ/ÿ2ÿ5ÿ5ÿ6ÿ8ÿ9ÿ8ÿ8ÿ;ÿ<ÿ<ÿ<ÿ=ÿ=ÿ=ÿ;ÿ:ÿ:ÿ;ÿ>ÿ>ÿ>ÿ<ÿ9ÿ:ÿ:ÿ:ÿ:ÿ8ÿ7ÿ3ÿ2ÿ1ÿ0ÿ0ÿ.ÿ,ÿ+ÿ*ÿ*ÿ*ÿ+ÿ)ÿ)ÿ(ÿ(ÿ&ÿ&ÿ%ÿ%ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ%ÿ&ÿ*ÿ,ÿ.ÿ1ÿ1ÿ/ÿ.ÿ-ÿ*ÿ%ÿ%ÿ$ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ'ÿ(ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ2ÿ3ÿ6ÿ>ÿGÿQÿYÿaÿjÿpÿuÿxÿrÿnÿhÿcÿ^ÿ[ÿVÿYÿ^ÿ`ÿeÿkÿmÿnÿnÿnÿhÿcÿ\ÿVÿSÿNÿHÿDÿAÿ@ÿCÿCÿBÿ@ÿ=ÿ=ÿ:ÿ;ÿ<ÿ;ÿ:ÿ:ÿ:ÿ:ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ;ÿ<ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ=ÿ>ÿ=ÿ=ÿBÿDÿIÿPÿSÿWÿXÿZÿ\ÿYÿWÿTÿNÿJÿKÿJÿJÿNÿSÿWÿ\ÿdÿgÿkÿqÿtÿxÿyÿxÿvÿrÿoÿmÿqÿrÿrÿrÿtÿyÿ|ÿ{ÿ{ÿ|ÿÿÿÿÿ~ÿ}ÿwÿqÿlÿaÿUÿLÿIÿHÿEÿBÿCÿBÿ?ÿ=ÿ;ÿ9ÿ7ÿ6ÿ3ÿ3ÿ+}+x,},x,},w,}+w*}*w*}*u)~)w)~)w(~(w(~&w&~&w&~&w&~%x%~%x%~%x%~%x%%v%%w%%z%%{%%|%%~%%}&&~''~&&~&~'(~((((((}('}'&~&~%~%~%~#}%~%}%%~%%~%~%}%~%|%~%|&~&|&~&}%~&}&~&~&~&~'~)~)~(~'~'*~()~)(~))~)~)~)~)~)~*|*~*|*~+}+~*~)~)~)~)~)~)~)~(~(~(~(~)~))~)}(~(~)~)~)~(~(~(~)~)~)~)})~)}(~(}(~(})~)})})})})})~)})~)|)){**{)){)){)~)y)(y((y))x((x''x((w&$y$~%z$~#z"!{!!{||~ ~%'~*/~27:@EGFGEFIKNNMJ}D7|,*{((z(){*+z$#}A{mx_^uedzj_%}+1{03|8/|++~-8DLORSUZ[}^]yQJxB=wHRtWYrQRpZRqTbursytxy}|xwuwsouh`u\YwVUyTP{HH}IH}GG|EF|JGzDAy>9y76y54z3.x('z''z((z('|)*|++{++|-/}/.},*}'%}"$|'(|+.|04|67{76{67{9={<<z<=z<:{97{67}98}87}7:};<|<;~76~32~0/}.,}**}**}*)})(}('~&&~&#$$$$$#$'*-.10}/.|,)~&$$$%~%%~%&~&'}'(}()})*}*+}+,},-}--}-.|./}//},-}--}..}./}/.}-,~,+~+*~*+~+++,--~--~,----....//00001112221~133334455434467<DLT~_
eultjv
scmhdc_nYUyT
X|^
cxgksmmnmjje
]mXRvNH~E
?<:<>>?>>=~>>~??~?<~<=~>?}??}?@~@?~??~?>~>>=<>BHLPTVwX
YpZWpUPvLK~H
INRUY_d}hnwrvryzqyvprquqqyrs{svxy~qjg
g
e~
yhriq_
Q~GF
C?==;9644330+ÿ+ÿ+ÿ,ÿ,ÿ+ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ&ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ(ÿ(ÿ(ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ$ÿ#ÿ#ÿ#ÿ!ÿ!ÿÿÿÿÿÿÿ ÿ%ÿ)ÿ,ÿ/ÿ4ÿ:ÿ=ÿBÿGÿIÿIÿHÿFÿEÿGÿJÿNÿOÿNÿNÿGÿ>ÿ3ÿ)ÿ)ÿ)ÿ)ÿ*ÿ)ÿ(ÿ(ÿ-ÿqÿwÿiÿ`ÿdÿiÿjÿbÿ5ÿ%ÿ.ÿ1ÿ1ÿ2ÿ2ÿ-ÿ*ÿ*ÿ,ÿ5ÿBÿLÿNÿNÿSÿWÿ[ÿ]ÿ\ÿZÿNÿFÿ<ÿ8ÿHÿVÿYÿ[ÿ[ÿSÿ`ÿdÿYÿ[ÿbÿmÿpÿtÿsÿwÿwÿwÿuÿnÿjÿaÿZÿTÿQÿPÿPÿLÿFÿGÿHÿIÿFÿDÿDÿEÿIÿJÿHÿCÿBÿ?ÿ;ÿ9ÿ8ÿ8ÿ5ÿ1ÿ+ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ)ÿ'ÿ)ÿ*ÿ+ÿ+ÿ,ÿ.ÿ.ÿ.ÿ,ÿ-ÿ,ÿ(ÿ%ÿ$ÿ$ÿ'ÿ*ÿ.ÿ2ÿ6ÿ8ÿ8ÿ7ÿ6ÿ6ÿ8ÿ:ÿ;ÿ=ÿ<ÿ<ÿ<ÿ;ÿ:ÿ9ÿ6ÿ6ÿ4ÿ6ÿ6ÿ6ÿ5ÿ7ÿ7ÿ;ÿ9ÿ8ÿ9ÿ7ÿ5ÿ2ÿ1ÿ/ÿ/ÿ.ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ*ÿ*ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ$ÿ%ÿ(ÿ*ÿ.ÿ0ÿ1ÿ/ÿ/ÿ0ÿ-ÿ)ÿ(ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ(ÿ(ÿ)ÿ)ÿ)ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ2ÿ1ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ4ÿ5ÿ5ÿ7ÿ7ÿ7ÿ8ÿ7ÿ7ÿ7ÿ7ÿ8ÿ;ÿ@ÿIÿQÿYÿdÿhÿpÿtÿtÿmÿhÿaÿ[ÿZÿSÿRÿVÿ]ÿ`ÿdÿkÿmÿmÿlÿiÿdÿ^ÿXÿUÿOÿLÿHÿCÿ?ÿ@ÿ@ÿ@ÿAÿAÿAÿ@ÿ@ÿ@ÿAÿAÿAÿ@ÿ@ÿ?ÿ@ÿAÿAÿAÿAÿ@ÿ@ÿ?ÿ?ÿ=ÿ=ÿ>ÿ>ÿ<ÿ;ÿ;ÿ<ÿ?ÿBÿGÿMÿSÿUÿVÿXÿYÿYÿVÿRÿMÿLÿJÿLÿNÿRÿTÿXÿ\ÿaÿfÿkÿqÿsÿvÿwÿzÿyÿwÿtÿrÿqÿqÿtÿvÿxÿxÿ}ÿÿÿÿÿÿ
ÿÿ
ÿÿÿwÿoÿcÿVÿGÿCÿ@ÿ<ÿ;ÿ:ÿ9ÿ6ÿ4ÿ3ÿ3ÿ2ÿ/ÿ.ÿ*}*x)})x*}*x*}*x)})y(}(x)~)x(~(x'~'w'~&w&~&w&~&w%~$x#~#x$~$y$~$y$$w%%x%%z%%{%%|%%~%%}%%~&&~&&~&~%&~&(&'(('&&'~'~&~&~%~%}%~%}%%~%%~%~%}%~%|%~%|%~%|%~%}$~$}%~&}'~'}'~'}'~'}'~'(~((~((~((~(*~((((~((|(~(}(~)~)~)~)~(~(~(~(~(~(~(~(~'~'}'~'|'~'}(~(}(~(~(~(~(~(~(~(}(~(}(~(}(~(})~)}((}((}(~(})~)|**{**{))x))x))w*)w()x))w((w''w((v'%x&~&z%~$z##{""{! | |~"~%(~,/~4;?AEHIGFEFLNONM~NG|7'{')z**{)(~(]{|mcueivp~h}@}!.|20{/.|,*|)+~-7AFJRRTX^~c^zOEyA=xM_uffsjnpwqp_YsS`uedxkrxxywumwjdv[VwSPyOJ{DE|FH}FD|DD|HHzGF{FFyC>y:8y52w+)x))y+*y+,z,(z('{)+|,-}.-},.},*})'{&&{(+{.5|98|54|79|9<|<={<:{:9{75{55{45{55{56{53{66|62|10|//}/.}-,}/.}.,}++},*~**~*'%$$$#$$$%),.211z21|.+(%#%
'~'&~&&}'(}((}()}*+}++}+,},,},.|..}.-}--}-.}..}.,}-.}.,}++}+*~**~*++,,-~-/~.....//..//00223221243~345556677888888779>EM
U]{e
mns
rbmiab]j[
WxS
V~\_|cgtkiljijecm[XtTO}LG
EDBCC~DC}BC~CB~BA}AA}AB}BB}B@~@?~=<~;;~;;99:;@EK
PRzWYsZ
ZpXStPO{L
OQSXY\ach}lqysvxy{s{xrusuuuyuw{z|voie
cczqhfYuHA<98755211.,,*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ#ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ'ÿ'ÿ(ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ'ÿ%ÿ&ÿ&ÿ&ÿ%ÿ$ÿ#ÿ"ÿ"ÿ!ÿ ÿ ÿÿÿÿ$ÿ&ÿ(ÿ,ÿ.ÿ3ÿ7ÿ;ÿ<ÿ?ÿAÿDÿAÿ@ÿAÿEÿKÿLÿMÿMÿLÿHÿCÿ4ÿ(ÿ(ÿ'ÿ)ÿ)ÿ*ÿ%ÿUÿwÿoÿlÿhÿnÿrÿpÿWÿ&ÿ+ÿ/ÿ3ÿ/ÿ.ÿ.ÿ,ÿ+ÿ*ÿ*ÿ+ÿ8ÿ@ÿKÿOÿRÿRÿUÿVÿVÿYÿ^ÿQÿCÿCÿOÿ^ÿgÿkÿjÿgÿmÿjÿVÿYÿaÿ^ÿZÿWÿWÿcÿoÿrÿtÿpÿlÿnÿiÿ_ÿYÿVÿRÿNÿHÿCÿ@ÿCÿEÿGÿFÿEÿFÿGÿEÿFÿHÿIÿIÿFÿDÿ?ÿ;ÿ9ÿ4ÿ.ÿ*ÿ*ÿ-ÿ*ÿ*ÿ.ÿ1ÿ1ÿ.ÿ+ÿ(ÿ'ÿ)ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ+ÿ,ÿ+ÿ,ÿ)ÿ)ÿ(ÿ)ÿ-ÿ1ÿ6ÿ4ÿ3ÿ4ÿ5ÿ6ÿ8ÿ8ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ6ÿ4ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ/ÿ/ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ,ÿ-ÿ-ÿ*ÿ*ÿ*ÿ*ÿ(ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ(ÿ*ÿ.ÿ1ÿ0ÿ0ÿ2ÿ1ÿ0ÿ.ÿ+ÿ(ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ.ÿ,ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ-ÿ-ÿ/ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ1ÿ1ÿ2ÿ3ÿ4ÿ3ÿ2ÿ3ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ5ÿ6ÿ6ÿ6ÿ7ÿ7ÿ8ÿ9ÿ9ÿ9ÿ8ÿ8ÿ8ÿ9ÿ9ÿ8ÿ;ÿAÿIÿQÿ[ÿbÿnÿrÿsÿoÿlÿeÿ`ÿ[ÿ[ÿYÿXÿYÿ_ÿcÿgÿiÿkÿjÿjÿhÿeÿ^ÿ[ÿXÿSÿPÿJÿGÿEÿDÿEÿFÿEÿCÿCÿCÿBÿBÿAÿAÿAÿAÿBÿBÿBÿBÿAÿ@ÿ?ÿ=ÿ<ÿ;ÿ:ÿ:ÿ9ÿ9ÿ9ÿ9ÿ:ÿ>ÿEÿHÿNÿRÿUÿWÿ[ÿZÿYÿVÿSÿQÿQÿRÿTÿVÿXÿ[ÿ]ÿ`ÿbÿbÿhÿmÿpÿsÿwÿxÿ|ÿ|ÿzÿxÿuÿsÿvÿxÿ|ÿ|ÿ~ÿÿ
ÿÿÿÿÿÿÿ~ÿyÿrÿiÿ[ÿJÿ>ÿ8ÿ7ÿ5ÿ4ÿ2ÿ2ÿ0ÿ/ÿ.ÿ-ÿ+ÿ,ÿ*}*z)})z(}(z)})z)})y(}(x)~(x'~'x'~&x&~&x%~%w$~$w$~$x$~$x$$y%%y$$z$$y%~%y%~%{%~%{%~%|&~&~&~&~%~%}'~''~''~''&''&&&&''~&%~%%}&%}%~%}%~%}$~$}$~$}$$}%%}%~%}%}%}%~&|&~&|'~&}'~'~(~('~''~%'~('}&&}&&~&&~&'~'&~('~''~''~'~&~&~&~%~%~%~%~%}%~&}&~&}&~&}%~%}&~&}'~'}'~'}'~''~''~''~''(}((|((|)(|(({**z))z((y))x)(x()w))u)'x((x((w((w''x'&y$#x##z!~ {~|~ $~%*~,-~1467:<=;>@BIJLJJF>|2*{'({)(~%Sy|ocwagxso|]$'|//{.-{-+{+*|**},/BKMRSSQP~U[}ZLyDNwbkwooseroj_o[hqeasaMuWgwloxmkvjgwc^x]XxNGyB?y@A|CE|FD{DE|HI|IHzFDyB@y=7x/,w,/w/0x36y82y.*y'(z(){)+{,+},-}-.{.+{+-z-0|00{01{12{36{67z65z44z11z10z12z22y21y00z./z//{//{/.{.-|-.|./|/1}1/}.,~*
)~)*~)(~'&~&#%$~$%)-132}33{30|/,)&%~%%}&'}''}((}(*}*+}+,},,},,}-.}..}..}/.}./}//}/.|.-|+*}*+}+*~))~++~++~++,.//01111222222233333444556666666887799888777?FOZ{cijq
u^sn\jef`
^u[Y|X\{`eughokloi
dn`
\sW
U}PK
HHGFEEC}CB}A?~?@~@B}BA}AA|@?}=<};:~987889<@DJOTWwY
[r[
YrVTyVV~XY[\]_ddghlprt|x|x|{txuvvxxz}wr
jfe~|byreiZmI;}5531/.-,++++)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ%ÿ%ÿ&ÿ&ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ&ÿ'ÿ(ÿ(ÿ*ÿ*ÿ)ÿ)ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ'ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ'ÿ'ÿ&ÿ#ÿ$ÿ#ÿ#ÿ!ÿ ÿ ÿÿÿ ÿ"ÿ%ÿ(ÿ+ÿ-ÿ0ÿ2ÿ3ÿ3ÿ4ÿ7ÿ:ÿ;ÿ>ÿBÿDÿFÿIÿKÿKÿJÿFÿBÿ6ÿ*ÿ'ÿ)ÿ)ÿ&ÿGÿ|ÿoÿ`ÿ_ÿfÿqÿqÿeÿ7ÿ ÿ,ÿ0ÿ.ÿ-ÿ-ÿ-ÿ+ÿ+ÿ*ÿ*ÿ*ÿ-ÿ=ÿGÿDÿGÿQÿTÿWÿRÿOÿQÿYÿYÿRÿJÿSÿbÿjÿqÿ^ÿoÿvÿbÿeÿoÿhÿaÿ`ÿbÿRÿPÿ\ÿdÿhÿjÿhÿdÿ_ÿ`ÿbÿ^ÿYÿQÿGÿBÿ@ÿ?ÿ@ÿBÿCÿCÿDÿDÿEÿFÿFÿFÿFÿEÿDÿBÿ@ÿ=ÿ7ÿ/ÿ-ÿ/ÿ1ÿ3ÿ4ÿ2ÿ5ÿ4ÿ2ÿ0ÿ+ÿ)ÿ(ÿ(ÿ(ÿ(ÿ*ÿ*ÿ*ÿ,ÿ/ÿ1ÿ2ÿ2ÿ0ÿ0ÿ.ÿ.ÿ-ÿ-ÿ/ÿ/ÿ.ÿ.ÿ/ÿ0ÿ2ÿ2ÿ4ÿ1ÿ1ÿ1ÿ1ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ2ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ/ÿ.ÿ.ÿ,ÿ,ÿ,ÿ*ÿ)ÿ(ÿ'ÿ&ÿ&ÿ$ÿ%ÿ$ÿ$ÿ$ÿ'ÿ+ÿ-ÿ2ÿ5ÿ5ÿ5ÿ4ÿ1ÿ0ÿ-ÿ+ÿ(ÿ&ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ*ÿ*ÿ+ÿ+ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ/ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ/ÿ-ÿ+ÿ+ÿ+ÿ+ÿ+ÿ*ÿ*ÿ+ÿ,ÿ,ÿ,ÿ,ÿ+ÿ,ÿ.ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ4ÿ3ÿ4ÿ5ÿ6ÿ6ÿ6ÿ5ÿ5ÿ5ÿ5ÿ7ÿ8ÿ7ÿ7ÿ9ÿ9ÿ8ÿ8ÿ8ÿ7ÿ6ÿ6ÿ6ÿ<ÿCÿNÿXÿaÿiÿqÿtÿvÿtÿqÿjÿgÿbÿ`ÿ]ÿ[ÿZÿ]ÿdÿgÿkÿjÿhÿhÿfÿcÿ^ÿYÿVÿTÿPÿKÿHÿGÿFÿEÿDÿCÿBÿAÿAÿAÿBÿBÿBÿBÿBÿBÿAÿ@ÿ?ÿ=ÿ<ÿ;ÿ:ÿ9ÿ7ÿ7ÿ8ÿ8ÿ9ÿ<ÿAÿEÿJÿOÿQÿUÿWÿZÿ[ÿ[ÿ[ÿYÿ[ÿ[ÿZÿ[ÿ\ÿ_ÿaÿcÿcÿeÿfÿfÿhÿkÿnÿqÿtÿxÿ{ÿ|ÿzÿwÿxÿxÿ{ÿÿÿÿÿÿÿÿ|ÿ{ÿ{ÿyÿuÿqÿgÿ^ÿMÿ=ÿ2ÿ0ÿ0ÿ.ÿ-ÿ,ÿ,ÿ+ÿ+ÿ+ÿ+ÿ+ÿ(}(z(}(z(}(z)})z)})y(}'x'~'x'~'x'~&x&~%x%~%x$~$x$~$y$~$y##z%%z%%z$%y&~%z%~%{&~&{%~%|$~$~%~%~%~%}&~&'~''~''%((''&&''''&&~%%~%~%}%~%}$~$}%~%}$$}$$}$~$}%}%}&~&}&~&}&~&}'~&~'~'&~&'~'%~%%}%%}%&~&&~&&~&&~&&~&~&~&~%~&~%~%~%~%~$~$~$~$}$~$}$~$}$~$}$~$}$~$}%~%}%~%}&~&~&~%~&~&~&~&~&&|''}''|''|'(z))y))y))y**x*)x((w))u('v''v((v'&v&&v('w$%x#"z"~"z ~ { ~ ~#'~).~/1~2237:=@BCFHJMLGA|4){(*|'
6u~vkukjvpqzj8%
-{.-{--{-+|+)})*~0FCBIQSQPOSR~MXzY[y`kx]Ktnopvnoigpd^oYYqVRsYaubev`\y][z[\zSKyFAy?>{?@{CF{FE|DC|EEzCBzA@z<7x2.w04w87x2-y12y1-y,*z)){(){()|,/|25|54|42|..{,.{.-{-/{//{./z//z//z//y00y13y33z32z11z1.z./{/0{00{0.|.-|-0|00}00}/.}-
,},*~)(~'&~$%~%$~$$$(+.24552~11.+)'%~%
'}'(}((})+}++}+-}--}-.}..}..}..}.0}01}11}12~00~/,~,-~--~-,~,-~-,~,,~,.~.//111111112211110122344455444456668888888~8659BLV~_jns
x_|z\w
r^kggc`qZX{]a|dgvh
hpj
hnd
`s\Y}VQLIGFE}DC}@?}?B}DC|DE|CB|A@}?;};;~:89:::<?CHNSW{Z
\u^^r__u__{^^_
bdffffcbehkorvzz||wzzw{}xxr~k{yjvtgsnei^jQAw2..-+,,-++**(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ&ÿ'ÿ(ÿ(ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ'ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ'ÿ)ÿ)ÿ(ÿ(ÿ'ÿ&ÿ&ÿ&ÿ'ÿ&ÿ'ÿ%ÿ$ÿ#ÿ#ÿ"ÿ"ÿ ÿ ÿ ÿ"ÿ"ÿ'ÿ)ÿ,ÿ.ÿ0ÿ1ÿ3ÿ5ÿ8ÿ<ÿBÿCÿDÿDÿFÿJÿLÿOÿLÿGÿAÿ2ÿ(ÿ(ÿ,ÿ,ÿlÿ|ÿkÿkÿqÿrÿnÿlÿAÿÿ#ÿ*ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ+ÿ+ÿ*ÿ)ÿ)ÿ-ÿ/ÿ9ÿFÿLÿQÿPÿPÿPÿPÿKÿLÿPÿWÿ^ÿ`ÿ[ÿbÿ^ÿWÿ_ÿeÿpÿrÿoÿiÿiÿeÿcÿ]ÿ[ÿXÿ]ÿ`ÿaÿaÿ]ÿ\ÿZÿUÿXÿXÿSÿOÿLÿEÿ?ÿ<ÿ=ÿ>ÿ?ÿCÿCÿBÿAÿAÿEÿFÿBÿ@ÿ?ÿAÿBÿAÿ9ÿ0ÿ.ÿ1ÿ5ÿ5ÿ0ÿ-ÿ/ÿ2ÿ1ÿ1ÿ/ÿ.ÿ,ÿ)ÿ)ÿ)ÿ+ÿ+ÿ/ÿ-ÿ0ÿ3ÿ5ÿ8ÿ8ÿ3ÿ0ÿ.ÿ,ÿ,ÿ,ÿ-ÿ-ÿ.ÿ.ÿ.ÿ-ÿ.ÿ-ÿ-ÿ.ÿ.ÿ-ÿ0ÿ/ÿ0ÿ1ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ.ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ/ÿ.ÿ,ÿ+ÿ+ÿ*ÿ)ÿ(ÿ'ÿ'ÿ&ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ%ÿ)ÿ,ÿ0ÿ2ÿ4ÿ4ÿ2ÿ2ÿ1ÿ/ÿ-ÿ,ÿ(ÿ'ÿ%ÿ%ÿ%ÿ(ÿ(ÿ(ÿ)ÿ+ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ0ÿ1ÿ1ÿ/ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ1ÿ1ÿ0ÿ1ÿ2ÿ2ÿ2ÿ3ÿ4ÿ4ÿ4ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ7ÿ4ÿ4ÿ4ÿ8ÿ@ÿLÿWÿaÿjÿuÿ|ÿ~ÿ}ÿzÿvÿpÿiÿeÿ_ÿYÿWÿ[ÿ]ÿcÿfÿfÿiÿjÿiÿfÿbÿ\ÿZÿVÿQÿLÿIÿGÿGÿFÿEÿBÿBÿDÿFÿGÿFÿGÿGÿEÿCÿBÿ@ÿ@ÿ?ÿ=ÿ<ÿ;ÿ9ÿ:ÿ:ÿ:ÿ;ÿ=ÿAÿDÿHÿNÿSÿWÿZÿ\ÿ_ÿ_ÿ`ÿ`ÿaÿaÿ`ÿ`ÿaÿcÿeÿgÿgÿgÿfÿcÿ`ÿ`ÿbÿdÿjÿoÿsÿxÿ}ÿ|ÿ|ÿzÿzÿ{ÿ~ÿÿÿÿÿ}ÿxÿvÿtÿrÿqÿmÿhÿ_ÿVÿFÿ6ÿ-ÿ-ÿ-ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ+ÿ+ÿ'~'{(~({(~(y(~(y)~)x(~'x&~&x&~&x&~'y&~%y%~%y$~$y$~$y#~#y$$z%%z&~&z&~&|&&|&&|'~'}&}&}&~&}&~&}&~&}&~&'~'%~%%&((''(('~''~''&~&&~$~&}&~&}%%}%%}%%}%%}%~%~%~%~&~&}'~'}&~&&~&%~%&~&%}%&}&$~$%~%~%~%$~$%~%%~%~%~$$~$$~#~#~#~#~#~"~"~"~""~"!~""~""~"~"~"}#~#}#~#}$~$|#~#|$~%|%~%|%%|%&|&&|&&{'(z((y''w))x))x))w))u))u((v))v))u''u&(v(&v&~'x%~%y$~"y"~ { !~#~#'~)*}+.}039<@BFJMNNOOJ~FA|1(})*[wxlmuvsyoP ~ #|,,{,,|,,|,,},*|*+~15:@HORONKHKKU~ad{XQxZeuigracqkhp[Xodan__o_\q__s\ZvYSwQOzRRyNHzB@y><z;;}=<}<=}?=}=={=?{DKzA1y**y.0y./y/3y32z01z/,y*-z01|10|21|48|83|/+|*)z),z+,{,,z,,y-.y..z./y//x02x12z32z34z43z32z21z10|0/|/.|.0|01|10|/.|,
,|+
+}*(}''~&&~%$~%#~$#&'++/0111//-+)''~'(}((}))}*+}++},,|,+|++|,.|..|-0|00}0/}/0}10}01}10}0.~.-~-.~.-~-,,----,..../00////0000112~22~233443333466666777~65~4447@LW}clhv|^Y|
vZrice\uUS}X\za
eui
iqj
inf
bt_
[}VRMII~HG}GF{GHzJJ{JJ{GE|DC|C@|>=~<;~;=~=?~?AEHMRV}Z^v`arc
crb
bua
b{e
de
ff
ffc`][\d~jn~suyx{{vzzx{~v~s~yowumroinlei
cfYMq=.{*
*
*+++,+))(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ'ÿ'ÿ'ÿ'ÿ(ÿ(ÿ'ÿ'ÿ'ÿ'ÿ&ÿ%ÿ&ÿ&ÿ&ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ#ÿ#ÿ#ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ$ÿ#ÿ#ÿ#ÿ#ÿ"ÿ#ÿ#ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ"ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ"ÿ"ÿ!ÿ!ÿ"ÿ"ÿ"ÿ"ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ'ÿ'ÿ'ÿ'ÿ&ÿ$ÿ$ÿ#ÿ!ÿ!ÿ!ÿ!ÿ"ÿ$ÿ'ÿ)ÿ*ÿ+ÿ-ÿ/ÿ1ÿ6ÿ>ÿBÿFÿIÿMÿPÿRÿRÿTÿRÿJÿFÿ?ÿ.ÿ(ÿ)ÿ9ÿzÿrÿiÿnÿwÿwÿqÿTÿ!ÿ ÿ"ÿ&ÿ,ÿ,ÿ+ÿ+ÿ,ÿ+ÿ+ÿ,ÿ,ÿ*ÿ*ÿ*ÿ/ÿ0ÿ5ÿ>ÿFÿIÿLÿLÿIÿJÿLÿMÿLÿXÿbÿcÿYÿEÿHÿSÿTÿ_ÿfÿlÿdÿdÿaÿ_ÿ`ÿbÿiÿkÿeÿ^ÿ^ÿ_ÿ^ÿ]ÿZÿRÿPÿQÿSÿRÿLÿEÿBÿAÿ>ÿ<ÿ8ÿ7ÿ7ÿ8ÿ8ÿ9ÿ8ÿ9ÿ9ÿ9ÿ9ÿ;ÿBÿPÿIÿ1ÿ*ÿ)ÿ,ÿ-ÿ0ÿ2ÿ2ÿ4ÿ2ÿ1ÿ/ÿ0ÿ/ÿ/ÿ0ÿ2ÿ3ÿ4ÿ4ÿ5ÿ2ÿ2ÿ2ÿ3ÿ3ÿ0ÿ-ÿ*ÿ)ÿ*ÿ*ÿ)ÿ+ÿ+ÿ+ÿ-ÿ-ÿ-ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ0ÿ/ÿ0ÿ1ÿ0ÿ0ÿ0ÿ2ÿ3ÿ4ÿ4ÿ3ÿ3ÿ3ÿ3ÿ2ÿ2ÿ1ÿ1ÿ0ÿ0ÿ.ÿ.ÿ/ÿ/ÿ0ÿ0ÿ/ÿ.ÿ/ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ(ÿ)ÿ(ÿ&ÿ&ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ'ÿ(ÿ*ÿ-ÿ.ÿ/ÿ0ÿ1ÿ0ÿ0ÿ/ÿ.ÿ,ÿ)ÿ'ÿ(ÿ(ÿ)ÿ(ÿ(ÿ)ÿ*ÿ*ÿ)ÿ+ÿ,ÿ,ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ.ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ,ÿ-ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ-ÿ.ÿ.ÿ.ÿ-ÿ-ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ,ÿ-ÿ.ÿ.ÿ-ÿ.ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ0ÿ0ÿ0ÿ0ÿ1ÿ1ÿ0ÿ0ÿ0ÿ0ÿ2ÿ3ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ3ÿ3ÿ4ÿ5ÿ5ÿ4ÿ4ÿ4ÿ4ÿ4ÿ6ÿ9ÿ@ÿLÿYÿcÿpÿvÿ{ÿ{ÿ}ÿ}ÿwÿqÿhÿ_ÿVÿPÿQÿWÿ\ÿaÿeÿhÿjÿjÿlÿgÿdÿaÿ\ÿVÿRÿMÿLÿKÿJÿJÿJÿKÿLÿLÿMÿMÿLÿIÿGÿFÿDÿCÿ@ÿ?ÿ=ÿ<ÿ<ÿ<ÿ=ÿ=ÿ?ÿ?ÿCÿGÿHÿMÿRÿVÿYÿ]ÿ_ÿ_ÿcÿdÿdÿeÿbÿdÿdÿeÿeÿfÿfÿfÿfÿcÿ]ÿ]ÿZÿXÿ[ÿaÿfÿlÿpÿsÿyÿzÿ{ÿ{ÿyÿzÿ~ÿ~ÿ}ÿxÿvÿrÿpÿmÿnÿlÿjÿeÿ^ÿTÿCÿ1ÿ+ÿ)ÿ*ÿ)ÿ*ÿ*ÿ+ÿ*ÿ(ÿ(ÿ'~'{'~({)~)y(~'y)~)x(~'x'~'x'~'x&~&y%~%y%~%z%~%z%~%{$~${$$z%%z&}&{&~&|&&}''}'~'}'}'}&~&}&~&}&~&}%~%'~'&~&''''''''&&&&''%%&%%%$$%&%%&&%~%%~%%~&}&~&}%~%'~'&~&%~%%~%$~$$~$"~#~#~#$~$$~$#~#~"~"~"~"~"~"~!~!~!~!~!~ ~!~!!~!!~! ~! ~!~!~!}!~!}~}#~#|"~"|!~"|#~#|$$|$%|%%{%%z&'z&&y((w((x((w))v))u))u))v))v))u**u**v((w((w&%x$$x"~"z!!}!$(~)*}++}./5=AEJSUTTVQJ~E;}*)(Tu~qmvsyxte}*~ "|!%{*,{,*|**|+,}.,|++~+*3=BHKGHHFDFP~_S{FKvSQtTbrgeoijnf^o_\nbjndZpX[r\\sZSvPTyVSzQL|CBz?;x:7y54y45{45{68{8:{AEz@8z,)z+,y.3y34y42y21y45y66z56|76|31|0/|/,|+*|)(|((|(){+-z--y-.y0/y01x1/y//y/0y00y13z31z22|33|33|11|0/|,-|/.|./|-.|,
-|,
+{++|*)|)(|&$}$$~$$~#$~$
$'
(+,,/0221/-+*~))})*}**}**}*+|,.|.0|0/|0/|-.|//}//}/.}..}./}//}/.~..~..~.-~,,,++++,,--....///~//~/0~0/~//~//~///001222111111233~34~43~45~78ANY{enhwz\{|Wy
uXoecXPtPQ}T
Z~b
fyg
jtk
jog
drb]}XRPOM~OM|NO{ON{NL{IH|FE|D@|@?~>=~<>~>>~>AEINQU[]y]asc
emf
fof
gvf
f{g
g}f
fdb_[URQ}V^|dh~krv|z|wz{v|zuzxquqnrlklkgi
ff_XiJ:s,)~+*))))(('ÿ'ÿ'ÿ(ÿ)ÿ)ÿ(ÿ'ÿ)ÿ)ÿ(ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ$ÿ#ÿ$ÿ$ÿ$ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ'ÿ'ÿ&ÿ&ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ&ÿ&ÿ%ÿ%ÿ%ÿ%ÿ$ÿ$ÿ$ÿ$ÿ#ÿ"ÿ"ÿ"ÿ#ÿ#ÿ#ÿ#ÿ"ÿ"ÿ!ÿ!ÿ!ÿ!ÿ!ÿ!ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ!ÿ!ÿ!ÿ ÿ!ÿ!ÿ!ÿ ÿ ÿÿ!ÿ$ÿ$ÿ!ÿ!ÿ!ÿ"ÿ#ÿ#ÿ$ÿ%ÿ$ÿ$ÿ%ÿ%ÿ&ÿ&ÿ&ÿ'ÿ)ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ)ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ*ÿ(ÿ(ÿ(ÿ(ÿ'ÿ%ÿ$ÿ$ÿ#ÿ#ÿ"ÿ"ÿ#ÿ&ÿ(ÿ)ÿ)ÿ*ÿ+ÿ.ÿ.ÿ4ÿ8ÿ>ÿHÿKÿRÿVÿUÿUÿRÿKÿHÿDÿ9ÿ)ÿ(ÿ,ÿkÿsÿjÿqÿxÿsÿiÿ7ÿÿ$ÿ"ÿ#ÿ'ÿ*ÿ+ÿ+ÿ*ÿ*ÿ)ÿ+ÿ,ÿ.ÿ/ÿ,ÿ*ÿ,ÿ+ÿ5ÿAÿGÿGÿIÿIÿFÿ@ÿ?ÿ;ÿ=ÿFÿRÿJÿ?ÿIÿRÿYÿkÿhÿfÿhÿhÿcÿeÿgÿ\ÿPÿ]ÿgÿaÿXÿTÿWÿXÿZÿZÿVÿQÿUÿSÿMÿMÿJÿCÿCÿ@ÿ=ÿ=ÿ:ÿ7ÿ5ÿ5ÿ5ÿ3ÿ4ÿ6ÿ8ÿ8ÿ<ÿ>ÿ>ÿ>ÿ<ÿ0ÿ)ÿ)ÿ+ÿ-ÿ0ÿ1ÿ4ÿ4ÿ4ÿ3ÿ4ÿ6ÿ8ÿ8ÿ8ÿ7ÿ5ÿ4ÿ3ÿ1ÿ/ÿ-ÿ-ÿ-ÿ,ÿ+ÿ*ÿ)ÿ(ÿ(ÿ(ÿ(ÿ(ÿ)ÿ*ÿ,ÿ.ÿ.ÿ/ÿ0ÿ/ÿ0ÿ1ÿ1ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ1ÿ1ÿ1ÿ3ÿ2ÿ3ÿ4ÿ4ÿ4ÿ5ÿ3ÿ2ÿ0ÿ/ÿ,ÿ+ÿ.ÿ.ÿ/ÿ-ÿ-ÿ-ÿ-ÿ,ÿ+ÿ+ÿ*ÿ*ÿ+ÿ+ÿ(ÿ'ÿ'ÿ%ÿ$ÿ&ÿ$ÿ$ÿ$ÿ$ÿ#ÿ&ÿ&ÿ(ÿ*ÿ+ÿ.ÿ1ÿ3ÿ3ÿ2ÿ2ÿ0ÿ/ÿ/ÿ,ÿ*ÿ*ÿ+ÿ+ÿ+ÿ+ÿ,ÿ,ÿ,ÿ.ÿ0ÿ0ÿ0ÿ0ÿ/ÿ0ÿ1ÿ1ÿ0ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ/ÿ/ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ-ÿ,ÿ,ÿ,ÿ-ÿ-ÿ-ÿ-ÿ.ÿ.ÿ-ÿ-ÿ.ÿ.ÿ.ÿ.ÿ/ÿ/ÿ1ÿ1ÿ1ÿ1ÿ0ÿ0ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ/ÿ0ÿ0ÿ2ÿ2ÿ1ÿ1ÿ2ÿ2ÿ2ÿ2ÿ2ÿ3ÿ3ÿ3ÿ4ÿ4ÿ5ÿ4ÿ5ÿ7ÿ6ÿ:ÿCÿNÿYÿcÿjÿsÿyÿyÿyÿuÿrÿjÿ_ÿVÿOÿSÿRÿWÿ\ÿbÿgÿmÿkÿlÿkÿiÿeÿbÿ\ÿXÿWÿRÿQÿPÿPÿOÿPÿPÿNÿNÿJÿHÿGÿEÿDÿDÿBÿ@ÿ@ÿ?ÿ?ÿ=ÿ?ÿ?ÿAÿAÿCÿEÿGÿNÿQÿUÿ[ÿ]ÿ^ÿbÿdÿgÿiÿhÿgÿhÿgÿgÿgÿgÿfÿeÿdÿaÿ]ÿXÿRÿOÿNÿOÿRÿYÿaÿgÿlÿpÿtÿxÿ{ÿzÿyÿzÿzÿxÿxÿwÿsÿmÿmÿlÿiÿgÿdÿ\ÿSÿCÿ4ÿ-ÿ*ÿ)ÿ(ÿ(ÿ'ÿ'ÿ(ÿ(ÿ
\ No newline at end of file
diff --git a/src/media/test/data/bear-1280x720-zero-stsz-entry.mp4 b/src/media/test/data/bear-1280x720-zero-stsz-entry.mp4
deleted file mode 100644
index 9d38659..0000000
--- a/src/media/test/data/bear-1280x720-zero-stsz-entry.mp4
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-16x9-aspect.webm b/src/media/test/data/bear-320x240-16x9-aspect.webm
deleted file mode 100644
index e29e809..0000000
--- a/src/media/test/data/bear-320x240-16x9-aspect.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-altref.webm b/src/media/test/data/bear-320x240-altref.webm
deleted file mode 100644
index b1db07a..0000000
--- a/src/media/test/data/bear-320x240-altref.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-audio-only.webm b/src/media/test/data/bear-320x240-audio-only.webm
deleted file mode 100644
index 6594754..0000000
--- a/src/media/test/data/bear-320x240-audio-only.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-cues-in-front.webm b/src/media/test/data/bear-320x240-cues-in-front.webm
deleted file mode 100644
index d15f3b2..0000000
--- a/src/media/test/data/bear-320x240-cues-in-front.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-encrypted.webm b/src/media/test/data/bear-320x240-encrypted.webm
deleted file mode 100644
index 0aa6c63..0000000
--- a/src/media/test/data/bear-320x240-encrypted.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-live.webm b/src/media/test/data/bear-320x240-live.webm
deleted file mode 100644
index c196c9b..0000000
--- a/src/media/test/data/bear-320x240-live.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-manifest.js b/src/media/test/data/bear-320x240-manifest.js
deleted file mode 100644
index 9e6602a..0000000
--- a/src/media/test/data/bear-320x240-manifest.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-{
- duration: 2.744000,
- type: 'video/webm; codecs="vp8, vorbis"',
- init: { offset: 0, size: 4370},
- media: [
- { offset: 4370, size: 40778, timecode: 0.000000 },
- { offset: 45148, size: 27589, timecode: 0.396000 },
- { offset: 72737, size: 28183, timecode: 0.779000 },
- { offset: 100920, size: 31600, timecode: 1.197000 },
- { offset: 132520, size: 33922, timecode: 1.589000 },
- { offset: 166442, size: 30587, timecode: 1.987000 },
- { offset: 197029, size: 22079, timecode: 2.400000 },
- ]
-}
diff --git a/src/media/test/data/bear-320x240-multitrack.webm b/src/media/test/data/bear-320x240-multitrack.webm
deleted file mode 100644
index 987b193..0000000
--- a/src/media/test/data/bear-320x240-multitrack.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240-video-only.webm b/src/media/test/data/bear-320x240-video-only.webm
deleted file mode 100644
index a6601f4..0000000
--- a/src/media/test/data/bear-320x240-video-only.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-320x240.webm b/src/media/test/data/bear-320x240.webm
deleted file mode 100644
index a1b4150..0000000
--- a/src/media/test/data/bear-320x240.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear-640x360-manifest.js b/src/media/test/data/bear-640x360-manifest.js
deleted file mode 100644
index d7787b0..0000000
--- a/src/media/test/data/bear-640x360-manifest.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-{
- duration: 2.740000,
- type: 'video/webm; codecs="vp8, vorbis"',
- init: { offset: 0, size: 4340},
- media: [
- { offset: 4340, size: 50950, timecode: 0.000000},
- { offset: 55290, size: 18785, timecode: 0.527000},
- { offset: 74075, size: 19810, timecode: 1.014000},
- { offset: 93885, size: 21706, timecode: 1.522000},
- { offset: 115591, size: 20249, timecode: 2.016000},
- { offset: 135840, size: 9946, timecode: 2.515000},
- ]
-}
diff --git a/src/media/test/data/bear-640x360.webm b/src/media/test/data/bear-640x360.webm
deleted file mode 100644
index 02ae36c..0000000
--- a/src/media/test/data/bear-640x360.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear.1280x720.mp4 b/src/media/test/data/bear.1280x720.mp4
deleted file mode 100644
index b424a0f..0000000
--- a/src/media/test/data/bear.1280x720.mp4
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear.1280x720_dash.mp4 b/src/media/test/data/bear.1280x720_dash.mp4
deleted file mode 100644
index a05e1a0..0000000
--- a/src/media/test/data/bear.1280x720_dash.mp4
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear.640x360_dash.mp4 b/src/media/test/data/bear.640x360_dash.mp4
deleted file mode 100644
index e5a045a..0000000
--- a/src/media/test/data/bear.640x360_dash.mp4
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/bear.ogv b/src/media/test/data/bear.ogv
deleted file mode 100644
index b2e1134..0000000
--- a/src/media/test/data/bear.ogv
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/id3_png_test.mp3 b/src/media/test/data/id3_png_test.mp3
deleted file mode 100644
index 61efa2c..0000000
--- a/src/media/test/data/id3_png_test.mp3
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/id3_test.mp3 b/src/media/test/data/id3_test.mp3
deleted file mode 100644
index ddf43b9..0000000
--- a/src/media/test/data/id3_test.mp3
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/no_audio_video.webm b/src/media/test/data/no_audio_video.webm
deleted file mode 100644
index 924c091..0000000
--- a/src/media/test/data/no_audio_video.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/no_streams.webm b/src/media/test/data/no_streams.webm
deleted file mode 100644
index e0bc3e8..0000000
--- a/src/media/test/data/no_streams.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/nonzero-start-time.webm b/src/media/test/data/nonzero-start-time.webm
deleted file mode 100644
index 9429bc8..0000000
--- a/src/media/test/data/nonzero-start-time.webm
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx.m4a b/src/media/test/data/sfx.m4a
deleted file mode 100644
index 6dc4621..0000000
--- a/src/media/test/data/sfx.m4a
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx.mp3 b/src/media/test/data/sfx.mp3
deleted file mode 100644
index d9eb2f3..0000000
--- a/src/media/test/data/sfx.mp3
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx.ogg b/src/media/test/data/sfx.ogg
deleted file mode 100644
index c569c8f..0000000
--- a/src/media/test/data/sfx.ogg
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx_f32le.wav b/src/media/test/data/sfx_f32le.wav
deleted file mode 100644
index d5ea6a1..0000000
--- a/src/media/test/data/sfx_f32le.wav
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx_s16le.wav b/src/media/test/data/sfx_s16le.wav
deleted file mode 100644
index 00bc95f..0000000
--- a/src/media/test/data/sfx_s16le.wav
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx_s24le.wav b/src/media/test/data/sfx_s24le.wav
deleted file mode 100644
index d2d5d46..0000000
--- a/src/media/test/data/sfx_s24le.wav
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sfx_u8.wav b/src/media/test/data/sfx_u8.wav
deleted file mode 100644
index f8b397c..0000000
--- a/src/media/test/data/sfx_u8.wav
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/speech_16b_mono_44kHz.raw b/src/media/test/data/speech_16b_mono_44kHz.raw
deleted file mode 100644
index 29f7704..0000000
--- a/src/media/test/data/speech_16b_mono_44kHz.raw
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/speech_16b_mono_48kHz.raw b/src/media/test/data/speech_16b_mono_48kHz.raw
deleted file mode 100644
index 3241743..0000000
--- a/src/media/test/data/speech_16b_mono_48kHz.raw
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/speech_16b_stereo_44kHz.raw b/src/media/test/data/speech_16b_stereo_44kHz.raw
deleted file mode 100644
index cdbb644..0000000
--- a/src/media/test/data/speech_16b_stereo_44kHz.raw
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/speech_16b_stereo_48kHz.raw b/src/media/test/data/speech_16b_stereo_48kHz.raw
deleted file mode 100644
index e29432e..0000000
--- a/src/media/test/data/speech_16b_stereo_48kHz.raw
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/speex_audio_vorbis_video.ogv b/src/media/test/data/speex_audio_vorbis_video.ogv
deleted file mode 100644
index 313eddd..0000000
--- a/src/media/test/data/speex_audio_vorbis_video.ogv
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/sweep02_16b_mono_16KHz.raw b/src/media/test/data/sweep02_16b_mono_16KHz.raw
deleted file mode 100644
index 5dd4dc4..0000000
--- a/src/media/test/data/sweep02_16b_mono_16KHz.raw
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/ten_byte_file b/src/media/test/data/ten_byte_file
deleted file mode 100644
index ad47100..0000000
--- a/src/media/test/data/ten_byte_file
+++ /dev/null
@@ -1 +0,0 @@
-0123456789
\ No newline at end of file
diff --git a/src/media/test/data/vorbis-extradata b/src/media/test/data/vorbis-extradata
deleted file mode 100644
index 1f95309..0000000
--- a/src/media/test/data/vorbis-extradata
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vorbis-packet-0 b/src/media/test/data/vorbis-packet-0
deleted file mode 100644
index 0e3739c..0000000
--- a/src/media/test/data/vorbis-packet-0
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vorbis-packet-1 b/src/media/test/data/vorbis-packet-1
deleted file mode 100644
index 035ccdb..0000000
--- a/src/media/test/data/vorbis-packet-1
+++ /dev/null
@@ -1 +0,0 @@
-ìÊRdïÊRdKL£8/YÏù8v'·)ê}7¢Õ¢7o
\ No newline at end of file
diff --git a/src/media/test/data/vorbis-packet-2 b/src/media/test/data/vorbis-packet-2
deleted file mode 100644
index a01cb5d..0000000
--- a/src/media/test/data/vorbis-packet-2
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vorbis-packet-3 b/src/media/test/data/vorbis-packet-3
deleted file mode 100644
index b222e7d..0000000
--- a/src/media/test/data/vorbis-packet-3
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vorbis_audio_wmv_video.mkv b/src/media/test/data/vorbis_audio_wmv_video.mkv
deleted file mode 100644
index 612319f..0000000
--- a/src/media/test/data/vorbis_audio_wmv_video.mkv
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-I-frame-160x240 b/src/media/test/data/vp8-I-frame-160x240
deleted file mode 100644
index 65e4337..0000000
--- a/src/media/test/data/vp8-I-frame-160x240
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-I-frame-320x120 b/src/media/test/data/vp8-I-frame-320x120
deleted file mode 100644
index ba4e1e2..0000000
--- a/src/media/test/data/vp8-I-frame-320x120
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-I-frame-320x240 b/src/media/test/data/vp8-I-frame-320x240
deleted file mode 100644
index 5b7bfb9..0000000
--- a/src/media/test/data/vp8-I-frame-320x240
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-I-frame-320x480 b/src/media/test/data/vp8-I-frame-320x480
deleted file mode 100644
index 1ed59f0..0000000
--- a/src/media/test/data/vp8-I-frame-320x480
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-I-frame-640x240 b/src/media/test/data/vp8-I-frame-640x240
deleted file mode 100644
index 87299cc..0000000
--- a/src/media/test/data/vp8-I-frame-640x240
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/vp8-corrupt-I-frame b/src/media/test/data/vp8-corrupt-I-frame
deleted file mode 100644
index 23afa84..0000000
--- a/src/media/test/data/vp8-corrupt-I-frame
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/webm_content_encodings b/src/media/test/data/webm_content_encodings
deleted file mode 100644
index c49fed2..0000000
--- a/src/media/test/data/webm_content_encodings
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/webm_ebml_element b/src/media/test/data/webm_ebml_element
deleted file mode 100644
index 9f0eb1b..0000000
--- a/src/media/test/data/webm_ebml_element
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/webm_info_element b/src/media/test/data/webm_info_element
deleted file mode 100644
index 1097d13..0000000
--- a/src/media/test/data/webm_info_element
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/webm_vorbis_track_entry b/src/media/test/data/webm_vorbis_track_entry
deleted file mode 100644
index a873521..0000000
--- a/src/media/test/data/webm_vorbis_track_entry
+++ /dev/null
Binary files differ
diff --git a/src/media/test/data/webm_vp8_track_entry b/src/media/test/data/webm_vp8_track_entry
deleted file mode 100644
index f544e9d..0000000
--- a/src/media/test/data/webm_vp8_track_entry
+++ /dev/null
Binary files differ
diff --git a/src/media/test/ffmpeg_tests/ffmpeg_tests.cc b/src/media/test/ffmpeg_tests/ffmpeg_tests.cc
deleted file mode 100644
index 262d532..0000000
--- a/src/media/test/ffmpeg_tests/ffmpeg_tests.cc
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Software qualification test for FFmpeg. This test is used to certify that
-// software decoding quality and performance of FFmpeg meets a mimimum
-// standard.
-
-#include <iomanip>
-#include <iostream>
-#include <string>
-
-#include "base/at_exit.h"
-#include "base/basictypes.h"
-#include "base/command_line.h"
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/md5.h"
-#include "base/path_service.h"
-#include "base/string_util.h"
-#include "base/time.h"
-#include "base/utf_string_conversions.h"
-#include "media/base/djb2.h"
-#include "media/base/media.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/filters/ffmpeg_video_decoder.h"
-#include "media/filters/in_memory_url_protocol.h"
-
-#ifdef DEBUG
-#define SHOW_VERBOSE 1
-#else
-#define SHOW_VERBOSE 0
-#endif
-
-#if defined(OS_WIN)
-
-// Enable to build with exception handler
-//#define ENABLE_WINDOWS_EXCEPTIONS 1
-
-#ifdef ENABLE_WINDOWS_EXCEPTIONS
-// warning: disable warning about exception handler.
-#pragma warning(disable:4509)
-#endif
-
-// Thread priorities to make benchmark more stable.
-
-void EnterTimingSection() {
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
-}
-
-void LeaveTimingSection() {
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
-}
-#else
-void EnterTimingSection() {
- pthread_attr_t pta;
- struct sched_param param;
-
- pthread_attr_init(&pta);
- memset(¶m, 0, sizeof(param));
- param.sched_priority = 78;
- pthread_attr_setschedparam(&pta, ¶m);
- pthread_attr_destroy(&pta);
-}
-
-void LeaveTimingSection() {
-}
-#endif
-
-int main(int argc, const char** argv) {
- base::AtExitManager exit_manager;
-
- CommandLine::Init(argc, argv);
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
-
- const CommandLine::StringVector& filenames = cmd_line->GetArgs();
-
- if (filenames.empty()) {
- std::cerr << "Usage: " << argv[0] << " MEDIAFILE" << std::endl;
- return 1;
- }
-
- // Initialize our media library (try loading DLLs, etc.) before continuing.
- FilePath media_path;
- PathService::Get(base::DIR_MODULE, &media_path);
- if (!media::InitializeMediaLibrary(media_path)) {
- std::cerr << "Unable to initialize the media library.";
- return 1;
- }
-
- // Retrieve command line options.
- FilePath in_path(filenames[0]);
- FilePath out_path;
- if (filenames.size() > 1)
- out_path = FilePath(filenames[1]);
-
- // Default flags that match Chrome defaults.
- int video_threads = 2;
- int max_frames = 0;
- int max_loops = 0;
- bool flush = false;
-
- unsigned int hash_value = 5381u; // Seed for DJB2.
- bool hash_djb2 = false;
-
- base::MD5Context ctx; // Intermediate MD5 data: do not use
- base::MD5Init(&ctx);
- bool hash_md5 = false;
-
- std::ostream* log_out = &std::cout;
-#if defined(ENABLE_WINDOWS_EXCEPTIONS)
- // Catch exceptions so this tool can be used in automated testing.
- __try {
-#endif
-
- file_util::MemoryMappedFile file_data;
- file_data.Initialize(in_path);
- media::InMemoryUrlProtocol protocol(
- file_data.data(), file_data.length(), false);
-
- // Register FFmpeg and attempt to open file.
- media::FFmpegGlue glue(&protocol);
- if (!glue.OpenContext()) {
- std::cerr << "Error: Could not open input for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- AVFormatContext* format_context = glue.format_context();
-
- // Open output file.
- FILE *output = NULL;
- if (!out_path.empty()) {
- output = file_util::OpenFile(out_path, "wb");
- if (!output) {
- std::cerr << "Error: Could not open output "
- << out_path.value() << std::endl;
- return 1;
- }
- }
-
- // Parse a little bit of the stream to fill out the format context.
- if (avformat_find_stream_info(format_context, NULL) < 0) {
- std::cerr << "Error: Could not find stream info for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- // Find our target stream(s)
- int video_stream = -1;
- int audio_stream = -1;
- for (size_t i = 0; i < format_context->nb_streams; ++i) {
- AVCodecContext* codec_context = format_context->streams[i]->codec;
-
- if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && video_stream < 0) {
-#if SHOW_VERBOSE
- *log_out << "V ";
-#endif
- video_stream = i;
- } else {
- if (codec_context->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream < 0) {
-#if SHOW_VERBOSE
- *log_out << "A ";
-#endif
- audio_stream = i;
- } else {
-#if SHOW_VERBOSE
- *log_out << " ";
-#endif
- }
- }
-
-#if SHOW_VERBOSE
- AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
- if (!codec || (codec_context->codec_type == AVMEDIA_TYPE_UNKNOWN)) {
- *log_out << "Stream #" << i << ": Unknown" << std::endl;
- } else {
- // Print out stream information
- *log_out << "Stream #" << i << ": " << codec->name << " ("
- << codec->long_name << ")" << std::endl;
- }
-#endif
- }
- int target_stream = video_stream;
- AVMediaType target_codec = AVMEDIA_TYPE_VIDEO;
- if (target_stream < 0) {
- target_stream = audio_stream;
- target_codec = AVMEDIA_TYPE_AUDIO;
- }
-
- // Only continue if we found our target stream.
- if (target_stream < 0) {
- std::cerr << "Error: Could not find target stream "
- << target_stream << " for " << in_path.value() << std::endl;
- return 1;
- }
-
- // Prepare FFmpeg structures.
- AVPacket packet;
- AVCodecContext* codec_context = format_context->streams[target_stream]->codec;
- AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
-
- // Only continue if we found our codec.
- if (!codec) {
- std::cerr << "Error: Could not find codec for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
-
- // Initialize threaded decode.
- if (target_codec == AVMEDIA_TYPE_VIDEO && video_threads > 0) {
- codec_context->thread_count = video_threads;
- }
-
- // Initialize our codec.
- if (avcodec_open2(codec_context, codec, NULL) < 0) {
- std::cerr << "Error: Could not open codec "
- << codec_context->codec->name << " for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- // Buffer used for audio decoding.
- scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> audio_frame(
- avcodec_alloc_frame());
- if (!audio_frame.get()) {
- std::cerr << "Error: avcodec_alloc_frame for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- // Buffer used for video decoding.
- scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> video_frame(
- avcodec_alloc_frame());
- if (!video_frame.get()) {
- std::cerr << "Error: avcodec_alloc_frame for "
- << in_path.value() << std::endl;
- return 1;
- }
-
- // Stats collector.
- EnterTimingSection();
- std::vector<double> decode_times;
- decode_times.reserve(4096);
- // Parse through the entire stream until we hit EOF.
-#if SHOW_VERBOSE
- base::TimeTicks start = base::TimeTicks::HighResNow();
-#endif
- int frames = 0;
- int read_result = 0;
- do {
- read_result = av_read_frame(format_context, &packet);
-
- if (read_result < 0) {
- if (max_loops) {
- --max_loops;
- }
- if (max_loops > 0) {
- av_seek_frame(format_context, -1, 0, AVSEEK_FLAG_BACKWARD);
- read_result = 0;
- continue;
- }
- if (flush) {
- packet.stream_index = target_stream;
- packet.size = 0;
- } else {
- break;
- }
- }
-
- // Only decode packets from our target stream.
- if (packet.stream_index == target_stream) {
- int result = -1;
- if (target_codec == AVMEDIA_TYPE_AUDIO) {
- int size_out = 0;
- int got_audio = 0;
-
- avcodec_get_frame_defaults(audio_frame.get());
-
- base::TimeTicks decode_start = base::TimeTicks::HighResNow();
- result = avcodec_decode_audio4(codec_context, audio_frame.get(),
- &got_audio, &packet);
- base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
-
- if (got_audio) {
- size_out = av_samples_get_buffer_size(
- NULL, codec_context->channels, audio_frame->nb_samples,
- codec_context->sample_fmt, 1);
- }
-
- if (got_audio && size_out) {
- decode_times.push_back(delta.InMillisecondsF());
- ++frames;
- read_result = 0; // Force continuation.
-
- if (output) {
- if (fwrite(audio_frame->data[0], 1, size_out, output) !=
- static_cast<size_t>(size_out)) {
- std::cerr << "Error: Could not write "
- << size_out << " bytes for " << in_path.value()
- << std::endl;
- return 1;
- }
- }
-
- const uint8* u8_samples =
- reinterpret_cast<const uint8*>(audio_frame->data[0]);
- if (hash_djb2) {
- hash_value = DJB2Hash(u8_samples, size_out, hash_value);
- }
- if (hash_md5) {
- base::MD5Update(
- &ctx,
- base::StringPiece(reinterpret_cast<const char*>(u8_samples),
- size_out));
- }
- }
- } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
- int got_picture = 0;
-
- avcodec_get_frame_defaults(video_frame.get());
-
- base::TimeTicks decode_start = base::TimeTicks::HighResNow();
- result = avcodec_decode_video2(codec_context, video_frame.get(),
- &got_picture, &packet);
- base::TimeDelta delta = base::TimeTicks::HighResNow() - decode_start;
-
- if (got_picture) {
- decode_times.push_back(delta.InMillisecondsF());
- ++frames;
- read_result = 0; // Force continuation.
-
- for (int plane = 0; plane < 3; ++plane) {
- const uint8* source = video_frame->data[plane];
- const size_t source_stride = video_frame->linesize[plane];
- size_t bytes_per_line = codec_context->width;
- size_t copy_lines = codec_context->height;
- if (plane != 0) {
- switch (codec_context->pix_fmt) {
- case PIX_FMT_YUV420P:
- case PIX_FMT_YUVJ420P:
- bytes_per_line /= 2;
- copy_lines = (copy_lines + 1) / 2;
- break;
- case PIX_FMT_YUV422P:
- case PIX_FMT_YUVJ422P:
- bytes_per_line /= 2;
- break;
- case PIX_FMT_YUV444P:
- case PIX_FMT_YUVJ444P:
- break;
- default:
- std::cerr << "Error: Unknown video format "
- << codec_context->pix_fmt;
- return 1;
- }
- }
- if (output) {
- for (size_t i = 0; i < copy_lines; ++i) {
- if (fwrite(source, 1, bytes_per_line, output) !=
- bytes_per_line) {
- std::cerr << "Error: Could not write data after "
- << copy_lines << " lines for "
- << in_path.value() << std::endl;
- return 1;
- }
- source += source_stride;
- }
- }
- if (hash_djb2) {
- for (size_t i = 0; i < copy_lines; ++i) {
- hash_value = DJB2Hash(source, bytes_per_line, hash_value);
- source += source_stride;
- }
- }
- if (hash_md5) {
- for (size_t i = 0; i < copy_lines; ++i) {
- base::MD5Update(
- &ctx,
- base::StringPiece(reinterpret_cast<const char*>(source),
- bytes_per_line));
- source += source_stride;
- }
- }
- }
- }
- } else {
- NOTREACHED();
- }
-
- // Make sure our decoding went OK.
- if (result < 0) {
- std::cerr << "Error: avcodec_decode returned "
- << result << " for " << in_path.value() << std::endl;
- return 1;
- }
- }
- // Free our packet.
- av_free_packet(&packet);
-
- if (max_frames && (frames >= max_frames))
- break;
- } while (read_result >= 0);
-#if SHOW_VERBOSE
- base::TimeDelta total = base::TimeTicks::HighResNow() - start;
-#endif
- LeaveTimingSection();
-
- // Clean up.
- if (output)
- file_util::CloseFile(output);
-
- // Calculate the sum of times. Note that some of these may be zero.
- double sum = 0;
- for (size_t i = 0; i < decode_times.size(); ++i) {
- sum += decode_times[i];
- }
-
- if (sum > 0) {
- if (target_codec == AVMEDIA_TYPE_AUDIO) {
- // Calculate the average milliseconds per frame.
- // Audio decoding is usually in the millisecond or range, and
- // best expressed in time (ms) rather than FPS, which can approach
- // infinity.
- double ms = sum / frames;
- // Print our results.
- log_out->setf(std::ios::fixed);
- log_out->precision(2);
- *log_out << "TIME PER FRAME (MS):" << std::setw(11) << ms << std::endl;
- } else if (target_codec == AVMEDIA_TYPE_VIDEO) {
- // Calculate the average frames per second.
- // Video decoding is expressed in Frames Per Second - a term easily
- // understood and should exceed a typical target of 30 fps.
- double fps = frames * 1000.0 / sum;
- // Print our results.
- log_out->setf(std::ios::fixed);
- log_out->precision(2);
- *log_out << "FPS:" << std::setw(11) << fps << std::endl;
- }
- }
-
-#if SHOW_VERBOSE
- // Print our results.
- log_out->setf(std::ios::fixed);
- log_out->precision(2);
- *log_out << std::endl;
- *log_out << " Frames:" << std::setw(11) << frames
- << std::endl;
- *log_out << " Total:" << std::setw(11) << total.InMillisecondsF()
- << " ms" << std::endl;
- *log_out << " Summation:" << std::setw(11) << sum
- << " ms" << std::endl;
-
- if (frames > 0) {
- // Calculate the average time per frame.
- double average = sum / frames;
-
- // Calculate the sum of the squared differences.
- // Standard deviation will only be accurate if no threads are used.
- // TODO(fbarchard): Rethink standard deviation calculation.
- double squared_sum = 0;
- for (int i = 0; i < frames; ++i) {
- double difference = decode_times[i] - average;
- squared_sum += difference * difference;
- }
-
- // Calculate the standard deviation (jitter).
- double stddev = sqrt(squared_sum / frames);
-
- *log_out << " Average:" << std::setw(11) << average
- << " ms" << std::endl;
- *log_out << " StdDev:" << std::setw(11) << stddev
- << " ms" << std::endl;
- }
- if (hash_djb2) {
- *log_out << " DJB2 Hash:" << std::setw(11) << hash_value
- << " " << in_path.value() << std::endl;
- }
- if (hash_md5) {
- base::MD5Digest digest; // The result of the computation.
- base::MD5Final(&digest, &ctx);
- *log_out << " MD5 Hash: " << base::MD5DigestToBase16(digest)
- << " " << in_path.value() << std::endl;
- }
-#endif // SHOW_VERBOSE
-#if defined(ENABLE_WINDOWS_EXCEPTIONS)
- } __except(EXCEPTION_EXECUTE_HANDLER) {
- *log_out << " Exception:" << std::setw(11) << GetExceptionCode()
- << " " << in_path.value() << std::endl;
- return 1;
- }
-#endif
- CommandLine::Reset();
- return 0;
-}
diff --git a/src/media/video/capture/fake_video_capture_device.cc b/src/media/video/capture/fake_video_capture_device.cc
deleted file mode 100644
index b137192..0000000
--- a/src/media/video/capture/fake_video_capture_device.cc
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/fake_video_capture_device.h"
-
-#include <string>
-
-#include "base/bind.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/stringprintf.h"
-#include "media/audio/fake_audio_input_stream.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkPaint.h"
-
-namespace media {
-
-static const int kFakeCaptureTimeoutMs = 50;
-static const int kFakeCaptureBeepCycle = 20; // Visual beep every 1s.
-enum { kNumberOfFakeDevices = 2 };
-
-bool FakeVideoCaptureDevice::fail_next_create_ = false;
-
-void FakeVideoCaptureDevice::GetDeviceNames(Names* const device_names) {
- // Empty the name list.
- device_names->erase(device_names->begin(), device_names->end());
-
- for (int n = 0; n < kNumberOfFakeDevices; n++) {
- Name name;
- name.unique_id = StringPrintf("/dev/video%d", n);
- name.device_name = StringPrintf("fake_device_%d", n);
- device_names->push_back(name);
- }
-}
-
-VideoCaptureDevice* FakeVideoCaptureDevice::Create(const Name& device_name) {
- if (fail_next_create_) {
- fail_next_create_ = false;
- return NULL;
- }
- for (int n = 0; n < kNumberOfFakeDevices; ++n) {
- std::string possible_id = StringPrintf("/dev/video%d", n);
- if (device_name.unique_id.compare(possible_id) == 0) {
- return new FakeVideoCaptureDevice(device_name);
- }
- }
- return NULL;
-}
-
-void FakeVideoCaptureDevice::SetFailNextCreate() {
- fail_next_create_ = true;
-}
-
-FakeVideoCaptureDevice::FakeVideoCaptureDevice(const Name& device_name)
- : device_name_(device_name),
- observer_(NULL),
- state_(kIdle),
- capture_thread_("CaptureThread"),
- frame_size_(0),
- frame_count_(0),
- frame_width_(0),
- frame_height_(0) {
-}
-
-FakeVideoCaptureDevice::~FakeVideoCaptureDevice() {
- // Check if the thread is running.
- // This means that the device have not been DeAllocated properly.
- DCHECK(!capture_thread_.IsRunning());
-}
-
-void FakeVideoCaptureDevice::Allocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer) {
- if (state_ != kIdle) {
- return; // Wrong state.
- }
-
- observer_ = observer;
- VideoCaptureCapability current_settings;
- current_settings.color = VideoCaptureCapability::kI420;
- current_settings.expected_capture_delay = 0;
- current_settings.interlaced = false;
- if (width > 320) { // VGA
- current_settings.width = 640;
- current_settings.height = 480;
- current_settings.frame_rate = 30;
- } else { // QVGA
- current_settings.width = 320;
- current_settings.height = 240;
- current_settings.frame_rate = 30;
- }
-
- size_t fake_frame_size =
- current_settings.width * current_settings.height * 3 / 2;
- fake_frame_.reset(new uint8[fake_frame_size]);
- memset(fake_frame_.get(), 0, fake_frame_size);
- frame_size_ = fake_frame_size;
- frame_width_ = current_settings.width;
- frame_height_ = current_settings.height;
-
- state_ = kAllocated;
- observer_->OnFrameInfo(current_settings);
-}
-
-void FakeVideoCaptureDevice::Start() {
- if (state_ != kAllocated) {
- return; // Wrong state.
- }
- state_ = kCapturing;
- capture_thread_.Start();
- capture_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&FakeVideoCaptureDevice::OnCaptureTask,
- base::Unretained(this)));
-}
-
-void FakeVideoCaptureDevice::Stop() {
- if (state_ != kCapturing) {
- return; // Wrong state.
- }
- capture_thread_.Stop();
- state_ = kAllocated;
-}
-
-void FakeVideoCaptureDevice::DeAllocate() {
- if (state_ != kAllocated && state_ != kCapturing) {
- return; // Wrong state.
- }
- capture_thread_.Stop();
- state_ = kIdle;
-}
-
-const VideoCaptureDevice::Name& FakeVideoCaptureDevice::device_name() {
- return device_name_;
-}
-
-void FakeVideoCaptureDevice::OnCaptureTask() {
- if (state_ != kCapturing) {
- return;
- }
-
- memset(fake_frame_.get(), 0, frame_size_);
-
- SkBitmap bitmap;
- bitmap.setConfig(SkBitmap::kA8_Config, frame_width_, frame_height_,
- frame_width_);
- bitmap.setPixels(fake_frame_.get());
-
- SkCanvas canvas(bitmap);
-
- // Draw a sweeping circle to show an animation.
- int radius = std::min(frame_width_, frame_height_) / 4;
- SkRect rect = SkRect::MakeXYWH(
- frame_width_ / 2 - radius, frame_height_ / 2 - radius,
- 2 * radius, 2 * radius);
-
- SkPaint paint;
- paint.setStyle(SkPaint::kFill_Style);
-
- // Only Y plane is being drawn and this gives 50% grey on the Y
- // plane. The result is a light green color in RGB space.
- paint.setAlpha(128);
-
- int end_angle = (frame_count_ % kFakeCaptureBeepCycle * 360) /
- kFakeCaptureBeepCycle;
- if (!end_angle)
- end_angle = 360;
- canvas.drawArc(rect, 0, end_angle, true, paint);
-
- // Draw current time.
- int elapsed_ms = kFakeCaptureTimeoutMs * frame_count_;
- int milliseconds = elapsed_ms % 1000;
- int seconds = (elapsed_ms / 1000) % 60;
- int minutes = (elapsed_ms / 1000 / 60) % 60;
- int hours = (elapsed_ms / 1000 / 60 / 60) % 60;
-
- std::string time_string =
- base::StringPrintf("%d:%02d:%02d:%03d %d", hours, minutes,
- seconds, milliseconds, frame_count_);
- canvas.scale(3, 3);
- canvas.drawText(time_string.data(), time_string.length(), 30, 20,
- paint);
-
- if (frame_count_ % kFakeCaptureBeepCycle == 0) {
- // Generate a synchronized beep sound if there is one audio input
- // stream created.
- FakeAudioInputStream::BeepOnce();
- }
-
- frame_count_++;
-
- // Give the captured frame to the observer.
- observer_->OnIncomingCapturedFrame(fake_frame_.get(),
- frame_size_,
- base::Time::Now());
- // Reschedule next CaptureTask.
- capture_thread_.message_loop()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&FakeVideoCaptureDevice::OnCaptureTask,
- base::Unretained(this)),
- base::TimeDelta::FromMilliseconds(kFakeCaptureTimeoutMs));
-}
-
-} // namespace media
diff --git a/src/media/video/capture/fake_video_capture_device.h b/src/media/video/capture/fake_video_capture_device.h
deleted file mode 100644
index 7350982..0000000
--- a/src/media/video/capture/fake_video_capture_device.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implementation of a fake VideoCaptureDevice class. Used for testing other
-// video capture classes when no real hardware is available.
-
-#ifndef MEDIA_VIDEO_CAPTURE_FAKE_VIDEO_CAPTURE_DEVICE_H_
-#define MEDIA_VIDEO_CAPTURE_FAKE_VIDEO_CAPTURE_DEVICE_H_
-
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/threading/thread.h"
-#include "media/video/capture/video_capture_device.h"
-
-namespace media {
-
-class MEDIA_EXPORT FakeVideoCaptureDevice : public VideoCaptureDevice {
- public:
- static VideoCaptureDevice* Create(const Name& device_name);
- virtual ~FakeVideoCaptureDevice();
- // Used for testing. This will make sure the next call to Create will
- // return NULL;
- static void SetFailNextCreate();
-
- static void GetDeviceNames(Names* device_names);
-
- // VideoCaptureDevice implementation.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
- virtual const Name& device_name() OVERRIDE;
-
- private:
- // Flag indicating the internal state.
- enum InternalState {
- kIdle,
- kAllocated,
- kCapturing,
- kError
- };
- explicit FakeVideoCaptureDevice(const Name& device_name);
-
- // Called on the capture_thread_.
- void OnCaptureTask();
-
- Name device_name_;
- VideoCaptureDevice::EventHandler* observer_;
- InternalState state_;
- base::Thread capture_thread_;
- int frame_size_;
- scoped_array<uint8> fake_frame_;
- int frame_count_;
- int frame_width_;
- int frame_height_;
-
- static bool fail_next_create_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(FakeVideoCaptureDevice);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_FAKE_VIDEO_CAPTURE_DEVICE_H_
diff --git a/src/media/video/capture/linux/video_capture_device_linux.cc b/src/media/video/capture/linux/video_capture_device_linux.cc
deleted file mode 100644
index 64654fd..0000000
--- a/src/media/video/capture/linux/video_capture_device_linux.cc
+++ /dev/null
@@ -1,479 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/linux/video_capture_device_linux.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#if defined(OS_OPENBSD)
-#include <sys/videoio.h>
-#else
-#include <linux/videodev2.h>
-#endif
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-
-#include <list>
-#include <string>
-
-#include "base/bind.h"
-#include "base/file_util.h"
-#include "base/stringprintf.h"
-
-// Workaround for some device. This query of all controls magically brings
-// device back to normal from bad state.
-// See http://crbug.com/94134.
-static void ResetCameraByEnumeratingIoctlsHACK(int fd) {
- struct v4l2_queryctrl query_ctrl;
- memset(&query_ctrl, 0, sizeof(query_ctrl));
-
- for (query_ctrl.id = V4L2_CID_BASE;
- query_ctrl.id < V4L2_CID_LASTP1;
- query_ctrl.id++) {
- ioctl(fd, VIDIOC_QUERYCTRL, &query_ctrl);
- }
-}
-
-namespace media {
-
-// Max number of video buffers VideoCaptureDeviceLinux can allocate.
-enum { kMaxVideoBuffers = 2 };
-// Timeout in microseconds v4l2_thread_ blocks waiting for a frame from the hw.
-enum { kCaptureTimeoutUs = 200000 };
-// Time to wait in milliseconds before v4l2_thread_ reschedules OnCaptureTask
-// if an event is triggered (select) but no video frame is read.
-enum { kCaptureSelectWaitMs = 10 };
-// MJPEG is prefered if the width or height is larger than this.
-enum { kMjpegWidth = 640 };
-enum { kMjpegHeight = 480 };
-
-// V4L2 color formats VideoCaptureDeviceLinux support.
-static const int32 kV4l2RawFmts[] = {
- V4L2_PIX_FMT_YUV420,
- V4L2_PIX_FMT_YUYV
-};
-
-static VideoCaptureCapability::Format V4l2ColorToVideoCaptureColorFormat(
- int32 v4l2_fourcc) {
- VideoCaptureCapability::Format result = VideoCaptureCapability::kColorUnknown;
- switch (v4l2_fourcc) {
- case V4L2_PIX_FMT_YUV420:
- result = VideoCaptureCapability::kI420;
- break;
- case V4L2_PIX_FMT_YUYV:
- result = VideoCaptureCapability::kYUY2;
- break;
- case V4L2_PIX_FMT_MJPEG:
- result = VideoCaptureCapability::kMJPEG;
- }
- DCHECK_NE(result, VideoCaptureCapability::kColorUnknown);
- return result;
-}
-
-void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
- int fd = -1;
-
- // Empty the name list.
- device_names->clear();
-
- FilePath path("/dev/");
- file_util::FileEnumerator enumerator(
- path, false, file_util::FileEnumerator::FILES, "video*");
-
- while (!enumerator.Next().empty()) {
- file_util::FileEnumerator::FindInfo info;
- enumerator.GetFindInfo(&info);
-
- Name name;
- name.unique_id = path.value() + info.filename;
- if ((fd = open(name.unique_id.c_str() , O_RDONLY)) < 0) {
- // Failed to open this device.
- continue;
- }
- // Test if this is a V4L2 capture device.
- v4l2_capability cap;
- if ((ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) &&
- (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
- !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) {
- // This is a V4L2 video capture device
- name.device_name = StringPrintf("%s", cap.card);
- device_names->push_back(name);
- }
- close(fd);
- }
-}
-
-VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
- VideoCaptureDeviceLinux* self = new VideoCaptureDeviceLinux(device_name);
- if (!self)
- return NULL;
- // Test opening the device driver. This is to make sure it is available.
- // We will reopen it again in our worker thread when someone
- // allocates the camera.
- int fd = open(device_name.unique_id.c_str(), O_RDONLY);
- if (fd < 0) {
- DVLOG(1) << "Cannot open device";
- delete self;
- return NULL;
- }
- close(fd);
-
- return self;
-}
-
-VideoCaptureDeviceLinux::VideoCaptureDeviceLinux(const Name& device_name)
- : state_(kIdle),
- observer_(NULL),
- device_name_(device_name),
- device_fd_(-1),
- v4l2_thread_("V4L2Thread"),
- buffer_pool_(NULL),
- buffer_pool_size_(0) {
-}
-
-VideoCaptureDeviceLinux::~VideoCaptureDeviceLinux() {
- state_ = kIdle;
- // Check if the thread is running.
- // This means that the device have not been DeAllocated properly.
- DCHECK(!v4l2_thread_.IsRunning());
-
- v4l2_thread_.Stop();
- if (device_fd_ >= 0) {
- close(device_fd_);
- }
-}
-
-void VideoCaptureDeviceLinux::Allocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer) {
- if (v4l2_thread_.IsRunning()) {
- return; // Wrong state.
- }
- v4l2_thread_.Start();
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnAllocate, base::Unretained(this),
- width, height, frame_rate, observer));
-}
-
-void VideoCaptureDeviceLinux::Start() {
- if (!v4l2_thread_.IsRunning()) {
- return; // Wrong state.
- }
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnStart, base::Unretained(this)));
-}
-
-void VideoCaptureDeviceLinux::Stop() {
- if (!v4l2_thread_.IsRunning()) {
- return; // Wrong state.
- }
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnStop, base::Unretained(this)));
-}
-
-void VideoCaptureDeviceLinux::DeAllocate() {
- if (!v4l2_thread_.IsRunning()) {
- return; // Wrong state.
- }
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnDeAllocate,
- base::Unretained(this)));
- v4l2_thread_.Stop();
-
- // Make sure no buffers are still allocated.
- // This can happen (theoretically) if an error occurs when trying to stop
- // the camera.
- DeAllocateVideoBuffers();
-}
-
-const VideoCaptureDevice::Name& VideoCaptureDeviceLinux::device_name() {
- return device_name_;
-}
-
-void VideoCaptureDeviceLinux::OnAllocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer) {
- DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
-
- observer_ = observer;
-
- // Need to open camera with O_RDWR after Linux kernel 3.3.
- if ((device_fd_ = open(device_name_.unique_id.c_str(), O_RDWR)) < 0) {
- SetErrorState("Failed to open V4L2 device driver.");
- return;
- }
-
- // Test if this is a V4L2 capture device.
- v4l2_capability cap;
- if (!((ioctl(device_fd_, VIDIOC_QUERYCAP, &cap) == 0) &&
- (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
- !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT))) {
- // This is not a V4L2 video capture device.
- close(device_fd_);
- device_fd_ = -1;
- SetErrorState("This is not a V4L2 video capture device");
- return;
- }
-
- v4l2_format video_fmt;
- memset(&video_fmt, 0, sizeof(video_fmt));
- video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- video_fmt.fmt.pix.sizeimage = 0;
- video_fmt.fmt.pix.width = width;
- video_fmt.fmt.pix.height = height;
-
- // Some device failed in first VIDIOC_TRY_FMT with EBUSY or EIO.
- // But second VIDIOC_TRY_FMT succeeds.
- // See http://crbug.com/94134.
- // For large resolutions, favour mjpeg over raw formats.
- bool format_match = false;
- std::list<int> v4l2_formats;
-
- if (width > kMjpegWidth || height > kMjpegHeight) {
- v4l2_formats.push_back(V4L2_PIX_FMT_MJPEG);
- }
- for (size_t i = 0; i < arraysize(kV4l2RawFmts); ++i) {
- v4l2_formats.push_back(kV4l2RawFmts[i]);
- }
-
- for (std::list<int>::const_iterator it = v4l2_formats.begin();
- it != v4l2_formats.end() && !format_match; ++it) {
- video_fmt.fmt.pix.pixelformat = *it;
- for (int attempt = 0; attempt < 2 && !format_match; ++attempt) {
- ResetCameraByEnumeratingIoctlsHACK(device_fd_);
- if (ioctl(device_fd_, VIDIOC_TRY_FMT, &video_fmt) < 0) {
- if (errno != EIO)
- break;
- } else {
- format_match = true;
- }
- }
- }
-
- if (!format_match) {
- SetErrorState("Failed to find supported camera format.");
- return;
- }
- // Set format and frame size now.
- if (ioctl(device_fd_, VIDIOC_S_FMT, &video_fmt) < 0) {
- SetErrorState("Failed to set camera format");
- return;
- }
-
- // Store our current width and height.
- VideoCaptureCapability current_settings;
- current_settings.color = V4l2ColorToVideoCaptureColorFormat(
- video_fmt.fmt.pix.pixelformat);
- current_settings.width = video_fmt.fmt.pix.width;
- current_settings.height = video_fmt.fmt.pix.height;
- current_settings.frame_rate = frame_rate;
- current_settings.expected_capture_delay = 0;
- current_settings.interlaced = false;
-
- state_ = kAllocated;
- // Report the resulting frame size to the observer.
- observer_->OnFrameInfo(current_settings);
-}
-
-void VideoCaptureDeviceLinux::OnDeAllocate() {
- DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
-
- // If we are in error state or capturing
- // try to stop the camera.
- if (state_ == kCapturing) {
- OnStop();
- }
- if (state_ == kAllocated) {
- state_ = kIdle;
- }
-
- // We need to close and open the device if we want to change the settings
- // Otherwise VIDIOC_S_FMT will return error
- // Sad but true.
- close(device_fd_);
- device_fd_ = -1;
-}
-
-void VideoCaptureDeviceLinux::OnStart() {
- DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
-
- if (state_ != kAllocated) {
- return;
- }
-
- if (!AllocateVideoBuffers()) {
- // Error, We can not recover.
- SetErrorState("Allocate buffer failed");
- return;
- }
-
- // Start UVC camera.
- v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (ioctl(device_fd_, VIDIOC_STREAMON, &type) == -1) {
- SetErrorState("VIDIOC_STREAMON failed");
- return;
- }
-
- state_ = kCapturing;
- // Post task to start fetching frames from v4l2.
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
- base::Unretained(this)));
-}
-
-void VideoCaptureDeviceLinux::OnStop() {
- DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
-
- state_ = kAllocated;
-
- v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (ioctl(device_fd_, VIDIOC_STREAMOFF, &type) < 0) {
- SetErrorState("VIDIOC_STREAMOFF failed");
- return;
- }
- // We don't dare to deallocate the buffers if we can't stop
- // the capture device.
- DeAllocateVideoBuffers();
-}
-
-void VideoCaptureDeviceLinux::OnCaptureTask() {
- DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
-
- if (state_ != kCapturing) {
- return;
- }
-
- fd_set r_set;
- FD_ZERO(&r_set);
- FD_SET(device_fd_, &r_set);
- timeval timeout;
-
- timeout.tv_sec = 0;
- timeout.tv_usec = kCaptureTimeoutUs;
-
- // First argument to select is the highest numbered file descriptor +1.
- // Refer to http://linux.die.net/man/2/select for more information.
- int result = select(device_fd_ + 1, &r_set, NULL, NULL, &timeout);
- // Check if select have failed.
- if (result < 0) {
- // EINTR is a signal. This is not really an error.
- if (errno != EINTR) {
- SetErrorState("Select failed");
- return;
- }
- v4l2_thread_.message_loop()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
- base::Unretained(this)),
- base::TimeDelta::FromMilliseconds(kCaptureSelectWaitMs));
- }
-
- // Check if the driver have filled a buffer.
- if (FD_ISSET(device_fd_, &r_set)) {
- v4l2_buffer buffer;
- memset(&buffer, 0, sizeof(buffer));
- buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buffer.memory = V4L2_MEMORY_MMAP;
- // Dequeue a buffer.
- if (ioctl(device_fd_, VIDIOC_DQBUF, &buffer) == 0) {
- observer_->OnIncomingCapturedFrame(
- static_cast<uint8*> (buffer_pool_[buffer.index].start),
- buffer.bytesused, base::Time::Now());
-
- // Enqueue the buffer again.
- if (ioctl(device_fd_, VIDIOC_QBUF, &buffer) == -1) {
- SetErrorState(
- StringPrintf("Failed to enqueue capture buffer errno %d", errno));
- }
- } // Else wait for next event.
- }
-
- v4l2_thread_.message_loop()->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
- base::Unretained(this)));
-}
-
-bool VideoCaptureDeviceLinux::AllocateVideoBuffers() {
- v4l2_requestbuffers r_buffer;
- memset(&r_buffer, 0, sizeof(r_buffer));
-
- r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- r_buffer.memory = V4L2_MEMORY_MMAP;
- r_buffer.count = kMaxVideoBuffers;
-
- if (ioctl(device_fd_, VIDIOC_REQBUFS, &r_buffer) < 0) {
- return false;
- }
-
- if (r_buffer.count > kMaxVideoBuffers) {
- r_buffer.count = kMaxVideoBuffers;
- }
-
- buffer_pool_size_ = r_buffer.count;
-
- // Map the buffers.
- buffer_pool_ = new Buffer[r_buffer.count];
- for (unsigned int i = 0; i < r_buffer.count; i++) {
- v4l2_buffer buffer;
- memset(&buffer, 0, sizeof(buffer));
- buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buffer.memory = V4L2_MEMORY_MMAP;
- buffer.index = i;
-
- if (ioctl(device_fd_, VIDIOC_QUERYBUF, &buffer) < 0) {
- return false;
- }
-
- buffer_pool_[i].start = mmap(NULL, buffer.length, PROT_READ,
- MAP_SHARED, device_fd_, buffer.m.offset);
- if (buffer_pool_[i].start == MAP_FAILED) {
- return false;
- }
- buffer_pool_[i].length = buffer.length;
- // Enqueue the buffer in the drivers incoming queue.
- if (ioctl(device_fd_, VIDIOC_QBUF, &buffer) < 0) {
- return false;
- }
- }
- return true;
-}
-
-void VideoCaptureDeviceLinux::DeAllocateVideoBuffers() {
- if (!buffer_pool_)
- return;
-
- // Unmaps buffers.
- for (int i = 0; i < buffer_pool_size_; i++) {
- munmap(buffer_pool_[i].start, buffer_pool_[i].length);
- }
- v4l2_requestbuffers r_buffer;
- memset(&r_buffer, 0, sizeof(r_buffer));
- r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- r_buffer.memory = V4L2_MEMORY_MMAP;
- r_buffer.count = 0;
-
- if (ioctl(device_fd_, VIDIOC_REQBUFS, &r_buffer) < 0) {
- SetErrorState("Failed to reset buf.");
- }
-
- delete [] buffer_pool_;
- buffer_pool_ = NULL;
- buffer_pool_size_ = 0;
-}
-
-void VideoCaptureDeviceLinux::SetErrorState(const std::string& reason) {
- DVLOG(1) << reason;
- state_ = kError;
- observer_->OnError();
-}
-
-} // namespace media
diff --git a/src/media/video/capture/linux/video_capture_device_linux.h b/src/media/video/capture/linux/video_capture_device_linux.h
deleted file mode 100644
index d1d2a19..0000000
--- a/src/media/video/capture/linux/video_capture_device_linux.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Linux specific implementation of VideoCaptureDevice.
-// V4L2 is used for capturing. V4L2 does not provide its own thread for
-// capturing so this implementation uses a Chromium thread for fetching frames
-// from V4L2.
-
-#ifndef MEDIA_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_DEVICE_LINUX_H_
-#define MEDIA_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_DEVICE_LINUX_H_
-
-#include <string>
-
-#include "base/threading/thread.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-
-namespace media {
-
-class VideoCaptureDeviceLinux : public VideoCaptureDevice {
- public:
- explicit VideoCaptureDeviceLinux(const Name& device_name);
- virtual ~VideoCaptureDeviceLinux();
-
- // VideoCaptureDevice implementation.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
- virtual const Name& device_name() OVERRIDE;
-
- private:
- enum InternalState {
- kIdle, // The device driver is opened but camera is not in use.
- kAllocated, // The camera has been allocated and can be started.
- kCapturing, // Video is being captured.
- kError // Error accessing HW functions.
- // User needs to recover by destroying the object.
- };
-
- // Buffers used to receive video frames from with v4l2.
- struct Buffer {
- Buffer() : start(0), length(0) {}
- void* start;
- size_t length;
- };
-
- // Called on the v4l2_thread_.
- void OnAllocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer);
- void OnStart();
- void OnStop();
- void OnDeAllocate();
- void OnCaptureTask();
-
- bool AllocateVideoBuffers();
- void DeAllocateVideoBuffers();
- void SetErrorState(const std::string& reason);
-
- InternalState state_;
- VideoCaptureDevice::EventHandler* observer_;
- Name device_name_;
- int device_fd_; // File descriptor for the opened camera device.
- base::Thread v4l2_thread_; // Thread used for reading data from the device.
- Buffer* buffer_pool_;
- int buffer_pool_size_; // Number of allocated buffers.
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureDeviceLinux);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_LINUX_VIDEO_CAPTURE_DEVICE_LINUX_H_
diff --git a/src/media/video/capture/mac/video_capture_device_mac.h b/src/media/video/capture/mac/video_capture_device_mac.h
deleted file mode 100644
index 95d78e0..0000000
--- a/src/media/video/capture/mac/video_capture_device_mac.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// OS X implementation of VideoCaptureDevice, using QTKit as native capture API.
-
-#ifndef MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_
-#define MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-
-@class VideoCaptureDeviceQTKit;
-
-namespace media {
-
-// Called by VideoCaptureManager to open, close and start, stop video capture
-// devices.
-class VideoCaptureDeviceMac : public VideoCaptureDevice {
- public:
- explicit VideoCaptureDeviceMac(const Name& device_name);
- virtual ~VideoCaptureDeviceMac();
-
- // VideoCaptureDevice implementation.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
- virtual const Name& device_name() OVERRIDE;
-
- bool Init();
-
- // Called to deliver captured video frames.
- void ReceiveFrame(const uint8* video_frame, int video_frame_length,
- const VideoCaptureCapability& frame_info);
-
- private:
- void SetErrorState(const std::string& reason);
-
- // Flag indicating the internal state.
- enum InternalState {
- kNotInitialized,
- kIdle,
- kAllocated,
- kCapturing,
- kError
- };
-
- VideoCaptureDevice::Name device_name_;
- VideoCaptureDevice::EventHandler* observer_;
- InternalState state_;
-
- VideoCaptureDeviceQTKit* capture_device_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceMac);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_
diff --git a/src/media/video/capture/mac/video_capture_device_mac.mm b/src/media/video/capture/mac/video_capture_device_mac.mm
deleted file mode 100644
index 0bb14a79b7..0000000
--- a/src/media/video/capture/mac/video_capture_device_mac.mm
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/mac/video_capture_device_mac.h"
-
-#import <QTKit/QTKit.h>
-
-#include "base/logging.h"
-#include "base/time.h"
-#include "media/video/capture/mac/video_capture_device_qtkit_mac.h"
-
-namespace {
-
-const int kMinFrameRate = 1;
-const int kMaxFrameRate = 30;
-
-}
-
-namespace media {
-
-void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
- // Loop through all available devices and add to |device_names|.
- device_names->clear();
-
- NSDictionary* capture_devices = [VideoCaptureDeviceQTKit deviceNames];
- for (NSString* key in capture_devices) {
- Name name;
- name.device_name = [[capture_devices valueForKey:key] UTF8String];
- name.unique_id = [key UTF8String];
- device_names->push_back(name);
- }
-}
-
-VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
- VideoCaptureDeviceMac* capture_device =
- new VideoCaptureDeviceMac(device_name);
- if (!capture_device->Init()) {
- LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
- delete capture_device;
- capture_device = NULL;
- }
- return capture_device;
-}
-
-VideoCaptureDeviceMac::VideoCaptureDeviceMac(const Name& device_name)
- : device_name_(device_name),
- observer_(NULL),
- state_(kNotInitialized),
- capture_device_(nil) {
-}
-
-VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
- [capture_device_ release];
-}
-
-void VideoCaptureDeviceMac::Allocate(int width, int height, int frame_rate,
- EventHandler* observer) {
- if (state_ != kIdle) {
- return;
- }
- observer_ = observer;
- NSString* deviceId =
- [NSString stringWithUTF8String:device_name_.unique_id.c_str()];
-
- [capture_device_ setFrameReceiver:this];
-
- if (![capture_device_ setCaptureDevice:deviceId]) {
- SetErrorState("Could not open capture device.");
- return;
- }
- if (frame_rate < kMinFrameRate)
- frame_rate = kMinFrameRate;
- else if (frame_rate > kMaxFrameRate)
- frame_rate = kMaxFrameRate;
-
- if (![capture_device_ setCaptureHeight:height
- width:width
- frameRate:frame_rate]) {
- SetErrorState("Could not configure capture device.");
- return;
- }
-
- state_ = kAllocated;
- VideoCaptureCapability current_settings;
- current_settings.color = VideoCaptureCapability::kARGB;
- current_settings.width = width;
- current_settings.height = height;
- current_settings.frame_rate = frame_rate;
- current_settings.expected_capture_delay = 0;
- current_settings.interlaced = false;
-
- observer_->OnFrameInfo(current_settings);
-}
-
-void VideoCaptureDeviceMac::Start() {
- DCHECK_EQ(state_, kAllocated);
- if (![capture_device_ startCapture]) {
- SetErrorState("Could not start capture device.");
- return;
- }
- state_ = kCapturing;
-}
-
-void VideoCaptureDeviceMac::Stop() {
- DCHECK_EQ(state_, kCapturing);
- [capture_device_ stopCapture];
- state_ = kAllocated;
-}
-
-void VideoCaptureDeviceMac::DeAllocate() {
- if (state_ != kAllocated && state_ != kCapturing) {
- return;
- }
- if (state_ == kCapturing) {
- [capture_device_ stopCapture];
- }
- [capture_device_ setCaptureDevice:nil];
- [capture_device_ setFrameReceiver:nil];
-
- state_ = kIdle;
-}
-
-const VideoCaptureDevice::Name& VideoCaptureDeviceMac::device_name() {
- return device_name_;
-}
-
-bool VideoCaptureDeviceMac::Init() {
- DCHECK_EQ(state_, kNotInitialized);
-
- Names device_names;
- GetDeviceNames(&device_names);
- for (Names::iterator it = device_names.begin();
- it != device_names.end();
- ++it) {
- if (device_name_.unique_id == it->unique_id) {
- capture_device_ =
- [[VideoCaptureDeviceQTKit alloc] initWithFrameReceiver:this];
- if (!capture_device_) {
- return false;
- }
- state_ = kIdle;
- return true;
- }
- }
- return false;
-}
-
-void VideoCaptureDeviceMac::ReceiveFrame(
- const uint8* video_frame,
- int video_frame_length,
- const VideoCaptureCapability& frame_info) {
- observer_->OnIncomingCapturedFrame(video_frame, video_frame_length,
- base::Time::Now());
-}
-
-void VideoCaptureDeviceMac::SetErrorState(const std::string& reason) {
- DLOG(ERROR) << reason;
- state_ = kError;
- observer_->OnError();
-}
-
-} // namespace media
diff --git a/src/media/video/capture/mac/video_capture_device_qtkit_mac.h b/src/media/video/capture/mac/video_capture_device_qtkit_mac.h
deleted file mode 100644
index 3c9a3db..0000000
--- a/src/media/video/capture/mac/video_capture_device_qtkit_mac.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// VideoCaptureDeviceQTKit implements all QTKit related code for
-// communicating with a QTKit capture device.
-
-#ifndef MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_
-#define MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_
-
-#import <Foundation/Foundation.h>
-
-#include <vector>
-
-namespace media {
- class VideoCaptureDeviceMac;
-}
-
-@class QTCaptureDeviceInput;
-@class QTCaptureSession;
-
-@interface VideoCaptureDeviceQTKit : NSObject {
- @private
- // Settings.
- int frameRate_;
- int frameWidth_;
- int frameHeight_;
-
- NSLock *lock_;
- media::VideoCaptureDeviceMac *frameReceiver_;
-
- // QTKit variables.
- QTCaptureSession *captureSession_;
- QTCaptureDeviceInput *captureDeviceInput_;
-
- // Buffer for adjusting frames which do not fit receiver
- // assumptions. scoped_array<> might make more sense, if the size
- // can be proven invariant.
- std::vector<UInt8> adjustedFrame_;
-}
-
-// Returns a dictionary of capture devices with friendly name and unique id.
-+ (NSDictionary *)deviceNames;
-
-// Initializes the instance and registers the frame receiver.
-- (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver;
-
-// Set the frame receiver.
-- (void)setFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver;
-
-// Sets which capture device to use. Returns YES on sucess, NO otherwise.
-- (BOOL)setCaptureDevice:(NSString *)deviceId;
-
-// Configures the capture properties.
-- (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate;
-
-// Start video capturing. Returns YES on sucess, NO otherwise.
-- (BOOL)startCapture;
-
-// Stops video capturing.
-- (void)stopCapture;
-
-@end
-
-#endif // MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_
diff --git a/src/media/video/capture/mac/video_capture_device_qtkit_mac.mm b/src/media/video/capture/mac/video_capture_device_qtkit_mac.mm
deleted file mode 100644
index 0318f5f..0000000
--- a/src/media/video/capture/mac/video_capture_device_qtkit_mac.mm
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
-
-#import <QTKit/QTKit.h>
-
-#include "base/logging.h"
-#include "base/mac/crash_logging.h"
-#include "media/video/capture/mac/video_capture_device_mac.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-
-@implementation VideoCaptureDeviceQTKit
-
-#pragma mark Class methods
-
-+ (void)getDeviceNames:(NSMutableDictionary*)deviceNames {
- NSArray* captureDevices =
- [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
-
- for (QTCaptureDevice* device in captureDevices) {
- [deviceNames setObject:[device localizedDisplayName]
- forKey:[device uniqueID]];
- }
-}
-
-+ (NSDictionary*)deviceNames {
- NSMutableDictionary* deviceNames =
- [[[NSMutableDictionary alloc] init] autorelease];
-
- // TODO(shess): Post to the main thread to see if that helps
- // http://crbug.com/139164
- [self performSelectorOnMainThread:@selector(getDeviceNames:)
- withObject:deviceNames
- waitUntilDone:YES];
- return deviceNames;
-}
-
-#pragma mark Public methods
-
-- (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
- self = [super init];
- if (self) {
- frameReceiver_ = frameReceiver;
- lock_ = [[NSLock alloc] init];
- }
- return self;
-}
-
-- (void)dealloc {
- [captureSession_ release];
- [captureDeviceInput_ release];
- [super dealloc];
-}
-
-- (void)setFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
- [lock_ lock];
- frameReceiver_ = frameReceiver;
- [lock_ unlock];
-}
-
-- (BOOL)setCaptureDevice:(NSString *)deviceId {
- if (deviceId) {
- // Set the capture device.
- if (captureDeviceInput_) {
- DLOG(ERROR) << "Video capture device already set.";
- return NO;
- }
-
- NSArray *captureDevices =
- [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
- NSArray *captureDevicesNames =
- [captureDevices valueForKey:@"uniqueID"];
- NSUInteger index = [captureDevicesNames indexOfObject:deviceId];
- if (index == NSNotFound) {
- DLOG(ERROR) << "Video capture device not found.";
- return NO;
- }
- QTCaptureDevice *device = [captureDevices objectAtIndex:index];
- NSError *error;
- if (![device open:&error]) {
- DLOG(ERROR) << "Could not open video capture device."
- << [[error localizedDescription] UTF8String];
- return NO;
- }
- captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
- captureSession_ = [[QTCaptureSession alloc] init];
-
- QTCaptureDecompressedVideoOutput *captureDecompressedOutput =
- [[[QTCaptureDecompressedVideoOutput alloc] init] autorelease];
- [captureDecompressedOutput setDelegate:self];
- if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) {
- DLOG(ERROR) << "Could not connect video capture output."
- << [[error localizedDescription] UTF8String];
- return NO;
- }
-
- // This key can be used to check if video capture code was related to a
- // particular crash.
- base::mac::SetCrashKeyValue(@"VideoCaptureDeviceQTKit", @"OpenedDevice");
-
- return YES;
- } else {
- // Remove the previously set capture device.
- if (!captureDeviceInput_) {
- DLOG(ERROR) << "No video capture device set.";
- return YES;
- }
- if ([[captureSession_ inputs] count] > 0) {
- // The device is still running.
- [self stopCapture];
- }
- if ([[captureSession_ outputs] count] > 0) {
- // Only one output is set for |captureSession_|.
- id output = [[captureSession_ outputs] objectAtIndex:0];
- [output setDelegate:nil];
-
- // TODO(shess): QTKit achieves thread safety by posting messages
- // to the main thread. As part of -addOutput:, it posts a
- // message to the main thread which in turn posts a notification
- // which will run in a future spin after the original method
- // returns. -removeOutput: can post a main-thread message in
- // between while holding a lock which the notification handler
- // will need. Posting either -addOutput: or -removeOutput: to
- // the main thread should fix it, remove is likely safer.
- // http://crbug.com/152757
- [captureSession_ performSelectorOnMainThread:@selector(removeOutput:)
- withObject:output
- waitUntilDone:YES];
- }
- [captureSession_ release];
- captureSession_ = nil;
- [captureDeviceInput_ release];
- captureDeviceInput_ = nil;
- return YES;
- }
-}
-
-- (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
- if (!captureDeviceInput_) {
- DLOG(ERROR) << "No video capture device set.";
- return NO;
- }
- if ([[captureSession_ outputs] count] != 1) {
- DLOG(ERROR) << "Video capture capabilities already set.";
- return NO;
- }
- if (frameRate <= 0) {
- DLOG(ERROR) << "Wrong frame rate.";
- return NO;
- }
-
- frameWidth_ = width;
- frameHeight_ = height;
- frameRate_ = frameRate;
-
- // Set up desired output properties.
- NSDictionary *captureDictionary =
- [NSDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithDouble:frameWidth_],
- (id)kCVPixelBufferWidthKey,
- [NSNumber numberWithDouble:frameHeight_],
- (id)kCVPixelBufferHeightKey,
- [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA],
- (id)kCVPixelBufferPixelFormatTypeKey,
- nil];
- [[[captureSession_ outputs] objectAtIndex:0]
- setPixelBufferAttributes:captureDictionary];
-
- [[[captureSession_ outputs] objectAtIndex:0]
- setMinimumVideoFrameInterval:(NSTimeInterval)1/(float)frameRate];
- return YES;
-}
-
-- (BOOL)startCapture {
- if ([[captureSession_ outputs] count] == 0) {
- // Capture properties not set.
- DLOG(ERROR) << "Video capture device not initialized.";
- return NO;
- }
- if ([[captureSession_ inputs] count] == 0) {
- NSError *error;
- if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
- DLOG(ERROR) << "Could not connect video capture device."
- << [[error localizedDescription] UTF8String];
- return NO;
- }
- [captureSession_ startRunning];
- }
- return YES;
-}
-
-- (void)stopCapture {
- if ([[captureSession_ inputs] count] == 1) {
- [captureSession_ removeInput:captureDeviceInput_];
- [captureSession_ stopRunning];
- }
-}
-
-// |captureOutput| is called by the capture device to deliver a new frame.
-- (void)captureOutput:(QTCaptureOutput *)captureOutput
- didOutputVideoFrame:(CVImageBufferRef)videoFrame
- withSampleBuffer:(QTSampleBuffer *)sampleBuffer
- fromConnection:(QTCaptureConnection *)connection {
- [lock_ lock];
- if(!frameReceiver_) {
- [lock_ unlock];
- return;
- }
-
- // Lock the frame and calculate frame size.
- const int kLockFlags = 0;
- if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags)
- == kCVReturnSuccess) {
- void *baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
- size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
- int frameHeight = CVPixelBufferGetHeight(videoFrame);
- int frameSize = bytesPerRow * frameHeight;
-
- // TODO(shess): bytesPerRow may not correspond to frameWidth_*4,
- // but VideoCaptureController::OnIncomingCapturedFrame() requires
- // it to do so. Plumbing things through is intrusive, for now
- // just deliver an adjusted buffer.
- UInt8* addressToPass = static_cast<UInt8*>(baseAddress);
- size_t expectedBytesPerRow = frameWidth_ * 4;
- if (bytesPerRow > expectedBytesPerRow) {
- // TODO(shess): frameHeight and frameHeight_ are not the same,
- // try to do what the surrounding code seems to assume.
- // Ironically, captureCapability and frameSize are ignored
- // anyhow.
- adjustedFrame_.resize(expectedBytesPerRow * frameHeight);
- // std::vector is contiguous according to standard.
- UInt8* adjustedAddress = &adjustedFrame_[0];
-
- for (int y = 0; y < frameHeight; ++y) {
- memcpy(adjustedAddress + y * expectedBytesPerRow,
- addressToPass + y * bytesPerRow,
- expectedBytesPerRow);
- }
-
- addressToPass = adjustedAddress;
- frameSize = frameHeight * expectedBytesPerRow;
- }
- media::VideoCaptureCapability captureCapability;
- captureCapability.width = frameWidth_;
- captureCapability.height = frameHeight_;
- captureCapability.frame_rate = frameRate_;
- captureCapability.color = media::VideoCaptureCapability::kARGB;
- captureCapability.expected_capture_delay = 0;
- captureCapability.interlaced = false;
-
- // Deliver the captured video frame.
- frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureCapability);
-
- CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
- }
- [lock_ unlock];
-}
-
-@end
diff --git a/src/media/video/capture/video_capture.h b/src/media/video/capture/video_capture.h
deleted file mode 100644
index a63843e..0000000
--- a/src/media/video/capture/video_capture.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// This file contains abstract classes used for media filter to handle video
-// capture devices.
-
-#ifndef MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_H_
-#define MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_H_
-
-#include "base/memory/ref_counted.h"
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/base/video_frame.h"
-#include "media/video/capture/video_capture_types.h"
-
-namespace media {
-
-class MEDIA_EXPORT VideoCapture {
- public:
- // TODO(wjia): consider merging with media::VideoFrame if possible.
- class VideoFrameBuffer : public base::RefCountedThreadSafe<VideoFrameBuffer> {
- public:
- VideoFrameBuffer()
- : width(0),
- height(0),
- stride(0),
- buffer_size(0),
- memory_pointer(NULL) {}
-
- int width;
- int height;
- int stride;
- size_t buffer_size;
- uint8* memory_pointer;
- base::Time timestamp;
-
- private:
- friend class base::RefCountedThreadSafe<VideoFrameBuffer>;
- ~VideoFrameBuffer() {}
-
- DISALLOW_COPY_AND_ASSIGN(VideoFrameBuffer);
- };
-
- // TODO(wjia): add error codes.
- // TODO(wjia): support weak ptr.
- // Callbacks provided by client for notification of events.
- class MEDIA_EXPORT EventHandler {
- public:
- // Notify client that video capture has been started.
- virtual void OnStarted(VideoCapture* capture) = 0;
-
- // Notify client that video capture has been stopped.
- virtual void OnStopped(VideoCapture* capture) = 0;
-
- // Notify client that video capture has been paused.
- virtual void OnPaused(VideoCapture* capture) = 0;
-
- // Notify client that video capture has hit some error |error_code|.
- virtual void OnError(VideoCapture* capture, int error_code) = 0;
-
- // Notify client that the client has been removed and no more calls will be
- // received.
- virtual void OnRemoved(VideoCapture* capture) = 0;
-
- // Notify client that a buffer is available.
- virtual void OnBufferReady(VideoCapture* capture,
- scoped_refptr<VideoFrameBuffer> buffer) = 0;
-
- // Notify client about device info.
- virtual void OnDeviceInfoReceived(
- VideoCapture* capture,
- const VideoCaptureParams& device_info) = 0;
-
- protected:
- virtual ~EventHandler() {}
- };
-
- VideoCapture() {}
-
- // Request video capture to start capturing with |capability|.
- // Also register |handler| with video capture for event handling.
- // |handler| must remain valid until it has received |OnRemoved()|.
- virtual void StartCapture(EventHandler* handler,
- const VideoCaptureCapability& capability) = 0;
-
- // Request video capture to stop capturing for client |handler|.
- // |handler| must remain valid until it has received |OnRemoved()|.
- virtual void StopCapture(EventHandler* handler) = 0;
-
- // Feed buffer to video capture when done with it.
- virtual void FeedBuffer(scoped_refptr<VideoFrameBuffer> buffer) = 0;
-
- virtual bool CaptureStarted() = 0;
- virtual int CaptureWidth() = 0;
- virtual int CaptureHeight() = 0;
- virtual int CaptureFrameRate() = 0;
-
- protected:
- virtual ~VideoCapture() {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(VideoCapture);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_H_
diff --git a/src/media/video/capture/video_capture_device.h b/src/media/video/capture/video_capture_device.h
deleted file mode 100644
index fdfd8be..0000000
--- a/src/media/video/capture/video_capture_device.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// VideoCaptureDevice is the abstract base class for realizing video capture
-// device support in Chromium. It provides the interface for OS dependent
-// implementations.
-// The class is created and functions are invoked on a thread owned by
-// VideoCaptureManager. Capturing is done on other threads depended on the OS
-// specific implementation.
-
-#ifndef MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_H_
-#define MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_H_
-
-#include <list>
-#include <string>
-
-#include "base/time.h"
-#include "media/base/media_export.h"
-#include "media/video/capture/video_capture_types.h"
-
-namespace media {
-
-class MEDIA_EXPORT VideoCaptureDevice {
- public:
-
- struct Name {
- // Friendly name of a device
- std::string device_name;
-
- // Unique name of a device. Even if there are multiple devices with the same
- // friendly name connected to the computer this will be unique.
- std::string unique_id;
- };
- typedef std::list<Name> Names;
-
- class MEDIA_EXPORT EventHandler {
- public:
- // Captured a new video frame.
- virtual void OnIncomingCapturedFrame(const uint8* data,
- int length,
- base::Time timestamp) = 0;
- // An error has occurred that can not be handled
- // and VideoCaptureDevice must be DeAllocated.
- virtual void OnError() = 0;
- // Called when VideoCaptureDevice::Allocate has been called
- // to inform of the resulting frame size and color format.
- virtual void OnFrameInfo(const VideoCaptureCapability& info) = 0;
-
- protected:
- virtual ~EventHandler() {}
- };
- // Creates a VideoCaptureDevice object.
- // Return NULL if the hardware is not available.
- static VideoCaptureDevice* Create(const Name& device_name);
- virtual ~VideoCaptureDevice() {}
-
- // Gets the names of all video capture devices connected to this computer.
- static void GetDeviceNames(Names* device_names);
-
- // Prepare the camera for use. After this function has been called no other
- // applications can use the camera. On completion EventHandler::OnFrameInfo is
- // called informing of the resulting resolution and frame rate.
- // DeAllocate must be called before this function can be called again and
- // before the object is deleted.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- EventHandler* observer) = 0;
-
- // Start capturing video frames. Allocate must be called before this function.
- virtual void Start() = 0;
-
- // Stop capturing video frames.
- virtual void Stop() = 0;
-
- // DeAllocates the camera. This means other applications can use it.
- // After this function has been called the Capture device is reset to the
- // state it was when created.
- virtual void DeAllocate() = 0;
-
- // Get the name of the capture device.
- virtual const Name& device_name() = 0;
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_H_
diff --git a/src/media/video/capture/video_capture_device_dummy.cc b/src/media/video/capture/video_capture_device_dummy.cc
deleted file mode 100644
index 8d0355d..0000000
--- a/src/media/video/capture/video_capture_device_dummy.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/video/capture/video_capture_device_dummy.h"
-
-namespace media {
-
-VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
- return NULL;
-}
-
-void VideoCaptureDevice::GetDeviceNames(Names* device_names) {}
-
-VideoCaptureDeviceDummy::VideoCaptureDeviceDummy() {}
-
-VideoCaptureDeviceDummy::~VideoCaptureDeviceDummy() {}
-
-void VideoCaptureDeviceDummy::Allocate(
- int width, int height, int frame_rate,
- VideoCaptureDevice::EventHandler* observer) {}
-
-void VideoCaptureDeviceDummy::Start() {}
-
-void VideoCaptureDeviceDummy::Stop() {}
-
-void VideoCaptureDeviceDummy::DeAllocate() {}
-
-} // namespace media
diff --git a/src/media/video/capture/video_capture_device_dummy.h b/src/media/video/capture/video_capture_device_dummy.h
deleted file mode 100644
index 395a46b..0000000
--- a/src/media/video/capture/video_capture_device_dummy.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// A dummy implementation of VideoCaptureDevice to use for platforms without
-// real video capture support. The class will be removed once the other
-// platforms have real video capture device support.
-//
-// TODO(mflodman) Remove when video_capture_device_mac and
-// video_capture_device_win are available.
-
-#ifndef MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_DUMMY_H_
-#define MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_DUMMY_H_
-
-#include "base/compiler_specific.h"
-#include "media/video/capture/video_capture_device.h"
-
-namespace media {
-
-class VideoCaptureDeviceDummy : public VideoCaptureDevice {
- public:
- virtual void Allocate(int width, int height, int frame_rate,
- VideoCaptureDevice::EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
-
- private:
- VideoCaptureDeviceDummy();
- virtual ~VideoCaptureDeviceDummy();
-
- DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceDummy);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_DEVICE_DUMMY_H_
diff --git a/src/media/video/capture/video_capture_device_unittest.cc b/src/media/video/capture/video_capture_device_unittest.cc
deleted file mode 100644
index 4c8b046..0000000
--- a/src/media/video/capture/video_capture_device_unittest.cc
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread.h"
-#include "media/video/capture/fake_video_capture_device.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#endif
-
-#if defined(OS_MACOSX)
-// Mac/QTKit will always give you the size you ask for and this case will fail.
-#define MAYBE_AllocateBadSize DISABLED_AllocateBadSize
-// We will always get ARGB from the Mac/QTKit implementation.
-#define MAYBE_CaptureMjpeg DISABLED_CaptureMjpeg
-#elif defined(OS_WIN)
-#define MAYBE_AllocateBadSize AllocateBadSize
-// Windows currently uses DirectShow to convert from MJPEG and a raw format is
-// always delivered.
-#define MAYBE_CaptureMjpeg DISABLED_CaptureMjpeg
-#else
-#define MAYBE_AllocateBadSize AllocateBadSize
-#define MAYBE_CaptureMjpeg CaptureMjpeg
-#endif
-
-using ::testing::_;
-using ::testing::AnyNumber;
-using ::testing::Return;
-using ::testing::AtLeast;
-
-namespace media {
-
-class MockFrameObserver : public media::VideoCaptureDevice::EventHandler {
- public:
- MOCK_METHOD0(OnErr, void());
- MOCK_METHOD4(OnFrameInfo, void(int width, int height, int frame_rate,
- VideoCaptureCapability::Format format));
-
- explicit MockFrameObserver(base::WaitableEvent* wait_event)
- : wait_event_(wait_event) {}
-
- virtual void OnError() OVERRIDE {
- OnErr();
- }
-
- virtual void OnFrameInfo(
- const VideoCaptureCapability& info) OVERRIDE {
- OnFrameInfo(info.width, info.height, info.frame_rate, info.color);
- }
-
- virtual void OnIncomingCapturedFrame(const uint8* data, int length,
- base::Time timestamp) OVERRIDE {
- wait_event_->Signal();
- }
-
- private:
- base::WaitableEvent* wait_event_;
-};
-
-class VideoCaptureDeviceTest : public testing::Test {
- public:
- VideoCaptureDeviceTest(): wait_event_(false, false) { }
-
- void PostQuitTask() {
- loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
- loop_->Run();
- }
-
- protected:
- virtual void SetUp() {
- frame_observer_.reset(new MockFrameObserver(&wait_event_));
- loop_.reset(new MessageLoopForUI());
- }
-
- virtual void TearDown() {
- }
-
-#if defined(OS_WIN)
- base::win::ScopedCOMInitializer initialize_com_;
-#endif
- base::WaitableEvent wait_event_;
- scoped_ptr<MockFrameObserver> frame_observer_;
- VideoCaptureDevice::Names names_;
- scoped_ptr<MessageLoop> loop_;
-};
-
-TEST_F(VideoCaptureDeviceTest, OpenInvalidDevice) {
- VideoCaptureDevice::Name device_name;
- device_name.device_name = "jibberish";
- device_name.unique_id = "jibberish";
- VideoCaptureDevice* device = VideoCaptureDevice::Create(device_name);
- EXPECT_TRUE(device == NULL);
-}
-
-TEST_F(VideoCaptureDeviceTest, CaptureVGA) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
-
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_FALSE(device.get() == NULL);
-
- // Get info about the new resolution.
- EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30, _))
- .Times(1);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
-
- device->Allocate(640, 480, 30, frame_observer_.get());
- device->Start();
- // Get captured video frames.
- PostQuitTask();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->Stop();
- device->DeAllocate();
-}
-
-TEST_F(VideoCaptureDeviceTest, Capture720p) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
-
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_FALSE(device.get() == NULL);
-
- // Get info about the new resolution.
- // We don't care about the resulting resolution or frame rate as it might
- // be different from one machine to the next.
- EXPECT_CALL(*frame_observer_, OnFrameInfo(_, _, _, _))
- .Times(1);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
-
- device->Allocate(1280, 720, 30, frame_observer_.get());
- device->Start();
- // Get captured video frames.
- PostQuitTask();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->Stop();
- device->DeAllocate();
-}
-
-TEST_F(VideoCaptureDeviceTest, MAYBE_AllocateBadSize) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_TRUE(device.get() != NULL);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
-
- // get info about the new resolution
- EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480 , _, _))
- .Times(AtLeast(1));
-
- device->Allocate(637, 472, 35, frame_observer_.get());
- device->DeAllocate();
-}
-
-TEST_F(VideoCaptureDeviceTest, ReAllocateCamera) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_TRUE(device.get() != NULL);
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
- // get info about the new resolution
- EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, _, _));
-
- EXPECT_CALL(*frame_observer_, OnFrameInfo(320, 240, _, _));
-
- device->Allocate(640, 480, 30, frame_observer_.get());
- device->Start();
- // Nothing shall happen.
- device->Allocate(1280, 1024, 30, frame_observer_.get());
- device->DeAllocate();
- // Allocate new size 320, 240
- device->Allocate(320, 240, 30, frame_observer_.get());
-
- device->Start();
- // Get captured video frames.
- PostQuitTask();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->Stop();
- device->DeAllocate();
-}
-
-TEST_F(VideoCaptureDeviceTest, DeAllocateCameraWhileRunning) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_TRUE(device.get() != NULL);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
- // Get info about the new resolution.
- EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30, _));
-
- device->Allocate(640, 480, 30, frame_observer_.get());
-
- device->Start();
- // Get captured video frames.
- PostQuitTask();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->DeAllocate();
-}
-
-TEST_F(VideoCaptureDeviceTest, TestFakeCapture) {
- VideoCaptureDevice::Names names;
-
- FakeVideoCaptureDevice::GetDeviceNames(&names);
-
- ASSERT_GT(static_cast<int>(names.size()), 0);
-
- scoped_ptr<VideoCaptureDevice> device(
- FakeVideoCaptureDevice::Create(names.front()));
- ASSERT_TRUE(device.get() != NULL);
-
- // Get info about the new resolution.
- EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30, _))
- .Times(1);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
-
- device->Allocate(640, 480, 30, frame_observer_.get());
-
- device->Start();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->Stop();
- device->DeAllocate();
-}
-
-// Start the camera in 720p to capture MJPEG instead of a raw format.
-TEST_F(VideoCaptureDeviceTest, MAYBE_CaptureMjpeg) {
- VideoCaptureDevice::GetDeviceNames(&names_);
- if (!names_.size()) {
- DVLOG(1) << "No camera available. Exiting test.";
- return;
- }
- scoped_ptr<VideoCaptureDevice> device(
- VideoCaptureDevice::Create(names_.front()));
- ASSERT_TRUE(device.get() != NULL);
-
- EXPECT_CALL(*frame_observer_, OnErr())
- .Times(0);
- // Verify we get MJPEG from the device. Not all devices can capture 1280x720
- // @ 30 fps, so we don't care about the exact resolution we get.
- EXPECT_CALL(*frame_observer_,
- OnFrameInfo(_, _, _, VideoCaptureCapability::kMJPEG));
-
- device->Allocate(1280, 720, 30, frame_observer_.get());
-
- device->Start();
- // Get captured video frames.
- PostQuitTask();
- EXPECT_TRUE(wait_event_.TimedWait(TestTimeouts::action_max_timeout()));
- device->DeAllocate();
-}
-
-}; // namespace media
diff --git a/src/media/video/capture/video_capture_proxy.cc b/src/media/video/capture/video_capture_proxy.cc
deleted file mode 100644
index 463d77f..0000000
--- a/src/media/video/capture/video_capture_proxy.cc
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/video/capture/video_capture_proxy.h"
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/message_loop_proxy.h"
-
-namespace {
-
-// Called on VC thread: extracts the state out of the VideoCapture, and
-// serialize it into a VideoCaptureState.
-media::VideoCaptureHandlerProxy::VideoCaptureState GetState(
- media::VideoCapture* capture) {
- media::VideoCaptureHandlerProxy::VideoCaptureState state;
- state.started = capture->CaptureStarted();
- state.width = capture->CaptureWidth();
- state.height = capture->CaptureHeight();
- state.frame_rate = capture->CaptureFrameRate();
- return state;
-}
-
-} // anonymous namespace
-
-namespace media {
-
-VideoCaptureHandlerProxy::VideoCaptureHandlerProxy(
- VideoCapture::EventHandler* proxied,
- scoped_refptr<base::MessageLoopProxy> main_message_loop)
- : proxied_(proxied),
- main_message_loop_(main_message_loop) {
-}
-
-VideoCaptureHandlerProxy::~VideoCaptureHandlerProxy() {
-}
-
-void VideoCaptureHandlerProxy::OnStarted(VideoCapture* capture) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnStartedOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture)));
-}
-
-void VideoCaptureHandlerProxy::OnStopped(VideoCapture* capture) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnStoppedOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture)));
-}
-
-void VideoCaptureHandlerProxy::OnPaused(VideoCapture* capture) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnPausedOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture)));
-}
-
-void VideoCaptureHandlerProxy::OnError(VideoCapture* capture, int error_code) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnErrorOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture),
- error_code));
-}
-
-void VideoCaptureHandlerProxy::OnRemoved(VideoCapture* capture) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnRemovedOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture)));
-}
-
-void VideoCaptureHandlerProxy::OnBufferReady(
- VideoCapture* capture,
- scoped_refptr<VideoCapture::VideoFrameBuffer> buffer) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnBufferReadyOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture),
- buffer));
-}
-
-void VideoCaptureHandlerProxy::OnDeviceInfoReceived(
- VideoCapture* capture,
- const VideoCaptureParams& device_info) {
- main_message_loop_->PostTask(FROM_HERE, base::Bind(
- &VideoCaptureHandlerProxy::OnDeviceInfoReceivedOnMainThread,
- base::Unretained(this),
- capture,
- GetState(capture),
- device_info));
-}
-
-void VideoCaptureHandlerProxy::OnStartedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state) {
- state_ = state;
- proxied_->OnStarted(capture);
-}
-
-void VideoCaptureHandlerProxy::OnStoppedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state) {
- state_ = state;
- proxied_->OnStopped(capture);
-}
-
-void VideoCaptureHandlerProxy::OnPausedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state) {
- state_ = state;
- proxied_->OnPaused(capture);
-}
-
-void VideoCaptureHandlerProxy::OnErrorOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- int error_code) {
- state_ = state;
- proxied_->OnError(capture, error_code);
-}
-
-void VideoCaptureHandlerProxy::OnRemovedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state) {
- state_ = state;
- proxied_->OnRemoved(capture);
-}
-
-void VideoCaptureHandlerProxy::OnBufferReadyOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- scoped_refptr<VideoCapture::VideoFrameBuffer> buffer) {
- state_ = state;
- proxied_->OnBufferReady(capture, buffer);
-}
-
-void VideoCaptureHandlerProxy::OnDeviceInfoReceivedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- const VideoCaptureParams& device_info) {
- state_ = state;
- proxied_->OnDeviceInfoReceived(capture, device_info);
-}
-
-} // namespace media
diff --git a/src/media/video/capture/video_capture_proxy.h b/src/media/video/capture/video_capture_proxy.h
deleted file mode 100644
index 78c459d..0000000
--- a/src/media/video/capture/video_capture_proxy.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_PROXY_H_
-#define MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_PROXY_H_
-
-#include "base/compiler_specific.h"
-#include "base/memory/ref_counted.h"
-#include "media/video/capture/video_capture.h"
-
-namespace base {
-class MessageLoopProxy;
-}
-
-namespace media {
-
-// This is a helper class to proxy a VideoCapture::EventHandler. In the renderer
-// process, the VideoCaptureImpl calls its handler on a "Video Capture" thread,
-// this class allows seamless proxying to another thread ("main thread"), which
-// would be the thread where the instance of this class is created. The
-// "proxied" handler is then called on that thread.
-// Since the VideoCapture is living on the "Video Capture" thread, querying its
-// state from the "main thread" is fundamentally racy. Instead this class keeps
-// track of the state every time it is called by the VideoCapture (on the VC
-// thread), and forwards that information to the main thread.
-class MEDIA_EXPORT VideoCaptureHandlerProxy
- : public VideoCapture::EventHandler {
- public:
- struct VideoCaptureState {
- VideoCaptureState() : started(false), width(0), height(0), frame_rate(0) {}
- bool started;
- int width;
- int height;
- int frame_rate;
- };
-
- // Called on main thread.
- VideoCaptureHandlerProxy(
- VideoCapture::EventHandler* proxied,
- scoped_refptr<base::MessageLoopProxy> main_message_loop);
- virtual ~VideoCaptureHandlerProxy();
-
- // Retrieves the state of the VideoCapture. Must be called on main thread.
- const VideoCaptureState& state() const { return state_; }
-
- // VideoCapture::EventHandler implementation, called on VC thread.
- virtual void OnStarted(VideoCapture* capture) OVERRIDE;
- virtual void OnStopped(VideoCapture* capture) OVERRIDE;
- virtual void OnPaused(VideoCapture* capture) OVERRIDE;
- virtual void OnError(VideoCapture* capture, int error_code) OVERRIDE;
- virtual void OnRemoved(VideoCapture* capture) OVERRIDE;
- virtual void OnBufferReady(
- VideoCapture* capture,
- scoped_refptr<VideoCapture::VideoFrameBuffer> buffer) OVERRIDE;
- virtual void OnDeviceInfoReceived(
- VideoCapture* capture,
- const VideoCaptureParams& device_info) OVERRIDE;
-
- private:
- // Called on main thread.
- void OnStartedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state);
- void OnStoppedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state);
- void OnPausedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state);
- void OnErrorOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- int error_code);
- void OnRemovedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state);
- void OnBufferReadyOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- scoped_refptr<VideoCapture::VideoFrameBuffer> buffer);
- void OnDeviceInfoReceivedOnMainThread(
- VideoCapture* capture,
- const VideoCaptureState& state,
- const VideoCaptureParams& device_info);
-
- // Only accessed from main thread.
- VideoCapture::EventHandler* proxied_;
- VideoCaptureState state_;
-
- scoped_refptr<base::MessageLoopProxy> main_message_loop_;
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_PROXY_H_
diff --git a/src/media/video/capture/video_capture_types.h b/src/media/video/capture/video_capture_types.h
deleted file mode 100644
index 072e256..0000000
--- a/src/media/video/capture/video_capture_types.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_TYPES_H_
-#define MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_TYPES_H_
-
-#include "media/base/video_frame.h"
-
-namespace media {
-
-// TODO(wjia): this type should be defined in a common place and
-// shared with device manager.
-typedef int VideoCaptureSessionId;
-
-// Parameters for starting video capture and device information.
-struct VideoCaptureParams {
- int width;
- int height;
- int frame_per_second;
- VideoCaptureSessionId session_id;
-};
-
-// Capabilities describe the format a camera capture video in.
-struct VideoCaptureCapability {
- // Color formats from camera.
- enum Format {
- kColorUnknown, // Color format not set.
- kI420,
- kYUY2,
- kUYVY,
- kRGB24,
- kARGB,
- kMJPEG,
- kNV21,
- kYV12,
- };
-
- int width; // Desired width.
- int height; // Desired height.
- int frame_rate; // Desired frame rate.
- Format color; // Desired video type.
- int expected_capture_delay; // Expected delay in millisecond.
- bool interlaced; // Need interlace format.
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_VIDEO_CAPTURE_TYPES_H_
diff --git a/src/media/video/capture/win/capability_list_win.cc b/src/media/video/capture/win/capability_list_win.cc
deleted file mode 100644
index 63bd69b..0000000
--- a/src/media/video/capture/win/capability_list_win.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/capability_list_win.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-
-namespace media {
-namespace {
-
-// Help structure used for comparing video capture capabilities.
-struct ResolutionDiff {
- const VideoCaptureCapabilityWin* capability;
- int diff_height;
- int diff_width;
- int diff_frame_rate;
-};
-
-bool CompareHeight(const ResolutionDiff& item1, const ResolutionDiff& item2) {
- return abs(item1.diff_height) < abs(item2.diff_height);
-}
-
-bool CompareWidth(const ResolutionDiff& item1, const ResolutionDiff& item2) {
- return abs(item1.diff_width) < abs(item2.diff_width);
-}
-
-bool CompareFrameRate(const ResolutionDiff& item1,
- const ResolutionDiff& item2) {
- return abs(item1.diff_frame_rate) < abs(item2.diff_frame_rate);
-}
-
-bool CompareColor(const ResolutionDiff& item1, const ResolutionDiff& item2) {
- return item1.capability->color < item2.capability->color;
-}
-
-} // namespace.
-
-CapabilityList::CapabilityList() {
- DetachFromThread();
-}
-
-CapabilityList::~CapabilityList() {}
-
-// Appends an entry to the list.
-void CapabilityList::Add(const VideoCaptureCapabilityWin& capability) {
- DCHECK(CalledOnValidThread());
- capabilities_.push_back(capability);
-}
-
-const VideoCaptureCapabilityWin& CapabilityList::GetBestMatchedCapability(
- int requested_width,
- int requested_height,
- int requested_frame_rate) const {
- DCHECK(CalledOnValidThread());
- DCHECK(!capabilities_.empty());
-
- std::list<ResolutionDiff> diff_list;
-
- // Loop through the candidates to create a list of differentials between the
- // requested resolution and the camera capability.
- for (Capabilities::const_iterator it = capabilities_.begin();
- it != capabilities_.end(); ++it) {
- ResolutionDiff diff;
- diff.capability = &(*it);
- diff.diff_width = it->width - requested_width;
- diff.diff_height = it->height - requested_height;
- diff.diff_frame_rate = it->frame_rate - requested_frame_rate;
- diff_list.push_back(diff);
- }
-
- // Sort the best height candidates.
- diff_list.sort(&CompareHeight);
- int best_diff = diff_list.front().diff_height;
- for (std::list<ResolutionDiff>::iterator it = diff_list.begin();
- it != diff_list.end(); ++it) {
- if (it->diff_height != best_diff) {
- // Remove all candidates but the best.
- diff_list.erase(it, diff_list.end());
- break;
- }
- }
-
- // Sort the best width candidates.
- diff_list.sort(&CompareWidth);
- best_diff = diff_list.front().diff_width;
- for (std::list<ResolutionDiff>::iterator it = diff_list.begin();
- it != diff_list.end(); ++it) {
- if (it->diff_width != best_diff) {
- // Remove all candidates but the best.
- diff_list.erase(it, diff_list.end());
- break;
- }
- }
-
- // Sort the best frame rate candidates.
- diff_list.sort(&CompareFrameRate);
- best_diff = diff_list.front().diff_frame_rate;
- for (std::list<ResolutionDiff>::iterator it = diff_list.begin();
- it != diff_list.end(); ++it) {
- if (it->diff_frame_rate != best_diff) {
- diff_list.erase(it, diff_list.end());
- break;
- }
- }
-
- // Decide the best color format.
- diff_list.sort(&CompareColor);
- return *diff_list.front().capability;
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/capability_list_win.h b/src/media/video/capture/win/capability_list_win.h
deleted file mode 100644
index 6f96942..0000000
--- a/src/media/video/capture/win/capability_list_win.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Windows specific implementation of VideoCaptureDevice.
-// DirectShow is used for capturing. DirectShow provide its own threads
-// for capturing.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_CAPABILITY_LIST_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_CAPABILITY_LIST_WIN_H_
-
-#include <list>
-
-#include "base/threading/non_thread_safe.h"
-#include "media/video/capture/video_capture_types.h"
-
-namespace media {
-
-struct VideoCaptureCapabilityWin : public VideoCaptureCapability {
- explicit VideoCaptureCapabilityWin(int index) : stream_index(index) {}
- int stream_index;
-};
-
-class CapabilityList : public base::NonThreadSafe {
- public:
- CapabilityList();
- ~CapabilityList();
-
- bool empty() const { return capabilities_.empty(); }
-
- // Appends an entry to the list.
- void Add(const VideoCaptureCapabilityWin& capability);
-
- // Loops through the list of capabilities and returns an index of the best
- // matching capability. The algorithm prioritizes height, width, frame rate
- // and color format in that order.
- const VideoCaptureCapabilityWin& GetBestMatchedCapability(
- int requested_width, int requested_height,
- int requested_frame_rate) const;
-
- private:
- typedef std::list<VideoCaptureCapabilityWin> Capabilities;
- Capabilities capabilities_;
-
- DISALLOW_COPY_AND_ASSIGN(CapabilityList);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_CAPABILITY_LIST_WIN_H_
diff --git a/src/media/video/capture/win/filter_base_win.cc b/src/media/video/capture/win/filter_base_win.cc
deleted file mode 100644
index 89309df..0000000
--- a/src/media/video/capture/win/filter_base_win.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/video/capture/win/filter_base_win.h"
-
-#pragma comment(lib, "strmiids.lib")
-
-namespace media {
-
-// Implement IEnumPins.
-class PinEnumerator
- : public IEnumPins,
- public base::RefCounted<PinEnumerator> {
- public:
- explicit PinEnumerator(FilterBase* filter)
- : filter_(filter),
- index_(0) {
- }
-
- ~PinEnumerator() {
- }
-
- // IUnknown implementation.
- STDMETHOD(QueryInterface)(REFIID iid, void** object_ptr) {
- if (iid == IID_IEnumPins || iid == IID_IUnknown) {
- AddRef();
- *object_ptr = static_cast<IEnumPins*>(this);
- return S_OK;
- }
- return E_NOINTERFACE;
- }
-
- STDMETHOD_(ULONG, AddRef)() {
- base::RefCounted<PinEnumerator>::AddRef();
- return 1;
- }
-
- STDMETHOD_(ULONG, Release)() {
- base::RefCounted<PinEnumerator>::Release();
- return 1;
- }
-
- // Implement IEnumPins.
- STDMETHOD(Next)(ULONG count, IPin** pins, ULONG* fetched) {
- ULONG pins_fetched = 0;
- while (pins_fetched < count && filter_->NoOfPins() > index_) {
- IPin* pin = filter_->GetPin(index_++);
- pin->AddRef();
- pins[pins_fetched++] = pin;
- }
-
- if (fetched)
- *fetched = pins_fetched;
-
- return pins_fetched == count ? S_OK : S_FALSE;
- }
-
- STDMETHOD(Skip)(ULONG count) {
- if (filter_->NoOfPins()- index_ > count) {
- index_ += count;
- return S_OK;
- }
- index_ = 0;
- return S_FALSE;
- }
-
- STDMETHOD(Reset)() {
- index_ = 0;
- return S_OK;
- }
-
- STDMETHOD(Clone)(IEnumPins** clone) {
- PinEnumerator* pin_enum = new PinEnumerator(filter_);
- if (!pin_enum)
- return E_OUTOFMEMORY;
- pin_enum->AddRef();
- pin_enum->index_ = index_;
- *clone = pin_enum;
- return S_OK;
- }
-
- private:
- scoped_refptr<FilterBase> filter_;
- size_t index_;
-};
-
-FilterBase::FilterBase() : state_(State_Stopped) {
-}
-
-FilterBase::~FilterBase() {
-}
-
-STDMETHODIMP FilterBase::EnumPins(IEnumPins** enum_pins) {
- *enum_pins = new PinEnumerator(this);
- (*enum_pins)->AddRef();
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::FindPin(LPCWSTR id, IPin** pin) {
- return E_NOTIMPL;
-}
-
-STDMETHODIMP FilterBase::QueryFilterInfo(FILTER_INFO* info) {
- info->pGraph = owning_graph_;
- info->achName[0] = L'\0';
- if (info->pGraph)
- info->pGraph->AddRef();
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::JoinFilterGraph(IFilterGraph* graph, LPCWSTR name) {
- owning_graph_ = graph;
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::QueryVendorInfo(LPWSTR *pVendorInfo) {
- return S_OK;
-}
-
-// Implement IMediaFilter.
-STDMETHODIMP FilterBase::Stop() {
- state_ = State_Stopped;
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::Pause() {
- state_ = State_Paused;
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::Run(REFERENCE_TIME start) {
- state_ = State_Running;
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::GetState(DWORD msec_timeout, FILTER_STATE* state) {
- *state = state_;
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::SetSyncSource(IReferenceClock* clock) {
- return S_OK;
-}
-
-STDMETHODIMP FilterBase::GetSyncSource(IReferenceClock** clock) {
- return E_NOTIMPL;
-}
-
-// Implement from IPersistent.
-STDMETHODIMP FilterBase::GetClassID(CLSID* class_id) {
- NOTREACHED();
- return E_NOTIMPL;
-}
-
-// Implement IUnknown.
-STDMETHODIMP FilterBase::QueryInterface(REFIID id, void** object_ptr) {
- if (id == IID_IMediaFilter || id == IID_IUnknown) {
- *object_ptr = static_cast<IMediaFilter*>(this);
- } else if (id == IID_IPersist) {
- *object_ptr = static_cast<IPersist*>(this);
- } else {
- return E_NOINTERFACE;
- }
- AddRef();
- return S_OK;
-}
-
-ULONG STDMETHODCALLTYPE FilterBase::AddRef() {
- base::RefCounted<FilterBase>::AddRef();
- return 1;
-}
-
-ULONG STDMETHODCALLTYPE FilterBase::Release() {
- base::RefCounted<FilterBase>::Release();
- return 1;
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/filter_base_win.h b/src/media/video/capture/win/filter_base_win.h
deleted file mode 100644
index 9d5aa76..0000000
--- a/src/media/video/capture/win/filter_base_win.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implement a simple base class for DirectShow filters. It may only be used in
-// a single threaded apartment.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_
-
-// Avoid including strsafe.h via dshow as it will cause build warnings.
-#define NO_DSHOW_STRSAFE
-#include <dshow.h>
-
-#include "base/memory/ref_counted.h"
-#include "base/win/scoped_comptr.h"
-
-namespace media {
-
-class FilterBase
- : public IBaseFilter,
- public base::RefCounted<FilterBase> {
- public:
- FilterBase();
- virtual ~FilterBase();
-
- // Number of pins connected to this filter.
- virtual size_t NoOfPins() = 0;
- // Returns the IPin interface pin no index.
- virtual IPin* GetPin(int index) = 0;
-
- // Inherited from IUnknown.
- STDMETHOD(QueryInterface)(REFIID id, void** object_ptr);
- STDMETHOD_(ULONG, AddRef)();
- STDMETHOD_(ULONG, Release)();
-
- // Inherited from IBaseFilter.
- STDMETHOD(EnumPins)(IEnumPins** enum_pins);
-
- STDMETHOD(FindPin)(LPCWSTR id, IPin** pin);
-
- STDMETHOD(QueryFilterInfo)(FILTER_INFO* info);
-
- STDMETHOD(JoinFilterGraph)(IFilterGraph* graph, LPCWSTR name);
-
- STDMETHOD(QueryVendorInfo)(LPWSTR* vendor_info);
-
- // Inherited from IMediaFilter.
- STDMETHOD(Stop)();
-
- STDMETHOD(Pause)();
-
- STDMETHOD(Run)(REFERENCE_TIME start);
-
- STDMETHOD(GetState)(DWORD msec_timeout, FILTER_STATE* state);
-
- STDMETHOD(SetSyncSource)(IReferenceClock* clock);
-
- STDMETHOD(GetSyncSource)(IReferenceClock** clock);
-
- // Inherited from IPersistent.
- STDMETHOD(GetClassID)(CLSID* class_id) = 0;
-
- private:
- FILTER_STATE state_;
- base::win::ScopedComPtr<IFilterGraph> owning_graph_;
-
- DISALLOW_COPY_AND_ASSIGN(FilterBase);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_
diff --git a/src/media/video/capture/win/pin_base_win.cc b/src/media/video/capture/win/pin_base_win.cc
deleted file mode 100644
index 7e2f7b0..0000000
--- a/src/media/video/capture/win/pin_base_win.cc
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/pin_base_win.h"
-
-#include "base/logging.h"
-
-namespace media {
-
-// Implement IEnumPins.
-class TypeEnumerator
- : public IEnumMediaTypes,
- public base::RefCounted<TypeEnumerator> {
- public:
- explicit TypeEnumerator(PinBase* pin)
- : pin_(pin),
- index_(0) {
- }
-
- ~TypeEnumerator() {
- }
-
- // Implement from IUnknown.
- STDMETHOD(QueryInterface)(REFIID iid, void** object_ptr) {
- if (iid == IID_IEnumMediaTypes || iid == IID_IUnknown) {
- AddRef();
- *object_ptr = static_cast<IEnumMediaTypes*>(this);
- return S_OK;
- }
- return E_NOINTERFACE;
- }
-
- STDMETHOD_(ULONG, AddRef)() {
- base::RefCounted<TypeEnumerator>::AddRef();
- return 1;
- }
-
- STDMETHOD_(ULONG, Release)() {
- base::RefCounted<TypeEnumerator>::Release();
- return 1;
- }
-
- // Implement IEnumMediaTypes.
- STDMETHOD(Next)(ULONG count, AM_MEDIA_TYPE** types, ULONG* fetched) {
- ULONG types_fetched = 0;
-
- while (types_fetched < count) {
- // Allocate AM_MEDIA_TYPE that we will store the media type in.
- AM_MEDIA_TYPE* type = reinterpret_cast<AM_MEDIA_TYPE*>(CoTaskMemAlloc(
- sizeof(AM_MEDIA_TYPE)));
- if (!type) {
- FreeAllocatedMediaTypes(types_fetched, types);
- return E_OUTOFMEMORY;
- }
- ZeroMemory(type, sizeof(AM_MEDIA_TYPE));
-
- // Allocate a VIDEOINFOHEADER and connect it to the AM_MEDIA_TYPE.
- type->cbFormat = sizeof(VIDEOINFOHEADER);
- BYTE *format = reinterpret_cast<BYTE*>(CoTaskMemAlloc(
- sizeof(VIDEOINFOHEADER)));
- if (!format) {
- CoTaskMemFree(type);
- FreeAllocatedMediaTypes(types_fetched, types);
- return E_OUTOFMEMORY;
- }
- type->pbFormat = format;
- // Get the media type from the pin.
- if (pin_->GetValidMediaType(index_++, type)) {
- types[types_fetched++] = type;
- } else {
- CoTaskMemFree(format);
- CoTaskMemFree(type);
- break;
- }
- }
-
- if (fetched)
- *fetched = types_fetched;
-
- return types_fetched == count ? S_OK : S_FALSE;
- }
-
- STDMETHOD(Skip)(ULONG count) {
- index_ += count;
- return S_OK;
- }
-
- STDMETHOD(Reset)() {
- index_ = 0;
- return S_OK;
- }
-
- STDMETHOD(Clone)(IEnumMediaTypes** clone) {
- TypeEnumerator* type_enum = new TypeEnumerator(pin_);
- if (!type_enum)
- return E_OUTOFMEMORY;
- type_enum->AddRef();
- type_enum->index_ = index_;
- *clone = type_enum;
- return S_OK;
- }
-
- private:
- void FreeAllocatedMediaTypes(ULONG allocated, AM_MEDIA_TYPE** types) {
- for (ULONG i = 0; i < allocated; ++i) {
- CoTaskMemFree(types[i]->pbFormat);
- CoTaskMemFree(types[i]);
- }
- }
-
- scoped_refptr<PinBase> pin_;
- int index_;
-};
-
-PinBase::PinBase(IBaseFilter* owner)
- : owner_(owner) {
- memset(¤t_media_type_, 0, sizeof(current_media_type_));
-}
-
-PinBase::~PinBase() {
-}
-
-void PinBase::SetOwner(IBaseFilter* owner) {
- owner_ = owner;
-}
-
-// Called on an output pin to and establish a
-// connection.
-STDMETHODIMP PinBase::Connect(IPin* receive_pin,
- const AM_MEDIA_TYPE* media_type) {
- if (!receive_pin || !media_type)
- return E_POINTER;
-
- current_media_type_ = *media_type;
- receive_pin->AddRef();
- connected_pin_.Attach(receive_pin);
- HRESULT hr = receive_pin->ReceiveConnection(this, media_type);
-
- return hr;
-}
-
-// Called from an output pin on an input pin to and establish a
-// connection.
-STDMETHODIMP PinBase::ReceiveConnection(IPin* connector,
- const AM_MEDIA_TYPE* media_type) {
- if (!IsMediaTypeValid(media_type))
- return VFW_E_TYPE_NOT_ACCEPTED;
-
- current_media_type_ = *media_type;
- connector->AddRef();
- connected_pin_.Attach(connector);
- return S_OK;
-}
-
-STDMETHODIMP PinBase::Disconnect() {
- if (!connected_pin_)
- return S_FALSE;
-
- connected_pin_.Release();
- return S_OK;
-}
-
-STDMETHODIMP PinBase::ConnectedTo(IPin** pin) {
- *pin = connected_pin_;
- if (!connected_pin_)
- return VFW_E_NOT_CONNECTED;
-
- connected_pin_.get()->AddRef();
- return S_OK;
-}
-
-STDMETHODIMP PinBase::ConnectionMediaType(AM_MEDIA_TYPE* media_type) {
- if (!connected_pin_)
- return VFW_E_NOT_CONNECTED;
- *media_type = current_media_type_;
- return S_OK;
-}
-
-STDMETHODIMP PinBase::QueryPinInfo(PIN_INFO* info) {
- info->dir = PINDIR_INPUT;
- info->pFilter = owner_;
- if (owner_)
- owner_->AddRef();
- info->achName[0] = L'\0';
-
- return S_OK;
-}
-
-STDMETHODIMP PinBase::QueryDirection(PIN_DIRECTION* pin_dir) {
- *pin_dir = PINDIR_INPUT;
- return S_OK;
-}
-
-STDMETHODIMP PinBase::QueryId(LPWSTR* id) {
- NOTREACHED();
- return E_OUTOFMEMORY;
-}
-
-STDMETHODIMP PinBase::QueryAccept(const AM_MEDIA_TYPE* media_type) {
- return S_FALSE;
-}
-
-STDMETHODIMP PinBase::EnumMediaTypes(IEnumMediaTypes** types) {
- *types = new TypeEnumerator(this);
- (*types)->AddRef();
- return S_OK;
-}
-
-STDMETHODIMP PinBase::QueryInternalConnections(IPin** pins, ULONG* no_pins) {
- return E_NOTIMPL;
-}
-
-STDMETHODIMP PinBase::EndOfStream() {
- return S_OK;
-}
-
-STDMETHODIMP PinBase::BeginFlush() {
- return S_OK;
-}
-
-STDMETHODIMP PinBase::EndFlush() {
- return S_OK;
-}
-
-STDMETHODIMP PinBase::NewSegment(REFERENCE_TIME start,
- REFERENCE_TIME stop,
- double rate) {
- NOTREACHED();
- return E_NOTIMPL;
-}
-
-// Inherited from IMemInputPin.
-STDMETHODIMP PinBase::GetAllocator(IMemAllocator** allocator) {
- return VFW_E_NO_ALLOCATOR;
-}
-
-STDMETHODIMP PinBase::NotifyAllocator(IMemAllocator* allocator,
- BOOL read_only) {
- return S_OK;
-}
-
-STDMETHODIMP PinBase::GetAllocatorRequirements(
- ALLOCATOR_PROPERTIES* properties) {
- return E_NOTIMPL;
-}
-
-STDMETHODIMP PinBase::ReceiveMultiple(IMediaSample** samples,
- long sample_count,
- long* processed) {
- NOTREACHED();
- return VFW_E_INVALIDMEDIATYPE;
-}
-
-STDMETHODIMP PinBase::ReceiveCanBlock() {
- return S_FALSE;
-}
-
-// Inherited from IUnknown.
-STDMETHODIMP PinBase::QueryInterface(REFIID id, void** object_ptr) {
- if (id == IID_IPin || id == IID_IUnknown) {
- *object_ptr = static_cast<IPin*>(this);
- } else if (id == IID_IMemInputPin) {
- *object_ptr = static_cast<IMemInputPin*>(this);
- } else {
- return E_NOINTERFACE;
- }
- AddRef();
- return S_OK;
-}
-
-STDMETHODIMP_(ULONG) PinBase::AddRef() {
- base::RefCounted<PinBase>::AddRef();
- return 1;
-}
-
-STDMETHODIMP_(ULONG) PinBase::Release() {
- base::RefCounted<PinBase>::Release();
- return 1;
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/pin_base_win.h b/src/media/video/capture/win/pin_base_win.h
deleted file mode 100644
index 33f403f..0000000
--- a/src/media/video/capture/win/pin_base_win.h
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implement a simple base class for a DirectShow input pin. It may only be
-// used in a single threaded apartment.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_
-
-// Avoid including strsafe.h via dshow as it will cause build warnings.
-#define NO_DSHOW_STRSAFE
-#include <dshow.h>
-
-#include "base/memory/ref_counted.h"
-#include "base/win/scoped_comptr.h"
-
-namespace media {
-
-class PinBase
- : public IPin,
- public IMemInputPin,
- public base::RefCounted<PinBase> {
- public:
- explicit PinBase(IBaseFilter* owner);
- virtual ~PinBase();
-
- // Function used for changing the owner.
- // If the owner is deleted the owner should first call this function
- // with owner = NULL.
- void SetOwner(IBaseFilter* owner);
-
- // Checks if a media type is acceptable. This is called when this pin is
- // connected to an output pin. Must return true if the media type is
- // acceptable, false otherwise.
- virtual bool IsMediaTypeValid(const AM_MEDIA_TYPE* media_type) = 0;
-
- // Enumerates valid media types.
- virtual bool GetValidMediaType(int index, AM_MEDIA_TYPE* media_type) = 0;
-
- // Called when new media is received. Note that this is not on the same
- // thread as where the pin is created.
- STDMETHOD(Receive)(IMediaSample* sample) = 0;
-
- STDMETHOD(Connect)(IPin* receive_pin, const AM_MEDIA_TYPE* media_type);
-
- STDMETHOD(ReceiveConnection)(IPin* connector,
- const AM_MEDIA_TYPE* media_type);
-
- STDMETHOD(Disconnect)();
-
- STDMETHOD(ConnectedTo)(IPin** pin);
-
- STDMETHOD(ConnectionMediaType)(AM_MEDIA_TYPE* media_type);
-
- STDMETHOD(QueryPinInfo)(PIN_INFO* info);
-
- STDMETHOD(QueryDirection)(PIN_DIRECTION* pin_dir);
-
- STDMETHOD(QueryId)(LPWSTR* id);
-
- STDMETHOD(QueryAccept)(const AM_MEDIA_TYPE* media_type);
-
- STDMETHOD(EnumMediaTypes)(IEnumMediaTypes** types);
-
- STDMETHOD(QueryInternalConnections)(IPin** pins, ULONG* no_pins);
-
- STDMETHOD(EndOfStream)();
-
- STDMETHOD(BeginFlush)();
-
- STDMETHOD(EndFlush)();
-
- STDMETHOD(NewSegment)(REFERENCE_TIME start,
- REFERENCE_TIME stop,
- double dRate);
-
- // Inherited from IMemInputPin.
- STDMETHOD(GetAllocator)(IMemAllocator** allocator);
-
- STDMETHOD(NotifyAllocator)(IMemAllocator* allocator, BOOL read_only);
-
- STDMETHOD(GetAllocatorRequirements)(ALLOCATOR_PROPERTIES* properties);
-
- STDMETHOD(ReceiveMultiple)(IMediaSample** samples,
- long sample_count,
- long* processed);
- STDMETHOD(ReceiveCanBlock)();
-
- // Inherited from IUnknown.
- STDMETHOD(QueryInterface)(REFIID id, void** object_ptr);
-
- STDMETHOD_(ULONG, AddRef)();
-
- STDMETHOD_(ULONG, Release)();
-
- private:
- AM_MEDIA_TYPE current_media_type_;
- base::win::ScopedComPtr<IPin> connected_pin_;
- // owner_ is the filter owning this pin. We don't reference count it since
- // that would create a circular reference count.
- IBaseFilter* owner_;
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_
diff --git a/src/media/video/capture/win/sink_filter_observer_win.h b/src/media/video/capture/win/sink_filter_observer_win.h
deleted file mode 100644
index 13451b4..0000000
--- a/src/media/video/capture/win/sink_filter_observer_win.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Observer class of Sinkfilter. The implementor of this class receive video
-// frames from the SinkFilter DirectShow filter.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_
-
-namespace media {
-
-class SinkFilterObserver {
- public:
- // SinkFilter will call this function with all frames delivered to it.
- // buffer in only valid during this function call.
- virtual void FrameReceived(const uint8* buffer, int length) = 0;
- protected:
- virtual ~SinkFilterObserver();
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_
diff --git a/src/media/video/capture/win/sink_filter_win.cc b/src/media/video/capture/win/sink_filter_win.cc
deleted file mode 100644
index c3fc410..0000000
--- a/src/media/video/capture/win/sink_filter_win.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/sink_filter_win.h"
-
-#include "base/logging.h"
-#include "media/video/capture/win/sink_input_pin_win.h"
-
-// Define GUID for I420. This is the color format we would like to support but
-// it is not defined in the DirectShow SDK.
-// http://msdn.microsoft.com/en-us/library/dd757532.aspx
-// 30323449-0000-0010-8000-00AA00389B71.
-GUID kMediaSubTypeI420 = {
- 0x30323449, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}
-};
-
-namespace media {
-
-SinkFilterObserver::~SinkFilterObserver() {}
-
-SinkFilter::SinkFilter(SinkFilterObserver* observer)
- : input_pin_(NULL) {
- input_pin_ = new SinkInputPin(this, observer);
-}
-
-SinkFilter::~SinkFilter() {
- input_pin_->SetOwner(NULL);
-}
-
-void SinkFilter::SetRequestedMediaCapability(
- const VideoCaptureCapability& capability) {
- input_pin_->SetRequestedMediaCapability(capability);
-}
-
-const VideoCaptureCapability& SinkFilter::ResultingCapability() {
- return input_pin_->ResultingCapability();
-}
-
-size_t SinkFilter::NoOfPins() {
- return 1;
-}
-
-IPin* SinkFilter::GetPin(int index) {
- return index == 0 ? input_pin_ : NULL;
-}
-
-STDMETHODIMP SinkFilter::GetClassID(CLSID* clsid) {
- *clsid = __uuidof(SinkFilter);
- return S_OK;
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/sink_filter_win.h b/src/media/video/capture/win/sink_filter_win.h
deleted file mode 100644
index 36bb124..0000000
--- a/src/media/video/capture/win/sink_filter_win.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implement a DirectShow sink filter used for receiving captured frames from
-// a DirectShow Capture filter.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_
-
-#include <windows.h>
-
-#include "base/memory/ref_counted.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-#include "media/video/capture/win/filter_base_win.h"
-#include "media/video/capture/win/sink_filter_observer_win.h"
-
-// Define GUID for I420. This is the color format we would like to support but
-// it is not defined in the DirectShow SDK.
-// http://msdn.microsoft.com/en-us/library/dd757532.aspx
-// 30323449-0000-0010-8000-00AA00389B71.
-extern GUID kMediaSubTypeI420;
-
-namespace media {
-
-class SinkInputPin;
-
-class __declspec(uuid("88cdbbdc-a73b-4afa-acbf-15d5e2ce12c3"))
- SinkFilter : public FilterBase {
- public:
- explicit SinkFilter(SinkFilterObserver* observer);
- virtual ~SinkFilter();
-
- void SetRequestedMediaCapability(
- const VideoCaptureCapability& capability);
- // Returns the capability that is negotiated when this
- // filter is connected to a media filter.
- const VideoCaptureCapability& ResultingCapability();
-
- // Implement FilterBase.
- virtual size_t NoOfPins();
- virtual IPin* GetPin(int index);
-
- STDMETHOD(GetClassID)(CLSID* clsid);
-
- private:
- scoped_refptr<SinkInputPin> input_pin_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(SinkFilter);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_
diff --git a/src/media/video/capture/win/sink_input_pin_win.cc b/src/media/video/capture/win/sink_input_pin_win.cc
deleted file mode 100644
index 7e55531..0000000
--- a/src/media/video/capture/win/sink_input_pin_win.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/sink_input_pin_win.h"
-
-#include <cstring>
-
-// Avoid including strsafe.h via dshow as it will cause build warnings.
-#define NO_DSHOW_STRSAFE
-#include <dshow.h>
-
-#include "base/logging.h"
-
-namespace media {
-
-const REFERENCE_TIME kSecondsToReferenceTime = 10000000;
-
-SinkInputPin::SinkInputPin(IBaseFilter* filter,
- SinkFilterObserver* observer)
- : observer_(observer),
- PinBase(filter) {
- memset(&requested_capability_, 0, sizeof(requested_capability_));
- memset(&resulting_capability_, 0, sizeof(resulting_capability_));
-}
-
-SinkInputPin::~SinkInputPin() {}
-
-bool SinkInputPin::GetValidMediaType(int index, AM_MEDIA_TYPE* media_type) {
- if (media_type->cbFormat < sizeof(VIDEOINFOHEADER))
- return false;
-
- VIDEOINFOHEADER* pvi =
- reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
-
- ZeroMemory(pvi, sizeof(VIDEOINFOHEADER));
- pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- pvi->bmiHeader.biPlanes = 1;
- pvi->bmiHeader.biClrImportant = 0;
- pvi->bmiHeader.biClrUsed = 0;
- if (requested_capability_.frame_rate > 0) {
- pvi->AvgTimePerFrame = kSecondsToReferenceTime /
- requested_capability_.frame_rate;
- }
-
- media_type->majortype = MEDIATYPE_Video;
- media_type->formattype = FORMAT_VideoInfo;
- media_type->bTemporalCompression = FALSE;
-
- switch (index) {
- case 0: {
- pvi->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0');
- pvi->bmiHeader.biBitCount = 12; // bit per pixel
- pvi->bmiHeader.biWidth = requested_capability_.width;
- pvi->bmiHeader.biHeight = requested_capability_.height;
- pvi->bmiHeader.biSizeImage = 3 * requested_capability_.height *
- requested_capability_.width / 2;
- media_type->subtype = kMediaSubTypeI420;
- break;
- }
- case 1: {
- pvi->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2');
- pvi->bmiHeader.biBitCount = 16;
- pvi->bmiHeader.biWidth = requested_capability_.width;
- pvi->bmiHeader.biHeight = requested_capability_.height;
- pvi->bmiHeader.biSizeImage = 2 * requested_capability_.width *
- requested_capability_.height;
- media_type->subtype = MEDIASUBTYPE_YUY2;
- break;
- }
- case 2: {
- pvi->bmiHeader.biCompression = BI_RGB;
- pvi->bmiHeader.biBitCount = 24;
- pvi->bmiHeader.biWidth = requested_capability_.width;
- pvi->bmiHeader.biHeight = requested_capability_.height;
- pvi->bmiHeader.biSizeImage = 3 * requested_capability_.height *
- requested_capability_.width;
- media_type->subtype = MEDIASUBTYPE_RGB24;
- break;
- }
- default:
- return false;
- }
-
- media_type->bFixedSizeSamples = TRUE;
- media_type->lSampleSize = pvi->bmiHeader.biSizeImage;
- return true;
-}
-
-bool SinkInputPin::IsMediaTypeValid(const AM_MEDIA_TYPE* media_type) {
- GUID type = media_type->majortype;
- if (type != MEDIATYPE_Video)
- return false;
-
- GUID format_type = media_type->formattype;
- if (format_type != FORMAT_VideoInfo)
- return false;
-
- // Check for the sub types we support.
- GUID sub_type = media_type->subtype;
- VIDEOINFOHEADER* pvi =
- reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
- if (pvi == NULL)
- return false;
-
- // Store the incoming width and height.
- resulting_capability_.width = pvi->bmiHeader.biWidth;
- resulting_capability_.height = abs(pvi->bmiHeader.biHeight);
- if (pvi->AvgTimePerFrame > 0) {
- resulting_capability_.frame_rate =
- static_cast<int>(kSecondsToReferenceTime / pvi->AvgTimePerFrame);
- } else {
- resulting_capability_.frame_rate = requested_capability_.frame_rate;
- }
- if (sub_type == kMediaSubTypeI420 &&
- pvi->bmiHeader.biCompression == MAKEFOURCC('I', '4', '2', '0')) {
- resulting_capability_.color = VideoCaptureCapability::kI420;
- return true; // This format is acceptable.
- }
- if (sub_type == MEDIASUBTYPE_YUY2 &&
- pvi->bmiHeader.biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) {
- resulting_capability_.color = VideoCaptureCapability::kYUY2;
- return true; // This format is acceptable.
- }
- if (sub_type == MEDIASUBTYPE_RGB24 &&
- pvi->bmiHeader.biCompression == BI_RGB) {
- resulting_capability_.color = VideoCaptureCapability::kRGB24;
- return true; // This format is acceptable.
- }
- return false;
-}
-
-HRESULT SinkInputPin::Receive(IMediaSample* sample) {
- const int length = sample->GetActualDataLength();
- uint8* buffer = NULL;
- if (FAILED(sample->GetPointer(&buffer)))
- return S_FALSE;
-
- observer_->FrameReceived(buffer, length);
- return S_OK;
-}
-
-void SinkInputPin::SetRequestedMediaCapability(
- const VideoCaptureCapability& capability) {
- requested_capability_ = capability;
- resulting_capability_.width = 0;
- resulting_capability_.height = 0;
- resulting_capability_.frame_rate = 0;
- resulting_capability_.color = VideoCaptureCapability::kColorUnknown;
- resulting_capability_.expected_capture_delay = 0;
- resulting_capability_.interlaced = false;
-}
-
-const VideoCaptureCapability& SinkInputPin::ResultingCapability() {
- return resulting_capability_;
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/sink_input_pin_win.h b/src/media/video/capture/win/sink_input_pin_win.h
deleted file mode 100644
index 16168a3..0000000
--- a/src/media/video/capture/win/sink_input_pin_win.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Implement a DirectShow input pin used for receiving captured frames from
-// a DirectShow Capture filter.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_
-
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-#include "media/video/capture/win/pin_base_win.h"
-#include "media/video/capture/win/sink_filter_win.h"
-
-namespace media {
-
-// Const used for converting Seconds to REFERENCE_TIME.
-extern const REFERENCE_TIME kSecondsToReferenceTime;
-
-// Input pin of the SinkFilter.
-class SinkInputPin : public PinBase {
- public:
- SinkInputPin(IBaseFilter* filter, SinkFilterObserver* observer);
- virtual ~SinkInputPin();
-
- void SetRequestedMediaCapability(const VideoCaptureCapability& capability);
- // Returns the capability that is negotiated when this
- // pin is connected to a media filter.
- const VideoCaptureCapability& ResultingCapability();
-
- // Implement PinBase.
- virtual bool IsMediaTypeValid(const AM_MEDIA_TYPE* media_type);
- virtual bool GetValidMediaType(int index, AM_MEDIA_TYPE* media_type);
-
- STDMETHOD(Receive)(IMediaSample* media_sample);
-
- private:
- VideoCaptureCapability requested_capability_;
- VideoCaptureCapability resulting_capability_;
- SinkFilterObserver* observer_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(SinkInputPin);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_
diff --git a/src/media/video/capture/win/video_capture_device_mf_win.cc b/src/media/video/capture/win/video_capture_device_mf_win.cc
deleted file mode 100644
index 96bfdb2..0000000
--- a/src/media/video/capture/win/video_capture_device_mf_win.cc
+++ /dev/null
@@ -1,428 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/video_capture_device_mf_win.h"
-
-#include <mfapi.h>
-#include <mferror.h>
-
-#include "base/lazy_instance.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/sys_string_conversions.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/windows_version.h"
-#include "media/video/capture/win/capability_list_win.h"
-
-using base::win::ScopedCoMem;
-using base::win::ScopedComPtr;
-
-namespace media {
-namespace {
-
-class MFInitializerSingleton {
- public:
- MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); }
- ~MFInitializerSingleton() { MFShutdown(); }
-};
-
-static base::LazyInstance<MFInitializerSingleton> g_mf_initialize =
- LAZY_INSTANCE_INITIALIZER;
-
-void EnsureMFInit() {
- g_mf_initialize.Get();
-}
-
-bool PrepareVideoCaptureAttributes(IMFAttributes** attributes, int count) {
- EnsureMFInit();
-
- if (FAILED(MFCreateAttributes(attributes, count)))
- return false;
-
- return SUCCEEDED((*attributes)->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
- MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID));
-}
-
-bool EnumerateVideoDevices(IMFActivate*** devices,
- UINT32* count) {
- ScopedComPtr<IMFAttributes> attributes;
- if (!PrepareVideoCaptureAttributes(attributes.Receive(), 1))
- return false;
-
- return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count));
-}
-
-bool CreateVideoCaptureDevice(const char* sym_link, IMFMediaSource** source) {
- ScopedComPtr<IMFAttributes> attributes;
- if (!PrepareVideoCaptureAttributes(attributes.Receive(), 2))
- return false;
-
- attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
- base::SysUTF8ToWide(sym_link).c_str());
-
- return SUCCEEDED(MFCreateDeviceSource(attributes, source));
-}
-
-bool FormatFromGuid(const GUID& guid, VideoCaptureCapability::Format* format) {
- struct {
- const GUID& guid;
- const VideoCaptureCapability::Format format;
- } static const kFormatMap[] = {
- { MFVideoFormat_I420, VideoCaptureCapability::kI420 },
- { MFVideoFormat_YUY2, VideoCaptureCapability::kYUY2 },
- { MFVideoFormat_UYVY, VideoCaptureCapability::kUYVY },
- { MFVideoFormat_RGB24, VideoCaptureCapability::kRGB24 },
- { MFVideoFormat_ARGB32, VideoCaptureCapability::kARGB },
- { MFVideoFormat_MJPG, VideoCaptureCapability::kMJPEG },
- { MFVideoFormat_YV12, VideoCaptureCapability::kYV12 },
- };
-
- for (int i = 0; i < arraysize(kFormatMap); ++i) {
- if (kFormatMap[i].guid == guid) {
- *format = kFormatMap[i].format;
- return true;
- }
- }
-
- return false;
-}
-
-bool GetFrameSize(IMFMediaType* type, int* width, int* height) {
- UINT32 width32, height32;
- if (FAILED(MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width32, &height32)))
- return false;
- *width = width32;
- *height = height32;
- return true;
-}
-
-bool GetFrameRate(IMFMediaType* type, int* frame_rate) {
- UINT32 numerator, denominator;
- if (FAILED(MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator,
- &denominator))) {
- return false;
- }
-
- *frame_rate = denominator ? numerator / denominator : 0;
-
- return true;
-}
-
-bool FillCapabilitiesFromType(IMFMediaType* type,
- VideoCaptureCapability* capability) {
- GUID type_guid;
- if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &type_guid)) ||
- !FormatFromGuid(type_guid, &capability->color) ||
- !GetFrameSize(type, &capability->width, &capability->height) ||
- !GetFrameRate(type, &capability->frame_rate)) {
- return false;
- }
-
- capability->expected_capture_delay = 0; // Currently not used.
- capability->interlaced = false; // Currently not used.
-
- return true;
-}
-
-HRESULT FillCapabilities(IMFSourceReader* source,
- CapabilityList* capabilities) {
- DWORD stream_index = 0;
- ScopedComPtr<IMFMediaType> type;
- HRESULT hr;
- while (SUCCEEDED(hr = source->GetNativeMediaType(
- MF_SOURCE_READER_FIRST_VIDEO_STREAM, stream_index, type.Receive()))) {
- VideoCaptureCapabilityWin capability(stream_index++);
- if (FillCapabilitiesFromType(type, &capability))
- capabilities->Add(capability);
- type.Release();
- }
-
- if (SUCCEEDED(hr) && capabilities->empty())
- hr = HRESULT_FROM_WIN32(ERROR_EMPTY);
-
- return (hr == MF_E_NO_MORE_TYPES) ? S_OK : hr;
-}
-
-bool LoadMediaFoundationDlls() {
- static const wchar_t* const kMfDLLs[] = {
- L"%WINDIR%\\system32\\mf.dll",
- L"%WINDIR%\\system32\\mfplat.dll",
- L"%WINDIR%\\system32\\mfreadwrite.dll",
- };
-
- for (int i = 0; i < arraysize(kMfDLLs); ++i) {
- wchar_t path[MAX_PATH] = {0};
- ExpandEnvironmentStringsW(kMfDLLs[i], path, arraysize(path));
- if (!LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH))
- return false;
- }
-
- return true;
-}
-
-} // namespace
-
-class MFReaderCallback
- : public base::RefCountedThreadSafe<MFReaderCallback>,
- public IMFSourceReaderCallback {
- public:
- MFReaderCallback(VideoCaptureDeviceMFWin* observer)
- : observer_(observer), wait_event_(NULL) {
- }
-
- void SetSignalOnFlush(base::WaitableEvent* event) {
- wait_event_ = event;
- }
-
- STDMETHOD(QueryInterface)(REFIID riid, void** object) {
- if (riid != IID_IUnknown && riid != IID_IMFSourceReaderCallback)
- return E_NOINTERFACE;
- *object = static_cast<IMFSourceReaderCallback*>(this);
- AddRef();
- return S_OK;
- }
-
- STDMETHOD_(ULONG, AddRef)() {
- base::RefCountedThreadSafe<MFReaderCallback>::AddRef();
- return 1U;
- }
-
- STDMETHOD_(ULONG, Release)() {
- base::RefCountedThreadSafe<MFReaderCallback>::Release();
- return 1U;
- }
-
- STDMETHOD(OnReadSample)(HRESULT status, DWORD stream_index,
- DWORD stream_flags, LONGLONG time_stamp, IMFSample* sample) {
- base::Time stamp(base::Time::Now());
- if (!sample) {
- observer_->OnIncomingCapturedFrame(NULL, 0, stamp);
- return S_OK;
- }
-
- DWORD count = 0;
- sample->GetBufferCount(&count);
-
- for (DWORD i = 0; i < count; ++i) {
- ScopedComPtr<IMFMediaBuffer> buffer;
- sample->GetBufferByIndex(i, buffer.Receive());
- if (buffer) {
- DWORD length = 0, max_length = 0;
- BYTE* data = NULL;
- buffer->Lock(&data, &max_length, &length);
- observer_->OnIncomingCapturedFrame(data, length, stamp);
- buffer->Unlock();
- }
- }
- return S_OK;
- }
-
- STDMETHOD(OnFlush)(DWORD stream_index) {
- if (wait_event_) {
- wait_event_->Signal();
- wait_event_ = NULL;
- }
- return S_OK;
- }
-
- STDMETHOD(OnEvent)(DWORD stream_index, IMFMediaEvent* event) {
- NOTIMPLEMENTED();
- return S_OK;
- }
-
- private:
- friend class base::RefCountedThreadSafe<MFReaderCallback>;
- ~MFReaderCallback() {}
-
- VideoCaptureDeviceMFWin* observer_;
- base::WaitableEvent* wait_event_;
-};
-
-// static
-bool VideoCaptureDeviceMFWin::PlatformSupported() {
- // Even though the DLLs might be available on Vista, we get crashes
- // when running our tests on the build bots.
- if (base::win::GetVersion() < base::win::VERSION_WIN7)
- return false;
-
- static bool g_dlls_available = LoadMediaFoundationDlls();
- return g_dlls_available;
-}
-
-// static
-void VideoCaptureDeviceMFWin::GetDeviceNames(Names* device_names) {
- ScopedCoMem<IMFActivate*> devices;
- UINT32 count;
- if (!EnumerateVideoDevices(&devices, &count))
- return;
-
- HRESULT hr;
- for (UINT32 i = 0; i < count; ++i) {
- UINT32 name_size, id_size;
- ScopedCoMem<wchar_t> name, id;
- if (SUCCEEDED(hr = devices[i]->GetAllocatedString(
- MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size)) &&
- SUCCEEDED(hr = devices[i]->GetAllocatedString(
- MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id,
- &id_size))) {
- std::wstring name_w(name, name_size), id_w(id, id_size);
- Name device;
- device.device_name = base::SysWideToUTF8(name_w);
- device.unique_id = base::SysWideToUTF8(id_w);
- device_names->push_back(device);
- } else {
- DLOG(WARNING) << "GetAllocatedString failed: " << std::hex << hr;
- }
- devices[i]->Release();
- }
-}
-
-VideoCaptureDeviceMFWin::VideoCaptureDeviceMFWin(const Name& device_name)
- : name_(device_name), observer_(NULL), capture_(0) {
- DetachFromThread();
-}
-
-VideoCaptureDeviceMFWin::~VideoCaptureDeviceMFWin() {
- DCHECK(CalledOnValidThread());
-}
-
-bool VideoCaptureDeviceMFWin::Init() {
- DCHECK(CalledOnValidThread());
- DCHECK(!reader_);
-
- ScopedComPtr<IMFMediaSource> source;
- if (!CreateVideoCaptureDevice(name_.unique_id.c_str(), source.Receive()))
- return false;
-
- ScopedComPtr<IMFAttributes> attributes;
- MFCreateAttributes(attributes.Receive(), 1);
- DCHECK(attributes);
-
- callback_ = new MFReaderCallback(this);
- attributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback_.get());
-
- return SUCCEEDED(MFCreateSourceReaderFromMediaSource(source, attributes,
- reader_.Receive()));
-}
-
-void VideoCaptureDeviceMFWin::Allocate(
- int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) {
- DCHECK(CalledOnValidThread());
-
- base::AutoLock lock(lock_);
-
- if (observer_) {
- DCHECK_EQ(observer, observer_);
- return;
- }
-
- observer_ = observer;
- DCHECK_EQ(capture_, false);
-
- CapabilityList capabilities;
- HRESULT hr = S_OK;
- if (!reader_ || FAILED(hr = FillCapabilities(reader_, &capabilities))) {
- OnError(hr);
- return;
- }
-
- const VideoCaptureCapabilityWin& found_capability =
- capabilities.GetBestMatchedCapability(width, height, frame_rate);
-
- ScopedComPtr<IMFMediaType> type;
- if (FAILED(hr = reader_->GetNativeMediaType(
- MF_SOURCE_READER_FIRST_VIDEO_STREAM, found_capability.stream_index,
- type.Receive())) ||
- FAILED(hr = reader_->SetCurrentMediaType(
- MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, type))) {
- OnError(hr);
- return;
- }
-
- observer_->OnFrameInfo(found_capability);
-}
-
-void VideoCaptureDeviceMFWin::Start() {
- DCHECK(CalledOnValidThread());
-
- base::AutoLock lock(lock_);
- if (!capture_) {
- capture_ = true;
- HRESULT hr;
- if (FAILED(hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
- NULL, NULL, NULL, NULL))) {
- OnError(hr);
- capture_ = false;
- }
- }
-}
-
-void VideoCaptureDeviceMFWin::Stop() {
- DCHECK(CalledOnValidThread());
- base::WaitableEvent flushed(false, false);
- bool wait = false;
- {
- base::AutoLock lock(lock_);
- if (capture_) {
- capture_ = false;
- callback_->SetSignalOnFlush(&flushed);
- HRESULT hr = reader_->Flush(MF_SOURCE_READER_ALL_STREAMS);
- wait = SUCCEEDED(hr);
- if (!wait) {
- callback_->SetSignalOnFlush(NULL);
- OnError(hr);
- }
- }
- }
-
- if (wait)
- flushed.Wait();
-}
-
-void VideoCaptureDeviceMFWin::DeAllocate() {
- DCHECK(CalledOnValidThread());
-
- Stop();
-
- base::AutoLock lock(lock_);
- observer_ = NULL;
-}
-
-const VideoCaptureDevice::Name& VideoCaptureDeviceMFWin::device_name() {
- DCHECK(CalledOnValidThread());
- return name_;
-}
-
-void VideoCaptureDeviceMFWin::OnIncomingCapturedFrame(
- const uint8* data,
- int length,
- const base::Time& time_stamp) {
- base::AutoLock lock(lock_);
- if (data && observer_)
- observer_->OnIncomingCapturedFrame(data, length, time_stamp);
-
- if (capture_) {
- HRESULT hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
- NULL, NULL, NULL, NULL);
- if (FAILED(hr)) {
- // If running the *VideoCap* unit tests on repeat, this can sometimes
- // fail with HRESULT_FROM_WINHRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION).
- // It's not clear to me why this is, but it is possible that it has
- // something to do with this bug:
- // http://support.microsoft.com/kb/979567
- OnError(hr);
- }
- }
-}
-
-void VideoCaptureDeviceMFWin::OnError(HRESULT hr) {
- DLOG(ERROR) << "VideoCaptureDeviceMFWin: " << std::hex << hr;
- if (observer_)
- observer_->OnError();
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/video_capture_device_mf_win.h b/src/media/video/capture/win/video_capture_device_mf_win.h
deleted file mode 100644
index 773b1ed..0000000
--- a/src/media/video/capture/win/video_capture_device_mf_win.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Windows specific implementation of VideoCaptureDevice.
-// DirectShow is used for capturing. DirectShow provide its own threads
-// for capturing.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_MF_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_MF_WIN_H_
-
-#include <mfidl.h>
-#include <mfreadwrite.h>
-
-#include <vector>
-
-#include "base/synchronization/lock.h"
-#include "base/threading/non_thread_safe.h"
-#include "base/win/scoped_comptr.h"
-#include "media/video/capture/video_capture_device.h"
-
-interface IMFSourceReader;
-
-namespace media {
-
-class MFReaderCallback;
-
-class VideoCaptureDeviceMFWin
- : public base::NonThreadSafe,
- public VideoCaptureDevice {
- public:
- explicit VideoCaptureDeviceMFWin(const Name& device_name);
- virtual ~VideoCaptureDeviceMFWin();
-
- // Opens the device driver for this device.
- // This function is used by the static VideoCaptureDevice::Create function.
- bool Init();
-
- // VideoCaptureDevice implementation.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
- virtual const Name& device_name() OVERRIDE;
-
- // Returns true iff the current platform supports the Media Foundation API
- // and that the DLLs are available. On Vista this API is an optional download
- // but the API is advertised as a part of Windows 7 and onwards. However,
- // we've seen that the required DLLs are not available in some Win7
- // distributions such as Windows 7 N and Windows 7 KN.
- static bool PlatformSupported();
-
- static void GetDeviceNames(Names* device_names);
-
- // Captured a new video frame.
- void OnIncomingCapturedFrame(const uint8* data, int length,
- const base::Time& time_stamp);
-
- private:
- void OnError(HRESULT hr);
-
- Name name_;
- base::win::ScopedComPtr<IMFActivate> device_;
- scoped_refptr<MFReaderCallback> callback_;
-
- base::Lock lock_; // Used to guard the below variables.
- VideoCaptureDevice::EventHandler* observer_;
- base::win::ScopedComPtr<IMFSourceReader> reader_;
- bool capture_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureDeviceMFWin);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_MF_WIN_H_
diff --git a/src/media/video/capture/win/video_capture_device_win.cc b/src/media/video/capture/win/video_capture_device_win.cc
deleted file mode 100644
index 875ca00..0000000
--- a/src/media/video/capture/win/video_capture_device_win.cc
+++ /dev/null
@@ -1,599 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/video/capture/win/video_capture_device_win.h"
-
-#include <algorithm>
-#include <list>
-
-#include "base/string_util.h"
-#include "base/sys_string_conversions.h"
-#include "base/win/scoped_variant.h"
-#include "media/video/capture/win/video_capture_device_mf_win.h"
-
-using base::win::ScopedComPtr;
-using base::win::ScopedVariant;
-
-namespace {
-
-// Finds and creates a DirectShow Video Capture filter matching the device_name.
-HRESULT GetDeviceFilter(const media::VideoCaptureDevice::Name& device_name,
- IBaseFilter** filter) {
- DCHECK(filter);
-
- ScopedComPtr<ICreateDevEnum> dev_enum;
- HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
- CLSCTX_INPROC);
- if (FAILED(hr))
- return hr;
-
- ScopedComPtr<IEnumMoniker> enum_moniker;
- hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
- enum_moniker.Receive(), 0);
- // CreateClassEnumerator returns S_FALSE on some Windows OS
- // when no camera exist. Therefore the FAILED macro can't be used.
- if (hr != S_OK)
- return NULL;
-
- ScopedComPtr<IMoniker> moniker;
- ScopedComPtr<IBaseFilter> capture_filter;
- DWORD fetched = 0;
- while (enum_moniker->Next(1, moniker.Receive(), &fetched) == S_OK) {
- ScopedComPtr<IPropertyBag> prop_bag;
- hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
- if (FAILED(hr)) {
- moniker.Release();
- continue;
- }
-
- // Find the description or friendly name.
- static const wchar_t* kPropertyNames[] = {
- L"DevicePath", L"Description", L"FriendlyName"
- };
- ScopedVariant name;
- for (size_t i = 0;
- i < arraysize(kPropertyNames) && name.type() != VT_BSTR; ++i) {
- prop_bag->Read(kPropertyNames[i], name.Receive(), 0);
- }
- if (name.type() == VT_BSTR) {
- std::string device_path(base::SysWideToUTF8(V_BSTR(&name)));
- if (device_path.compare(device_name.unique_id) == 0) {
- // We have found the requested device
- hr = moniker->BindToObject(0, 0, IID_IBaseFilter,
- capture_filter.ReceiveVoid());
- DVPLOG_IF(2, FAILED(hr)) << "Failed to bind camera filter.";
- break;
- }
- }
- moniker.Release();
- }
-
- *filter = capture_filter.Detach();
- if (!*filter && SUCCEEDED(hr))
- hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
-
- return hr;
-}
-
-// Check if a Pin matches a category.
-bool PinMatchesCategory(IPin* pin, REFGUID category) {
- DCHECK(pin);
- bool found = false;
- ScopedComPtr<IKsPropertySet> ks_property;
- HRESULT hr = ks_property.QueryFrom(pin);
- if (SUCCEEDED(hr)) {
- GUID pin_category;
- DWORD return_value;
- hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
- &pin_category, sizeof(pin_category), &return_value);
- if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) {
- found = (pin_category == category);
- }
- }
- return found;
-}
-
-// Finds a IPin on a IBaseFilter given the direction an category.
-HRESULT GetPin(IBaseFilter* filter, PIN_DIRECTION pin_dir, REFGUID category,
- IPin** pin) {
- DCHECK(pin);
- ScopedComPtr<IEnumPins> pin_emum;
- HRESULT hr = filter->EnumPins(pin_emum.Receive());
- if (pin_emum == NULL)
- return hr;
-
- // Get first unconnected pin.
- hr = pin_emum->Reset(); // set to first pin
- while ((hr = pin_emum->Next(1, pin, NULL)) == S_OK) {
- PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1);
- hr = (*pin)->QueryDirection(&this_pin_dir);
- if (pin_dir == this_pin_dir) {
- if (category == GUID_NULL || PinMatchesCategory(*pin, category))
- return S_OK;
- }
- (*pin)->Release();
- }
-
- return E_FAIL;
-}
-
-// Release the format block for a media type.
-// http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
-void FreeMediaType(AM_MEDIA_TYPE* mt) {
- if (mt->cbFormat != 0) {
- CoTaskMemFree(mt->pbFormat);
- mt->cbFormat = 0;
- mt->pbFormat = NULL;
- }
- if (mt->pUnk != NULL) {
- NOTREACHED();
- // pUnk should not be used.
- mt->pUnk->Release();
- mt->pUnk = NULL;
- }
-}
-
-// Delete a media type structure that was allocated on the heap.
-// http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
-void DeleteMediaType(AM_MEDIA_TYPE* mt) {
- if (mt != NULL) {
- FreeMediaType(mt);
- CoTaskMemFree(mt);
- }
-}
-
-} // namespace
-
-namespace media {
-
-// static
-void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
- if (VideoCaptureDeviceMFWin::PlatformSupported()) {
- VideoCaptureDeviceMFWin::GetDeviceNames(device_names);
- } else {
- VideoCaptureDeviceWin::GetDeviceNames(device_names);
- }
-}
-
-// static
-VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
- VideoCaptureDevice* ret = NULL;
- if (VideoCaptureDeviceMFWin::PlatformSupported()) {
- scoped_ptr<VideoCaptureDeviceMFWin> device(
- new VideoCaptureDeviceMFWin(device_name));
- if (device->Init())
- ret = device.release();
- } else {
- scoped_ptr<VideoCaptureDeviceWin> device(
- new VideoCaptureDeviceWin(device_name));
- if (device->Init())
- ret = device.release();
- }
-
- return ret;
-}
-
-// static
-void VideoCaptureDeviceWin::GetDeviceNames(Names* device_names) {
- DCHECK(device_names);
-
- ScopedComPtr<ICreateDevEnum> dev_enum;
- HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
- CLSCTX_INPROC);
- if (FAILED(hr))
- return;
-
- ScopedComPtr<IEnumMoniker> enum_moniker;
- hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
- enum_moniker.Receive(), 0);
- // CreateClassEnumerator returns S_FALSE on some Windows OS
- // when no camera exist. Therefore the FAILED macro can't be used.
- if (hr != S_OK)
- return;
-
- device_names->clear();
-
- // Name of a fake DirectShow filter that exist on computers with
- // GTalk installed.
- static const char kGoogleCameraAdapter[] = "google camera adapter";
-
- // Enumerate all video capture devices.
- ScopedComPtr<IMoniker> moniker;
- int index = 0;
- while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) {
- Name device;
- ScopedComPtr<IPropertyBag> prop_bag;
- hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
- if (FAILED(hr)) {
- moniker.Release();
- continue;
- }
-
- // Find the description or friendly name.
- ScopedVariant name;
- hr = prop_bag->Read(L"Description", name.Receive(), 0);
- if (FAILED(hr))
- hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
-
- if (SUCCEEDED(hr) && name.type() == VT_BSTR) {
- // Ignore all VFW drivers and the special Google Camera Adapter.
- // Google Camera Adapter is not a real DirectShow camera device.
- // VFW is very old Video for Windows drivers that can not be used.
- const wchar_t* str_ptr = V_BSTR(&name);
- const int name_length = arraysize(kGoogleCameraAdapter) - 1;
-
- if ((wcsstr(str_ptr, L"(VFW)") == NULL) &&
- lstrlenW(str_ptr) < name_length ||
- (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length,
- kGoogleCameraAdapter)))) {
- device.device_name = base::SysWideToUTF8(str_ptr);
- name.Reset();
- hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
- if (FAILED(hr)) {
- device.unique_id = device.device_name;
- } else if (name.type() == VT_BSTR) {
- device.unique_id = base::SysWideToUTF8(V_BSTR(&name));
- }
-
- device_names->push_back(device);
- }
- }
- moniker.Release();
- }
-}
-
-VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name)
- : device_name_(device_name),
- state_(kIdle),
- observer_(NULL) {
- DetachFromThread();
-}
-
-VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
- DCHECK(CalledOnValidThread());
- if (media_control_)
- media_control_->Stop();
-
- if (graph_builder_) {
- if (sink_filter_) {
- graph_builder_->RemoveFilter(sink_filter_);
- sink_filter_ = NULL;
- }
-
- if (capture_filter_)
- graph_builder_->RemoveFilter(capture_filter_);
-
- if (mjpg_filter_)
- graph_builder_->RemoveFilter(mjpg_filter_);
- }
-}
-
-bool VideoCaptureDeviceWin::Init() {
- DCHECK(CalledOnValidThread());
- HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive());
- if (!capture_filter_) {
- DVLOG(2) << "Failed to create capture filter.";
- return false;
- }
-
- hr = GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE,
- output_capture_pin_.Receive());
- if (!output_capture_pin_) {
- DVLOG(2) << "Failed to get capture output pin";
- return false;
- }
-
- // Create the sink filter used for receiving Captured frames.
- sink_filter_ = new SinkFilter(this);
- if (sink_filter_ == NULL) {
- DVLOG(2) << "Failed to create send filter";
- return false;
- }
-
- input_sink_pin_ = sink_filter_->GetPin(0);
-
- hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
- CLSCTX_INPROC_SERVER);
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to create graph builder.";
- return false;
- }
-
- hr = graph_builder_.QueryInterface(media_control_.Receive());
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to create media control builder.";
- return false;
- }
-
- hr = graph_builder_->AddFilter(capture_filter_, NULL);
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to add the capture device to the graph.";
- return false;
- }
-
- hr = graph_builder_->AddFilter(sink_filter_, NULL);
- if (FAILED(hr)) {
- DVLOG(2)<< "Failed to add the send filter to the graph.";
- return false;
- }
-
- return CreateCapabilityMap();
-}
-
-void VideoCaptureDeviceWin::Allocate(
- int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) {
- DCHECK(CalledOnValidThread());
- if (state_ != kIdle)
- return;
-
- observer_ = observer;
-
- // Get the camera capability that best match the requested resolution.
- const VideoCaptureCapabilityWin& found_capability =
- capabilities_.GetBestMatchedCapability(width, height, frame_rate);
- VideoCaptureCapability capability = found_capability;
-
- // Reduce the frame rate if the requested frame rate is lower
- // than the capability.
- if (capability.frame_rate > frame_rate)
- capability.frame_rate = frame_rate;
-
- AM_MEDIA_TYPE* pmt = NULL;
- VIDEO_STREAM_CONFIG_CAPS caps;
-
- ScopedComPtr<IAMStreamConfig> stream_config;
- HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
- if (FAILED(hr)) {
- SetErrorState("Can't get the Capture format settings");
- return;
- }
-
- // Get the windows capability from the capture device.
- hr = stream_config->GetStreamCaps(found_capability.stream_index, &pmt,
- reinterpret_cast<BYTE*>(&caps));
- if (SUCCEEDED(hr)) {
- if (pmt->formattype == FORMAT_VideoInfo) {
- VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);
- if (capability.frame_rate > 0)
- h->AvgTimePerFrame = kSecondsToReferenceTime / capability.frame_rate;
- }
- // Set the sink filter to request this capability.
- sink_filter_->SetRequestedMediaCapability(capability);
- // Order the capture device to use this capability.
- hr = stream_config->SetFormat(pmt);
- }
-
- if (FAILED(hr))
- SetErrorState("Failed to set capture device output format");
-
- if (capability.color == VideoCaptureCapability::kMJPEG &&
- !mjpg_filter_.get()) {
- // Create MJPG filter if we need it.
- hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC);
-
- if (SUCCEEDED(hr)) {
- GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL, input_mjpg_pin_.Receive());
- GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL,
- output_mjpg_pin_.Receive());
- hr = graph_builder_->AddFilter(mjpg_filter_, NULL);
- }
-
- if (FAILED(hr)) {
- mjpg_filter_.Release();
- input_mjpg_pin_.Release();
- output_mjpg_pin_.Release();
- }
- }
-
- if (capability.color == VideoCaptureCapability::kMJPEG &&
- mjpg_filter_.get()) {
- // Connect the camera to the MJPEG decoder.
- hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_,
- NULL);
- // Connect the MJPEG filter to the Capture filter.
- hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_,
- NULL);
- } else {
- hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_,
- NULL);
- }
-
- if (FAILED(hr)) {
- SetErrorState("Failed to connect the Capture graph.");
- return;
- }
-
- hr = media_control_->Pause();
- if (FAILED(hr)) {
- SetErrorState("Failed to Pause the Capture device. "
- "Is it already occupied?");
- return;
- }
-
- // Get the capability back from the sink filter after the filter have been
- // connected.
- const VideoCaptureCapability& used_capability
- = sink_filter_->ResultingCapability();
- observer_->OnFrameInfo(used_capability);
-
- state_ = kAllocated;
-}
-
-void VideoCaptureDeviceWin::Start() {
- DCHECK(CalledOnValidThread());
- if (state_ != kAllocated)
- return;
-
- HRESULT hr = media_control_->Run();
- if (FAILED(hr)) {
- SetErrorState("Failed to start the Capture device.");
- return;
- }
-
- state_ = kCapturing;
-}
-
-void VideoCaptureDeviceWin::Stop() {
- DCHECK(CalledOnValidThread());
- if (state_ != kCapturing)
- return;
-
- HRESULT hr = media_control_->Stop();
- if (FAILED(hr)) {
- SetErrorState("Failed to stop the capture graph.");
- return;
- }
-
- state_ = kAllocated;
-}
-
-void VideoCaptureDeviceWin::DeAllocate() {
- DCHECK(CalledOnValidThread());
- if (state_ == kIdle)
- return;
-
- HRESULT hr = media_control_->Stop();
- graph_builder_->Disconnect(output_capture_pin_);
- graph_builder_->Disconnect(input_sink_pin_);
-
- // If the _mjpg filter exist disconnect it even if it has not been used.
- if (mjpg_filter_) {
- graph_builder_->Disconnect(input_mjpg_pin_);
- graph_builder_->Disconnect(output_mjpg_pin_);
- }
-
- if (FAILED(hr)) {
- SetErrorState("Failed to Stop the Capture device");
- return;
- }
-
- state_ = kIdle;
-}
-
-const VideoCaptureDevice::Name& VideoCaptureDeviceWin::device_name() {
- DCHECK(CalledOnValidThread());
- return device_name_;
-}
-
-// Implements SinkFilterObserver::SinkFilterObserver.
-void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer,
- int length) {
- observer_->OnIncomingCapturedFrame(buffer, length, base::Time::Now());
-}
-
-bool VideoCaptureDeviceWin::CreateCapabilityMap() {
- DCHECK(CalledOnValidThread());
- ScopedComPtr<IAMStreamConfig> stream_config;
- HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to get IAMStreamConfig interface from "
- "capture device";
- return false;
- }
-
- // Get interface used for getting the frame rate.
- ScopedComPtr<IAMVideoControl> video_control;
- hr = capture_filter_.QueryInterface(video_control.Receive());
- DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED";
-
- AM_MEDIA_TYPE* media_type = NULL;
- VIDEO_STREAM_CONFIG_CAPS caps;
- int count, size;
-
- hr = stream_config->GetNumberOfCapabilities(&count, &size);
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to GetNumberOfCapabilities";
- return false;
- }
-
- for (int i = 0; i < count; ++i) {
- hr = stream_config->GetStreamCaps(i, &media_type,
- reinterpret_cast<BYTE*>(&caps));
- if (FAILED(hr)) {
- DVLOG(2) << "Failed to GetStreamCaps";
- return false;
- }
-
- if (media_type->majortype == MEDIATYPE_Video &&
- media_type->formattype == FORMAT_VideoInfo) {
- VideoCaptureCapabilityWin capability(i);
- REFERENCE_TIME time_per_frame = 0;
-
- VIDEOINFOHEADER* h =
- reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
- capability.width = h->bmiHeader.biWidth;
- capability.height = h->bmiHeader.biHeight;
- time_per_frame = h->AvgTimePerFrame;
-
- // Try to get the max frame rate from IAMVideoControl.
- if (video_control.get()) {
- LONGLONG* max_fps_ptr;
- LONG list_size;
- SIZE size;
- size.cx = capability.width;
- size.cy = capability.height;
-
- // GetFrameRateList doesn't return max frame rate always
- // eg: Logitech Notebook. This may be due to a bug in that API
- // because GetFrameRateList array is reversed in the above camera. So
- // a util method written. Can't assume the first value will return
- // the max fps.
- hr = video_control->GetFrameRateList(output_capture_pin_, i, size,
- &list_size, &max_fps_ptr);
-
- if (SUCCEEDED(hr) && list_size > 0) {
- int min_time = *std::min_element(max_fps_ptr,
- max_fps_ptr + list_size);
- capability.frame_rate = (min_time > 0) ?
- kSecondsToReferenceTime / min_time : 0;
- } else {
- // Get frame rate from VIDEOINFOHEADER.
- capability.frame_rate = (time_per_frame > 0) ?
- static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0;
- }
- } else {
- // Get frame rate from VIDEOINFOHEADER since IAMVideoControl is
- // not supported.
- capability.frame_rate = (time_per_frame > 0) ?
- static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0;
- }
-
- // We can't switch MEDIATYPE :~(.
- if (media_type->subtype == kMediaSubTypeI420) {
- capability.color = VideoCaptureCapability::kI420;
- } else if (media_type->subtype == MEDIASUBTYPE_IYUV) {
- // This is identical to kI420.
- capability.color = VideoCaptureCapability::kI420;
- } else if (media_type->subtype == MEDIASUBTYPE_RGB24) {
- capability.color = VideoCaptureCapability::kRGB24;
- } else if (media_type->subtype == MEDIASUBTYPE_YUY2) {
- capability.color = VideoCaptureCapability::kYUY2;
- } else if (media_type->subtype == MEDIASUBTYPE_MJPG) {
- capability.color = VideoCaptureCapability::kMJPEG;
- } else {
- WCHAR guid_str[128];
- StringFromGUID2(media_type->subtype, guid_str, arraysize(guid_str));
- DVLOG(2) << "Device support unknown media type " << guid_str;
- continue;
- }
- capabilities_.Add(capability);
- }
- DeleteMediaType(media_type);
- media_type = NULL;
- }
-
- return !capabilities_.empty();
-}
-
-void VideoCaptureDeviceWin::SetErrorState(const char* reason) {
- DCHECK(CalledOnValidThread());
- DVLOG(1) << reason;
- state_ = kError;
- observer_->OnError();
-}
-
-} // namespace media
diff --git a/src/media/video/capture/win/video_capture_device_win.h b/src/media/video/capture/win/video_capture_device_win.h
deleted file mode 100644
index 9e1f005..0000000
--- a/src/media/video/capture/win/video_capture_device_win.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Windows specific implementation of VideoCaptureDevice.
-// DirectShow is used for capturing. DirectShow provide its own threads
-// for capturing.
-
-#ifndef MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_
-#define MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_
-
-// Avoid including strsafe.h via dshow as it will cause build warnings.
-#define NO_DSHOW_STRSAFE
-#include <dshow.h>
-
-#include <map>
-#include <string>
-
-#include "base/threading/non_thread_safe.h"
-#include "base/threading/thread.h"
-#include "base/win/scoped_comptr.h"
-#include "media/video/capture/video_capture_device.h"
-#include "media/video/capture/video_capture_types.h"
-#include "media/video/capture/win/capability_list_win.h"
-#include "media/video/capture/win/sink_filter_win.h"
-#include "media/video/capture/win/sink_input_pin_win.h"
-
-namespace media {
-
-// All the methods in the class can only be run on a COM initialized thread.
-class VideoCaptureDeviceWin
- : public base::NonThreadSafe,
- public VideoCaptureDevice,
- public SinkFilterObserver {
- public:
- explicit VideoCaptureDeviceWin(const Name& device_name);
- virtual ~VideoCaptureDeviceWin();
- // Opens the device driver for this device.
- // This function is used by the static VideoCaptureDevice::Create function.
- bool Init();
-
- // VideoCaptureDevice implementation.
- virtual void Allocate(int width,
- int height,
- int frame_rate,
- VideoCaptureDevice::EventHandler* observer) OVERRIDE;
- virtual void Start() OVERRIDE;
- virtual void Stop() OVERRIDE;
- virtual void DeAllocate() OVERRIDE;
- virtual const Name& device_name() OVERRIDE;
-
- static void GetDeviceNames(Names* device_names);
-
- private:
- enum InternalState {
- kIdle, // The device driver is opened but camera is not in use.
- kAllocated, // The camera has been allocated and can be started.
- kCapturing, // Video is being captured.
- kError // Error accessing HW functions.
- // User needs to recover by destroying the object.
- };
-
- // Implements SinkFilterObserver.
- virtual void FrameReceived(const uint8* buffer, int length);
-
- bool CreateCapabilityMap();
- void SetErrorState(const char* reason);
-
- Name device_name_;
- InternalState state_;
- VideoCaptureDevice::EventHandler* observer_;
-
- base::win::ScopedComPtr<IBaseFilter> capture_filter_;
- base::win::ScopedComPtr<IGraphBuilder> graph_builder_;
- base::win::ScopedComPtr<IMediaControl> media_control_;
- base::win::ScopedComPtr<IPin> input_sink_pin_;
- base::win::ScopedComPtr<IPin> output_capture_pin_;
- // Used when using a MJPEG decoder.
- base::win::ScopedComPtr<IBaseFilter> mjpg_filter_;
- base::win::ScopedComPtr<IPin> input_mjpg_pin_;
- base::win::ScopedComPtr<IPin> output_mjpg_pin_;
-
- scoped_refptr<SinkFilter> sink_filter_;
-
- // Map of all capabilities this device support.
- CapabilityList capabilities_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureDeviceWin);
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_
diff --git a/src/media/video/picture.cc b/src/media/video/picture.cc
deleted file mode 100644
index 54e4a23..0000000
--- a/src/media/video/picture.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/video/picture.h"
-
-namespace media {
-
-PictureBuffer::PictureBuffer(int32 id, gfx::Size size, uint32 texture_id)
- : id_(id),
- size_(size),
- texture_id_(texture_id) {
-}
-
-Picture::Picture(int32 picture_buffer_id, int32 bitstream_buffer_id)
- : picture_buffer_id_(picture_buffer_id),
- bitstream_buffer_id_(bitstream_buffer_id) {
-}
-
-} // namespace media
diff --git a/src/media/video/picture.h b/src/media/video/picture.h
deleted file mode 100644
index 5d3b775..0000000
--- a/src/media/video/picture.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_VIDEO_PICTURE_H_
-#define MEDIA_VIDEO_PICTURE_H_
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-// A picture buffer that is composed of a GLES2 texture.
-// This is the media-namespace equivalent of PP_PictureBuffer_Dev.
-class MEDIA_EXPORT PictureBuffer {
- public:
- PictureBuffer(int32 id, gfx::Size size, uint32 texture_id);
-
- // Returns the client-specified id of the buffer.
- int32 id() const {
- return id_;
- }
-
- // Returns the size of the buffer.
- gfx::Size size() const {
- return size_;
- }
-
- // Returns the id of the texture.
- // NOTE: The texture id in the renderer process corresponds to a different
- // texture id in the GPU process.
- uint32 texture_id() const {
- return texture_id_;
- }
-
- private:
- int32 id_;
- gfx::Size size_;
- uint32 texture_id_;
-};
-
-// A decoded picture frame.
-// This is the media-namespace equivalent of PP_Picture_Dev.
-class MEDIA_EXPORT Picture {
- public:
- Picture(int32 picture_buffer_id, int32 bitstream_buffer_id);
-
- // Returns the id of the picture buffer where this picture is contained.
- int32 picture_buffer_id() const {
- return picture_buffer_id_;
- }
-
- // Returns the id of the bitstream buffer from which this frame was decoded.
- int32 bitstream_buffer_id() const {
- return bitstream_buffer_id_;
- }
-
- void set_bitstream_buffer_id(int32 bitstream_buffer_id) {
- bitstream_buffer_id_ = bitstream_buffer_id;
- }
-
- private:
- int32 picture_buffer_id_;
- int32 bitstream_buffer_id_;
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_PICTURE_H_
diff --git a/src/media/video/video_decode_accelerator.cc b/src/media/video/video_decode_accelerator.cc
deleted file mode 100644
index 9063d68..0000000
--- a/src/media/video/video_decode_accelerator.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/video/video_decode_accelerator.h"
-
-namespace media {
-
-VideoDecodeAccelerator::~VideoDecodeAccelerator() {}
-
-}
diff --git a/src/media/video/video_decode_accelerator.h b/src/media/video/video_decode_accelerator.h
deleted file mode 100644
index 63553a9..0000000
--- a/src/media/video/video_decode_accelerator.h
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_VIDEO_VIDEO_DECODE_ACCELERATOR_H_
-#define MEDIA_VIDEO_VIDEO_DECODE_ACCELERATOR_H_
-
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/memory/weak_ptr.h"
-#include "media/base/bitstream_buffer.h"
-#include "media/base/video_decoder_config.h"
-#include "media/video/picture.h"
-#include "ui/gfx/size.h"
-
-namespace media {
-
-// Video decoder interface.
-// This interface is extended by the various components that ultimately
-// implement the backend of PPB_VideoDecode_Dev.
-class MEDIA_EXPORT VideoDecodeAccelerator
- : public base::SupportsWeakPtr<VideoDecodeAccelerator> {
- public:
- virtual ~VideoDecodeAccelerator();
-
- // Enumeration of potential errors generated by the API.
- // Note: Keep these in sync with PP_VideoDecodeError_Dev.
- enum Error {
- // An operation was attempted during an incompatible decoder state.
- ILLEGAL_STATE = 1,
- // Invalid argument was passed to an API method.
- INVALID_ARGUMENT,
- // Encoded input is unreadable.
- UNREADABLE_INPUT,
- // A failure occurred at the browser layer or one of its dependencies.
- // Examples of such failures include GPU hardware failures, GPU driver
- // failures, GPU library failures, browser programming errors, and so on.
- PLATFORM_FAILURE,
- };
-
- // Interface for collaborating with picture interface to provide memory for
- // output picture and blitting them.
- // This interface is extended by the various layers that relay messages back
- // to the plugin, through the PPP_VideoDecode_Dev interface the plugin
- // implements.
- class MEDIA_EXPORT Client {
- public:
- // Callback to notify client that decoder has been initialized.
- virtual void NotifyInitializeDone() = 0;
-
- // Callback to tell client how many and what size of buffers to provide.
- virtual void ProvidePictureBuffers(uint32 requested_num_of_buffers,
- const gfx::Size& dimensions,
- uint32 texture_target) = 0;
-
- // Callback to dismiss picture buffer that was assigned earlier.
- virtual void DismissPictureBuffer(int32 picture_buffer_id) = 0;
-
- // Callback to deliver decoded pictures ready to be displayed.
- virtual void PictureReady(const Picture& picture) = 0;
-
- // Callback to notify that decoded has decoded the end of the current
- // bitstream buffer.
- virtual void NotifyEndOfBitstreamBuffer(int32 bitstream_buffer_id) = 0;
-
- // Flush completion callback.
- virtual void NotifyFlushDone() = 0;
-
- // Reset completion callback.
- virtual void NotifyResetDone() = 0;
-
- // Callback to notify about decoding errors.
- virtual void NotifyError(Error error) = 0;
-
- protected:
- virtual ~Client() {}
- };
-
- // Video decoder functions.
-
- // Initializes the video decoder with specific configuration.
- // Parameters:
- // |profile| is the video stream's format profile.
- //
- // Returns true when command successfully accepted. Otherwise false.
- virtual bool Initialize(VideoCodecProfile profile) = 0;
-
- // Decodes given bitstream buffer. Once decoder is done with processing
- // |bitstream_buffer| it will call NotifyEndOfBitstreamBuffer() with the
- // bitstream buffer id.
- // Parameters:
- // |bitstream_buffer| is the input bitstream that is sent for decoding.
- virtual void Decode(const BitstreamBuffer& bitstream_buffer) = 0;
-
- // Assigns a set of texture-backed picture buffers to the video decoder.
- //
- // Ownership of each picture buffer remains with the client, but the client
- // is not allowed to deallocate the buffer before the DismissPictureBuffer
- // callback has been initiated for a given buffer.
- //
- // Parameters:
- // |buffers| contains the allocated picture buffers for the output.
- virtual void AssignPictureBuffers(
- const std::vector<PictureBuffer>& buffers) = 0;
-
- // Sends picture buffers to be reused by the decoder. This needs to be called
- // for each buffer that has been processed so that decoder may know onto which
- // picture buffers it can write the output to.
- //
- // Parameters:
- // |picture_buffer_id| id of the picture buffer that is to be reused.
- virtual void ReusePictureBuffer(int32 picture_buffer_id) = 0;
-
- // Flushes the decoder: all pending inputs will be decoded and pictures handed
- // back to the client, followed by NotifyFlushDone() being called on the
- // client. Can be used to implement "end of stream" notification.
- virtual void Flush() = 0;
-
- // Resets the decoder: all pending inputs are dropped immediately and the
- // decoder returned to a state ready for further Decode()s, followed by
- // NotifyResetDone() being called on the client. Can be used to implement
- // "seek".
- virtual void Reset() = 0;
-
- // Destroys the decoder: all pending inputs are dropped immediately and the
- // component is freed. This call may asynchornously free system resources,
- // but its client-visible effects are synchronous. After this method returns
- // no more callbacks will be made on the client. Deletes |this|
- // unconditionally, so make sure to drop all pointers to it!
- virtual void Destroy() = 0;
-};
-
-} // namespace media
-
-#endif // MEDIA_VIDEO_VIDEO_DECODE_ACCELERATOR_H_
diff --git a/src/media/webm/chromeos/ebml_writer.cc b/src/media/webm/chromeos/ebml_writer.cc
deleted file mode 100644
index 5c5f07d..0000000
--- a/src/media/webm/chromeos/ebml_writer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/chromeos/ebml_writer.h"
-
-#include "media/base/media_export.h"
-
-extern "C" {
-#include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h"
-
-EbmlGlobal::EbmlGlobal() {
-}
-
-EbmlGlobal::~EbmlGlobal() {
-}
-
-// These functions must be in the global namespace and visible to libmkv.
-
-void MEDIA_EXPORT Ebml_Write(EbmlGlobal* glob,
- const void* buffer,
- unsigned long len) {
- glob->write_cb.Run(buffer, len);
-}
-
-void MEDIA_EXPORT Ebml_Serialize(EbmlGlobal* glob,
- const void* buffer,
- int buffer_size,
- unsigned long len) {
- glob->serialize_cb.Run(buffer, buffer_size, len);
-}
-
-} // extern "C"
diff --git a/src/media/webm/chromeos/ebml_writer.h b/src/media/webm/chromeos/ebml_writer.h
deleted file mode 100644
index 0714ebf..0000000
--- a/src/media/webm/chromeos/ebml_writer.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_CHROMEOS_EBML_WRITER_H_
-#define MEDIA_WEBM_CHROMEOS_EBML_WRITER_H_
-
-#include "base/callback.h"
-
-// This struct serves as a bridge betweeen static libmkv interface and Chrome's
-// base::Callback. Must be in the global namespace. See EbmlWriter.h.
-struct EbmlGlobal {
- EbmlGlobal();
- ~EbmlGlobal();
-
- base::Callback<void(const void* buffer, unsigned long len)> write_cb;
- base::Callback<void(const void* buffer, int buffer_size, unsigned long len)>
- serialize_cb;
-};
-
-#endif // MEDIA_WEBM_CHROMEOS_EBML_WRITER_H_
diff --git a/src/media/webm/chromeos/webm_encoder.cc b/src/media/webm/chromeos/webm_encoder.cc
deleted file mode 100644
index 62ddad0..0000000
--- a/src/media/webm/chromeos/webm_encoder.cc
+++ /dev/null
@@ -1,323 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/chromeos/webm_encoder.h"
-
-#include "base/bind.h"
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/memory/scoped_generic_obj.h"
-#include "libyuv/convert.h"
-#include "libyuv/video_common.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-
-extern "C" {
-// Getting the right degree of C compatibility has been a constant struggle.
-// - Stroustrup, C++ Report, 12(7), July/August 2000.
-#define private priv
-#include "third_party/libvpx/source/libvpx/libmkv/EbmlIDs.h"
-#include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h"
-#undef private
-}
-
-// Number of encoder threads to use.
-static const int kNumEncoderThreads = 2;
-
-// Need a fixed size serializer for the track ID. libmkv provides a 64 bit
-// one, but not a 32 bit one.
-static void Ebml_SerializeUnsigned32(EbmlGlobal* ebml,
- unsigned long class_id,
- uint64_t value) {
- uint8 size_serialized = 4 | 0x80;
- Ebml_WriteID(ebml, class_id);
- Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1);
- Ebml_Serialize(ebml, &value, sizeof(value), 4);
-}
-
-// Wrapper functor for vpx_codec_destroy().
-class VpxCodecDestroyHelper {
- public:
- void operator()(vpx_codec_ctx_t* codec) {
- vpx_codec_destroy(codec);
- }
-};
-
-// Wrapper functor for vpx_img_free().
-class VpxImgFreeHelper {
- public:
- void operator()(vpx_image_t* image) {
- vpx_img_free(image);
- }
-};
-
-namespace media {
-
-namespace chromeos {
-
-WebmEncoder::WebmEncoder(const FilePath& output_path,
- int bitrate,
- bool realtime)
- : bitrate_(bitrate),
- deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY),
- output_path_(output_path),
- has_errors_(false) {
- ebml_writer_.write_cb = base::Bind(
- &WebmEncoder::EbmlWrite, base::Unretained(this));
- ebml_writer_.serialize_cb = base::Bind(
- &WebmEncoder::EbmlSerialize, base::Unretained(this));
-}
-
-WebmEncoder::~WebmEncoder() {
-}
-
-bool WebmEncoder::EncodeFromSprite(const SkBitmap& sprite,
- int fps_n,
- int fps_d) {
- DCHECK(!sprite.isNull());
- DCHECK(!sprite.empty());
-
- has_errors_ = false;
- width_ = sprite.width();
- height_ = sprite.width();
- fps_.num = fps_n;
- fps_.den = fps_d;
-
- // Sprite is tiled vertically.
- frame_count_ = sprite.height() / width_;
-
- vpx_image_t image;
- vpx_img_alloc(&image, VPX_IMG_FMT_I420, width_, height_, 16);
- // Ensure that image is freed after return.
- ScopedGenericObj<vpx_image_t*, VpxImgFreeHelper> image_ptr(&image);
-
- const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx();
- DCHECK(codec_iface);
- vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0);
- DCHECK_EQ(VPX_CODEC_OK, ret);
-
- config_.rc_target_bitrate = bitrate_;
- config_.g_w = width_;
- config_.g_h = height_;
- config_.g_pass = VPX_RC_ONE_PASS;
- config_.g_profile = 0; // Default profile.
- config_.g_threads = kNumEncoderThreads;
- config_.rc_min_quantizer = 0;
- config_.rc_max_quantizer = 63; // Maximum possible range.
- config_.g_timebase.num = fps_.den;
- config_.g_timebase.den = fps_.num;
- config_.kf_mode = VPX_KF_AUTO; // Auto key frames.
-
- vpx_codec_ctx_t codec;
- ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0);
- if (ret != VPX_CODEC_OK)
- return false;
- // Ensure that codec context is freed after return.
- ScopedGenericObj<vpx_codec_ctx_t*, VpxCodecDestroyHelper> codec_ptr(&codec);
-
- SkAutoLockPixels lock_sprite(sprite);
-
- const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0));
- size_t src_frame_size = sprite.getSize();
- int crop_y = 0;
-
- if (!WriteWebmHeader())
- return false;
-
- for (size_t frame = 0; frame < frame_count_ && !has_errors_; ++frame) {
- int res = libyuv::ConvertToI420(
- src, src_frame_size,
- image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y],
- image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U],
- image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V],
- 0, crop_y, // src origin
- width_, sprite.height(), // src size
- width_, height_, // dest size
- libyuv::kRotate0,
- libyuv::FOURCC_ARGB);
- if (res) {
- has_errors_ = true;
- break;
- }
- crop_y += height_;
-
- ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_);
- if (ret != VPX_CODEC_OK) {
- has_errors_ = true;
- break;
- }
-
- vpx_codec_iter_t iter = NULL;
- const vpx_codec_cx_pkt_t* packet;
- while (!has_errors_ && (packet = vpx_codec_get_cx_data(&codec, &iter))) {
- if (packet->kind == VPX_CODEC_CX_FRAME_PKT)
- WriteWebmBlock(packet);
- }
- }
-
- return WriteWebmFooter();
-}
-
-bool WebmEncoder::WriteWebmHeader() {
- output_ = file_util::OpenFile(output_path_, "wb");
- if (!output_)
- return false;
-
- // Global header.
- StartSubElement(EBML);
- {
- Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1);
- Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1);
- Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4);
- Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8);
- Ebml_SerializeString(&ebml_writer_, DocType, "webm");
- Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2);
- Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2);
- }
- EndSubElement(); // EBML
-
- // Single segment with a video track.
- StartSubElement(Segment);
- {
- StartSubElement(Info);
- {
- // All timecodes in the segment will be expressed in milliseconds.
- Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000);
- double duration = 1000. * frame_count_ * fps_.den / fps_.num;
- Ebml_SerializeFloat(&ebml_writer_, Segment_Duration, duration);
- }
- EndSubElement(); // Info
-
- StartSubElement(Tracks);
- {
- StartSubElement(TrackEntry);
- {
- Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1);
- Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1);
- Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video
- Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8");
-
- StartSubElement(Video);
- {
- Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_);
- Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_);
- Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono
- float fps = static_cast<float>(fps_.num) / fps_.den;
- Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps);
- }
- EndSubElement(); // Video
- }
- EndSubElement(); // TrackEntry
- }
- EndSubElement(); // Tracks
-
- StartSubElement(Cluster); {
- Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0);
- } // Cluster left open.
- } // Segment left open.
-
- // No check for |has_errors_| here because |false| is only returned when
- // opening file fails.
- return true;
-}
-
-void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) {
- bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY;
- int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num;
-
- DVLOG(1) << "Video packet @" << pts_ms << " ms "
- << packet->data.frame.sz << " bytes "
- << (is_keyframe ? "K" : "");
-
- Ebml_WriteID(&ebml_writer_, SimpleBlock);
-
- uint32 block_length = (packet->data.frame.sz + 4) | 0x10000000;
- EbmlSerializeHelper(&block_length, 4);
-
- uint8 track_number = 1 | 0x80;
- EbmlSerializeHelper(&track_number, 1);
-
- EbmlSerializeHelper(&pts_ms, 2);
-
- uint8 flags = 0;
- if (is_keyframe)
- flags |= 0x80;
- if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
- flags |= 0x08;
- EbmlSerializeHelper(&flags, 1);
-
- EbmlWrite(packet->data.frame.buf, packet->data.frame.sz);
-}
-
-bool WebmEncoder::WriteWebmFooter() {
- EndSubElement(); // Cluster
- EndSubElement(); // Segment
- DCHECK(ebml_sub_elements_.empty());
- return file_util::CloseFile(output_) && !has_errors_;
-}
-
-void WebmEncoder::StartSubElement(unsigned long class_id) {
- Ebml_WriteID(&ebml_writer_, class_id);
- ebml_sub_elements_.push(ftell(output_));
- static const uint64_t kUnknownLen = 0x01FFFFFFFFFFFFFFLLU;
- EbmlSerializeHelper(&kUnknownLen, 8);
-}
-
-void WebmEncoder::EndSubElement() {
- DCHECK(!ebml_sub_elements_.empty());
-
- long int end_pos = ftell(output_);
- long int start_pos = ebml_sub_elements_.top();
- ebml_sub_elements_.pop();
-
- uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL;
- // Seek to the beginning of the sub-element and patch in the calculated size.
- if (fseek(output_, start_pos, SEEK_SET)) {
- has_errors_ = true;
- LOG(ERROR) << "Error writing to " << output_path_.value();
- }
- EbmlSerializeHelper(&size, 8);
-
- // Restore write position.
- if (fseek(output_, end_pos, SEEK_SET)) {
- has_errors_ = true;
- LOG(ERROR) << "Error writing to " << output_path_.value();
- }
-}
-
-void WebmEncoder::EbmlWrite(const void* buffer,
- unsigned long len) {
- if (fwrite(buffer, 1, len, output_) != len) {
- has_errors_ = true;
- LOG(ERROR) << "Error writing to " << output_path_.value();
- }
-}
-
-template <class T>
-void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) {
- for (int i = len - 1; i >= 0; i--) {
- uint8 c = *buffer >> (i * CHAR_BIT);
- EbmlWrite(&c, 1);
- }
-}
-
-void WebmEncoder::EbmlSerialize(const void* buffer,
- int buffer_size,
- unsigned long len) {
- switch (buffer_size) {
- case 1:
- return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len);
- case 2:
- return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len);
- case 4:
- return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len);
- case 8:
- return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len);
- default:
- NOTREACHED() << "Invalid EbmlSerialize length: " << len;
- }
-}
-
-} // namespace chromeos
-
-} // namespace media
diff --git a/src/media/webm/chromeos/webm_encoder.h b/src/media/webm/chromeos/webm_encoder.h
deleted file mode 100644
index 231a0b0..0000000
--- a/src/media/webm/chromeos/webm_encoder.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_CHROMEOS_WEBM_ENCODER_H_
-#define MEDIA_WEBM_CHROMEOS_WEBM_ENCODER_H_
-
-#include <stack>
-#include <stdio.h>
-
-#include "base/file_path.h"
-#include "media/base/media_export.h"
-#include "media/webm/chromeos/ebml_writer.h"
-
-extern "C" {
-#define VPX_CODEC_DISABLE_COMPAT 1
-#include "third_party/libvpx/libvpx.h"
-}
-
-class FilePath;
-class SkBitmap;
-
-namespace media {
-
-namespace chromeos {
-
-// WebM encoder using libvpx. Currently only supports one-pass, constant bitrate
-// encoding of short files consisting of a single video track. Seek info and
-// cues are not supported, so generated .webm file does not strictly adhere to
-// WebM standard (http://www.webmproject.org/code/specs/container/).
-class MEDIA_EXPORT WebmEncoder {
- public:
- // Create new instance for writing to |output_path|. If |realtime| is |true|,
- // uses realtime deadline, otherwise - "good quality" deadline.
- WebmEncoder(const FilePath& output_path, int bitrate, bool realtime);
- ~WebmEncoder();
-
- // Encodes video from a Nx(N*M) sprite, having M frames of size NxN with FPS
- // |fps_n/fps_d|. Must be called on a thread that allows disk IO.
- // Returns |true| iff encoding and writing to file is successful.
- bool EncodeFromSprite(const SkBitmap& sprite, int fps_n, int fps_d);
-
- private:
- // Writes global WebM header and starts a single video track. Returns |false|
- // if there was an error opening file for writing.
- bool WriteWebmHeader();
-
- // Writes VPX packet to output file.
- void WriteWebmBlock(const vpx_codec_cx_pkt_t* packet);
-
- // Finishes video track and closes output file. Returns |false| if there were
- // any error during encoding/writing file.
- bool WriteWebmFooter();
-
- // Starts a new WebM sub-element of given type. Those can be nested.
- void StartSubElement(unsigned long class_id);
-
- // Closes current top-level sub-element.
- void EndSubElement();
-
- // libmkv callbacks.
- void EbmlWrite(const void* buffer, unsigned long len);
- void EbmlSerialize(const void* buffer, int buffer_size, unsigned long len);
-
- template <typename T>
- void EbmlSerializeHelper(const T* buffer, unsigned long len);
-
- // Video dimensions and FPS.
- size_t width_;
- size_t height_;
- vpx_rational_t fps_;
-
- // Number of frames in video.
- size_t frame_count_;
-
- // VPX config in use.
- vpx_codec_enc_cfg_t config_;
-
- // VPX parameters.
- int bitrate_;
- unsigned long deadline_;
-
- // EbmlWriter context.
- EbmlGlobal ebml_writer_;
-
- // Stack with start offsets of currently open sub-elements.
- std::stack<long int> ebml_sub_elements_;
-
- FilePath output_path_;
- FILE* output_;
-
- // True if an error occured while encoding/writing to file.
- bool has_errors_;
-
- DISALLOW_COPY_AND_ASSIGN(WebmEncoder);
-};
-
-} // namespace chromeos
-
-} // namespace media
-
-#endif // MEDIA_WEBM_CHROMEOS_WEBM_ENCODER_H_
diff --git a/src/media/webm/cluster_builder.cc b/src/media/webm/cluster_builder.cc
deleted file mode 100644
index 1c377a6..0000000
--- a/src/media/webm/cluster_builder.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/cluster_builder.h"
-
-#include "base/logging.h"
-#include "media/base/data_buffer.h"
-
-namespace media {
-
-static const uint8 kClusterHeader[] = {
- 0x1F, 0x43, 0xB6, 0x75, // CLUSTER ID
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cluster(size = 0)
- 0xE7, // Timecode ID
- 0x88, // timecode(size=8)
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timecode value
-};
-
-static const uint8 kSimpleBlockHeader[] = {
- 0xA3, // SimpleBlock ID
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SimpleBlock(size = 0)
-};
-
-static const uint8 kBlockGroupHeader[] = {
- 0xA0, // BlockGroup ID
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // BlockGroup(size = 0)
- 0x9B, // BlockDuration ID
- 0x88, // BlockDuration(size = 8)
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // duration
- 0xA1, // Block ID
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block(size = 0)
-};
-
-enum {
- kClusterSizeOffset = 4,
- kClusterTimecodeOffset = 14,
-
- kSimpleBlockSizeOffset = 1,
-
- kBlockGroupSizeOffset = 1,
- kBlockGroupDurationOffset = 11,
- kBlockGroupBlockSizeOffset = 20,
-
- kInitialBufferSize = 32768,
-};
-
-Cluster::Cluster(scoped_array<uint8> data, int size)
- : data_(data.Pass()), size_(size) {}
-Cluster::~Cluster() {}
-
-ClusterBuilder::ClusterBuilder() { Reset(); }
-ClusterBuilder::~ClusterBuilder() {}
-
-void ClusterBuilder::SetClusterTimecode(int64 cluster_timecode) {
- DCHECK_EQ(cluster_timecode_, -1);
-
- cluster_timecode_ = cluster_timecode;
-
- // Write the timecode into the header.
- uint8* buf = buffer_.get() + kClusterTimecodeOffset;
- for (int i = 7; i >= 0; --i) {
- buf[i] = cluster_timecode & 0xff;
- cluster_timecode >>= 8;
- }
-}
-
-void ClusterBuilder::AddSimpleBlock(int track_num, int64 timecode, int flags,
- const uint8* data, int size) {
- int block_size = size + 4;
- int bytes_needed = sizeof(kSimpleBlockHeader) + block_size;
- if (bytes_needed > (buffer_size_ - bytes_used_))
- ExtendBuffer(bytes_needed);
-
- uint8* buf = buffer_.get() + bytes_used_;
- int block_offset = bytes_used_;
- memcpy(buf, kSimpleBlockHeader, sizeof(kSimpleBlockHeader));
- UpdateUInt64(block_offset + kSimpleBlockSizeOffset, block_size);
- buf += sizeof(kSimpleBlockHeader);
-
- WriteBlock(buf, track_num, timecode, flags, data, size);
-
- bytes_used_ += bytes_needed;
-}
-
-void ClusterBuilder::AddBlockGroup(int track_num, int64 timecode, int duration,
- int flags, const uint8* data, int size) {
- int block_size = size + 4;
- int bytes_needed = sizeof(kBlockGroupHeader) + block_size;
- int block_group_size = bytes_needed - 9;
-
- if (bytes_needed > (buffer_size_ - bytes_used_))
- ExtendBuffer(bytes_needed);
-
- uint8* buf = buffer_.get() + bytes_used_;
- int block_group_offset = bytes_used_;
- memcpy(buf, kBlockGroupHeader, sizeof(kBlockGroupHeader));
- UpdateUInt64(block_group_offset + kBlockGroupSizeOffset, block_group_size);
- UpdateUInt64(block_group_offset + kBlockGroupDurationOffset, duration);
- UpdateUInt64(block_group_offset + kBlockGroupBlockSizeOffset, block_size);
- buf += sizeof(kBlockGroupHeader);
-
- WriteBlock(buf, track_num, timecode, flags, data, size);
-
- bytes_used_ += bytes_needed;
-}
-
-void ClusterBuilder::WriteBlock(uint8* buf, int track_num, int64 timecode,
- int flags, const uint8* data, int size) {
- DCHECK_GE(track_num, 0);
- DCHECK_LE(track_num, 126);
- DCHECK_GE(flags, 0);
- DCHECK_LE(flags, 0xff);
- DCHECK(data);
- DCHECK_GT(size, 0);
- DCHECK_NE(cluster_timecode_, -1);
-
- int64 timecode_delta = timecode - cluster_timecode_;
- DCHECK_GE(timecode_delta, -32768);
- DCHECK_LE(timecode_delta, 32767);
-
- buf[0] = 0x80 | (track_num & 0x7F);
- buf[1] = (timecode_delta >> 8) & 0xff;
- buf[2] = timecode_delta & 0xff;
- buf[3] = flags & 0xff;
- memcpy(buf + 4, data, size);
-}
-
-scoped_ptr<Cluster> ClusterBuilder::Finish() {
- DCHECK_NE(cluster_timecode_, -1);
-
- UpdateUInt64(kClusterSizeOffset, bytes_used_ - (kClusterSizeOffset + 8));
-
- scoped_ptr<Cluster> ret(new Cluster(buffer_.Pass(), bytes_used_));
- Reset();
- return ret.Pass();
-}
-
-void ClusterBuilder::Reset() {
- buffer_size_ = kInitialBufferSize;
- buffer_.reset(new uint8[buffer_size_]);
- memcpy(buffer_.get(), kClusterHeader, sizeof(kClusterHeader));
- bytes_used_ = sizeof(kClusterHeader);
- cluster_timecode_ = -1;
-}
-
-void ClusterBuilder::ExtendBuffer(int bytes_needed) {
- int new_buffer_size = 2 * buffer_size_;
-
- while ((new_buffer_size - bytes_used_) < bytes_needed)
- new_buffer_size *= 2;
-
- scoped_array<uint8> new_buffer(new uint8[new_buffer_size]);
-
- memcpy(new_buffer.get(), buffer_.get(), bytes_used_);
- buffer_.reset(new_buffer.release());
- buffer_size_ = new_buffer_size;
-}
-
-void ClusterBuilder::UpdateUInt64(int offset, int64 value) {
- DCHECK_LE(offset + 7, buffer_size_);
- uint8* buf = buffer_.get() + offset;
-
- // Fill the last 7 bytes of size field in big-endian order.
- for (int i = 7; i > 0; i--) {
- buf[i] = value & 0xff;
- value >>= 8;
- }
-}
-
-} // namespace media
diff --git a/src/media/webm/cluster_builder.h b/src/media/webm/cluster_builder.h
deleted file mode 100644
index 2d47599..0000000
--- a/src/media/webm/cluster_builder.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_CLUSTER_BUILDER_H_
-#define MEDIA_WEBM_CLUSTER_BUILDER_H_
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/buffers.h"
-
-namespace media {
-
-class Cluster {
- public:
- Cluster(scoped_array<uint8> data, int size);
- ~Cluster();
-
- const uint8* data() const { return data_.get(); }
- int size() const { return size_; }
-
- private:
- scoped_array<uint8> data_;
- int size_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(Cluster);
-};
-
-class ClusterBuilder {
- public:
- ClusterBuilder();
- ~ClusterBuilder();
-
- void SetClusterTimecode(int64 cluster_timecode);
- void AddSimpleBlock(int track_num, int64 timecode, int flags,
- const uint8* data, int size);
- void AddBlockGroup(int track_num, int64 timecode, int duration, int flags,
- const uint8* data, int size);
-
- scoped_ptr<Cluster> Finish();
-
- private:
- void Reset();
- void ExtendBuffer(int bytes_needed);
- void UpdateUInt64(int offset, int64 value);
- void WriteBlock(uint8* buf, int track_num, int64 timecode, int flags,
- const uint8* data, int size);
-
- scoped_array<uint8> buffer_;
- int buffer_size_;
- int bytes_used_;
- int64 cluster_timecode_;
-
- DISALLOW_COPY_AND_ASSIGN(ClusterBuilder);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_CLUSTER_BUILDER_H_
diff --git a/src/media/webm/webm_audio_client.cc b/src/media/webm/webm_audio_client.cc
deleted file mode 100644
index 76f7fd6..0000000
--- a/src/media/webm/webm_audio_client.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2014 The Chromium 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 "media/webm/webm_audio_client.h"
-
-#include "media/base/audio_decoder_config.h"
-#include "media/base/channel_layout.h"
-#include "media/base/sample_format.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-WebMAudioClient::WebMAudioClient(const LogCB& log_cb)
- : log_cb_(log_cb) {
- Reset();
-}
-
-WebMAudioClient::~WebMAudioClient() {
-}
-
-void WebMAudioClient::Reset() {
- channels_ = -1;
- samples_per_second_ = -1;
- output_samples_per_second_ = -1;
-}
-
-bool WebMAudioClient::InitializeConfig(
- const std::string& codec_id, const std::vector<uint8>& codec_private,
- int64 seek_preroll, int64 codec_delay, bool is_encrypted,
- AudioDecoderConfig* config) {
- DCHECK(config);
-
- AudioCodec audio_codec = kUnknownAudioCodec;
- if (codec_id == "A_VORBIS") {
- audio_codec = kCodecVorbis;
- } else if (codec_id == "A_OPUS") {
- audio_codec = kCodecOpus;
- } else {
- MEDIA_LOG(log_cb_) << "Unsupported audio codec_id " << codec_id;
- return false;
- }
-
- if (samples_per_second_ <= 0)
- return false;
-
- // Set channel layout default if a Channels element was not present.
- if (channels_ == -1)
- channels_ = 1;
-
- ChannelLayout channel_layout = GuessChannelLayout(channels_);
-
- if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
- MEDIA_LOG(log_cb_) << "Unsupported channel count " << channels_;
- return false;
- }
-
- int samples_per_second = samples_per_second_;
- if (output_samples_per_second_ > 0)
- samples_per_second = output_samples_per_second_;
-
- const uint8* extra_data = NULL;
- size_t extra_data_size = 0;
- if (codec_private.size() > 0) {
- extra_data = &codec_private[0];
- extra_data_size = codec_private.size();
- }
-
- // Convert |codec_delay| from nanoseconds into frames.
- int codec_delay_in_frames = 0;
- if (codec_delay != -1) {
- codec_delay_in_frames =
- 0.5 +
- samples_per_second * (static_cast<double>(codec_delay) /
- base::Time::kNanosecondsPerSecond);
- }
-
- config->Initialize(
- audio_codec,
- (audio_codec == kCodecOpus) ? kSampleFormatS16 : kSampleFormatPlanarF32,
- channel_layout,
- samples_per_second,
- extra_data,
- extra_data_size,
- is_encrypted,
- true);
- return config->IsValidConfig();
-}
-
-bool WebMAudioClient::OnUInt(int id, int64 val) {
- if (id == kWebMIdChannels) {
- if (channels_ != -1) {
- MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
- << " specified. (" << channels_ << " and " << val
- << ")";
- return false;
- }
-
- channels_ = val;
- }
- return true;
-}
-
-bool WebMAudioClient::OnFloat(int id, double val) {
- double* dst = NULL;
-
- switch (id) {
- case kWebMIdSamplingFrequency:
- dst = &samples_per_second_;
- break;
- case kWebMIdOutputSamplingFrequency:
- dst = &output_samples_per_second_;
- break;
- default:
- return true;
- }
-
- if (val <= 0)
- return false;
-
- if (*dst != -1) {
- MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
- << " specified (" << *dst << " and " << val << ")";
- return false;
- }
-
- *dst = val;
- return true;
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_audio_client.h b/src/media/webm/webm_audio_client.h
deleted file mode 100644
index 572d7c9..0000000
--- a/src/media/webm/webm_audio_client.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_
-#define MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_
-
-#include <string>
-#include <vector>
-
-#include "media/base/media_log.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-class AudioDecoderConfig;
-
-// Helper class used to parse an Audio element inside a TrackEntry element.
-class WebMAudioClient : public WebMParserClient {
- public:
- explicit WebMAudioClient(const LogCB& log_cb);
- virtual ~WebMAudioClient();
-
- // Reset this object's state so it can process a new audio track element.
- void Reset();
-
- // Initialize |config| with the data in |codec_id|, |codec_private|,
- // |is_encrypted| and the fields parsed from the last audio track element this
- // object was used to parse.
- // Returns true if |config| was successfully initialized.
- // Returns false if there was unexpected values in the provided parameters or
- // audio track element fields.
- bool InitializeConfig(const std::string& codec_id,
- const std::vector<uint8>& codec_private,
- const int64 seek_preroll,
- const int64 codec_delay,
- bool is_encrypted,
- AudioDecoderConfig* config);
-
- private:
- // WebMParserClient implementation.
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnFloat(int id, double val) OVERRIDE;
-
- LogCB log_cb_;
- int channels_;
- double samples_per_second_;
- double output_samples_per_second_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMAudioClient);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_AUDIO_CLIENT_H_
diff --git a/src/media/webm/webm_cluster_parser.cc b/src/media/webm/webm_cluster_parser.cc
deleted file mode 100644
index d21ca24..0000000
--- a/src/media/webm/webm_cluster_parser.cc
+++ /dev/null
@@ -1,431 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/webm_cluster_parser.h"
-
-#include <vector>
-
-#include "base/logging.h"
-#include "media/base/data_buffer.h"
-#include "media/base/decrypt_config.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-// Generates a 16 byte CTR counter block. The CTR counter block format is a
-// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
-// |iv_size| is the size of |iv| in btyes. Returns a string of
-// kDecryptionKeySize bytes.
-static std::string GenerateCounterBlock(const uint8* iv, int iv_size) {
- std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
- counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
- return counter_block;
-}
-
-namespace {
-
-uint32 ReadInteger(const uint8* buf, int size) {
- // Read in the big-endian integer.
- uint32 value = 0;
- for (int i = 0; i < size; ++i)
- value = (value << 8) | buf[i];
- return value;
-}
-
-bool ExtractSubsamples(const uint8* buf,
- size_t frame_data_size,
- size_t num_partitions,
- std::vector<SubsampleEntry>* subsample_entries) {
- subsample_entries->clear();
- uint32 clear_bytes = 0;
- // Partition is the wall between alternating sections. Partition offsets are
- // relative to the start of the actual frame data.
- // Size of clear/cipher sections can be calculated from the difference between
- // adjacent partition offsets.
- // Here is an example with 4 partitions (5 sections):
- // "clear |1 cipher |2 clear |3 cipher |4 clear"
- // With the first and the last implicit partition included:
- // "|0 clear |1 cipher |2 clear |3 cipher |4 clear |5"
- // where partition_offset_0 = 0, partition_offset_5 = frame_data_size
- // There are three subsamples in the above example:
- // Subsample0.clear_bytes = partition_offset_1 - partition_offset_0
- // Subsample0.cypher_bytes = partition_offset_2 - partition_offset_1
- // ...
- // Subsample2.clear_bytes = partition_offset_5 - partition_offset_4
- // Subsample2.cypher_bytes = 0
- uint32 partition_offset = 0;
- for (size_t i = 0, offset = 0; i <= num_partitions; ++i) {
- const uint32 prev_partition_offset = partition_offset;
- partition_offset =
- (i == num_partitions)
- ? frame_data_size
- : ReadInteger(buf + offset, kWebMEncryptedFramePartitionOffsetSize);
- offset += kWebMEncryptedFramePartitionOffsetSize;
- if (partition_offset < prev_partition_offset) {
- DVLOG(1) << "Partition should not be decreasing " << prev_partition_offset
- << " " << partition_offset;
- return false;
- }
-
- uint32 cypher_bytes = 0;
- bool new_subsample_entry = false;
- // Alternating clear and cipher sections.
- if ((i % 2) == 0) {
- clear_bytes = partition_offset - prev_partition_offset;
- // Generate a new subsample when finishing reading partition offsets.
- new_subsample_entry = i == num_partitions;
- } else {
- cypher_bytes = partition_offset - prev_partition_offset;
- // Generate a new subsample after seeing a cipher section.
- new_subsample_entry = true;
- }
-
- if (new_subsample_entry) {
- if (clear_bytes == 0 && cypher_bytes == 0) {
- DVLOG(1) << "Not expecting >2 partitions with the same offsets.";
- return false;
- }
- SubsampleEntry entry;
- entry.clear_bytes = clear_bytes;
- entry.cypher_bytes = cypher_bytes;
- subsample_entries->push_back(entry);
- }
- }
- return true;
-}
-
-} // namespace
-
-WebMClusterParser::WebMClusterParser(
- int64 timecode_scale, int audio_track_num, int video_track_num,
- const std::string& audio_encryption_key_id,
- const std::string& video_encryption_key_id,
- const LogCB& log_cb)
- : timecode_multiplier_(timecode_scale / 1000.0),
- audio_encryption_key_id_(audio_encryption_key_id),
- video_encryption_key_id_(video_encryption_key_id),
- parser_(kWebMIdCluster, this),
- last_block_timecode_(-1),
- block_data_size_(-1),
- block_duration_(-1),
- cluster_timecode_(-1),
- cluster_start_time_(kNoTimestamp()),
- cluster_ended_(false),
- audio_(audio_track_num),
- video_(video_track_num),
- log_cb_(log_cb) {
-}
-
-WebMClusterParser::~WebMClusterParser() {}
-
-void WebMClusterParser::Reset() {
- last_block_timecode_ = -1;
- cluster_timecode_ = -1;
- cluster_start_time_ = kNoTimestamp();
- cluster_ended_ = false;
- parser_.Reset();
- audio_.Reset();
- video_.Reset();
-}
-
-int WebMClusterParser::Parse(const uint8* buf, int size) {
- audio_.Reset();
- video_.Reset();
-
- int result = parser_.Parse(buf, size);
-
- if (result < 0) {
- cluster_ended_ = false;
- return result;
- }
-
- cluster_ended_ = parser_.IsParsingComplete();
- if (cluster_ended_) {
- // If there were no buffers in this cluster, set the cluster start time to
- // be the |cluster_timecode_|.
- if (cluster_start_time_ == kNoTimestamp()) {
- DCHECK_GT(cluster_timecode_, -1);
- cluster_start_time_ = base::TimeDelta::FromMicroseconds(
- cluster_timecode_ * timecode_multiplier_);
- }
-
- // Reset the parser if we're done parsing so that
- // it is ready to accept another cluster on the next
- // call.
- parser_.Reset();
-
- last_block_timecode_ = -1;
- cluster_timecode_ = -1;
- }
-
- return result;
-}
-
-WebMParserClient* WebMClusterParser::OnListStart(int id) {
- if (id == kWebMIdCluster) {
- cluster_timecode_ = -1;
- cluster_start_time_ = kNoTimestamp();
- } else if (id == kWebMIdBlockGroup) {
- block_data_.reset();
- block_data_size_ = -1;
- block_duration_ = -1;
- }
-
- return this;
-}
-
-bool WebMClusterParser::OnListEnd(int id) {
- if (id != kWebMIdBlockGroup)
- return true;
-
- // Make sure the BlockGroup actually had a Block.
- if (block_data_size_ == -1) {
- MEDIA_LOG(log_cb_) << "Block missing from BlockGroup.";
- return false;
- }
-
- bool result = ParseBlock(block_data_.get(), block_data_size_,
- block_duration_);
- block_data_.reset();
- block_data_size_ = -1;
- block_duration_ = -1;
- return result;
-}
-
-bool WebMClusterParser::OnUInt(int id, int64 val) {
- if (id == kWebMIdTimecode) {
- if (cluster_timecode_ != -1)
- return false;
-
- cluster_timecode_ = val;
- } else if (id == kWebMIdBlockDuration) {
- if (block_duration_ != -1)
- return false;
- block_duration_ = val;
- }
-
- return true;
-}
-
-bool WebMClusterParser::ParseBlock(const uint8* buf, int size, int duration) {
- if (size < 4)
- return false;
-
- // Return an error if the trackNum > 127. We just aren't
- // going to support large track numbers right now.
- if (!(buf[0] & 0x80)) {
- MEDIA_LOG(log_cb_) << "TrackNumber over 127 not supported";
- return false;
- }
-
- int track_num = buf[0] & 0x7f;
- int timecode = buf[1] << 8 | buf[2];
- int flags = buf[3] & 0xff;
- int lacing = (flags >> 1) & 0x3;
-
- if (lacing) {
- MEDIA_LOG(log_cb_) << "Lacing " << lacing << " is not supported yet.";
- return false;
- }
-
- // Sign extend negative timecode offsets.
- if (timecode & 0x8000)
- timecode |= (-1 << 16);
-
- const uint8* frame_data = buf + 4;
- int frame_size = size - (frame_data - buf);
- return OnBlock(track_num, timecode, duration, flags, frame_data, frame_size);
-}
-
-bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) {
- if (id == kWebMIdSimpleBlock)
- return ParseBlock(data, size, -1);
-
- if (id != kWebMIdBlock)
- return true;
-
- if (block_data_.get()) {
- MEDIA_LOG(log_cb_) << "More than 1 Block in a BlockGroup is not supported.";
- return false;
- }
-
- block_data_.reset(new uint8[size]);
- memcpy(block_data_.get(), data, size);
- block_data_size_ = size;
- return true;
-}
-
-bool WebMClusterParser::OnBlock(int track_num, int timecode,
- int block_duration,
- int flags,
- const uint8* data, int size) {
- DCHECK_GE(size, 0);
- if (cluster_timecode_ == -1) {
- MEDIA_LOG(log_cb_) << "Got a block before cluster timecode.";
- return false;
- }
-
- if (timecode < 0) {
- MEDIA_LOG(log_cb_) << "Got a block with negative timecode offset "
- << timecode;
- return false;
- }
-
- if (last_block_timecode_ != -1 && timecode < last_block_timecode_) {
- MEDIA_LOG(log_cb_)
- << "Got a block with a timecode before the previous block.";
- return false;
- }
-
- Track* track = NULL;
- std::string encryption_key_id;
- if (track_num == audio_.track_num()) {
- track = &audio_;
- encryption_key_id = audio_encryption_key_id_;
- } else if (track_num == video_.track_num()) {
- track = &video_;
- encryption_key_id = video_encryption_key_id_;
- } else {
- MEDIA_LOG(log_cb_) << "Unexpected track number " << track_num;
- return false;
- }
-
- last_block_timecode_ = timecode;
-
- base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
- (cluster_timecode_ + timecode) * timecode_multiplier_);
-
- // The first bit of the flags is set when the block contains only keyframes.
- // http://www.matroska.org/technical/specs/index.html
- bool is_keyframe = (flags & 0x80) != 0;
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- scoped_refptr<StreamParserBuffer> buffer;
- // Create buffer and copy data over for non-encrypted data. Encrypted data
- // will be handled in the following if block.
- if (encryption_key_id.empty())
- buffer = StreamParserBuffer::CopyFrom(data, size, is_keyframe);
-#else // defined(__LB_SHELL__) || defined(COBALT)
- scoped_refptr<StreamParserBuffer> buffer =
- StreamParserBuffer::CopyFrom(data, size, is_keyframe);
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // Every encrypted Block has a signal byte and IV prepended to it. Current
- // encrypted WebM request for comments specification is here
- // http://www.webmproject.org/docs/webm-encryption/
- if (!encryption_key_id.empty()) {
- DCHECK_EQ(kWebMSignalByteSize, 1);
- if (size < kWebMSignalByteSize) {
- MEDIA_LOG(log_cb_)
- << "Got a block from an encrypted stream with no data.";
- return false;
- }
- const uint8 signal_byte = data[0];
- int data_offset = sizeof(signal_byte);
-
- // Setting the DecryptConfig object of the buffer while leaving the
- // initialization vector empty will tell the decryptor that the frame is
- // unencrypted.
- std::string counter_block;
- std::vector<SubsampleEntry> subsample_entries;
-
- if (signal_byte & kWebMFlagEncryptedFrame) {
- if (size < kWebMSignalByteSize + kWebMIvSize) {
- MEDIA_LOG(log_cb_) << "Got an encrypted block with not enough data "
- << size;
- return false;
- }
- counter_block = GenerateCounterBlock(data + data_offset, kWebMIvSize);
- data_offset += kWebMIvSize;
-
- if (signal_byte & kWebMFlagEncryptedFramePartitioned) {
- if (size < data_offset + kWebMEncryptedFrameNumPartitionsSize) {
- DVLOG(1) << "Got a partitioned encrypted block with not enough data "
- << size;
- return false;
- }
-
- const size_t num_partitions = data[data_offset];
- if (num_partitions == 0) {
- DVLOG(1) << "Got a partitioned encrypted block with 0 partitions.";
- return false;
- }
- data_offset += kWebMEncryptedFrameNumPartitionsSize;
- const uint8* partition_data_start = data + data_offset;
- data_offset += kWebMEncryptedFramePartitionOffsetSize * num_partitions;
- if (size <= data_offset) {
- DVLOG(1) << "Got a partitioned encrypted block with "
- << num_partitions << " partitions but not enough data "
- << size;
- return false;
- }
- const size_t frame_data_size = size - data_offset;
- if (!ExtractSubsamples(partition_data_start, frame_data_size,
- num_partitions, &subsample_entries)) {
- return false;
- }
- }
- }
-
-#if defined(__LB_SHELL__) || defined(COBALT)
- // Don't copy prepended meta data as it is not used by the decryptor and
- // decoder.
- buffer = StreamParserBuffer::CopyFrom(data + data_offset,
- size - data_offset, is_keyframe);
-#endif // defined(__LB_SHELL__) || defined(COBALT)
-
- // TODO(fgalligan): Revisit if DecryptConfig needs to be set on unencrypted
- // frames after the CDM API is finalized.
- // Unencrypted frames of potentially encrypted streams currently set
- // DecryptConfig.
- if (buffer) {
- buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(
- new DecryptConfig(encryption_key_id, counter_block,
- #if !defined(__LB_SHELL__) && !defined(COBALT)
- data_offset,
- #endif // !defined(__LB_SHELL__) && !defined(COBALT)
- subsample_entries)));
- }
- }
-
- if (!buffer) {
- DLOG(WARNING) << "Failed to create StreamParserBuffer";
- return false;
- }
-
- buffer->SetTimestamp(timestamp);
- if (cluster_start_time_ == kNoTimestamp())
- cluster_start_time_ = timestamp;
-
- if (block_duration >= 0) {
- buffer->SetDuration(base::TimeDelta::FromMicroseconds(
- block_duration * timecode_multiplier_));
- }
-
- return track->AddBuffer(buffer);
-}
-
-WebMClusterParser::Track::Track(int track_num)
- : track_num_(track_num) {
-}
-
-WebMClusterParser::Track::~Track() {}
-
-bool WebMClusterParser::Track::AddBuffer(
- const scoped_refptr<StreamParserBuffer>& buffer) {
- DVLOG(2) << "AddBuffer() : " << track_num_
- << " ts " << buffer->GetTimestamp().InSecondsF()
- << " dur " << buffer->GetDuration().InSecondsF()
- << " kf " << buffer->IsKeyframe()
- << " size " << buffer->GetDataSize();
-
- buffers_.push_back(buffer);
- return true;
-}
-
-void WebMClusterParser::Track::Reset() {
- buffers_.clear();
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_cluster_parser.h b/src/media/webm/webm_cluster_parser.h
deleted file mode 100644
index f31a0cb..0000000
--- a/src/media/webm/webm_cluster_parser.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_
-#define MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_
-
-#include <deque>
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-
-class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
- public:
- typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
-
- WebMClusterParser(int64 timecode_scale,
- int audio_track_num,
- int video_track_num,
- const std::string& audio_encryption_key_id,
- const std::string& video_encryption_key_id,
- const LogCB& log_cb);
- virtual ~WebMClusterParser();
-
- // Resets the parser state so it can accept a new cluster.
- void Reset();
-
- // Parses a WebM cluster element in |buf|.
- //
- // Returns -1 if the parse fails.
- // Returns 0 if more data is needed.
- // Returns the number of bytes parsed on success.
- int Parse(const uint8* buf, int size);
-
- base::TimeDelta cluster_start_time() const { return cluster_start_time_; }
- const BufferQueue& audio_buffers() const { return audio_.buffers(); }
- const BufferQueue& video_buffers() const { return video_.buffers(); }
-
- // Returns true if the last Parse() call stopped at the end of a cluster.
- bool cluster_ended() const { return cluster_ended_; }
-
- private:
- // Helper class that manages per-track state.
- class Track {
- public:
- explicit Track(int track_num);
- ~Track();
-
- int track_num() const { return track_num_; }
- const BufferQueue& buffers() const { return buffers_; }
-
- bool AddBuffer(const scoped_refptr<StreamParserBuffer>& buffer);
-
- // Clears all buffer state.
- void Reset();
-
- private:
- int track_num_;
- BufferQueue buffers_;
- };
-
- // WebMParserClient methods.
- virtual WebMParserClient* OnListStart(int id) OVERRIDE;
- virtual bool OnListEnd(int id) OVERRIDE;
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
-
- bool ParseBlock(const uint8* buf, int size, int duration);
- bool OnBlock(int track_num, int timecode, int duration, int flags,
- const uint8* data, int size);
-
- double timecode_multiplier_; // Multiplier used to convert timecodes into
- // microseconds.
- std::string audio_encryption_key_id_;
- std::string video_encryption_key_id_;
-
- WebMListParser parser_;
-
- int64 last_block_timecode_;
- scoped_array<uint8> block_data_;
- int block_data_size_;
- int64 block_duration_;
-
- int64 cluster_timecode_;
- base::TimeDelta cluster_start_time_;
- bool cluster_ended_;
-
- Track audio_;
- Track video_;
-
- LogCB log_cb_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_
diff --git a/src/media/webm/webm_cluster_parser_unittest.cc b/src/media/webm/webm_cluster_parser_unittest.cc
deleted file mode 100644
index f89e68f..0000000
--- a/src/media/webm/webm_cluster_parser_unittest.cc
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 <algorithm>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "media/webm/cluster_builder.h"
-#include "media/webm/webm_cluster_parser.h"
-#include "media/webm/webm_constants.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::InSequence;
-using ::testing::Return;
-using ::testing::_;
-
-namespace media {
-
-enum {
- kTimecodeScale = 1000000, // Timecode scale for millisecond timestamps.
- kAudioTrackNum = 1,
- kVideoTrackNum = 2,
-};
-
-struct BlockInfo {
- int track_num;
- int timestamp;
- int duration;
- bool use_simple_block;
-};
-
-const BlockInfo kDefaultBlockInfo[] = {
- { kAudioTrackNum, 0, 23, true },
- { kAudioTrackNum, 23, 23, true },
- { kVideoTrackNum, 33, 34, true },
- { kAudioTrackNum, 46, 23, true },
- { kVideoTrackNum, 67, 33, false },
- { kAudioTrackNum, 69, 23, false },
- { kVideoTrackNum, 100, 33, false },
-};
-
-static scoped_ptr<Cluster> CreateCluster(int timecode,
- const BlockInfo* block_info,
- int block_count) {
- ClusterBuilder cb;
- cb.SetClusterTimecode(0);
-
- for (int i = 0; i < block_count; i++) {
- uint8 data[] = { 0x00 };
- if (block_info[i].use_simple_block) {
- cb.AddSimpleBlock(block_info[i].track_num,
- block_info[i].timestamp,
- 0, data, sizeof(data));
- continue;
- }
-
- CHECK_GE(block_info[i].duration, 0);
- cb.AddBlockGroup(block_info[i].track_num,
- block_info[i].timestamp,
- block_info[i].duration,
- 0, data, sizeof(data));
- }
-
- return cb.Finish();
-}
-
-static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
- const WebMClusterParser::BufferQueue& video_buffers,
- const BlockInfo* block_info,
- int block_count) {
- size_t audio_offset = 0;
- size_t video_offset = 0;
- for (int i = 0; i < block_count; i++) {
- const WebMClusterParser::BufferQueue* buffers = NULL;
- size_t* offset;
-
- if (block_info[i].track_num == kAudioTrackNum) {
- buffers = &audio_buffers;
- offset = &audio_offset;
- } else if (block_info[i].track_num == kVideoTrackNum) {
- buffers = &video_buffers;
- offset = &video_offset;
- } else {
- LOG(ERROR) << "Unexpected track number " << block_info[i].track_num;
- return false;
- }
-
- if (*offset >= buffers->size())
- return false;
-
- scoped_refptr<StreamParserBuffer> buffer = (*buffers)[(*offset)++];
-
-
- EXPECT_EQ(buffer->GetTimestamp().InMilliseconds(), block_info[i].timestamp);
-
- if (!block_info[i].use_simple_block)
- EXPECT_NE(buffer->GetDuration(), kNoTimestamp());
-
- if (buffer->GetDuration() != kNoTimestamp())
- EXPECT_EQ(buffer->GetDuration().InMilliseconds(), block_info[i].duration);
- }
-
- return true;
-}
-
-static bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser,
- const BlockInfo* block_info,
- int block_count) {
- return VerifyBuffers(parser->audio_buffers(),
- parser->video_buffers(),
- block_info,
- block_count);
-}
-
-static void AppendToEnd(const WebMClusterParser::BufferQueue& src,
- WebMClusterParser::BufferQueue* dest) {
- for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin();
- itr != src.end(); ++itr) {
- dest->push_back(*itr);
- }
-}
-
-class WebMClusterParserTest : public testing::Test {
- public:
- WebMClusterParserTest()
- : parser_(new WebMClusterParser(
- kTimecodeScale, kAudioTrackNum, kVideoTrackNum, "", "", LogCB())) {
- }
-
- protected:
- scoped_ptr<WebMClusterParser> parser_;
-};
-
-TEST_F(WebMClusterParserTest, TestReset) {
- InSequence s;
-
- int block_count = arraysize(kDefaultBlockInfo);
- scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
-
- // Send slightly less than the full cluster so all but the last block is
- // parsed.
- int result = parser_->Parse(cluster->data(), cluster->size() - 1);
- EXPECT_GT(result, 0);
- EXPECT_LT(result, cluster->size());
-
- ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count - 1));
- parser_->Reset();
-
- // Now parse a whole cluster to verify that all the blocks will get parsed.
- result = parser_->Parse(cluster->data(), cluster->size());
- EXPECT_EQ(result, cluster->size());
- ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
-}
-
-TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) {
- int block_count = arraysize(kDefaultBlockInfo);
- scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
-
- int result = parser_->Parse(cluster->data(), cluster->size());
- EXPECT_EQ(cluster->size(), result);
- ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
-}
-
-TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) {
- int block_count = arraysize(kDefaultBlockInfo);
- scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
-
- WebMClusterParser::BufferQueue audio_buffers;
- WebMClusterParser::BufferQueue video_buffers;
-
- const uint8* data = cluster->data();
- int size = cluster->size();
- int default_parse_size = 3;
- int parse_size = std::min(default_parse_size, size);
-
- while (size > 0) {
- int result = parser_->Parse(data, parse_size);
- ASSERT_GE(result, 0);
- ASSERT_LE(result, parse_size);
-
- if (result == 0) {
- // The parser needs more data so increase the parse_size a little.
- parse_size += default_parse_size;
- parse_size = std::min(parse_size, size);
- continue;
- }
-
- AppendToEnd(parser_->audio_buffers(), &audio_buffers);
- AppendToEnd(parser_->video_buffers(), &video_buffers);
-
- parse_size = default_parse_size;
-
- data += result;
- size -= result;
- }
- ASSERT_TRUE(VerifyBuffers(audio_buffers, video_buffers, kDefaultBlockInfo,
- block_count));
-}
-
-// Verify that both BlockGroups with the BlockDuration before the Block
-// and BlockGroups with the BlockDuration after the Block are supported
-// correctly.
-// Note: Raw bytes are use here because ClusterBuilder only generates
-// one of these scenarios.
-TEST_F(WebMClusterParserTest, ParseBlockGroup) {
- const BlockInfo kBlockInfo[] = {
- { kAudioTrackNum, 0, 23, false },
- { kVideoTrackNum, 33, 34, false },
- };
- int block_count = arraysize(kBlockInfo);
-
- const uint8 kClusterData[] = {
- 0x1F, 0x43, 0xB6, 0x75, 0x9B, // Cluster(size=27)
- 0xE7, 0x81, 0x00, // Timecode(size=1, value=0)
- // BlockGroup with BlockDuration before Block.
- 0xA0, 0x8A, // BlockGroup(size=10)
- 0x9B, 0x81, 0x17, // BlockDuration(size=1, value=23)
- 0xA1, 0x85, 0x81, 0x00, 0x00, 0x00, 0xaa, // Block(size=5, track=1, ts=0)
- // BlockGroup with BlockDuration after Block.
- 0xA0, 0x8A, // BlockGroup(size=10)
- 0xA1, 0x85, 0x82, 0x00, 0x21, 0x00, 0x55, // Block(size=5, track=2, ts=33)
- 0x9B, 0x81, 0x22, // BlockDuration(size=1, value=34)
- };
- const int kClusterSize = sizeof(kClusterData);
-
- int result = parser_->Parse(kClusterData, kClusterSize);
- EXPECT_EQ(result, kClusterSize);
- ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
-}
-
-TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) {
- const BlockInfo kBlockInfo[] = {
- { kAudioTrackNum, 0, 23, true },
- { kAudioTrackNum, 23, 23, false },
- { kVideoTrackNum, 33, 34, true },
- { kAudioTrackNum, 46, 23, false },
- { kVideoTrackNum, 67, 33, false },
- };
- int block_count = arraysize(kBlockInfo);
- scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
-
- int result = parser_->Parse(cluster->data(), cluster->size());
- EXPECT_EQ(cluster->size(), result);
- ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_colour_parser.cc b/src/media/webm/webm_colour_parser.cc
deleted file mode 100644
index 0b12c52..0000000
--- a/src/media/webm/webm_colour_parser.cc
+++ /dev/null
@@ -1,550 +0,0 @@
-// Copyright 2016 The Chromium 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 "media/webm/webm_colour_parser.h"
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-// The definitions below are copied from the current libwebm top-of-tree.
-// Chromium's third_party/libwebm doesn't have these yet. We can probably just
-// include libwebm header directly in the future.
-// ---- Begin copy/paste from libwebm/webm_parser/include/webm/dom_types.h ----
-
-/**
- A parsed \WebMID{MatrixCoefficients} element.
-
- Matroska/WebM adopted these values from Table 4 of ISO/IEC 23001-8:2013/DCOR1.
- See that document for further details.
- */
-enum MatrixCoefficients {
- /**
- The identity matrix.
-
- Typically used for GBR (often referred to as RGB); however, may also be used
- for YZX (often referred to as XYZ).
- */
- kMatrixCoefficientsRgb = 0,
-
- /**
- Rec. ITU-R BT.709-5.
- */
- kMatrixCoefficientsBt709 = 1,
-
- /**
- Image characteristics are unknown or are determined by the application.
- */
- kMatrixCoefficientsUnspecified = 2,
-
- /**
- United States Federal Communications Commission Title 47 Code of Federal
- Regulations (2003) 73.682 (a) (20).
- */
- kMatrixCoefficientsFcc = 4,
-
- /**
- Rec. ITU-R BT.470‑6 System B, G (historical).
- */
- kMatrixCoefficientsBt470Bg = 5,
-
- /**
- Society of Motion Picture and Television Engineers 170M (2004).
- */
- kMatrixCoefficientsSmpte170M = 6,
-
- /**
- Society of Motion Picture and Television Engineers 240M (1999).
- */
- kMatrixCoefficientsSmpte240M = 7,
-
- /**
- YCgCo.
- */
- kMatrixCoefficientsYCgCo = 8,
-
- /**
- Rec. ITU-R BT.2020 (non-constant luminance).
- */
- kMatrixCoefficientsBt2020NonconstantLuminance = 9,
-
- /**
- Rec. ITU-R BT.2020 (constant luminance).
- */
- kMatrixCoefficientsBt2020ConstantLuminance = 10,
-};
-
-/**
- A parsed \WebMID{Range} element.
- */
-enum Range {
- /**
- Unspecified.
- */
- kRangeUnspecified = 0,
-
- /**
- Broadcast range.
- */
- kRangeBroadcast = 1,
-
- /**
- Full range (no clipping).
- */
- kRangeFull = 2,
-
- /**
- Defined by MatrixCoefficients/TransferCharacteristics.
- */
- kRangeDerived = 3,
-};
-
-/**
- A parsed \WebMID{TransferCharacteristics} element.
-
- Matroska/WebM adopted these values from Table 3 of ISO/IEC 23001-8:2013/DCOR1.
- See that document for further details.
- */
-enum TransferCharacteristics {
- /**
- Rec. ITU-R BT.709-6.
- */
- kTransferCharacteristicsBt709 = 1,
-
- /**
- Image characteristics are unknown or are determined by the application.
- */
- kTransferCharacteristicsUnspecified = 2,
-
- /**
- Rec. ITU‑R BT.470‑6 System M (historical) with assumed display gamma 2.2.
- */
- kTransferCharacteristicsGamma22curve = 4,
-
- /**
- Rec. ITU‑R BT.470-6 System B, G (historical) with assumed display gamma 2.8.
- */
- kTransferCharacteristicsGamma28curve = 5,
-
- /**
- Society of Motion Picture and Television Engineers 170M (2004).
- */
- kTransferCharacteristicsSmpte170M = 6,
-
- /**
- Society of Motion Picture and Television Engineers 240M (1999).
- */
- kTransferCharacteristicsSmpte240M = 7,
-
- /**
- Linear transfer characteristics.
- */
- kTransferCharacteristicsLinear = 8,
-
- /**
- Logarithmic transfer characteristic (100:1 range).
- */
- kTransferCharacteristicsLog = 9,
-
- /**
- Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range).
- */
- kTransferCharacteristicsLogSqrt = 10,
-
- /**
- IEC 61966-2-4.
- */
- kTransferCharacteristicsIec6196624 = 11,
-
- /**
- Rec. ITU‑R BT.1361-0 extended colour gamut system (historical).
- */
- kTransferCharacteristicsBt1361ExtendedColourGamut = 12,
-
- /**
- IEC 61966-2-1 sRGB or sYCC.
- */
- kTransferCharacteristicsIec6196621 = 13,
-
- /**
- Rec. ITU-R BT.2020-2 (10-bit system).
- */
- kTransferCharacteristics10BitBt2020 = 14,
-
- /**
- Rec. ITU-R BT.2020-2 (12-bit system).
- */
- kTransferCharacteristics12BitBt2020 = 15,
-
- /**
- Society of Motion Picture and Television Engineers ST 2084.
- */
- kTransferCharacteristicsSmpteSt2084 = 16,
-
- /**
- Society of Motion Picture and Television Engineers ST 428-1.
- */
- kTransferCharacteristicsSmpteSt4281 = 17,
-
- /**
- Association of Radio Industries and Businesses (ARIB) STD-B67.
- */
- kTransferCharacteristicsAribStdB67Hlg = 18,
-};
-
-/**
- A parsed \WebMID{Primaries} element.
-
- Matroska/WebM adopted these values from Table 2 of ISO/IEC 23001-8:2013/DCOR1.
- See that document for further details.
- */
-enum Primaries {
- /**
- Rec. ITU‑R BT.709-6.
- */
- kPrimariesBt709 = 1,
-
- /**
- Image characteristics are unknown or are determined by the application.
- */
- kPrimariesUnspecified = 2,
-
- /**
- Rec. ITU‑R BT.470‑6 System M (historical).
- */
- kPrimariesBt470M = 4,
-
- /**
- Rec. ITU‑R BT.470‑6 System B, G (historical).
- */
- kPrimariesBt470Bg = 5,
-
- /**
- Society of Motion Picture and Television Engineers 170M (2004).
- */
- kPrimariesSmpte170M = 6,
-
- /**
- Society of Motion Picture and Television Engineers 240M (1999).
- */
- kPrimariesSmpte240M = 7,
-
- /**
- Generic film.
- */
- kPrimariesFilm = 8,
-
- /**
- Rec. ITU-R BT.2020-2.
- */
- kPrimariesBt2020 = 9,
-
- /**
- Society of Motion Picture and Television Engineers ST 428-1.
- */
- kPrimariesSmpteSt4281 = 10,
-
- /**
- JEDEC P22 phosphors/EBU Tech. 3213-E (1975).
- */
- kPrimariesJedecP22Phosphors = 22,
-};
-
-// ---- End copy/paste from libwebm/webm_parser/include/webm/dom_types.h ----
-
-// Ensure that libwebm enum values match enums in gfx::ColorSpace.
-#define STATIC_ASSERT_ENUM(a, b) \
- COMPILE_ASSERT(static_cast<int>(a) == static_cast<int>(gfx::ColorSpace::b), \
- mismatching_enums)
-
-STATIC_ASSERT_ENUM(kMatrixCoefficientsRgb, kMatrixIdRgb);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsBt709, kMatrixIdBt709);
-
-STATIC_ASSERT_ENUM(kMatrixCoefficientsUnspecified, kMatrixIdUnspecified);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsFcc, kMatrixIdFcc);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsBt470Bg, kMatrixIdBt470Bg);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsSmpte170M, kMatrixIdSmpte170M);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsSmpte240M, kMatrixIdSmpte240M);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsYCgCo, kMatrixIdYCgCo);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsBt2020NonconstantLuminance,
- kMatrixIdBt2020NonconstantLuminance);
-STATIC_ASSERT_ENUM(kMatrixCoefficientsBt2020ConstantLuminance,
- kMatrixIdBt2020ConstantLuminance);
-
-gfx::ColorSpace::MatrixID FromWebMMatrixCoefficients(MatrixCoefficients c) {
- return static_cast<gfx::ColorSpace::MatrixID>(c);
-}
-
-STATIC_ASSERT_ENUM(kRangeUnspecified, kRangeIdUnspecified);
-STATIC_ASSERT_ENUM(kRangeBroadcast, kRangeIdLimited);
-STATIC_ASSERT_ENUM(kRangeFull, kRangeIdFull);
-STATIC_ASSERT_ENUM(kRangeDerived, kRangeIdDerived);
-
-gfx::ColorSpace::RangeID FromWebMRange(Range range) {
- return static_cast<gfx::ColorSpace::RangeID>(range);
-}
-
-STATIC_ASSERT_ENUM(kTransferCharacteristicsBt709, kTransferIdBt709);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsUnspecified,
- kTransferIdUnspecified);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsGamma22curve, kTransferIdGamma22);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsGamma28curve, kTransferIdGamma28);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsSmpte170M, kTransferIdSmpte170M);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsSmpte240M, kTransferIdSmpte240M);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsLinear, kTransferIdLinear);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsLog, kTransferIdLog);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsLogSqrt, kTransferIdLogSqrt);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsIec6196624,
- kTransferIdIec6196624);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsBt1361ExtendedColourGamut,
- kTransferIdBt1361Ecg);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsIec6196621,
- kTransferIdIec6196621);
-STATIC_ASSERT_ENUM(kTransferCharacteristics10BitBt2020,
- kTransferId10BitBt2020);
-STATIC_ASSERT_ENUM(kTransferCharacteristics12BitBt2020,
- kTransferId12BitBt2020);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsSmpteSt2084,
- kTransferIdSmpteSt2084);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsSmpteSt4281,
- kTransferIdSmpteSt4281);
-STATIC_ASSERT_ENUM(kTransferCharacteristicsAribStdB67Hlg,
- kTransferIdAribStdB67);
-
-gfx::ColorSpace::TransferID FromWebMTransferCharacteristics(
- TransferCharacteristics tc) {
- return static_cast<gfx::ColorSpace::TransferID>(tc);
-}
-
-STATIC_ASSERT_ENUM(kPrimariesBt709, kPrimaryIdBt709);
-STATIC_ASSERT_ENUM(kPrimariesUnspecified, kPrimaryIdUnspecified);
-STATIC_ASSERT_ENUM(kPrimariesBt470M, kPrimaryIdBt470M);
-STATIC_ASSERT_ENUM(kPrimariesBt470Bg, kPrimaryIdBt470Bg);
-STATIC_ASSERT_ENUM(kPrimariesSmpte170M, kPrimaryIdSmpte170M);
-STATIC_ASSERT_ENUM(kPrimariesSmpte240M, kPrimaryIdSmpte240M);
-STATIC_ASSERT_ENUM(kPrimariesFilm, kPrimaryIdFilm);
-STATIC_ASSERT_ENUM(kPrimariesBt2020, kPrimaryIdBt2020);
-STATIC_ASSERT_ENUM(kPrimariesSmpteSt4281, kPrimaryIdSmpteSt4281);
-
-gfx::ColorSpace::PrimaryID FromWebMPrimaries(Primaries primaries) {
- return static_cast<gfx::ColorSpace::PrimaryID>(primaries);
-}
-
-WebMColorMetadata::WebMColorMetadata() {
- BitsPerChannel = 0;
- ChromaSubsamplingHorz = 0;
- ChromaSubsamplingVert = 0;
- CbSubsamplingHorz = 0;
- CbSubsamplingVert = 0;
- ChromaSitingHorz = 0;
- ChromaSitingVert = 0;
-}
-
-bool WebMColorMetadata::operator==(const WebMColorMetadata& rhs) const {
- return (BitsPerChannel == rhs.BitsPerChannel &&
- ChromaSubsamplingHorz == rhs.ChromaSubsamplingHorz &&
- ChromaSubsamplingVert == rhs.ChromaSubsamplingVert &&
- CbSubsamplingHorz == rhs.CbSubsamplingHorz &&
- CbSubsamplingVert == rhs.CbSubsamplingVert &&
- ChromaSitingHorz == rhs.ChromaSitingHorz &&
- ChromaSitingVert == rhs.ChromaSitingVert &&
- color_space == rhs.color_space && hdr_metadata == rhs.hdr_metadata);
-}
-
-WebMMasteringMetadataParser::WebMMasteringMetadataParser() {}
-WebMMasteringMetadataParser::~WebMMasteringMetadataParser() {}
-
-bool WebMMasteringMetadataParser::OnFloat(int id, double val) {
- switch (id) {
- case kWebMIdPrimaryRChromaticityX:
- mastering_metadata_.primary_r_chromaticity_x = val;
- break;
- case kWebMIdPrimaryRChromaticityY:
- mastering_metadata_.primary_r_chromaticity_y = val;
- break;
- case kWebMIdPrimaryGChromaticityX:
- mastering_metadata_.primary_g_chromaticity_x = val;
- break;
- case kWebMIdPrimaryGChromaticityY:
- mastering_metadata_.primary_g_chromaticity_y = val;
- break;
- case kWebMIdPrimaryBChromaticityX:
- mastering_metadata_.primary_b_chromaticity_x = val;
- break;
- case kWebMIdPrimaryBChromaticityY:
- mastering_metadata_.primary_b_chromaticity_y = val;
- break;
- case kWebMIdWhitePointChromaticityX:
- mastering_metadata_.white_point_chromaticity_x = val;
- break;
- case kWebMIdWhitePointChromaticityY:
- mastering_metadata_.white_point_chromaticity_y = val;
- break;
- case kWebMIdLuminanceMax:
- mastering_metadata_.luminance_max = val;
- break;
- case kWebMIdLuminanceMin:
- mastering_metadata_.luminance_min = val;
- break;
- default:
- DVLOG(1) << "Unexpected id in MasteringMetadata: 0x" << std::hex << id;
- return false;
- }
- return true;
-}
-
-WebMColourParser::WebMColourParser() {
- Reset();
-}
-
-WebMColourParser::~WebMColourParser() {}
-
-void WebMColourParser::Reset() {
- matrix_coefficients_ = -1;
- bits_per_channel_ = -1;
- chroma_subsampling_horz_ = -1;
- chroma_subsampling_vert_ = -1;
- cb_subsampling_horz_ = -1;
- cb_subsampling_vert_ = -1;
- chroma_siting_horz_ = -1;
- chroma_siting_vert_ = -1;
- range_ = -1;
- transfer_characteristics_ = -1;
- primaries_ = -1;
- max_cll_ = -1;
- max_fall_ = -1;
- mastering_metadata_parsed_ = false;
-}
-
-WebMParserClient* WebMColourParser::OnListStart(int id) {
- if (id == kWebMIdMasteringMetadata) {
- mastering_metadata_parsed_ = false;
- return &mastering_metadata_parser_;
- }
-
- return this;
-}
-
-bool WebMColourParser::OnListEnd(int id) {
- if (id == kWebMIdMasteringMetadata)
- mastering_metadata_parsed_ = true;
- return true;
-}
-
-bool WebMColourParser::OnUInt(int id, int64_t val) {
- int64_t* dst = NULL;
-
- switch (id) {
- case kWebMIdMatrixCoefficients:
- dst = &matrix_coefficients_;
- break;
- case kWebMIdBitsPerChannel:
- dst = &bits_per_channel_;
- break;
- case kWebMIdChromaSubsamplingHorz:
- dst = &chroma_subsampling_horz_;
- break;
- case kWebMIdChromaSubsamplingVert:
- dst = &chroma_subsampling_vert_;
- break;
- case kWebMIdCbSubsamplingHorz:
- dst = &cb_subsampling_horz_;
- break;
- case kWebMIdCbSubsamplingVert:
- dst = &cb_subsampling_vert_;
- break;
- case kWebMIdChromaSitingHorz:
- dst = &chroma_siting_horz_;
- break;
- case kWebMIdChromaSitingVert:
- dst = &chroma_siting_vert_;
- break;
- case kWebMIdRange:
- dst = &range_;
- break;
- case kWebMIdTransferCharacteristics:
- dst = &transfer_characteristics_;
- break;
- case kWebMIdPrimaries:
- dst = &primaries_;
- break;
- case kWebMIdMaxCLL:
- dst = &max_cll_;
- break;
- case kWebMIdMaxFALL:
- dst = &max_fall_;
- break;
- default:
- return true;
- }
-
- DCHECK(dst);
- if (*dst != -1) {
- LOG(ERROR) << "Multiple values for id " << std::hex << id << " specified ("
- << *dst << " and " << val << ")";
- return false;
- }
-
- *dst = val;
- return true;
-}
-
-WebMColorMetadata WebMColourParser::GetWebMColorMetadata() const {
- WebMColorMetadata color_metadata;
-
- if (bits_per_channel_ != -1)
- color_metadata.BitsPerChannel = bits_per_channel_;
-
- if (chroma_subsampling_horz_ != -1)
- color_metadata.ChromaSubsamplingHorz = chroma_subsampling_horz_;
- if (chroma_subsampling_vert_ != -1)
- color_metadata.ChromaSubsamplingVert = chroma_subsampling_vert_;
- if (cb_subsampling_horz_ != -1)
- color_metadata.CbSubsamplingHorz = cb_subsampling_horz_;
- if (cb_subsampling_vert_ != -1)
- color_metadata.CbSubsamplingVert = cb_subsampling_vert_;
- if (chroma_siting_horz_ != -1)
- color_metadata.ChromaSitingHorz = chroma_siting_horz_;
- if (chroma_siting_vert_ != -1)
- color_metadata.ChromaSitingVert = chroma_siting_vert_;
-
- gfx::ColorSpace::MatrixID matrix_id = gfx::ColorSpace::kMatrixIdUnspecified;
- if (matrix_coefficients_ != -1)
- matrix_id = FromWebMMatrixCoefficients(
- static_cast<MatrixCoefficients>(matrix_coefficients_));
-
- gfx::ColorSpace::RangeID range_id = gfx::ColorSpace::kRangeIdUnspecified;
- if (range_ != -1)
- range_id = FromWebMRange(static_cast<Range>(range_));
-
- gfx::ColorSpace::TransferID transfer_id =
- gfx::ColorSpace::kTransferIdUnspecified;
- if (transfer_characteristics_ != -1)
- transfer_id = FromWebMTransferCharacteristics(
- static_cast<TransferCharacteristics>(transfer_characteristics_));
-
- gfx::ColorSpace::PrimaryID primary_id =
- gfx::ColorSpace::kPrimaryIdUnspecified;
- if (primaries_ != -1)
- primary_id = FromWebMPrimaries(static_cast<Primaries>(primaries_));
-
- color_metadata.color_space =
- gfx::ColorSpace(primary_id, transfer_id, matrix_id, range_id);
-
- if (max_cll_ != -1)
- color_metadata.hdr_metadata.max_cll = max_cll_;
-
- if (max_fall_ != -1)
- color_metadata.hdr_metadata.max_fall = max_fall_;
-
- if (mastering_metadata_parsed_)
- color_metadata.hdr_metadata.mastering_metadata =
- mastering_metadata_parser_.GetMasteringMetadata();
-
- return color_metadata;
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_colour_parser.h b/src/media/webm/webm_colour_parser.h
deleted file mode 100644
index acf9a3d..0000000
--- a/src/media/webm/webm_colour_parser.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_COLOUR_PARSER_H_
-#define MEDIA_WEBM_WEBM_COLOUR_PARSER_H_
-
-#include "base/compiler_specific.h"
-#include "media/base/color_space.h"
-#include "media/base/hdr_metadata.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-
-// WebM color information, containing HDR metadata:
-// http://www.webmproject.org/docs/container/#Colour
-struct MEDIA_EXPORT WebMColorMetadata {
- unsigned int BitsPerChannel;
- unsigned int ChromaSubsamplingHorz;
- unsigned int ChromaSubsamplingVert;
- unsigned int CbSubsamplingHorz;
- unsigned int CbSubsamplingVert;
- unsigned int ChromaSitingHorz;
- unsigned int ChromaSitingVert;
-
- gfx::ColorSpace color_space;
-
- HDRMetadata hdr_metadata;
-
- WebMColorMetadata();
- WebMColorMetadata(const WebMColorMetadata& rhs);
- bool operator==(const WebMColorMetadata& rhs) const;
-};
-
-// Parser for WebM MasteringMetadata within Colour element:
-// http://www.webmproject.org/docs/container/#MasteringMetadata
-class WebMMasteringMetadataParser : public WebMParserClient {
- public:
- WebMMasteringMetadataParser();
- ~WebMMasteringMetadataParser() OVERRIDE;
-
- MasteringMetadata GetMasteringMetadata() const { return mastering_metadata_; }
-
- private:
- // WebMParserClient implementation.
- bool OnFloat(int id, double val) OVERRIDE;
-
- MasteringMetadata mastering_metadata_;
- DISALLOW_COPY_AND_ASSIGN(WebMMasteringMetadataParser);
-};
-
-// Parser for WebM Colour element:
-// http://www.webmproject.org/docs/container/#colour
-class WebMColourParser : public WebMParserClient {
- public:
- WebMColourParser();
- ~WebMColourParser() OVERRIDE;
-
- void Reset();
-
- WebMColorMetadata GetWebMColorMetadata() const;
-
- private:
- // WebMParserClient implementation.
- WebMParserClient* OnListStart(int id) OVERRIDE;
- bool OnListEnd(int id) OVERRIDE;
- bool OnUInt(int id, int64_t val) OVERRIDE;
-
- int64_t matrix_coefficients_;
- int64_t bits_per_channel_;
- int64_t chroma_subsampling_horz_;
- int64_t chroma_subsampling_vert_;
- int64_t cb_subsampling_horz_;
- int64_t cb_subsampling_vert_;
- int64_t chroma_siting_horz_;
- int64_t chroma_siting_vert_;
- int64_t range_;
- int64_t transfer_characteristics_;
- int64_t primaries_;
- int64_t max_cll_;
- int64_t max_fall_;
-
- WebMMasteringMetadataParser mastering_metadata_parser_;
- bool mastering_metadata_parsed_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMColourParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_COLOUR_PARSER_H_
diff --git a/src/media/webm/webm_constants.h b/src/media/webm/webm_constants.h
deleted file mode 100644
index 8e1bce2..0000000
--- a/src/media/webm/webm_constants.h
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_CONSTANTS_H_
-#define MEDIA_WEBM_WEBM_CONSTANTS_H_
-
-#include "base/basictypes.h"
-
-namespace media {
-
-// WebM element IDs.
-// This is a subset of the IDs in the Matroska spec.
-// http://www.matroska.org/technical/specs/index.html
-const int kWebMIdAESSettingsCipherMode = 0x47E8;
-const int kWebMIdAlphaMode = 0x53C0;
-const int kWebMIdAspectRatioType = 0x54B3;
-const int kWebMIdAttachedFile = 0x61A7;
-const int kWebMIdAttachmentLink = 0x7446;
-const int kWebMIdAttachments = 0x1941A469;
-const int kWebMIdAudio = 0xE1;
-const int kWebMIdBitDepth = 0x6264;
-const int kWebMIdBitsPerChannel = 0x55B2;
-const int kWebMIdBlock = 0xA1;
-const int kWebMIdBlockAddID = 0xEE;
-const int kWebMIdBlockAdditions = 0x75A1;
-const int kWebMIdBlockAdditional = 0xA5;
-const int kWebMIdBlockDuration = 0x9B;
-const int kWebMIdBlockGroup = 0xA0;
-const int kWebMIdBlockMore = 0xA6;
-const int kWebMIdCbSubsamplingHorz = 0x55B5;
-const int kWebMIdCbSubsamplingVert = 0x55B6;
-const int kWebMIdChannels = 0x9F;
-const int kWebMIdChapCountry = 0x437E;
-const int kWebMIdChapLanguage = 0x437C;
-const int kWebMIdChapProcess = 0x6944;
-const int kWebMIdChapProcessCodecID = 0x6955;
-const int kWebMIdChapProcessCommand = 0x6911;
-const int kWebMIdChapProcessData = 0x6933;
-const int kWebMIdChapProcessPrivate = 0x450D;
-const int kWebMIdChapProcessTime = 0x6922;
-const int kWebMIdChapString = 0x85;
-const int kWebMIdChapterAtom = 0xB6;
-const int kWebMIdChapterDisplay = 0x80;
-const int kWebMIdChapterFlagEnabled = 0x4598;
-const int kWebMIdChapterFlagHidden = 0x98;
-const int kWebMIdChapterPhysicalEquiv = 0x63C3;
-const int kWebMIdChapters = 0x1043A770;
-const int kWebMIdChapterSegmentEditionUID = 0x6EBC;
-const int kWebMIdChapterSegmentUID = 0x6E67;
-const int kWebMIdChapterTimeEnd = 0x92;
-const int kWebMIdChapterTimeStart = 0x91;
-const int kWebMIdChapterTrack = 0x8F;
-const int kWebMIdChapterTrackNumber = 0x89;
-const int kWebMIdChapterTranslate = 0x6924;
-const int kWebMIdChapterTranslateCodec = 0x69BF;
-const int kWebMIdChapterTranslateEditionUID = 0x69FC;
-const int kWebMIdChapterTranslateID = 0x69A5;
-const int kWebMIdChapterUID = 0x73C4;
-const int kWebMIdChromaSitingHorz = 0x55B7;
-const int kWebMIdChromaSitingVert = 0x55B8;
-const int kWebMIdChromaSubsamplingHorz = 0x55B3;
-const int kWebMIdChromaSubsamplingVert = 0x55B4;
-const int kWebMIdCluster = 0x1F43B675;
-const int kWebMIdCodecDecodeAll = 0xAA;
-const int kWebMIdCodecDelay = 0x56AA;
-const int kWebMIdCodecID = 0x86;
-const int kWebMIdCodecName = 0x258688;
-const int kWebMIdCodecPrivate = 0x63A2;
-const int kWebMIdCodecState = 0xA4;
-const int kWebMIdColorSpace = 0x2EB524;
-const int kWebMIdColour = 0x55B0;
-const int kWebMIdContentCompAlgo = 0x4254;
-const int kWebMIdContentCompression = 0x5034;
-const int kWebMIdContentCompSettings = 0x4255;
-const int kWebMIdContentEncAESSettings = 0x47E7;
-const int kWebMIdContentEncAlgo = 0x47E1;
-const int kWebMIdContentEncKeyID = 0x47E2;
-const int kWebMIdContentEncoding = 0x6240;
-const int kWebMIdContentEncodingOrder = 0x5031;
-const int kWebMIdContentEncodings = 0x6D80;
-const int kWebMIdContentEncodingScope = 0x5032;
-const int kWebMIdContentEncodingType = 0x5033;
-const int kWebMIdContentEncryption = 0x5035;
-const int kWebMIdContentSigAlgo = 0x47E5;
-const int kWebMIdContentSigHashAlgo = 0x47E6;
-const int kWebMIdContentSigKeyID = 0x47E4;
-const int kWebMIdContentSignature = 0x47E3;
-const int kWebMIdCRC32 = 0xBF;
-const int kWebMIdCueBlockNumber = 0x5378;
-const int kWebMIdCueClusterPosition = 0xF1;
-const int kWebMIdCueCodecState = 0xEA;
-const int kWebMIdCuePoint = 0xBB;
-const int kWebMIdCueReference = 0xDB;
-const int kWebMIdCueRefTime = 0x96;
-const int kWebMIdCues = 0x1C53BB6B;
-const int kWebMIdCueTime = 0xB3;
-const int kWebMIdCueTrack = 0xF7;
-const int kWebMIdCueTrackPositions = 0xB7;
-const int kWebMIdDateUTC = 0x4461;
-const int kWebMIdDefaultDuration = 0x23E383;
-const int kWebMIdDiscardPadding = 0x75A2;
-const int kWebMIdDisplayHeight = 0x54BA;
-const int kWebMIdDisplayUnit = 0x54B2;
-const int kWebMIdDisplayWidth = 0x54B0;
-const int kWebMIdDocType = 0x4282;
-const int kWebMIdDocTypeReadVersion = 0x4285;
-const int kWebMIdDocTypeVersion = 0x4287;
-const int kWebMIdDuration = 0x4489;
-const int kWebMIdEBMLHeader = 0x1A45DFA3;
-const int kWebMIdEBMLMaxIDLength = 0x42F2;
-const int kWebMIdEBMLMaxSizeLength = 0x42F3;
-const int kWebMIdEBMLReadVersion = 0x42F7;
-const int kWebMIdEBMLVersion = 0x4286;
-const int kWebMIdEditionEntry = 0x45B9;
-const int kWebMIdEditionFlagDefault = 0x45DB;
-const int kWebMIdEditionFlagHidden = 0x45BD;
-const int kWebMIdEditionFlagOrdered = 0x45DD;
-const int kWebMIdEditionUID = 0x45BC;
-const int kWebMIdFileData = 0x465C;
-const int kWebMIdFileDescription = 0x467E;
-const int kWebMIdFileMimeType = 0x4660;
-const int kWebMIdFileName = 0x466E;
-const int kWebMIdFileUID = 0x46AE;
-const int kWebMIdFlagDefault = 0x88;
-const int kWebMIdFlagEnabled = 0xB9;
-const int kWebMIdFlagForced = 0x55AA;
-const int kWebMIdFlagInterlaced = 0x9A;
-const int kWebMIdFlagLacing = 0x9C;
-const int kWebMIdFrameRate = 0x2383E3;
-const int kWebMIdInfo = 0x1549A966;
-const int kWebMIdJoinBlocks = 0xE9;
-const int kWebMIdLaceNumber = 0xCC;
-const int kWebMIdLanguage = 0x22B59C;
-const int kWebMIdLuminanceMax = 0x55D9;
-const int kWebMIdLuminanceMin = 0x55DA;
-const int kWebMIdMasteringMetadata = 0x55D0;
-const int kWebMIdMatrixCoefficients = 0x55B1;
-const int kWebMIdMaxBlockAdditionId = 0x55EE;
-const int kWebMIdMaxCache = 0x6DF8;
-const int kWebMIdMaxCLL = 0x55BC;
-const int kWebMIdMaxFALL = 0x55BD;
-const int kWebMIdMinCache = 0x6DE7;
-const int kWebMIdMuxingApp = 0x4D80;
-const int kWebMIdName = 0x536E;
-const int kWebMIdNextFilename = 0x3E83BB;
-const int kWebMIdNextUID = 0x3EB923;
-const int kWebMIdOutputSamplingFrequency = 0x78B5;
-const int kWebMIdPixelCropBottom = 0x54AA;
-const int kWebMIdPixelCropLeft = 0x54CC;
-const int kWebMIdPixelCropRight = 0x54DD;
-const int kWebMIdPixelCropTop = 0x54BB;
-const int kWebMIdPixelHeight = 0xBA;
-const int kWebMIdPixelWidth = 0xB0;
-const int kWebMIdPosition = 0xA7;
-const int kWebMIdPrevFilename = 0x3C83AB;
-const int kWebMIdPrevSize = 0xAB;
-const int kWebMIdPrevUID = 0x3CB923;
-const int kWebMIdProjection = 0x7670;
-const int kWebMIdProjectionPosePitch = 0x7674;
-const int kWebMIdProjectionPoseRoll = 0x7675;
-const int kWebMIdProjectionPoseYaw = 0x7673;
-const int kWebMIdProjectionPrivate = 0x7672;
-const int kWebMIdProjectionType = 0x7671;
-const int kWebMIdPrimaries = 0x55BB;
-const int kWebMIdPrimaryBChromaticityX = 0x55D5;
-const int kWebMIdPrimaryBChromaticityY = 0x55D6;
-const int kWebMIdPrimaryGChromaticityX = 0x55D3;
-const int kWebMIdPrimaryGChromaticityY = 0x55D4;
-const int kWebMIdPrimaryRChromaticityX = 0x55D1;
-const int kWebMIdPrimaryRChromaticityY = 0x55D2;
-const int kWebMIdRange = 0x55B9;
-const int kWebMIdReferenceBlock = 0xFB;
-const int kWebMIdReferencePriority = 0xFA;
-const int kWebMIdSamplingFrequency = 0xB5;
-const int kWebMIdSeek = 0x4DBB;
-const int kWebMIdSeekHead = 0x114D9B74;
-const int kWebMIdSeekID = 0x53AB;
-const int kWebMIdSeekPosition = 0x53AC;
-const int kWebMIdSeekPreRoll = 0x56BB;
-const int kWebMIdSegment = 0x18538067;
-const int kWebMIdSegmentFamily = 0x4444;
-const int kWebMIdSegmentFilename = 0x7384;
-const int kWebMIdSegmentUID = 0x73A4;
-const int kWebMIdSilentTrackNumber = 0x58D7;
-const int kWebMIdSilentTracks = 0x5854;
-const int kWebMIdSimpleBlock = 0xA3;
-const int kWebMIdSimpleTag = 0x67C8;
-const int kWebMIdSlices = 0x8E;
-const int kWebMIdStereoMode = 0x53B8;
-const int kWebMIdTag = 0x7373;
-const int kWebMIdTagAttachmentUID = 0x63C6;
-const int kWebMIdTagBinary = 0x4485;
-const int kWebMIdTagChapterUID = 0x63C4;
-const int kWebMIdTagDefault = 0x4484;
-const int kWebMIdTagEditionUID = 0x63C9;
-const int kWebMIdTagLanguage = 0x447A;
-const int kWebMIdTagName = 0x45A3;
-const int kWebMIdTags = 0x1254C367;
-const int kWebMIdTagString = 0x4487;
-const int kWebMIdTagTrackUID = 0x63C5;
-const int kWebMIdTargets = 0x63C0;
-const int kWebMIdTargetType = 0x63CA;
-const int kWebMIdTargetTypeValue = 0x68CA;
-const int kWebMIdTimecode = 0xE7;
-const int kWebMIdTimecodeScale = 0x2AD7B1;
-const int kWebMIdTimeSlice = 0xE8;
-const int kWebMIdTitle = 0x7BA9;
-const int kWebMIdTrackCombinePlanes = 0xE3;
-const int kWebMIdTrackEntry = 0xAE;
-const int kWebMIdTrackJoinUID = 0xED;
-const int kWebMIdTrackNumber = 0xD7;
-const int kWebMIdTrackOperation = 0xE2;
-const int kWebMIdTrackOverlay = 0x6FAB;
-const int kWebMIdTrackPlane = 0xE4;
-const int kWebMIdTrackPlaneType = 0xE6;
-const int kWebMIdTrackPlaneUID = 0xE5;
-const int kWebMIdTracks = 0x1654AE6B;
-const int kWebMIdTrackTimecodeScale = 0x23314F;
-const int kWebMIdTrackTranslate = 0x6624;
-const int kWebMIdTrackTranslateCodec = 0x66BF;
-const int kWebMIdTrackTranslateEditionUID = 0x66FC;
-const int kWebMIdTrackTranslateTrackID = 0x66A5;
-const int kWebMIdTrackType = 0x83;
-const int kWebMIdTrackUID = 0x73C5;
-const int kWebMIdTransferCharacteristics = 0x55BA;
-const int kWebMIdVideo = 0xE0;
-const int kWebMIdVoid = 0xEC;
-const int kWebMIdWhitePointChromaticityX = 0x55D7;
-const int kWebMIdWhitePointChromaticityY = 0x55D8;
-const int kWebMIdWritingApp = 0x5741;
-
-const int64 kWebMReservedId = 0x1FFFFFFF;
-const int64 kWebMUnknownSize = GG_LONGLONG(0x00FFFFFFFFFFFFFF);
-
-const uint8 kWebMFlagKeyframe = 0x80;
-
-// Current encrypted WebM request for comments specification is here
-// http://wiki.webmproject.org/encryption/webm-encryption-rfc
-const uint8 kWebMFlagEncryptedFrame = 0x1;
-const uint8 kWebMFlagEncryptedFramePartitioned = 0x2;
-const int kWebMIvSize = 8;
-const int kWebMSignalByteSize = 1;
-const int kWebMEncryptedFrameNumPartitionsSize = 1;
-const int kWebMEncryptedFramePartitionOffsetSize = 4;
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_CONSTANTS_H_
diff --git a/src/media/webm/webm_content_encodings.cc b/src/media/webm/webm_content_encodings.cc
deleted file mode 100644
index 9789c0f..0000000
--- a/src/media/webm/webm_content_encodings.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/logging.h"
-#include "media/webm/webm_content_encodings.h"
-
-namespace media {
-
-ContentEncoding::ContentEncoding()
- : order_(kOrderInvalid),
- scope_(kScopeInvalid),
- type_(kTypeInvalid),
- encryption_algo_(kEncAlgoInvalid),
- cipher_mode_(kCipherModeInvalid) {
-}
-
-ContentEncoding::~ContentEncoding() {}
-
-void ContentEncoding::SetEncryptionKeyId(const uint8* encryption_key_id,
- int size) {
- DCHECK(encryption_key_id);
- DCHECK_GT(size, 0);
- encryption_key_id_.assign(reinterpret_cast<const char*>(encryption_key_id),
- size);
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_content_encodings.h b/src/media/webm/webm_content_encodings.h
deleted file mode 100644
index 2866f25..0000000
--- a/src/media/webm/webm_content_encodings.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_
-#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-class MEDIA_EXPORT ContentEncoding {
- public:
- // The following enum definitions are based on the ContentEncoding element
- // specified in the Matroska spec.
-
- static const int kOrderInvalid = -1;
-
- enum Scope {
- kScopeInvalid = 0,
- kScopeAllFrameContents = 1,
- kScopeTrackPrivateData = 2,
- kScopeNextContentEncodingData = 4,
- kScopeMax = 7,
- };
-
- enum Type {
- kTypeInvalid = -1,
- kTypeCompression = 0,
- kTypeEncryption = 1,
- };
-
- enum EncryptionAlgo {
- kEncAlgoInvalid = -1,
- kEncAlgoNotEncrypted = 0,
- kEncAlgoDes = 1,
- kEncAlgo3des = 2,
- kEncAlgoTwofish = 3,
- kEncAlgoBlowfish = 4,
- kEncAlgoAes = 5,
- };
-
- enum CipherMode {
- kCipherModeInvalid = 0,
- kCipherModeCtr = 1,
- };
-
- ContentEncoding();
- ~ContentEncoding();
-
- int64 order() const { return order_; }
- void set_order(int64 order) { order_ = order; }
-
- Scope scope() const { return scope_; }
- void set_scope(Scope scope) { scope_ = scope; }
-
- Type type() const { return type_; }
- void set_type(Type type) { type_ = type; }
-
- EncryptionAlgo encryption_algo() const { return encryption_algo_; }
- void set_encryption_algo(EncryptionAlgo encryption_algo) {
- encryption_algo_ = encryption_algo;
- }
-
- const std::string& encryption_key_id() const { return encryption_key_id_; }
- void SetEncryptionKeyId(const uint8* encryption_key_id, int size);
-
- CipherMode cipher_mode() const { return cipher_mode_; }
- void set_cipher_mode(CipherMode mode) { cipher_mode_ = mode; }
-
- private:
- int64 order_;
- Scope scope_;
- Type type_;
- EncryptionAlgo encryption_algo_;
- std::string encryption_key_id_;
- CipherMode cipher_mode_;
-
- DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_
diff --git a/src/media/webm/webm_content_encodings_client.cc b/src/media/webm/webm_content_encodings_client.cc
deleted file mode 100644
index bcf964e..0000000
--- a/src/media/webm/webm_content_encodings_client.cc
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/webm_content_encodings_client.h"
-
-#include "base/logging.h"
-#include "base/stl_util.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-WebMContentEncodingsClient::WebMContentEncodingsClient(const LogCB& log_cb)
- : log_cb_(log_cb),
- content_encryption_encountered_(false),
- content_encodings_ready_(false) {
-}
-
-WebMContentEncodingsClient::~WebMContentEncodingsClient() {
- STLDeleteElements(&content_encodings_);
-}
-
-const ContentEncodings& WebMContentEncodingsClient::content_encodings() const {
- DCHECK(content_encodings_ready_);
- return content_encodings_;
-}
-
-WebMParserClient* WebMContentEncodingsClient::OnListStart(int id) {
- if (id == kWebMIdContentEncodings) {
- DCHECK(!cur_content_encoding_.get());
- DCHECK(!content_encryption_encountered_);
- STLDeleteElements(&content_encodings_);
- content_encodings_ready_ = false;
- return this;
- }
-
- if (id == kWebMIdContentEncoding) {
- DCHECK(!cur_content_encoding_.get());
- DCHECK(!content_encryption_encountered_);
- cur_content_encoding_.reset(new ContentEncoding());
- return this;
- }
-
- if (id == kWebMIdContentEncryption) {
- DCHECK(cur_content_encoding_.get());
- if (content_encryption_encountered_) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncryption.";
- return NULL;
- }
- content_encryption_encountered_ = true;
- return this;
- }
-
- if (id == kWebMIdContentEncAESSettings) {
- DCHECK(cur_content_encoding_.get());
- return this;
- }
-
- // This should not happen if WebMListParser is working properly.
- DCHECK(false);
- return NULL;
-}
-
-// Mandatory occurrence restriction is checked in this function. Multiple
-// occurrence restriction is checked in OnUInt and OnBinary.
-bool WebMContentEncodingsClient::OnListEnd(int id) {
- if (id == kWebMIdContentEncodings) {
- // ContentEncoding element is mandatory. Check this!
- if (content_encodings_.empty()) {
- MEDIA_LOG(log_cb_) << "Missing ContentEncoding.";
- return false;
- }
- content_encodings_ready_ = true;
- return true;
- }
-
- if (id == kWebMIdContentEncoding) {
- DCHECK(cur_content_encoding_.get());
-
- //
- // Specify default values to missing mandatory elements.
- //
-
- if (cur_content_encoding_->order() == ContentEncoding::kOrderInvalid) {
- // Default value of encoding order is 0, which should only be used on the
- // first ContentEncoding.
- if (!content_encodings_.empty()) {
- MEDIA_LOG(log_cb_) << "Missing ContentEncodingOrder.";
- return false;
- }
- cur_content_encoding_->set_order(0);
- }
-
- if (cur_content_encoding_->scope() == ContentEncoding::kScopeInvalid)
- cur_content_encoding_->set_scope(ContentEncoding::kScopeAllFrameContents);
-
- if (cur_content_encoding_->type() == ContentEncoding::kTypeInvalid)
- cur_content_encoding_->set_type(ContentEncoding::kTypeCompression);
-
- // Check for elements valid in spec but not supported for now.
- if (cur_content_encoding_->type() == ContentEncoding::kTypeCompression) {
- MEDIA_LOG(log_cb_) << "ContentCompression not supported.";
- return false;
- }
-
- // Enforce mandatory elements without default values.
- DCHECK(cur_content_encoding_->type() == ContentEncoding::kTypeEncryption);
- if (!content_encryption_encountered_) {
- MEDIA_LOG(log_cb_) << "ContentEncodingType is encryption but"
- << " ContentEncryption is missing.";
- return false;
- }
-
- content_encodings_.push_back(cur_content_encoding_.release());
- content_encryption_encountered_ = false;
- return true;
- }
-
- if (id == kWebMIdContentEncryption) {
- DCHECK(cur_content_encoding_.get());
- // Specify default value for elements that are not present.
- if (cur_content_encoding_->encryption_algo() ==
- ContentEncoding::kEncAlgoInvalid) {
- cur_content_encoding_->set_encryption_algo(
- ContentEncoding::kEncAlgoNotEncrypted);
- }
- return true;
- }
-
- if (id == kWebMIdContentEncAESSettings) {
- if (cur_content_encoding_->cipher_mode() ==
- ContentEncoding::kCipherModeInvalid)
- cur_content_encoding_->set_cipher_mode(ContentEncoding::kCipherModeCtr);
- return true;
- }
-
- // This should not happen if WebMListParser is working properly.
- DCHECK(false);
- return false;
-}
-
-// Multiple occurrence restriction and range are checked in this function.
-// Mandatory occurrence restriction is checked in OnListEnd.
-bool WebMContentEncodingsClient::OnUInt(int id, int64 val) {
- DCHECK(cur_content_encoding_.get());
-
- if (id == kWebMIdContentEncodingOrder) {
- if (cur_content_encoding_->order() != ContentEncoding::kOrderInvalid) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingOrder.";
- return false;
- }
-
- if (val != static_cast<int64>(content_encodings_.size())) {
- // According to the spec, encoding order starts with 0 and counts upwards.
- MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingOrder.";
- return false;
- }
-
- cur_content_encoding_->set_order(val);
- return true;
- }
-
- if (id == kWebMIdContentEncodingScope) {
- if (cur_content_encoding_->scope() != ContentEncoding::kScopeInvalid) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingScope.";
- return false;
- }
-
- if (val == ContentEncoding::kScopeInvalid ||
- val > ContentEncoding::kScopeMax) {
- MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingScope.";
- return false;
- }
-
- if (val & ContentEncoding::kScopeNextContentEncodingData) {
- MEDIA_LOG(log_cb_) << "Encoded next ContentEncoding is not supported.";
- return false;
- }
-
- cur_content_encoding_->set_scope(static_cast<ContentEncoding::Scope>(val));
- return true;
- }
-
- if (id == kWebMIdContentEncodingType) {
- if (cur_content_encoding_->type() != ContentEncoding::kTypeInvalid) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingType.";
- return false;
- }
-
- if (val == ContentEncoding::kTypeCompression) {
- MEDIA_LOG(log_cb_) << "ContentCompression not supported.";
- return false;
- }
-
- if (val != ContentEncoding::kTypeEncryption) {
- MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingType " << val << ".";
- return false;
- }
-
- cur_content_encoding_->set_type(static_cast<ContentEncoding::Type>(val));
- return true;
- }
-
- if (id == kWebMIdContentEncAlgo) {
- if (cur_content_encoding_->encryption_algo() !=
- ContentEncoding::kEncAlgoInvalid) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncAlgo.";
- return false;
- }
-
- if (val < ContentEncoding::kEncAlgoNotEncrypted ||
- val > ContentEncoding::kEncAlgoAes) {
- MEDIA_LOG(log_cb_) << "Unexpected ContentEncAlgo " << val << ".";
- return false;
- }
-
- cur_content_encoding_->set_encryption_algo(
- static_cast<ContentEncoding::EncryptionAlgo>(val));
- return true;
- }
-
- if (id == kWebMIdAESSettingsCipherMode) {
- if (cur_content_encoding_->cipher_mode() !=
- ContentEncoding::kCipherModeInvalid) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple AESSettingsCipherMode.";
- return false;
- }
-
- if (val != ContentEncoding::kCipherModeCtr) {
- MEDIA_LOG(log_cb_) << "Unexpected AESSettingsCipherMode " << val << ".";
- return false;
- }
-
- cur_content_encoding_->set_cipher_mode(
- static_cast<ContentEncoding::CipherMode>(val));
- return true;
- }
-
- // This should not happen if WebMListParser is working properly.
- DCHECK(false);
- return false;
-}
-
-// Multiple occurrence restriction is checked in this function. Mandatory
-// restriction is checked in OnListEnd.
-bool WebMContentEncodingsClient::OnBinary(int id, const uint8* data, int size) {
- DCHECK(cur_content_encoding_.get());
- DCHECK(data);
- DCHECK_GT(size, 0);
-
- if (id == kWebMIdContentEncKeyID) {
- if (!cur_content_encoding_->encryption_key_id().empty()) {
- MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncKeyID";
- return false;
- }
- cur_content_encoding_->SetEncryptionKeyId(data, size);
- return true;
- }
-
- // This should not happen if WebMListParser is working properly.
- DCHECK(false);
- return false;
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_content_encodings_client.h b/src/media/webm/webm_content_encodings_client.h
deleted file mode 100644
index e477fcf..0000000
--- a/src/media/webm/webm_content_encodings_client.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
-#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/webm/webm_content_encodings.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-
-typedef std::vector<ContentEncoding*> ContentEncodings;
-
-// Parser for WebM ContentEncodings element.
-class MEDIA_EXPORT WebMContentEncodingsClient : public WebMParserClient {
- public:
- explicit WebMContentEncodingsClient(const LogCB& log_cb);
- virtual ~WebMContentEncodingsClient();
-
- const ContentEncodings& content_encodings() const;
-
- // WebMParserClient methods
- virtual WebMParserClient* OnListStart(int id) OVERRIDE;
- virtual bool OnListEnd(int id) OVERRIDE;
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
-
- private:
- LogCB log_cb_;
- scoped_ptr<ContentEncoding> cur_content_encoding_;
- bool content_encryption_encountered_;
- ContentEncodings content_encodings_;
-
- // |content_encodings_| is ready. For debugging purpose.
- bool content_encodings_ready_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMContentEncodingsClient);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
diff --git a/src/media/webm/webm_content_encodings_client_unittest.cc b/src/media/webm/webm_content_encodings_client_unittest.cc
deleted file mode 100644
index bb9e694..0000000
--- a/src/media/webm/webm_content_encodings_client_unittest.cc
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "base/bind.h"
-#include "media/webm/webm_constants.h"
-#include "media/webm/webm_content_encodings_client.h"
-#include "media/webm/webm_parser.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-class WebMContentEncodingsClientTest : public testing::Test {
- public:
- WebMContentEncodingsClientTest()
- : client_(LogCB()),
- parser_(kWebMIdContentEncodings, &client_) {}
-
- void ParseAndExpectToFail(const uint8* buf, int size) {
- int result = parser_.Parse(buf, size);
- EXPECT_EQ(-1, result);
- }
-
- protected:
- WebMContentEncodingsClient client_;
- WebMListParser parser_;
-};
-
-TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x80, // ContentEncodings (size = 0)
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x83, // ContentEncodings (size = 3)
- 0x63, 0x40, 0x80, // ContentEncoding (size = 0)
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33)
- 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
- 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
- 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
- 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
- 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
- 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
- };
- int size = sizeof(kContentEncodings);
-
- int result = parser_.Parse(kContentEncodings, size);
- ASSERT_EQ(size, result);
- const ContentEncodings& content_encodings = client_.content_encodings();
-
- ASSERT_EQ(1u, content_encodings.size());
- ASSERT_TRUE(content_encodings[0]);
- EXPECT_EQ(0, content_encodings[0]->order());
- EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
- content_encodings[0]->scope());
- EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
- EXPECT_EQ(ContentEncoding::kEncAlgoAes,
- content_encodings[0]->encryption_algo());
- EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
-}
-
-TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0xC2, // ContentEncodings (size = 66)
- 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
- 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
- 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
- 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
- 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
- 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
- 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
- 0x50, 0x31, 0x81, 0x01, // ContentEncodingOrder (size = 1)
- 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
- 0x47, 0xE1, 0x81, 0x01, // ContentEncAlgo (size = 1)
- 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
- 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
- };
- int size = sizeof(kContentEncodings);
-
- int result = parser_.Parse(kContentEncodings, size);
- ASSERT_EQ(size, result);
- const ContentEncodings& content_encodings = client_.content_encodings();
- ASSERT_EQ(2u, content_encodings.size());
-
- for (int i = 0; i < 2; ++i) {
- ASSERT_TRUE(content_encodings[i]);
- EXPECT_EQ(i, content_encodings[i]->order());
- EXPECT_EQ(ContentEncoding::kScopeAllFrameContents |
- ContentEncoding::kScopeTrackPrivateData,
- content_encodings[i]->scope());
- EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type());
- EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes,
- content_encodings[i]->encryption_algo());
- EXPECT_EQ(8u, content_encodings[i]->encryption_key_id().size());
- }
-}
-
-TEST_F(WebMContentEncodingsClientTest, DefaultValues) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x8A, // ContentEncodings (size = 10)
- 0x62, 0x40, 0x87, // ContentEncoding (size = 7)
- // ContentEncodingOrder missing
- // ContentEncodingScope missing
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
- // ContentEncAlgo missing
- };
- int size = sizeof(kContentEncodings);
-
- int result = parser_.Parse(kContentEncodings, size);
- ASSERT_EQ(size, result);
- const ContentEncodings& content_encodings = client_.content_encodings();
-
- ASSERT_EQ(1u, content_encodings.size());
- ASSERT_TRUE(content_encodings[0]);
- EXPECT_EQ(0, content_encodings[0]->order());
- EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
- content_encodings[0]->scope());
- EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
- EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted,
- content_encodings[0]->encryption_algo());
- EXPECT_TRUE(content_encodings[0]->encryption_key_id().empty());
-}
-
-TEST_F(WebMContentEncodingsClientTest, ContentEncodingsClientReuse) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33)
- 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
- 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
- 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
- 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
- 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
- 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
- };
- int size = sizeof(kContentEncodings);
-
- // Parse for the first time.
- int result = parser_.Parse(kContentEncodings, size);
- ASSERT_EQ(size, result);
-
- // Parse again.
- parser_.Reset();
- result = parser_.Parse(kContentEncodings, size);
- ASSERT_EQ(size, result);
- const ContentEncodings& content_encodings = client_.content_encodings();
-
- ASSERT_EQ(1u, content_encodings.size());
- ASSERT_TRUE(content_encodings[0]);
- EXPECT_EQ(0, content_encodings[0]->order());
- EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
- content_encodings[0]->scope());
- EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
- EXPECT_EQ(ContentEncoding::kEncAlgoAes,
- content_encodings[0]->encryption_algo());
- EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
-}
-
-TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
- 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
- 0x50, 0x31, 0x81, 0xEE, // ContentEncodingOrder (size = 1), invalid
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
- 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
- 0x50, 0x32, 0x81, 0xEE, // ContentEncodingScope (size = 1), invalid
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
- 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
- 0x50, 0x33, 0x81, 0x00, // ContentEncodingType (size = 1), invalid
- 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-// ContentEncodingType is encryption but no ContentEncryption present.
-TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x87, // ContentEncodings (size = 7)
- 0x62, 0x40, 0x84, // ContentEncoding (size = 4)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- // ContentEncryption missing
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo) {
- const uint8 kContentEncodings[] = {
- 0x6D, 0x80, 0x99, // ContentEncodings (size = 25)
- 0x62, 0x40, 0x96, // ContentEncoding (size = 22)
- 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
- 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
- 0x47, 0xE1, 0x81, 0xEE, // ContentEncAlgo (size = 1), invalid
- 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
- 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
- };
- int size = sizeof(kContentEncodings);
- ParseAndExpectToFail(kContentEncodings, size);
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_info_parser.cc b/src/media/webm/webm_info_parser.cc
deleted file mode 100644
index 6df1690..0000000
--- a/src/media/webm/webm_info_parser.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 "media/webm/webm_info_parser.h"
-
-#include "base/logging.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-// Default timecode scale if the TimecodeScale element is
-// not specified in the INFO element.
-static const int kWebMDefaultTimecodeScale = 1000000;
-
-WebMInfoParser::WebMInfoParser()
- : timecode_scale_(-1),
- duration_(-1) {
-}
-
-WebMInfoParser::~WebMInfoParser() {}
-
-int WebMInfoParser::Parse(const uint8* buf, int size) {
- timecode_scale_ = -1;
- duration_ = -1;
-
- WebMListParser parser(kWebMIdInfo, this);
- int result = parser.Parse(buf, size);
-
- if (result <= 0)
- return result;
-
- // For now we do all or nothing parsing.
- return parser.IsParsingComplete() ? result : 0;
-}
-
-WebMParserClient* WebMInfoParser::OnListStart(int id) { return this; }
-
-bool WebMInfoParser::OnListEnd(int id) {
- if (id == kWebMIdInfo && timecode_scale_ == -1) {
- // Set timecode scale to default value if it isn't present in
- // the Info element.
- timecode_scale_ = kWebMDefaultTimecodeScale;
- }
- return true;
-}
-
-bool WebMInfoParser::OnUInt(int id, int64 val) {
- if (id != kWebMIdTimecodeScale)
- return true;
-
- if (timecode_scale_ != -1) {
- DVLOG(1) << "Multiple values for id " << std::hex << id << " specified";
- return false;
- }
-
- timecode_scale_ = val;
- return true;
-}
-
-bool WebMInfoParser::OnFloat(int id, double val) {
- if (id != kWebMIdDuration) {
- DVLOG(1) << "Unexpected float for id" << std::hex << id;
- return false;
- }
-
- if (duration_ != -1) {
- DVLOG(1) << "Multiple values for duration.";
- return false;
- }
-
- duration_ = val;
- return true;
-}
-
-bool WebMInfoParser::OnBinary(int id, const uint8* data, int size) {
- return true;
-}
-
-bool WebMInfoParser::OnString(int id, const std::string& str) {
- return true;
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_info_parser.h b/src/media/webm/webm_info_parser.h
deleted file mode 100644
index ab5de43..0000000
--- a/src/media/webm/webm_info_parser.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_INFO_PARSER_H_
-#define MEDIA_WEBM_WEBM_INFO_PARSER_H_
-
-#include "base/compiler_specific.h"
-#include "media/base/media_export.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-
-// Parser for WebM Info element.
-class MEDIA_EXPORT WebMInfoParser : public WebMParserClient {
- public:
- WebMInfoParser();
- virtual ~WebMInfoParser();
-
- // Parses a WebM Info element in |buf|.
- //
- // Returns -1 if the parse fails.
- // Returns 0 if more data is needed.
- // Returns the number of bytes parsed on success.
- int Parse(const uint8* buf, int size);
-
- int64 timecode_scale() const { return timecode_scale_; }
- double duration() const { return duration_; }
-
- private:
- // WebMParserClient methods
- virtual WebMParserClient* OnListStart(int id) OVERRIDE;
- virtual bool OnListEnd(int id) OVERRIDE;
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnFloat(int id, double val) OVERRIDE;
- virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
- virtual bool OnString(int id, const std::string& str) OVERRIDE;
-
- int64 timecode_scale_;
- double duration_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMInfoParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_INFO_PARSER_H_
diff --git a/src/media/webm/webm_parser.cc b/src/media/webm/webm_parser.cc
deleted file mode 100644
index c0c76c6..0000000
--- a/src/media/webm/webm_parser.cc
+++ /dev/null
@@ -1,976 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/webm_parser.h"
-
-// This file contains code to parse WebM file elements. It was created
-// from information in the Matroska spec.
-// http://www.matroska.org/technical/specs/index.html
-// This file contains code for encrypted WebM. Current WebM
-// encrypted request for comments specification is here
-// http://wiki.webmproject.org/encryption/webm-encryption-rfc
-
-#include <iomanip>
-
-#include "base/logging.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-enum ElementType {
- UNKNOWN,
- LIST, // Referred to as Master Element in the Matroska spec.
- UINT,
- FLOAT,
- BINARY,
- STRING,
- SKIP,
-};
-
-struct ElementIdInfo {
- ElementType type_;
- int id_;
-};
-
-struct ListElementInfo {
- int id_;
- int level_;
- const ElementIdInfo* id_info_;
- int id_info_count_;
-};
-
-// The following are tables indicating what IDs are valid sub-elements
-// of particular elements. If an element is encountered that doesn't
-// appear in the list, a parsing error is signalled. Some elements are
-// marked as SKIP because they are valid, but we don't care about them
-// right now.
-static const ElementIdInfo kEBMLHeaderIds[] = {
- {UINT, kWebMIdEBMLVersion},
- {UINT, kWebMIdEBMLReadVersion},
- {UINT, kWebMIdEBMLMaxIDLength},
- {UINT, kWebMIdEBMLMaxSizeLength},
- {STRING, kWebMIdDocType},
- {UINT, kWebMIdDocTypeVersion},
- {UINT, kWebMIdDocTypeReadVersion},
-};
-
-static const ElementIdInfo kSegmentIds[] = {
- {LIST, kWebMIdSeekHead},
- {LIST, kWebMIdInfo},
- {LIST, kWebMIdCluster},
- {LIST, kWebMIdTracks},
- {LIST, kWebMIdCues},
- {LIST, kWebMIdAttachments},
- {LIST, kWebMIdChapters},
- {LIST, kWebMIdTags},
-};
-
-static const ElementIdInfo kSeekHeadIds[] = {
- {LIST, kWebMIdSeek},
-};
-
-static const ElementIdInfo kSeekIds[] = {
- {BINARY, kWebMIdSeekID},
- {UINT, kWebMIdSeekPosition},
-};
-
-static const ElementIdInfo kInfoIds[] = {
- {BINARY, kWebMIdSegmentUID},
- {STRING, kWebMIdSegmentFilename},
- {BINARY, kWebMIdPrevUID},
- {STRING, kWebMIdPrevFilename},
- {BINARY, kWebMIdNextUID},
- {STRING, kWebMIdNextFilename},
- {BINARY, kWebMIdSegmentFamily},
- {LIST, kWebMIdChapterTranslate},
- {UINT, kWebMIdTimecodeScale},
- {FLOAT, kWebMIdDuration},
- {BINARY, kWebMIdDateUTC},
- {STRING, kWebMIdTitle},
- {STRING, kWebMIdMuxingApp},
- {STRING, kWebMIdWritingApp},
-};
-
-static const ElementIdInfo kChapterTranslateIds[] = {
- {UINT, kWebMIdChapterTranslateEditionUID},
- {UINT, kWebMIdChapterTranslateCodec},
- {BINARY, kWebMIdChapterTranslateID},
-};
-
-static const ElementIdInfo kClusterIds[] = {
- {BINARY, kWebMIdSimpleBlock},
- {UINT, kWebMIdTimecode},
- {LIST, kWebMIdSilentTracks},
- {UINT, kWebMIdPosition},
- {UINT, kWebMIdPrevSize},
- {LIST, kWebMIdBlockGroup},
-};
-
-static const ElementIdInfo kSilentTracksIds[] = {
- {UINT, kWebMIdSilentTrackNumber},
-};
-
-static const ElementIdInfo kBlockGroupIds[] = {
- {BINARY, kWebMIdBlock},
- {LIST, kWebMIdBlockAdditions},
- {UINT, kWebMIdBlockDuration},
- {UINT, kWebMIdReferencePriority},
- {BINARY, kWebMIdReferenceBlock},
- {BINARY, kWebMIdCodecState},
- {LIST, kWebMIdSlices},
-};
-
-static const ElementIdInfo kBlockAdditionsIds[] = {
- {LIST, kWebMIdBlockMore},
-};
-
-static const ElementIdInfo kBlockMoreIds[] = {
- {UINT, kWebMIdBlockAddID},
- {BINARY, kWebMIdBlockAdditional},
-};
-
-static const ElementIdInfo kSlicesIds[] = {
- {LIST, kWebMIdTimeSlice},
-};
-
-static const ElementIdInfo kTimeSliceIds[] = {
- {UINT, kWebMIdLaceNumber},
-};
-
-static const ElementIdInfo kTracksIds[] = {
- {LIST, kWebMIdTrackEntry},
-};
-
-static const ElementIdInfo kTrackEntryIds[] = {
- {UINT, kWebMIdTrackNumber},
- {UINT, kWebMIdTrackUID},
- {UINT, kWebMIdTrackType},
- {UINT, kWebMIdFlagEnabled},
- {UINT, kWebMIdFlagDefault},
- {UINT, kWebMIdFlagForced},
- {UINT, kWebMIdFlagLacing},
- {UINT, kWebMIdMinCache},
- {UINT, kWebMIdMaxCache},
- {UINT, kWebMIdDefaultDuration},
- {FLOAT, kWebMIdTrackTimecodeScale},
- {UINT, kWebMIdMaxBlockAdditionId},
- {STRING, kWebMIdName},
- {STRING, kWebMIdLanguage},
- {STRING, kWebMIdCodecID},
- {BINARY, kWebMIdCodecPrivate},
- {STRING, kWebMIdCodecName},
- {UINT, kWebMIdAttachmentLink},
- {UINT, kWebMIdCodecDecodeAll},
- {UINT, kWebMIdTrackOverlay},
- {LIST, kWebMIdTrackTranslate},
- {LIST, kWebMIdVideo},
- {LIST, kWebMIdAudio},
- {LIST, kWebMIdTrackOperation},
- {LIST, kWebMIdContentEncodings},
-};
-
-static const ElementIdInfo kTrackTranslateIds[] = {
- {UINT, kWebMIdTrackTranslateEditionUID},
- {UINT, kWebMIdTrackTranslateCodec},
- {BINARY, kWebMIdTrackTranslateTrackID},
-};
-
-static const ElementIdInfo kVideoIds[] = {
- {UINT, kWebMIdFlagInterlaced}, {UINT, kWebMIdStereoMode},
- {LIST, kWebMIdProjection}, {UINT, kWebMIdPixelWidth},
- {UINT, kWebMIdPixelHeight}, {UINT, kWebMIdPixelCropBottom},
- {UINT, kWebMIdPixelCropTop}, {UINT, kWebMIdPixelCropLeft},
- {UINT, kWebMIdPixelCropRight}, {UINT, kWebMIdDisplayWidth},
- {UINT, kWebMIdDisplayHeight}, {UINT, kWebMIdDisplayUnit},
- {UINT, kWebMIdAspectRatioType}, {BINARY, kWebMIdColorSpace},
- {LIST, kWebMIdColour},
-};
-
-static const ElementIdInfo kColourIds[] = {
- {UINT, kWebMIdMatrixCoefficients},
- {UINT, kWebMIdBitsPerChannel},
- {UINT, kWebMIdChromaSubsamplingHorz},
- {UINT, kWebMIdChromaSubsamplingVert},
- {UINT, kWebMIdCbSubsamplingHorz},
- {UINT, kWebMIdCbSubsamplingVert},
- {UINT, kWebMIdChromaSitingHorz},
- {UINT, kWebMIdChromaSitingVert},
- {UINT, kWebMIdRange},
- {UINT, kWebMIdTransferCharacteristics},
- {UINT, kWebMIdPrimaries},
- {UINT, kWebMIdMaxCLL},
- {UINT, kWebMIdMaxFALL},
- {LIST, kWebMIdMasteringMetadata},
-};
-
-static const ElementIdInfo kMasteringMetadataIds[] = {
- {FLOAT, kWebMIdPrimaryRChromaticityX},
- {FLOAT, kWebMIdPrimaryRChromaticityY},
- {FLOAT, kWebMIdPrimaryGChromaticityX},
- {FLOAT, kWebMIdPrimaryGChromaticityY},
- {FLOAT, kWebMIdPrimaryBChromaticityX},
- {FLOAT, kWebMIdPrimaryBChromaticityY},
- {FLOAT, kWebMIdWhitePointChromaticityX},
- {FLOAT, kWebMIdWhitePointChromaticityY},
- {FLOAT, kWebMIdLuminanceMax},
- {FLOAT, kWebMIdLuminanceMin},
-};
-
-static const ElementIdInfo kAudioIds[] = {
- {FLOAT, kWebMIdSamplingFrequency},
- {FLOAT, kWebMIdOutputSamplingFrequency},
- {UINT, kWebMIdChannels},
- {UINT, kWebMIdBitDepth},
-};
-
-static const ElementIdInfo kTrackOperationIds[] = {
- {LIST, kWebMIdTrackCombinePlanes},
- {LIST, kWebMIdJoinBlocks},
-};
-
-static const ElementIdInfo kTrackCombinePlanesIds[] = {
- {LIST, kWebMIdTrackPlane},
-};
-
-static const ElementIdInfo kTrackPlaneIds[] = {
- {UINT, kWebMIdTrackPlaneUID},
- {UINT, kWebMIdTrackPlaneType},
-};
-
-static const ElementIdInfo kJoinBlocksIds[] = {
- {UINT, kWebMIdTrackJoinUID},
-};
-
-static const ElementIdInfo kContentEncodingsIds[] = {
- {LIST, kWebMIdContentEncoding},
-};
-
-static const ElementIdInfo kContentEncodingIds[] = {
- {UINT, kWebMIdContentEncodingOrder},
- {UINT, kWebMIdContentEncodingScope},
- {UINT, kWebMIdContentEncodingType},
- {LIST, kWebMIdContentCompression},
- {LIST, kWebMIdContentEncryption},
-};
-
-static const ElementIdInfo kContentCompressionIds[] = {
- {UINT, kWebMIdContentCompAlgo},
- {BINARY, kWebMIdContentCompSettings},
-};
-
-static const ElementIdInfo kContentEncryptionIds[] = {
- {LIST, kWebMIdContentEncAESSettings},
- {UINT, kWebMIdContentEncAlgo},
- {BINARY, kWebMIdContentEncKeyID},
- {BINARY, kWebMIdContentSignature},
- {BINARY, kWebMIdContentSigKeyID},
- {UINT, kWebMIdContentSigAlgo},
- {UINT, kWebMIdContentSigHashAlgo},
-};
-
-static const ElementIdInfo kContentEncAESSettingsIds[] = {
- {UINT, kWebMIdAESSettingsCipherMode},
-};
-
-static const ElementIdInfo kCuesIds[] = {
- {LIST, kWebMIdCuePoint},
-};
-
-static const ElementIdInfo kCuePointIds[] = {
- {UINT, kWebMIdCueTime},
- {LIST, kWebMIdCueTrackPositions},
-};
-
-static const ElementIdInfo kCueTrackPositionsIds[] = {
- {UINT, kWebMIdCueTrack},
- {UINT, kWebMIdCueClusterPosition},
- {UINT, kWebMIdCueBlockNumber},
- {UINT, kWebMIdCueCodecState},
- {LIST, kWebMIdCueReference},
-};
-
-static const ElementIdInfo kCueReferenceIds[] = {
- {UINT, kWebMIdCueRefTime},
-};
-
-static const ElementIdInfo kAttachmentsIds[] = {
- {LIST, kWebMIdAttachedFile},
-};
-
-static const ElementIdInfo kAttachedFileIds[] = {
- {STRING, kWebMIdFileDescription},
- {STRING, kWebMIdFileName},
- {STRING, kWebMIdFileMimeType},
- {BINARY, kWebMIdFileData},
- {UINT, kWebMIdFileUID},
-};
-
-static const ElementIdInfo kChaptersIds[] = {
- {LIST, kWebMIdEditionEntry},
-};
-
-static const ElementIdInfo kEditionEntryIds[] = {
- {UINT, kWebMIdEditionUID},
- {UINT, kWebMIdEditionFlagHidden},
- {UINT, kWebMIdEditionFlagDefault},
- {UINT, kWebMIdEditionFlagOrdered},
- {LIST, kWebMIdChapterAtom},
-};
-
-static const ElementIdInfo kChapterAtomIds[] = {
- {UINT, kWebMIdChapterUID},
- {UINT, kWebMIdChapterTimeStart},
- {UINT, kWebMIdChapterTimeEnd},
- {UINT, kWebMIdChapterFlagHidden},
- {UINT, kWebMIdChapterFlagEnabled},
- {BINARY, kWebMIdChapterSegmentUID},
- {UINT, kWebMIdChapterSegmentEditionUID},
- {UINT, kWebMIdChapterPhysicalEquiv},
- {LIST, kWebMIdChapterTrack},
- {LIST, kWebMIdChapterDisplay},
- {LIST, kWebMIdChapProcess},
-};
-
-static const ElementIdInfo kChapterTrackIds[] = {
- {UINT, kWebMIdChapterTrackNumber},
-};
-
-static const ElementIdInfo kChapterDisplayIds[] = {
- {STRING, kWebMIdChapString},
- {STRING, kWebMIdChapLanguage},
- {STRING, kWebMIdChapCountry},
-};
-
-static const ElementIdInfo kChapProcessIds[] = {
- {UINT, kWebMIdChapProcessCodecID},
- {BINARY, kWebMIdChapProcessPrivate},
- {LIST, kWebMIdChapProcessCommand},
-};
-
-static const ElementIdInfo kChapProcessCommandIds[] = {
- {UINT, kWebMIdChapProcessTime},
- {BINARY, kWebMIdChapProcessData},
-};
-
-static const ElementIdInfo kTagsIds[] = {
- {LIST, kWebMIdTag},
-};
-
-static const ElementIdInfo kTagIds[] = {
- {LIST, kWebMIdTargets},
- {LIST, kWebMIdSimpleTag},
-};
-
-static const ElementIdInfo kTargetsIds[] = {
- {UINT, kWebMIdTargetTypeValue},
- {STRING, kWebMIdTargetType},
- {UINT, kWebMIdTagTrackUID},
- {UINT, kWebMIdTagEditionUID},
- {UINT, kWebMIdTagChapterUID},
- {UINT, kWebMIdTagAttachmentUID},
-};
-
-static const ElementIdInfo kSimpleTagIds[] = {
- {STRING, kWebMIdTagName},
- {STRING, kWebMIdTagLanguage},
- {UINT, kWebMIdTagDefault},
- {STRING, kWebMIdTagString},
- {BINARY, kWebMIdTagBinary},
-};
-
-static const ElementIdInfo kProjectionIds[] = {
- {FLOAT, kWebMIdProjectionPosePitch},
- {FLOAT, kWebMIdProjectionPoseRoll},
- {FLOAT, kWebMIdProjectionPoseYaw},
- {BINARY, kWebMIdProjectionPrivate},
- {UINT, kWebMIdProjectionType},
-};
-
-#define LIST_ELEMENT_INFO(id, level, id_info) \
- { (id), (level), (id_info), arraysize(id_info) }
-
-static const ListElementInfo kListElementInfo[] = {
- LIST_ELEMENT_INFO(kWebMIdCluster, 1, kClusterIds),
- LIST_ELEMENT_INFO(kWebMIdEBMLHeader, 0, kEBMLHeaderIds),
- LIST_ELEMENT_INFO(kWebMIdSegment, 0, kSegmentIds),
- LIST_ELEMENT_INFO(kWebMIdSeekHead, 1, kSeekHeadIds),
- LIST_ELEMENT_INFO(kWebMIdSeek, 2, kSeekIds),
- LIST_ELEMENT_INFO(kWebMIdInfo, 1, kInfoIds),
- LIST_ELEMENT_INFO(kWebMIdChapterTranslate, 2, kChapterTranslateIds),
- LIST_ELEMENT_INFO(kWebMIdSilentTracks, 2, kSilentTracksIds),
- LIST_ELEMENT_INFO(kWebMIdBlockGroup, 2, kBlockGroupIds),
- LIST_ELEMENT_INFO(kWebMIdBlockAdditions, 3, kBlockAdditionsIds),
- LIST_ELEMENT_INFO(kWebMIdBlockMore, 4, kBlockMoreIds),
- LIST_ELEMENT_INFO(kWebMIdSlices, 3, kSlicesIds),
- LIST_ELEMENT_INFO(kWebMIdTimeSlice, 4, kTimeSliceIds),
- LIST_ELEMENT_INFO(kWebMIdTracks, 1, kTracksIds),
- LIST_ELEMENT_INFO(kWebMIdTrackEntry, 2, kTrackEntryIds),
- LIST_ELEMENT_INFO(kWebMIdTrackTranslate, 3, kTrackTranslateIds),
- LIST_ELEMENT_INFO(kWebMIdVideo, 3, kVideoIds),
- LIST_ELEMENT_INFO(kWebMIdAudio, 3, kAudioIds),
- LIST_ELEMENT_INFO(kWebMIdTrackOperation, 3, kTrackOperationIds),
- LIST_ELEMENT_INFO(kWebMIdTrackCombinePlanes, 4, kTrackCombinePlanesIds),
- LIST_ELEMENT_INFO(kWebMIdTrackPlane, 5, kTrackPlaneIds),
- LIST_ELEMENT_INFO(kWebMIdJoinBlocks, 4, kJoinBlocksIds),
- LIST_ELEMENT_INFO(kWebMIdContentEncodings, 3, kContentEncodingsIds),
- LIST_ELEMENT_INFO(kWebMIdContentEncoding, 4, kContentEncodingIds),
- LIST_ELEMENT_INFO(kWebMIdContentCompression, 5, kContentCompressionIds),
- LIST_ELEMENT_INFO(kWebMIdContentEncryption, 5, kContentEncryptionIds),
- LIST_ELEMENT_INFO(kWebMIdContentEncAESSettings, 6, kContentEncAESSettingsIds),
- LIST_ELEMENT_INFO(kWebMIdCues, 1, kCuesIds),
- LIST_ELEMENT_INFO(kWebMIdCuePoint, 2, kCuePointIds),
- LIST_ELEMENT_INFO(kWebMIdCueTrackPositions, 3, kCueTrackPositionsIds),
- LIST_ELEMENT_INFO(kWebMIdCueReference, 4, kCueReferenceIds),
- LIST_ELEMENT_INFO(kWebMIdAttachments, 1, kAttachmentsIds),
- LIST_ELEMENT_INFO(kWebMIdAttachedFile, 2, kAttachedFileIds),
- LIST_ELEMENT_INFO(kWebMIdChapters, 1, kChaptersIds),
- LIST_ELEMENT_INFO(kWebMIdEditionEntry, 2, kEditionEntryIds),
- LIST_ELEMENT_INFO(kWebMIdChapterAtom, 3, kChapterAtomIds),
- LIST_ELEMENT_INFO(kWebMIdChapterTrack, 4, kChapterTrackIds),
- LIST_ELEMENT_INFO(kWebMIdChapterDisplay, 4, kChapterDisplayIds),
- LIST_ELEMENT_INFO(kWebMIdChapProcess, 4, kChapProcessIds),
- LIST_ELEMENT_INFO(kWebMIdChapProcessCommand, 5, kChapProcessCommandIds),
- LIST_ELEMENT_INFO(kWebMIdTags, 1, kTagsIds),
- LIST_ELEMENT_INFO(kWebMIdTag, 2, kTagIds),
- LIST_ELEMENT_INFO(kWebMIdTargets, 3, kTargetsIds),
- LIST_ELEMENT_INFO(kWebMIdSimpleTag, 3, kSimpleTagIds),
- LIST_ELEMENT_INFO(kWebMIdProjection, 4, kProjectionIds),
- LIST_ELEMENT_INFO(kWebMIdColour, 4, kColourIds),
- LIST_ELEMENT_INFO(kWebMIdMasteringMetadata, 5, kMasteringMetadataIds),
-};
-
-// Parses an element header id or size field. These fields are variable length
-// encoded. The first byte indicates how many bytes the field occupies.
-// |buf| - The buffer to parse.
-// |size| - The number of bytes in |buf|
-// |max_bytes| - The maximum number of bytes the field can be. ID fields
-// set this to 4 & element size fields set this to 8. If the
-// first byte indicates a larger field size than this it is a
-// parser error.
-// |mask_first_byte| - For element size fields the field length encoding bits
-// need to be masked off. This parameter is true for
-// element size fields and is false for ID field values.
-//
-// Returns: The number of bytes parsed on success. -1 on error.
-static int ParseWebMElementHeaderField(const uint8* buf, int size,
- int max_bytes, bool mask_first_byte,
- int64* num) {
- DCHECK(buf);
- DCHECK(num);
-
- if (size < 0)
- return -1;
-
- if (size == 0)
- return 0;
-
- int mask = 0x80;
- uint8 ch = buf[0];
- int extra_bytes = -1;
- bool all_ones = false;
- for (int i = 0; i < max_bytes; ++i) {
- if ((ch & mask) != 0) {
- mask = ~mask & 0xff;
- *num = mask_first_byte ? ch & mask : ch;
- all_ones = (ch & mask) == mask;
- extra_bytes = i;
- break;
- }
- mask = 0x80 | mask >> 1;
- }
-
- if (extra_bytes == -1)
- return -1;
-
- // Return 0 if we need more data.
- if ((1 + extra_bytes) > size)
- return 0;
-
- int bytes_used = 1;
-
- for (int i = 0; i < extra_bytes; ++i) {
- ch = buf[bytes_used++];
- all_ones &= (ch == 0xff);
- *num = (*num << 8) | ch;
- }
-
- if (all_ones)
- *num = kint64max;
-
- return bytes_used;
-}
-
-int WebMParseElementHeader(const uint8* buf, int size,
- int* id, int64* element_size) {
- DCHECK(buf);
- DCHECK_GE(size, 0);
- DCHECK(id);
- DCHECK(element_size);
-
- if (size == 0)
- return 0;
-
- int64 tmp = 0;
- int num_id_bytes = ParseWebMElementHeaderField(buf, size, 4, false, &tmp);
-
- if (num_id_bytes <= 0)
- return num_id_bytes;
-
- if (tmp == kint64max)
- tmp = kWebMReservedId;
-
- *id = static_cast<int>(tmp);
-
- int num_size_bytes = ParseWebMElementHeaderField(buf + num_id_bytes,
- size - num_id_bytes,
- 8, true, &tmp);
-
- if (num_size_bytes <= 0)
- return num_size_bytes;
-
- if (tmp == kint64max)
- tmp = kWebMUnknownSize;
-
- *element_size = tmp;
- DVLOG(3) << "WebMParseElementHeader() : id " << std::hex << *id << std::dec
- << " size " << *element_size;
- return num_id_bytes + num_size_bytes;
-}
-
-// Finds ElementType for a specific ID.
-static ElementType FindIdType(int id,
- const ElementIdInfo* id_info,
- int id_info_count) {
-
- // Check for global element IDs that can be anywhere.
- if (id == kWebMIdVoid || id == kWebMIdCRC32)
- return SKIP;
-
- for (int i = 0; i < id_info_count; ++i) {
- if (id == id_info[i].id_)
- return id_info[i].type_;
- }
-
- return UNKNOWN;
-}
-
-// Finds ListElementInfo for a specific ID.
-static const ListElementInfo* FindListInfo(int id) {
- for (size_t i = 0; i < arraysize(kListElementInfo); ++i) {
- if (id == kListElementInfo[i].id_)
- return &kListElementInfo[i];
- }
-
- return NULL;
-}
-
-static int FindListLevel(int id) {
- const ListElementInfo* list_info = FindListInfo(id);
- if (list_info)
- return list_info->level_;
-
- return -1;
-}
-
-static int ParseUInt(const uint8* buf, int size, int id,
- WebMParserClient* client) {
- if ((size <= 0) || (size > 8))
- return -1;
-
- // Read in the big-endian integer.
- int64 value = 0;
- for (int i = 0; i < size; ++i)
- value = (value << 8) | buf[i];
-
- if (!client->OnUInt(id, value))
- return -1;
-
- return size;
-}
-
-static int ParseFloat(const uint8* buf, int size, int id,
- WebMParserClient* client) {
-
- if ((size != 4) && (size != 8))
- return -1;
-
- double value = -1;
-
- // Read the bytes from big-endian form into a native endian integer.
- int64 tmp = 0;
- for (int i = 0; i < size; ++i)
- tmp = (tmp << 8) | buf[i];
-
- // Use a union to convert the integer bit pattern into a floating point
- // number.
- if (size == 4) {
- union {
- int32 src;
- float dst;
- } tmp2;
- tmp2.src = static_cast<int32>(tmp);
- value = tmp2.dst;
- } else if (size == 8) {
- union {
- int64 src;
- double dst;
- } tmp2;
- tmp2.src = tmp;
- value = tmp2.dst;
- } else {
- return -1;
- }
-
- if (!client->OnFloat(id, value))
- return -1;
-
- return size;
-}
-
-static int ParseBinary(const uint8* buf, int size, int id,
- WebMParserClient* client) {
- return client->OnBinary(id, buf, size) ? size : -1;
-}
-
-static int ParseString(const uint8* buf, int size, int id,
- WebMParserClient* client) {
- std::string str(reinterpret_cast<const char*>(buf), size);
- return client->OnString(id, str) ? size : -1;
-}
-
-static int ParseNonListElement(ElementType type, int id, int64 element_size,
- const uint8* buf, int size,
- WebMParserClient* client) {
- DCHECK_GE(size, element_size);
-
- int result = -1;
- switch(type) {
- case LIST:
- NOTIMPLEMENTED();
- result = -1;
- break;
- case UINT:
- result = ParseUInt(buf, element_size, id, client);
- break;
- case FLOAT:
- result = ParseFloat(buf, element_size, id, client);
- break;
- case BINARY:
- result = ParseBinary(buf, element_size, id, client);
- break;
- case STRING:
- result = ParseString(buf, element_size, id, client);
- break;
- case SKIP:
- result = element_size;
- break;
- default:
- DVLOG(1) << "Unhandled ID type " << type;
- return -1;
- };
-
- DCHECK_LE(result, size);
- return result;
-}
-
-WebMParserClient::WebMParserClient() {}
-WebMParserClient::~WebMParserClient() {}
-
-WebMParserClient* WebMParserClient::OnListStart(int id) {
- DVLOG(1) << "Unexpected list element start with ID " << std::hex << id;
- return NULL;
-}
-
-bool WebMParserClient::OnListEnd(int id) {
- DVLOG(1) << "Unexpected list element end with ID " << std::hex << id;
- return false;
-}
-
-bool WebMParserClient::OnUInt(int id, int64 val) {
- DVLOG(1) << "Unexpected unsigned integer element with ID " << std::hex << id;
- return false;
-}
-
-bool WebMParserClient::OnFloat(int id, double val) {
- DVLOG(1) << "Unexpected float element with ID " << std::hex << id;
- return false;
-}
-
-bool WebMParserClient::OnBinary(int id, const uint8* data, int size) {
- DVLOG(1) << "Unexpected binary element with ID " << std::hex << id;
- return false;
-}
-
-bool WebMParserClient::OnString(int id, const std::string& str) {
- DVLOG(1) << "Unexpected string element with ID " << std::hex << id;
- return false;
-}
-
-WebMListParser::WebMListParser(int id, WebMParserClient* client)
- : state_(NEED_LIST_HEADER),
- root_id_(id),
- root_level_(FindListLevel(id)),
- root_client_(client) {
- DCHECK_GE(root_level_, 0);
- DCHECK(client);
-}
-
-WebMListParser::~WebMListParser() {}
-
-void WebMListParser::Reset() {
- ChangeState(NEED_LIST_HEADER);
- list_state_stack_.clear();
-}
-
-int WebMListParser::Parse(const uint8* buf, int size) {
- DCHECK(buf);
-
- if (size < 0 || state_ == PARSE_ERROR || state_ == DONE_PARSING_LIST)
- return -1;
-
- if (size == 0)
- return 0;
-
- const uint8* cur = buf;
- int cur_size = size;
- int bytes_parsed = 0;
-
- while (cur_size > 0 && state_ != PARSE_ERROR && state_ != DONE_PARSING_LIST) {
- int element_id = 0;
- int64 element_size = 0;
- int result = WebMParseElementHeader(cur, cur_size, &element_id,
- &element_size);
-
- if (result < 0)
- return result;
-
- if (result == 0)
- return bytes_parsed;
-
- switch(state_) {
- case NEED_LIST_HEADER: {
- if (element_id != root_id_) {
- ChangeState(PARSE_ERROR);
- return -1;
- }
-
- // Only allow Segment & Cluster to have an unknown size.
- if (element_size == kWebMUnknownSize &&
- (element_id != kWebMIdSegment) &&
- (element_id != kWebMIdCluster)) {
- ChangeState(PARSE_ERROR);
- return -1;
- }
-
- ChangeState(INSIDE_LIST);
- if (!OnListStart(root_id_, element_size))
- return -1;
-
- break;
- }
-
- case INSIDE_LIST: {
- int header_size = result;
- const uint8* element_data = cur + header_size;
- int element_data_size = cur_size - header_size;
-
- if (element_size < element_data_size)
- element_data_size = element_size;
-
- result = ParseListElement(header_size, element_id, element_size,
- element_data, element_data_size);
-
- DCHECK_LE(result, header_size + element_data_size);
- if (result < 0) {
- ChangeState(PARSE_ERROR);
- return -1;
- }
-
- if (result == 0)
- return bytes_parsed;
-
- break;
- }
- case DONE_PARSING_LIST:
- case PARSE_ERROR:
- // Shouldn't be able to get here.
- NOTIMPLEMENTED();
- break;
- }
-
- cur += result;
- cur_size -= result;
- bytes_parsed += result;
- }
-
- return (state_ == PARSE_ERROR) ? -1 : bytes_parsed;
-}
-
-bool WebMListParser::IsParsingComplete() const {
- return state_ == DONE_PARSING_LIST;
-}
-
-void WebMListParser::ChangeState(State new_state) {
- state_ = new_state;
-}
-
-int WebMListParser::ParseListElement(int header_size,
- int id, int64 element_size,
- const uint8* data, int size) {
- DCHECK_GT(list_state_stack_.size(), 0u);
-
- ListState& list_state = list_state_stack_.back();
- DCHECK(list_state.element_info_);
-
- const ListElementInfo* element_info = list_state.element_info_;
- ElementType id_type =
- FindIdType(id, element_info->id_info_, element_info->id_info_count_);
-
- // Unexpected ID.
- if (id_type == UNKNOWN) {
- if (list_state.size_ != kWebMUnknownSize ||
- !IsSiblingOrAncestor(list_state.id_, id)) {
- DVLOG(1) << "No ElementType info for ID 0x" << std::hex << id;
- return -1;
- }
-
- // We've reached the end of a list of unknown size. Update the size now that
- // we know it and dispatch the end of list calls.
- list_state.size_ = list_state.bytes_parsed_;
-
- if (!OnListEnd())
- return -1;
-
- // Check to see if all open lists have ended.
- if (list_state_stack_.size() == 0)
- return 0;
-
- list_state = list_state_stack_.back();
- }
-
- // Make sure the whole element can fit inside the current list.
- int64 total_element_size = header_size + element_size;
- if (list_state.size_ != kWebMUnknownSize &&
- list_state.size_ < list_state.bytes_parsed_ + total_element_size) {
- return -1;
- }
-
- if (id_type == LIST) {
- list_state.bytes_parsed_ += header_size;
-
- if (!OnListStart(id, element_size))
- return -1;
- return header_size;
- }
-
- // Make sure we have the entire element before trying to parse a non-list
- // element.
- if (size < element_size)
- return 0;
-
- int bytes_parsed = ParseNonListElement(id_type, id, element_size,
- data, size, list_state.client_);
- DCHECK_LE(bytes_parsed, size);
-
- // Return if an error occurred or we need more data.
- // Note: bytes_parsed is 0 for a successful parse of a size 0 element. We
- // need to check the element_size to disambiguate the "need more data" case
- // from a successful parse.
- if (bytes_parsed < 0 || (bytes_parsed == 0 && element_size != 0))
- return bytes_parsed;
-
- int result = header_size + bytes_parsed;
- list_state.bytes_parsed_ += result;
-
- // See if we have reached the end of the current list.
- if (list_state.bytes_parsed_ == list_state.size_) {
- if (!OnListEnd())
- return -1;
- }
-
- return result;
-}
-
-bool WebMListParser::OnListStart(int id, int64 size) {
- const ListElementInfo* element_info = FindListInfo(id);
- if (!element_info)
- return false;
-
- int current_level = root_level_ + list_state_stack_.size() - 1;
- if (current_level + 1 != element_info->level_)
- return false;
-
- WebMParserClient* current_list_client = NULL;
- if (!list_state_stack_.empty()) {
- // Make sure the new list doesn't go past the end of the current list.
- ListState current_list_state = list_state_stack_.back();
- if (current_list_state.size_ != kWebMUnknownSize &&
- current_list_state.size_ < current_list_state.bytes_parsed_ + size)
- return false;
- current_list_client = current_list_state.client_;
- } else {
- current_list_client = root_client_;
- }
-
- WebMParserClient* new_list_client = current_list_client->OnListStart(id);
- if (!new_list_client)
- return false;
-
- ListState new_list_state = { id, size, 0, element_info, new_list_client };
- list_state_stack_.push_back(new_list_state);
-
- if (size == 0)
- return OnListEnd();
-
- return true;
-}
-
-bool WebMListParser::OnListEnd() {
- int lists_ended = 0;
- for (; !list_state_stack_.empty(); ++lists_ended) {
- const ListState& list_state = list_state_stack_.back();
-
- if (list_state.bytes_parsed_ != list_state.size_)
- break;
-
- list_state_stack_.pop_back();
-
- int64 bytes_parsed = list_state.bytes_parsed_;
- WebMParserClient* client = NULL;
- if (!list_state_stack_.empty()) {
- // Update the bytes_parsed_ for the parent element.
- list_state_stack_.back().bytes_parsed_ += bytes_parsed;
- client = list_state_stack_.back().client_;
- } else {
- client = root_client_;
- }
-
- if (!client->OnListEnd(list_state.id_))
- return false;
- }
-
- DCHECK_GE(lists_ended, 1);
-
- if (list_state_stack_.empty())
- ChangeState(DONE_PARSING_LIST);
-
- return true;
-}
-
-bool WebMListParser::IsSiblingOrAncestor(int id_a, int id_b) const {
- DCHECK((id_a == kWebMIdSegment) || (id_a == kWebMIdCluster));
-
- if (id_a == kWebMIdCluster) {
- // kWebMIdCluster siblings.
- for (size_t i = 0; i < arraysize(kSegmentIds); i++) {
- if (kSegmentIds[i].id_ == id_b)
- return true;
- }
- }
-
- // kWebMIdSegment siblings.
- return ((id_b == kWebMIdSegment) || (id_b == kWebMIdEBMLHeader));
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_parser.h b/src/media/webm/webm_parser.h
deleted file mode 100644
index 68611a8..0000000
--- a/src/media/webm/webm_parser.h
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_PARSER_H_
-#define MEDIA_WEBM_WEBM_PARSER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Interface for receiving WebM parser events.
-//
-// Each method is called when an element of the specified type is parsed.
-// The ID of the element that was parsed is given along with the value
-// stored in the element. List elements generate calls at the start and
-// end of the list. Any pointers passed to these methods are only guaranteed
-// to be valid for the life of that call. Each method (except for OnListStart)
-// returns a bool that indicates whether the parsed data is valid. OnListStart
-// returns a pointer to a WebMParserClient object, which should be used to
-// handle elements parsed out of the list being started. If false (or NULL by
-// OnListStart) is returned then the parse is immediately terminated and an
-// error is reported by the parser.
-class MEDIA_EXPORT WebMParserClient {
- public:
- virtual ~WebMParserClient();
-
- virtual WebMParserClient* OnListStart(int id);
- virtual bool OnListEnd(int id);
- virtual bool OnUInt(int id, int64 val);
- virtual bool OnFloat(int id, double val);
- virtual bool OnBinary(int id, const uint8* data, int size);
- virtual bool OnString(int id, const std::string& str);
-
- protected:
- WebMParserClient();
-
- DISALLOW_COPY_AND_ASSIGN(WebMParserClient);
-};
-
-struct ListElementInfo;
-
-// Parses a WebM list element and all of its children. This
-// class supports incremental parsing of the list so Parse()
-// can be called multiple times with pieces of the list.
-// IsParsingComplete() will return true once the entire list has
-// been parsed.
-class MEDIA_EXPORT WebMListParser {
- public:
- // |id| - Element ID of the list we intend to parse.
- // |client| - Called as different elements in the list are parsed.
- WebMListParser(int id, WebMParserClient* client);
- ~WebMListParser();
-
- // Resets the state of the parser so it can start parsing a new list.
- void Reset();
-
- // Parses list data contained in |buf|.
- //
- // Returns < 0 if the parse fails.
- // Returns 0 if more data is needed.
- // Returning > 0 indicates success & the number of bytes parsed.
- int Parse(const uint8* buf, int size);
-
- // Returns true if the entire list has been parsed.
- bool IsParsingComplete() const;
-
- private:
- enum State {
- NEED_LIST_HEADER,
- INSIDE_LIST,
- DONE_PARSING_LIST,
- PARSE_ERROR,
- };
-
- struct ListState {
- int id_;
- int64 size_;
- int64 bytes_parsed_;
- const ListElementInfo* element_info_;
- WebMParserClient* client_;
- };
-
- void ChangeState(State new_state);
-
- // Parses a single element in the current list.
- //
- // |header_size| - The size of the element header
- // |id| - The ID of the element being parsed.
- // |element_size| - The size of the element body.
- // |data| - Pointer to the element contents.
- // |size| - Number of bytes in |data|
- // |client| - Client to pass the parsed data to.
- //
- // Returns < 0 if the parse fails.
- // Returns 0 if more data is needed.
- // Returning > 0 indicates success & the number of bytes parsed.
- int ParseListElement(int header_size,
- int id, int64 element_size,
- const uint8* data, int size);
-
- // Called when starting to parse a new list.
- //
- // |id| - The ID of the new list.
- // |size| - The size of the new list.
- // |client| - The client object to notify that a new list is being parsed.
- //
- // Returns true if this list can be started in the current context. False
- // if starting this list causes some sort of parse error.
- bool OnListStart(int id, int64 size);
-
- // Called when the end of the current list has been reached. This may also
- // signal the end of the current list's ancestors if the current list happens
- // to be at the end of its parent.
- //
- // Returns true if no errors occurred while ending this list(s).
- bool OnListEnd();
-
- // Checks to see if |id_b| is a sibling or ancestor of |id_a|.
- bool IsSiblingOrAncestor(int id_a, int id_b) const;
-
- State state_;
-
- // Element ID passed to the constructor.
- const int root_id_;
-
- // Element level for |root_id_|. Used to verify that elements appear at
- // the correct level.
- const int root_level_;
-
- // WebMParserClient to handle the root list.
- WebMParserClient* const root_client_;
-
- // Stack of state for all the lists currently being parsed. Lists are
- // added and removed from this stack as they are parsed.
- std::vector<ListState> list_state_stack_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMListParser);
-};
-
-// Parses an element header & returns the ID and element size.
-//
-// Returns < 0 if the parse fails.
-// Returns 0 if more data is needed.
-// Returning > 0 indicates success & the number of bytes parsed.
-// |*id| contains the element ID on success and is undefined otherwise.
-// |*element_size| contains the element size on success and is undefined
-// otherwise.
-int MEDIA_EXPORT WebMParseElementHeader(const uint8* buf, int size,
- int* id, int64* element_size);
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_PARSER_H_
diff --git a/src/media/webm/webm_parser_unittest.cc b/src/media/webm/webm_parser_unittest.cc
deleted file mode 100644
index 4c140a6..0000000
--- a/src/media/webm/webm_parser_unittest.cc
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/cluster_builder.h"
-#include "media/webm/webm_constants.h"
-#include "media/webm/webm_parser.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::InSequence;
-using ::testing::Return;
-using ::testing::ReturnNull;
-using ::testing::StrictMock;
-using ::testing::_;
-
-namespace media {
-
-enum { kBlockCount = 5 };
-
-class MockWebMParserClient : public WebMParserClient {
- public:
- virtual ~MockWebMParserClient() {}
-
- // WebMParserClient methods.
- MOCK_METHOD1(OnListStart, WebMParserClient*(int));
- MOCK_METHOD1(OnListEnd, bool(int));
- MOCK_METHOD2(OnUInt, bool(int, int64));
- MOCK_METHOD2(OnFloat, bool(int, double));
- MOCK_METHOD3(OnBinary, bool(int, const uint8*, int));
- MOCK_METHOD2(OnString, bool(int, const std::string&));
-};
-
-class WebMParserTest : public testing::Test {
- protected:
- StrictMock<MockWebMParserClient> client_;
-};
-
-static scoped_ptr<Cluster> CreateCluster(int block_count) {
- ClusterBuilder cb;
- cb.SetClusterTimecode(0);
-
- for (int i = 0; i < block_count; i++) {
- uint8 data[] = { 0x00 };
- cb.AddSimpleBlock(0, i, 0, data, sizeof(data));
- }
-
- return cb.Finish();
-}
-
-static void CreateClusterExpectations(int block_count,
- bool is_complete_cluster,
- MockWebMParserClient* client) {
-
- InSequence s;
- EXPECT_CALL(*client, OnListStart(kWebMIdCluster)).WillOnce(Return(client));
- EXPECT_CALL(*client, OnUInt(kWebMIdTimecode, 0))
- .WillOnce(Return(true));
-
- for (int i = 0; i < block_count; i++) {
- EXPECT_CALL(*client, OnBinary(kWebMIdSimpleBlock, _, _))
- .WillOnce(Return(true));
- }
-
- if (is_complete_cluster)
- EXPECT_CALL(*client, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
-}
-
-TEST_F(WebMParserTest, EmptyCluster) {
- const uint8 kEmptyCluster[] = {
- 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0)
- };
- int size = sizeof(kEmptyCluster);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
- EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
-
- WebMListParser parser(kWebMIdCluster, &client_);
- int result = parser.Parse(kEmptyCluster, size);
- EXPECT_EQ(size, result);
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, EmptyClusterInSegment) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
- 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
- EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
- EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
- EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(size, result);
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-// Test the case where a non-list child element has a size
-// that is beyond the end of the parent.
-TEST_F(WebMParserTest, ChildNonListLargerThanParent) {
- const uint8 kBuffer[] = {
- 0x1F, 0x43, 0xB6, 0x75, 0x81, // CLUSTER (size = 1)
- 0xE7, 0x81, 0x01, // Timecode (size=1, value=1)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
-
- WebMListParser parser(kWebMIdCluster, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(-1, result);
- EXPECT_FALSE(parser.IsParsingComplete());
-}
-
-// Test the case where a list child element has a size
-// that is beyond the end of the parent.
-TEST_F(WebMParserTest, ChildListLargerThanParent) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
- 0x1F, 0x43, 0xB6, 0x75, 0x81, 0x11 // CLUSTER (size = 1)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(-1, result);
- EXPECT_FALSE(parser.IsParsingComplete());
-}
-
-// Expecting to parse a Cluster, but get a Segment.
-TEST_F(WebMParserTest, ListIdDoesNotMatch) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x80, // SEGMENT (size = 0)
- };
- int size = sizeof(kBuffer);
-
- WebMListParser parser(kWebMIdCluster, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(-1, result);
- EXPECT_FALSE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, InvalidElementInList) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x82, // SEGMENT (size = 2)
- 0xAE, 0x80, // TrackEntry (size = 0)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(-1, result);
- EXPECT_FALSE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, VoidAndCRC32InList) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x99, // SEGMENT (size = 25)
- 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
- 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
- 0x1F, 0x43, 0xB6, 0x75, 0x8A, // CLUSTER (size = 10)
- 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
- 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
- EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
- EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
- EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(size, result);
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-
-TEST_F(WebMParserTest, ParseListElementWithSingleCall) {
- scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
- CreateClusterExpectations(kBlockCount, true, &client_);
-
- WebMListParser parser(kWebMIdCluster, &client_);
- int result = parser.Parse(cluster->data(), cluster->size());
- EXPECT_EQ(cluster->size(), result);
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, ParseListElementWithMultipleCalls) {
- scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
- CreateClusterExpectations(kBlockCount, true, &client_);
-
- const uint8* data = cluster->data();
- int size = cluster->size();
- int default_parse_size = 3;
- WebMListParser parser(kWebMIdCluster, &client_);
- int parse_size = std::min(default_parse_size, size);
-
- while (size > 0) {
- int result = parser.Parse(data, parse_size);
- ASSERT_GE(result, 0);
- ASSERT_LE(result, parse_size);
-
- if (result == 0) {
- // The parser needs more data so increase the parse_size a little.
- EXPECT_FALSE(parser.IsParsingComplete());
- parse_size += default_parse_size;
- parse_size = std::min(parse_size, size);
- continue;
- }
-
- parse_size = default_parse_size;
-
- data += result;
- size -= result;
-
- EXPECT_EQ((size == 0), parser.IsParsingComplete());
- }
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, TestReset) {
- InSequence s;
- scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
-
- // First expect all but the last block.
- CreateClusterExpectations(kBlockCount - 1, false, &client_);
-
- // Now expect all blocks.
- CreateClusterExpectations(kBlockCount, true, &client_);
-
- WebMListParser parser(kWebMIdCluster, &client_);
-
- // Send slightly less than the full cluster so all but the last block is
- // parsed.
- int result = parser.Parse(cluster->data(), cluster->size() - 1);
- EXPECT_GT(result, 0);
- EXPECT_LT(result, cluster->size());
- EXPECT_FALSE(parser.IsParsingComplete());
-
- parser.Reset();
-
- // Now parse a whole cluster to verify that all the blocks will get parsed.
- result = parser.Parse(cluster->data(), cluster->size());
- EXPECT_EQ(result, cluster->size());
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-// Test the case where multiple clients are used for different lists.
-TEST_F(WebMParserTest, MultipleClients) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x94, // SEGMENT (size = 20)
- 0x16, 0x54, 0xAE, 0x6B, 0x85, // TRACKS (size = 5)
- 0xAE, 0x83, // TRACKENTRY (size = 3)
- 0xD7, 0x81, 0x01, // TRACKNUMBER (size = 1)
- 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5)
- 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
- };
- int size = sizeof(kBuffer);
-
- StrictMock<MockWebMParserClient> c1_;
- StrictMock<MockWebMParserClient> c2_;
- StrictMock<MockWebMParserClient> c3_;
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&c1_));
- EXPECT_CALL(c1_, OnListStart(kWebMIdTracks)).WillOnce(Return(&c2_));
- EXPECT_CALL(c2_, OnListStart(kWebMIdTrackEntry)).WillOnce(Return(&c3_));
- EXPECT_CALL(c3_, OnUInt(kWebMIdTrackNumber, 1)).WillOnce(Return(true));
- EXPECT_CALL(c2_, OnListEnd(kWebMIdTrackEntry)).WillOnce(Return(true));
- EXPECT_CALL(c1_, OnListEnd(kWebMIdTracks)).WillOnce(Return(true));
- EXPECT_CALL(c1_, OnListStart(kWebMIdCluster)).WillOnce(Return(&c2_));
- EXPECT_CALL(c1_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
- EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(size, result);
- EXPECT_TRUE(parser.IsParsingComplete());
-}
-
-// Test the case where multiple clients are used for different lists.
-TEST_F(WebMParserTest, InvalidClient) {
- const uint8 kBuffer[] = {
- 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 20)
- 0x16, 0x54, 0xAE, 0x6B, 0x80, // TRACKS (size = 5)
- };
- int size = sizeof(kBuffer);
-
- InSequence s;
- EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(ReturnNull());
-
- WebMListParser parser(kWebMIdSegment, &client_);
- int result = parser.Parse(kBuffer, size);
- EXPECT_EQ(-1, result);
- EXPECT_FALSE(parser.IsParsingComplete());
-}
-
-TEST_F(WebMParserTest, ReservedIds) {
- const uint8 k1ByteReservedId[] = { 0xFF, 0x81 };
- const uint8 k2ByteReservedId[] = { 0x7F, 0xFF, 0x81 };
- const uint8 k3ByteReservedId[] = { 0x3F, 0xFF, 0xFF, 0x81 };
- const uint8 k4ByteReservedId[] = { 0x1F, 0xFF, 0xFF, 0xFF, 0x81 };
- const uint8* kBuffers[] = {
- k1ByteReservedId,
- k2ByteReservedId,
- k3ByteReservedId,
- k4ByteReservedId
- };
-
- for (size_t i = 0; i < arraysize(kBuffers); i++) {
- int id;
- int64 element_size;
- int buffer_size = 2 + i;
- EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size,
- &id, &element_size));
- EXPECT_EQ(id, kWebMReservedId);
- EXPECT_EQ(element_size, 1);
- }
-}
-
-TEST_F(WebMParserTest, ReservedSizes) {
- const uint8 k1ByteReservedSize[] = { 0xA3, 0xFF };
- const uint8 k2ByteReservedSize[] = { 0xA3, 0x7F, 0xFF };
- const uint8 k3ByteReservedSize[] = { 0xA3, 0x3F, 0xFF, 0xFF };
- const uint8 k4ByteReservedSize[] = { 0xA3, 0x1F, 0xFF, 0xFF, 0xFF };
- const uint8 k5ByteReservedSize[] = { 0xA3, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF };
- const uint8 k6ByteReservedSize[] = { 0xA3, 0x07, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF };
- const uint8 k7ByteReservedSize[] = { 0xA3, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF };
- const uint8 k8ByteReservedSize[] = { 0xA3, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF, 0xFF };
- const uint8* kBuffers[] = {
- k1ByteReservedSize,
- k2ByteReservedSize,
- k3ByteReservedSize,
- k4ByteReservedSize,
- k5ByteReservedSize,
- k6ByteReservedSize,
- k7ByteReservedSize,
- k8ByteReservedSize
- };
-
- for (size_t i = 0; i < arraysize(kBuffers); i++) {
- int id;
- int64 element_size;
- int buffer_size = 2 + i;
- EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size,
- &id, &element_size));
- EXPECT_EQ(id, 0xA3);
- EXPECT_EQ(element_size, kWebMUnknownSize);
- }
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_stream_parser.cc b/src/media/webm/webm_stream_parser.cc
deleted file mode 100644
index b41633a..0000000
--- a/src/media/webm/webm_stream_parser.cc
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/webm_stream_parser.h"
-
-#include <string>
-
-#include "base/callback.h"
-#include "base/logging.h"
-#include "media/webm/webm_cluster_parser.h"
-#include "media/webm/webm_constants.h"
-#include "media/webm/webm_content_encodings.h"
-#include "media/webm/webm_info_parser.h"
-#include "media/webm/webm_tracks_parser.h"
-
-namespace media {
-
-// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
-static const char kWebMInitDataType[] = "video/webm";
-
-WebMStreamParser::WebMStreamParser()
- : state_(kWaitingForInit),
- waiting_for_buffers_(false) {
-}
-
-WebMStreamParser::~WebMStreamParser() {}
-
-void WebMStreamParser::Init(const InitCB& init_cb,
- const NewConfigCB& config_cb,
- const NewBuffersCB& audio_cb,
- const NewBuffersCB& video_cb,
- const NeedKeyCB& need_key_cb,
- const NewMediaSegmentCB& new_segment_cb,
- const base::Closure& end_of_segment_cb,
- const LogCB& log_cb) {
- DCHECK_EQ(state_, kWaitingForInit);
- DCHECK(init_cb_.is_null());
- DCHECK(!init_cb.is_null());
- DCHECK(!config_cb.is_null());
- DCHECK(!audio_cb.is_null() || !video_cb.is_null());
- DCHECK(!need_key_cb.is_null());
- DCHECK(!new_segment_cb.is_null());
- DCHECK(!end_of_segment_cb.is_null());
-
- ChangeState(kParsingHeaders);
- init_cb_ = init_cb;
- config_cb_ = config_cb;
- audio_cb_ = audio_cb;
- video_cb_ = video_cb;
- need_key_cb_ = need_key_cb;
- new_segment_cb_ = new_segment_cb;
- end_of_segment_cb_ = end_of_segment_cb;
- log_cb_ = log_cb;
-}
-
-void WebMStreamParser::Flush() {
- DCHECK_NE(state_, kWaitingForInit);
-
- byte_queue_.Reset();
-
- if (state_ != kParsingClusters)
- return;
-
- cluster_parser_->Reset();
-}
-
-bool WebMStreamParser::Parse(const uint8* buf, int size) {
- DCHECK_NE(state_, kWaitingForInit);
-
- if (state_ == kError)
- return false;
-
- byte_queue_.Push(buf, size);
-
- int result = 0;
- int bytes_parsed = 0;
- const uint8* cur = NULL;
- int cur_size = 0;
-
- byte_queue_.Peek(&cur, &cur_size);
- while (cur_size > 0) {
- State oldState = state_;
- switch (state_) {
- case kParsingHeaders:
- result = ParseInfoAndTracks(cur, cur_size);
- break;
-
- case kParsingClusters:
- result = ParseCluster(cur, cur_size);
- break;
-
- case kWaitingForInit:
- case kError:
- return false;
- }
-
- if (result < 0) {
- ChangeState(kError);
- return false;
- }
-
- if (state_ == oldState && result == 0)
- break;
-
- DCHECK_GE(result, 0);
- cur += result;
- cur_size -= result;
- bytes_parsed += result;
- }
-
- byte_queue_.Pop(bytes_parsed);
- return true;
-}
-
-void WebMStreamParser::ChangeState(State new_state) {
- DVLOG(1) << "ChangeState() : " << state_ << " -> " << new_state;
- state_ = new_state;
-}
-
-int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) {
- DVLOG(2) << "ParseInfoAndTracks()";
- DCHECK(data);
- DCHECK_GT(size, 0);
-
- const uint8* cur = data;
- int cur_size = size;
- int bytes_parsed = 0;
-
- int id;
- int64 element_size;
- int result = WebMParseElementHeader(cur, cur_size, &id, &element_size);
-
- if (result <= 0)
- return result;
-
- switch (id) {
- case kWebMIdEBMLHeader:
- case kWebMIdSeekHead:
- case kWebMIdVoid:
- case kWebMIdCRC32:
- case kWebMIdCues:
- case kWebMIdTags:
- if (cur_size < (result + element_size)) {
- // We don't have the whole element yet. Signal we need more data.
- return 0;
- }
- // Skip the element.
- return result + element_size;
- break;
- case kWebMIdSegment:
- // Just consume the segment header.
- return result;
- break;
- case kWebMIdInfo:
- // We've found the element we are looking for.
- break;
- default: {
- MEDIA_LOG(log_cb_) << "Unexpected element ID 0x" << std::hex << id;
- return -1;
- }
- }
-
- WebMInfoParser info_parser;
- result = info_parser.Parse(cur, cur_size);
-
- if (result <= 0)
- return result;
-
- cur += result;
- cur_size -= result;
- bytes_parsed += result;
-
- WebMTracksParser tracks_parser(log_cb_);
- result = tracks_parser.Parse(cur, cur_size);
-
- if (result <= 0)
- return result;
-
- bytes_parsed += result;
-
- base::TimeDelta duration = kInfiniteDuration();
-
- if (info_parser.duration() > 0) {
- double mult = info_parser.timecode_scale() / 1000.0;
- int64 duration_in_us = info_parser.duration() * mult;
- duration = base::TimeDelta::FromMicroseconds(duration_in_us);
- }
-
- const AudioDecoderConfig& audio_config = tracks_parser.audio_decoder_config();
- if (audio_config.is_encrypted())
- FireNeedKey(tracks_parser.audio_encryption_key_id());
-
- const VideoDecoderConfig& video_config = tracks_parser.video_decoder_config();
- if (video_config.is_encrypted())
- FireNeedKey(tracks_parser.video_encryption_key_id());
-
- if (!config_cb_.Run(audio_config, video_config)) {
- DVLOG(1) << "New config data isn't allowed.";
- return -1;
- }
-
- cluster_parser_.reset(new WebMClusterParser(
- info_parser.timecode_scale(),
- tracks_parser.audio_track_num(),
- tracks_parser.video_track_num(),
- tracks_parser.audio_encryption_key_id(),
- tracks_parser.video_encryption_key_id(),
- log_cb_));
-
- ChangeState(kParsingClusters);
-
- if (!init_cb_.is_null()) {
- init_cb_.Run(true, duration);
- init_cb_.Reset();
- }
-
- return bytes_parsed;
-}
-
-int WebMStreamParser::ParseCluster(const uint8* data, int size) {
- if (!cluster_parser_.get())
- return -1;
-
- int id;
- int64 element_size;
- int result = WebMParseElementHeader(data, size, &id, &element_size);
-
- if (result <= 0)
- return result;
-
- if (id == kWebMIdCluster)
- waiting_for_buffers_ = true;
-
- if (id == kWebMIdCues) {
- if (size < (result + element_size)) {
- // We don't have the whole element yet. Signal we need more data.
- return 0;
- }
- // Skip the element.
- return result + element_size;
- }
-
- if (id == kWebMIdEBMLHeader) {
- ChangeState(kParsingHeaders);
- return 0;
- }
-
- int bytes_parsed = cluster_parser_->Parse(data, size);
-
- if (bytes_parsed <= 0)
- return bytes_parsed;
-
- const BufferQueue& audio_buffers = cluster_parser_->audio_buffers();
- const BufferQueue& video_buffers = cluster_parser_->video_buffers();
- base::TimeDelta cluster_start_time = cluster_parser_->cluster_start_time();
- bool cluster_ended = cluster_parser_->cluster_ended();
-
- if (waiting_for_buffers_ && cluster_start_time != kNoTimestamp()) {
- new_segment_cb_.Run(cluster_start_time);
- waiting_for_buffers_ = false;
- }
-
- if (!audio_buffers.empty() && !audio_cb_.Run(audio_buffers))
- return -1;
-
- if (!video_buffers.empty() && !video_cb_.Run(video_buffers))
- return -1;
-
- if (cluster_ended)
- end_of_segment_cb_.Run();
-
- return bytes_parsed;
-}
-
-void WebMStreamParser::FireNeedKey(const std::string& key_id) {
- int key_id_size = key_id.size();
- DCHECK_GT(key_id_size, 0);
- scoped_array<uint8> key_id_array(new uint8[key_id_size]);
- memcpy(key_id_array.get(), key_id.data(), key_id_size);
- need_key_cb_.Run(kWebMInitDataType, key_id_array.Pass(), key_id_size);
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_stream_parser.h b/src/media/webm/webm_stream_parser.h
deleted file mode 100644
index d9e9a92..0000000
--- a/src/media/webm/webm_stream_parser.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_STREAM_PARSER_H_
-#define MEDIA_WEBM_WEBM_STREAM_PARSER_H_
-
-#include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/buffers.h"
-#include "media/base/byte_queue.h"
-#include "media/base/stream_parser.h"
-#include "media/base/video_decoder_config.h"
-#include "media/webm/webm_cluster_parser.h"
-
-namespace media {
-
-class WebMStreamParser : public StreamParser {
- public:
- WebMStreamParser();
- virtual ~WebMStreamParser();
-
- // StreamParser implementation.
- virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb,
- const NewBuffersCB& audio_cb,
- const NewBuffersCB& video_cb,
- const NeedKeyCB& need_key_cb,
- const NewMediaSegmentCB& new_segment_cb,
- const base::Closure& end_of_segment_cb,
- const LogCB& log_cb) OVERRIDE;
-
- virtual void Flush() OVERRIDE;
- virtual bool Parse(const uint8* buf, int size) OVERRIDE;
-
- private:
- enum State {
- kWaitingForInit,
- kParsingHeaders,
- kParsingClusters,
- kError
- };
-
- void ChangeState(State new_state);
-
- // Parses WebM Header, Info, Tracks elements. It also skips other level 1
- // elements that are not used right now. Once the Info & Tracks elements have
- // been parsed, this method will transition the parser from PARSING_HEADERS to
- // PARSING_CLUSTERS.
- //
- // Returns < 0 if the parse fails.
- // Returns 0 if more data is needed.
- // Returning > 0 indicates success & the number of bytes parsed.
- int ParseInfoAndTracks(const uint8* data, int size);
-
- // Incrementally parses WebM cluster elements. This method also skips
- // CUES elements if they are encountered since we currently don't use the
- // data in these elements.
- //
- // Returns < 0 if the parse fails.
- // Returns 0 if more data is needed.
- // Returning > 0 indicates success & the number of bytes parsed.
- int ParseCluster(const uint8* data, int size);
-
- // Fire needkey event through the |need_key_cb_|.
- void FireNeedKey(const std::string& key_id);
-
- State state_;
- InitCB init_cb_;
- NewConfigCB config_cb_;
- NewBuffersCB audio_cb_;
- NewBuffersCB video_cb_;
- NeedKeyCB need_key_cb_;
- NewMediaSegmentCB new_segment_cb_;
- base::Closure end_of_segment_cb_;
- LogCB log_cb_;
-
- // True if a new cluster id has been seen, but no audio or video buffers have
- // been parsed yet.
- bool waiting_for_buffers_;
-
- scoped_ptr<WebMClusterParser> cluster_parser_;
- ByteQueue byte_queue_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMStreamParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_STREAM_PARSER_H_
diff --git a/src/media/webm/webm_tracks_parser.cc b/src/media/webm/webm_tracks_parser.cc
deleted file mode 100644
index c4b8613..0000000
--- a/src/media/webm/webm_tracks_parser.cc
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright (c) 2012 The Chromium 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 "media/webm/webm_tracks_parser.h"
-
-#include "base/logging.h"
-#include "base/string_util.h"
-#include "media/base/buffers.h"
-#include "media/webm/webm_constants.h"
-#include "media/webm/webm_content_encodings.h"
-
-namespace media {
-
-// Values for TrackType element.
-static const int kWebMTrackTypeVideo = 1;
-static const int kWebMTrackTypeAudio = 2;
-
-WebMTracksParser::WebMTracksParser(const LogCB& log_cb)
- : track_type_(-1),
- track_num_(-1),
- audio_track_num_(-1),
- video_track_num_(-1),
- log_cb_(log_cb),
- audio_client_(log_cb),
- video_client_(log_cb) {
-}
-
-WebMTracksParser::~WebMTracksParser() {}
-
-int WebMTracksParser::Parse(const uint8* buf, int size) {
- track_type_ =-1;
- track_num_ = -1;
- audio_track_num_ = -1;
- AudioDecoderConfig invalid_audio_decoder_config;
- audio_decoder_config_.CopyFrom(invalid_audio_decoder_config);
- video_track_num_ = -1;
- VideoDecoderConfig invalid_video_decoder_config;
- video_decoder_config_.CopyFrom(invalid_video_decoder_config);
-
- WebMListParser parser(kWebMIdTracks, this);
- int result = parser.Parse(buf, size);
-
- if (result <= 0)
- return result;
-
- // For now we do all or nothing parsing.
- return parser.IsParsingComplete() ? result : 0;
-}
-
-WebMParserClient* WebMTracksParser::OnListStart(int id) {
- if (id == kWebMIdContentEncodings) {
- DCHECK(!track_content_encodings_client_.get());
- track_content_encodings_client_.reset(
- new WebMContentEncodingsClient(log_cb_));
- return track_content_encodings_client_->OnListStart(id);
- }
-
- if (id == kWebMIdTrackEntry) {
- track_type_ = -1;
- track_num_ = -1;
- codec_id_ = "";
- codec_private_.clear();
- audio_client_.Reset();
- video_client_.Reset();
- return this;
- }
-
- if (id == kWebMIdAudio)
- return &audio_client_;
-
- if (id == kWebMIdVideo)
- return &video_client_;
-
- return this;
-}
-
-bool WebMTracksParser::OnListEnd(int id) {
- if (id == kWebMIdContentEncodings) {
- DCHECK(track_content_encodings_client_.get());
- return track_content_encodings_client_->OnListEnd(id);
- }
-
- if (id == kWebMIdTrackEntry) {
- if (track_type_ == -1 || track_num_ == -1) {
- MEDIA_LOG(log_cb_) << "Missing TrackEntry data for "
- << " TrackType " << track_type_
- << " TrackNum " << track_num_;
- return false;
- }
-
- if (track_type_ != kWebMTrackTypeAudio &&
- track_type_ != kWebMTrackTypeVideo) {
- MEDIA_LOG(log_cb_) << "Unexpected TrackType " << track_type_;
- return false;
- }
-
- std::string encryption_key_id;
- if (track_content_encodings_client_.get()) {
- DCHECK(!track_content_encodings_client_->content_encodings().empty());
- // If we have multiple ContentEncoding in one track. Always choose the
- // key id in the first ContentEncoding as the key id of the track.
- encryption_key_id = track_content_encodings_client_->
- content_encodings()[0]->encryption_key_id();
- }
-
- if (track_type_ == kWebMTrackTypeAudio) {
- if (audio_track_num_ == -1) {
- audio_track_num_ = track_num_;
- audio_encryption_key_id_ = encryption_key_id;
-
- DCHECK(!audio_decoder_config_.IsValidConfig());
- if (!audio_client_.InitializeConfig(
- codec_id_, codec_private_, 0/*seek_preroll_*/, 0/*codec_delay_*/,
- !audio_encryption_key_id_.empty(), &audio_decoder_config_)) {
- return false;
- }
- } else {
- MEDIA_LOG(log_cb_) << "Ignoring audio track " << track_num_;
- }
- } else if (track_type_ == kWebMTrackTypeVideo) {
- if (video_track_num_ == -1) {
- video_track_num_ = track_num_;
- video_encryption_key_id_ = encryption_key_id;
-
- DCHECK(!video_decoder_config_.IsValidConfig());
- if (!video_client_.InitializeConfig(
- codec_id_, codec_private_, !video_encryption_key_id_.empty(),
- &video_decoder_config_)) {
- return false;
- }
- } else {
- MEDIA_LOG(log_cb_) << "Ignoring video track " << track_num_;
- }
- }
-
- track_type_ = -1;
- track_num_ = -1;
- track_content_encodings_client_.reset();
- codec_id_ = "";
- codec_private_.clear();
- audio_client_.Reset();
- video_client_.Reset();
- return true;
- }
-
- return true;
-}
-
-bool WebMTracksParser::OnUInt(int id, int64 val) {
- int64* dst = NULL;
-
- switch (id) {
- case kWebMIdTrackNumber:
- dst = &track_num_;
- break;
- case kWebMIdTrackType:
- dst = &track_type_;
- break;
- default:
- return true;
- }
-
- if (*dst != -1) {
- MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
- << " specified";
- return false;
- }
-
- *dst = val;
- return true;
-}
-
-bool WebMTracksParser::OnFloat(int id, double val) {
- return true;
-}
-
-bool WebMTracksParser::OnBinary(int id, const uint8* data, int size) {
- if (id == kWebMIdCodecPrivate) {
- if (!codec_private_.empty()) {
- MEDIA_LOG(log_cb_) << "Multiple CodecPrivate fields in a track.";
- return false;
- }
-
- codec_private_.assign(data, data + size);
- return true;
- }
- return true;
-}
-
-bool WebMTracksParser::OnString(int id, const std::string& str) {
- if (id == kWebMIdCodecID) {
- if (!codec_id_.empty()) {
- MEDIA_LOG(log_cb_) << "Multiple CodecID fields in a track";
- return false;
- }
-
- codec_id_ = str;
- return true;
- }
-
- return true;
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_tracks_parser.h b/src/media/webm/webm_tracks_parser.h
deleted file mode 100644
index b5b043b..0000000
--- a/src/media/webm/webm_tracks_parser.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_TRACKS_PARSER_H_
-#define MEDIA_WEBM_WEBM_TRACKS_PARSER_H_
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/media_log.h"
-#include "media/base/video_decoder_config.h"
-#include "media/webm/webm_audio_client.h"
-#include "media/webm/webm_content_encodings_client.h"
-#include "media/webm/webm_parser.h"
-#include "media/webm/webm_video_client.h"
-
-namespace media {
-
-// Parser for WebM Tracks element.
-class WebMTracksParser : public WebMParserClient {
- public:
- explicit WebMTracksParser(const LogCB& log_cb);
- virtual ~WebMTracksParser();
-
- // Parses a WebM Tracks element in |buf|.
- //
- // Returns -1 if the parse fails.
- // Returns 0 if more data is needed.
- // Returns the number of bytes parsed on success.
- int Parse(const uint8* buf, int size);
-
- int64 audio_track_num() const { return audio_track_num_; }
- int64 video_track_num() const { return video_track_num_; }
- const std::string& audio_encryption_key_id() const {
- return audio_encryption_key_id_;
- }
- const AudioDecoderConfig& audio_decoder_config() {
- return audio_decoder_config_;
- }
- const std::string& video_encryption_key_id() const {
- return video_encryption_key_id_;
- }
- const VideoDecoderConfig& video_decoder_config() {
- return video_decoder_config_;
- }
-
- private:
- // WebMParserClient methods
- virtual WebMParserClient* OnListStart(int id) OVERRIDE;
- virtual bool OnListEnd(int id) OVERRIDE;
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnFloat(int id, double val) OVERRIDE;
- virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
- virtual bool OnString(int id, const std::string& str) OVERRIDE;
-
- int64 track_type_;
- int64 track_num_;
- std::string codec_id_;
- std::vector<uint8> codec_private_;
- scoped_ptr<WebMContentEncodingsClient> track_content_encodings_client_;
-
- int64 audio_track_num_;
- int64 video_track_num_;
- std::string audio_encryption_key_id_;
- std::string video_encryption_key_id_;
- LogCB log_cb_;
-
- WebMAudioClient audio_client_;
- AudioDecoderConfig audio_decoder_config_;
-
- WebMVideoClient video_client_;
- VideoDecoderConfig video_decoder_config_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMTracksParser);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_TRACKS_PARSER_H_
diff --git a/src/media/webm/webm_video_client.cc b/src/media/webm/webm_video_client.cc
deleted file mode 100644
index bcd3b87..0000000
--- a/src/media/webm/webm_video_client.cc
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2014 The Chromium 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 "media/webm/webm_video_client.h"
-
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_types.h"
-#include "media/webm/webm_constants.h"
-
-namespace media {
-
-WebMVideoClient::WebMVideoClient(const LogCB& log_cb)
- : log_cb_(log_cb) {
- Reset();
-}
-
-WebMVideoClient::~WebMVideoClient() {
-}
-
-void WebMVideoClient::Reset() {
- pixel_width_ = -1;
- pixel_height_ = -1;
- crop_bottom_ = -1;
- crop_top_ = -1;
- crop_left_ = -1;
- crop_right_ = -1;
- display_width_ = -1;
- display_height_ = -1;
- display_unit_ = -1;
- alpha_mode_ = -1;
- inside_projection_list_ = false;
- colour_parsed_ = false;
-}
-
-bool WebMVideoClient::InitializeConfig(
- const std::string& codec_id, const std::vector<uint8>& codec_private,
- bool is_encrypted, VideoDecoderConfig* config) {
- DCHECK(config);
-
- VideoCodec video_codec = kUnknownVideoCodec;
- VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
- if (codec_id == "V_VP8") {
- video_codec = kCodecVP8;
- profile = VP8PROFILE_MAIN;
- } else if (codec_id == "V_VP9") {
- video_codec = kCodecVP9;
- profile = VP9PROFILE_MAIN;
- } else {
- MEDIA_LOG(log_cb_) << "Unsupported video codec_id " << codec_id;
- return false;
- }
-
- // We don't support YV12A.
- DCHECK_NE(alpha_mode_, 1);
- if (alpha_mode_ == 1)
- return false;
-
- VideoFrame::Format format = VideoFrame::YV12;
-
- if (pixel_width_ <= 0 || pixel_height_ <= 0)
- return false;
-
- // Set crop and display unit defaults if these elements are not present.
- if (crop_bottom_ == -1)
- crop_bottom_ = 0;
-
- if (crop_top_ == -1)
- crop_top_ = 0;
-
- if (crop_left_ == -1)
- crop_left_ = 0;
-
- if (crop_right_ == -1)
- crop_right_ = 0;
-
- if (display_unit_ == -1)
- display_unit_ = 0;
-
- gfx::Size coded_size(pixel_width_, pixel_height_);
- gfx::Rect visible_rect(crop_top_, crop_left_,
- pixel_width_ - (crop_left_ + crop_right_),
- pixel_height_ - (crop_top_ + crop_bottom_));
- if (display_unit_ == 0) {
- if (display_width_ <= 0)
- display_width_ = visible_rect.width();
- if (display_height_ <= 0)
- display_height_ = visible_rect.height();
- } else if (display_unit_ == 3) {
- if (display_width_ <= 0 || display_height_ <= 0)
- return false;
- } else {
- MEDIA_LOG(log_cb_) << "Unsupported display unit type " << display_unit_;
- return false;
- }
- gfx::Size natural_size = gfx::Size(display_width_, display_height_);
- const uint8* extra_data = NULL;
- size_t extra_data_size = 0;
- if (codec_private.size() > 0) {
- extra_data = &codec_private[0];
- extra_data_size = codec_private.size();
- }
-
- config->Initialize(
- video_codec, profile, format, COLOR_SPACE_HD_REC709, coded_size,
- visible_rect, natural_size, extra_data, extra_data_size, is_encrypted,
- true);
- if (colour_parsed_) {
- WebMColorMetadata color_metadata = colour_parser_.GetWebMColorMetadata();
- config->set_webm_color_metadata(color_metadata);
- }
- return config->IsValidConfig();
-}
-
-bool WebMVideoClient::OnUInt(int id, int64 val) {
- if (inside_projection_list_) {
- // Accept and ignore all integer fields under kWebMIdProjection list. This
- // currently includes the kWebMIdProjectionType field.
- return true;
- }
-
- int64* dst = NULL;
-
- switch (id) {
- case kWebMIdPixelWidth:
- dst = &pixel_width_;
- break;
- case kWebMIdPixelHeight:
- dst = &pixel_height_;
- break;
- case kWebMIdPixelCropTop:
- dst = &crop_top_;
- break;
- case kWebMIdPixelCropBottom:
- dst = &crop_bottom_;
- break;
- case kWebMIdPixelCropLeft:
- dst = &crop_left_;
- break;
- case kWebMIdPixelCropRight:
- dst = &crop_right_;
- break;
- case kWebMIdDisplayWidth:
- dst = &display_width_;
- break;
- case kWebMIdDisplayHeight:
- dst = &display_height_;
- break;
- case kWebMIdDisplayUnit:
- dst = &display_unit_;
- break;
- case kWebMIdAlphaMode:
- dst = &alpha_mode_;
- break;
- default:
- return true;
- }
-
- if (*dst != -1) {
- MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
- << " specified (" << *dst << " and " << val << ")";
- return false;
- }
-
- *dst = val;
- return true;
-}
-
-bool WebMVideoClient::OnBinary(int id, const uint8* data, int size) {
- if (inside_projection_list_) {
- // Accept and ignore all binary fields under kWebMIdProjection list. This
- // currently includes the kWebMIdProjectionPrivate field.
- return true;
- }
-
- // Accept binary fields we don't care about for now.
- return true;
-}
-
-bool WebMVideoClient::OnFloat(int id, double val) {
- if (inside_projection_list_) {
- // Accept and ignore float fields under kWebMIdProjection list. This
- // currently includes the kWebMIdProjectionPosePitch,
- // kWebMIdProjectionPoseYaw, kWebMIdProjectionPoseRoll fields.
- return true;
- }
- // Accept float fields we don't care about for now.
- return true;
-}
-
-WebMParserClient* WebMVideoClient::OnListStart(int id) {
- if (id == kWebMIdProjection && !inside_projection_list_) {
- inside_projection_list_ = true;
- return this;
- } else if (id == kWebMIdColour) {
- colour_parsed_ = false;
- return &colour_parser_;
- } else {
- return WebMParserClient::OnListStart(id);
- }
-}
-
-bool WebMVideoClient::OnListEnd(int id) {
- if (id == kWebMIdProjection && inside_projection_list_) {
- inside_projection_list_ = false;
- return true;
- } else if (id == kWebMIdColour) {
- colour_parsed_ = true;
- return true;
- } else {
- return WebMParserClient::OnListEnd(id);
- }
-}
-
-} // namespace media
diff --git a/src/media/webm/webm_video_client.h b/src/media/webm/webm_video_client.h
deleted file mode 100644
index ff60d1e..0000000
--- a/src/media/webm/webm_video_client.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_
-#define MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_
-
-#include <string>
-#include <vector>
-
-#include "media/base/media_log.h"
-#include "media/webm/webm_colour_parser.h"
-#include "media/webm/webm_parser.h"
-
-namespace media {
-class VideoDecoderConfig;
-
-// Helper class used to parse a Video element inside a TrackEntry element.
-class WebMVideoClient : public WebMParserClient {
- public:
- explicit WebMVideoClient(const LogCB& log_cb);
- virtual ~WebMVideoClient();
-
- // Reset this object's state so it can process a new video track element.
- void Reset();
-
- // Initialize |config| with the data in |codec_id|, |codec_private|,
- // |is_encrypted| and the fields parsed from the last video track element this
- // object was used to parse.
- // Returns true if |config| was successfully initialized.
- // Returns false if there was unexpected values in the provided parameters or
- // video track element fields. The contents of |config| are undefined in this
- // case and should not be relied upon.
- bool InitializeConfig(const std::string& codec_id,
- const std::vector<uint8>& codec_private,
- bool is_encrypted,
- VideoDecoderConfig* config);
-
- private:
- // WebMParserClient implementation.
- virtual bool OnUInt(int id, int64 val) OVERRIDE;
- virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
- virtual bool OnFloat(int id, double val) OVERRIDE;
- // Exist to ignore the new Projection list element. This element has UINT,
- // FLOAT and BINARY fields which must also be ignored.
- WebMParserClient* OnListStart(int id) OVERRIDE;
- bool OnListEnd(int id) OVERRIDE;
-
- LogCB log_cb_;
- int64 pixel_width_;
- int64 pixel_height_;
- int64 crop_bottom_;
- int64 crop_top_;
- int64 crop_left_;
- int64 crop_right_;
- int64 display_width_;
- int64 display_height_;
- int64 display_unit_;
- int64 alpha_mode_;
- bool inside_projection_list_;
-
- WebMColourParser colour_parser_;
- bool colour_parsed_;
-
- DISALLOW_COPY_AND_ASSIGN(WebMVideoClient);
-};
-
-} // namespace media
-
-#endif // MEDIA_WEBM_WEBM_VIDEO_CLIENT_H_
diff --git a/src/net/url_request/url_request_job_manager.cc b/src/net/url_request/url_request_job_manager.cc
index ed0f5db..b497b13 100644
--- a/src/net/url_request/url_request_job_manager.cc
+++ b/src/net/url_request/url_request_job_manager.cc
@@ -40,9 +40,6 @@
{"https", URLRequestHttpJob::Factory},
{"data", URLRequestDataJob::Factory},
{ "http", URLRequestHttpJob::Factory },
-#if defined(COBALT_ENABLE_FILE_SCHEME)
- { "file", URLRequestFileJob::Factory },
-#endif
};
#else
static const SchemeToFactory kBuiltinFactories[] = {
diff --git a/src/starboard/CHANGELOG.md b/src/starboard/CHANGELOG.md
index a77e4e1..fa69e65 100644
--- a/src/starboard/CHANGELOG.md
+++ b/src/starboard/CHANGELOG.md
@@ -6,6 +6,8 @@
this file describes the changes made to the Starboard interface since the
version previous to it.
+**NOTE: Starboard versions 3 and below are no longer supported.**
+
## Version 7
### `SbDecodeTargetInfoPlane` can specify color plane information
@@ -61,10 +63,13 @@
Add support for UYVY decode targets (e.g. YUV 422) via the
`kSbDecodeTargetFormat1PlaneUYVY` enum.
-### Add Color Remote Keys
+### Add More Remote Keys
-This adds SbKey codes for the colored keys found on most contemporary TV
-remotes.
+This adds SbKey codes for:
+
+ * Color keys
+ * Closed Caption key
+ * Application launch key
### `kSbEventTypeLowMemory`
diff --git a/src/starboard/accessibility.h b/src/starboard/accessibility.h
index 3e63bf1..e402f34 100644
--- a/src/starboard/accessibility.h
+++ b/src/starboard/accessibility.h
@@ -26,8 +26,6 @@
extern "C" {
#endif
-#if SB_API_VERSION >= 4
-
// A group of settings related to text-to-speech functionality, for platforms
// that expose system settings for text-to-speech.
typedef struct SbAccessibilityTextToSpeechSettings {
@@ -65,8 +63,6 @@
SB_EXPORT bool SbAccessibilityGetDisplaySettings(
SbAccessibilityDisplaySettings* out_settings);
-#endif // SB_API_VERSION >= 4
-
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/src/starboard/atomic.h b/src/starboard/atomic.h
index e9609c7..80c391f 100644
--- a/src/starboard/atomic.h
+++ b/src/starboard/atomic.h
@@ -436,7 +436,7 @@
class atomic_integral;
///////////////////////////////////////////////////////////////////////////////
-// Implimentation.
+// Implementation.
///////////////////////////////////////////////////////////////////////////////
// Similar to C++11 std::atomic<T>.
diff --git a/src/starboard/build/copy_data.py b/src/starboard/build/copy_data.py
index c19c020..a2bfdda 100644
--- a/src/starboard/build/copy_data.py
+++ b/src/starboard/build/copy_data.py
@@ -85,8 +85,14 @@
# paths (especially on build machines) such that shutil.copy fails.
filename = win32api.GetShortPathName(filename)
output_basedir = win32api.GetShortPathName(output_basedir)
- output_filename = posixpath.join(output_basedir, relative_filename)
- output_dir = posixpath.dirname(output_filename)
+
+ # In certain cases, files would fail to open on windows if relative paths
+ # were provided. Using absolute paths fixes this.
+ filename = os.path.abspath(filename)
+ output_basedir = os.path.abspath(output_basedir)
+ output_filename = os.path.abspath(os.path.join(output_basedir,
+ relative_filename))
+ output_dir = os.path.dirname(output_filename)
# In cases where a directory has turned into a file or vice versa, delete it
# before copying it below.
@@ -97,6 +103,7 @@
if not os.path.exists(output_dir):
os.makedirs(output_dir)
+
shutil.copy(filename, output_filename)
diff --git a/src/starboard/common/optional.h b/src/starboard/common/optional.h
index 8e8fc00..4c892f5 100644
--- a/src/starboard/common/optional.h
+++ b/src/starboard/common/optional.h
@@ -215,6 +215,8 @@
explicit operator bool() const { return engaged_; }
#endif
+ bool has_engaged() const { return engaged_; }
+
// Dereferences the internal object.
const T* operator->() const { return &(value()); }
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index e8a4f90..b9a1f5c 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -31,11 +31,14 @@
#error "You must define STARBOARD in Starboard builds."
#endif
+#define SB_TRUE 1
+#define SB_FALSE 0
+
// --- Common Defines --------------------------------------------------------
// The minimum API version allowed by this version of the Starboard headers,
// inclusive.
-#define SB_MINIMUM_API_VERSION 1
+#define SB_MINIMUM_API_VERSION 4
// The maximum API version allowed by this version of the Starboard headers,
// inclusive.
@@ -73,11 +76,14 @@
#define SB_DECODE_TARGET_PLANES_FOR_FORMAT SB_RELEASE_CANDIDATE_API_VERSION
#define SB_PRELOAD_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
#define SB_PLATFORM_ERROR_CLEANUP_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_COLOR_KEYCODES_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION \
+ SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_NEW_KEYCODES_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
#define SB_LOW_MEMORY_EVENT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_PLAYER_WRITE_SAMPLE_EXTRA_CONST_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_PLAYER_WRITE_SAMPLE_EXTRA_CONST_API_VERSION \
+ SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION \
+ SB_RELEASE_CANDIDATE_API_VERSION
#define SB_STORAGE_NAMES_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
// --- Common Detected Features ----------------------------------------------
@@ -106,11 +112,6 @@
#define SB_HAS_QUIRK(SB_FEATURE) \
((defined SB_HAS_QUIRK_##SB_FEATURE) && SB_HAS_QUIRK_##SB_FEATURE)
-// Determines at compile-time if this platform implements a given Starboard API
-// version number (or above).
-// This macro is deprecated, please instead use the expanded form directly.
-#define SB_VERSION(SB_API) (SB_API_VERSION >= SB_API)
-
// A constant expression that evaluates to the size_t size of a statically-sized
// array.
#define SB_ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
@@ -298,7 +299,6 @@
#define SB_DEPRECATED_EXTERNAL(FUNC) SB_DEPRECATED(FUNC)
#endif
-#if SB_API_VERSION >= 4
// Macro to annotate a function as noreturn, which signals to the compiler
// that the function cannot return.
#if !defined(SB_NORETURN)
@@ -311,7 +311,6 @@
#define SB_NORETURN
#endif
#endif // SB_NORETURN
-#endif // SB_API_VERSION >= 4
// Specifies the alignment for a class, struct, union, enum, class/struct field,
// or stack variable.
@@ -468,39 +467,18 @@
#error "Your platform must define SB_MAX_THREAD_NAME_LENGTH."
#endif
-#if SB_API_VERSION >= 2 && !defined(SB_HAS_MICROPHONE)
+#if !defined(SB_HAS_MICROPHONE)
#error "Your platform must define SB_HAS_MICROPHONE in API versions 2 or later."
#endif
-#if SB_API_VERSION >= 3 && !defined(SB_HAS_TIME_THREAD_NOW)
+#if !defined(SB_HAS_TIME_THREAD_NOW)
#error "Your platform must define SB_HAS_TIME_THREAD_NOW in API 3 or later."
#endif
-#if SB_API_VERSION < 4
-#if SB_HAS(PLAYER)
-#if !SB_IS(PLAYER_COMPOSITED) && !SB_IS(PLAYER_PUNCHED_OUT) && \
- !SB_IS(PLAYER_PRODUCING_TEXTURE)
-#error "Your player must choose a composition method."
-#endif
-#if SB_IS(PLAYER_COMPOSITED) && SB_IS(PLAYER_PUNCHED_OUT)
-#error "Your player can't be both composited and punched-out."
-#elif SB_IS(PLAYER_COMPOSITED) && SB_IS(PLAYER_PRODUCING_TEXTURE)
-#error "Your player can't be both composited and producing a texture."
-#elif SB_IS(PLAYER_PUNCHED_OUT) && SB_IS(PLAYER_PRODUCING_TEXTURE)
-#error "Your player can't be both punched-out and producing a texture."
-#endif
-#else // SB_HAS(PLAYER)
-#if SB_IS(PLAYER_COMPOSITED) || SB_IS(PLAYER_PUNCHED_OUT) || \
- SB_IS(PLAYER_PRODUCING_TEXTURE)
-#error "Your player can't have a composition method if it doesn't exist."
-#endif
-#endif // SB_HAS(PLAYER)
-#else // SB_API_VERSION < 4
#if defined(SB_IS_PLAYER_COMPOSITITED) || defined(SB_IS_PLAYER_PUNCHED_OUT) || \
defined(SB_IS_PLAYER_PRODUCING_TEXTURE)
#error "New versions of Starboard specify player output mode at runtime."
#endif
-#endif // // SB_API_VERSION < 4
#if (SB_HAS(MANY_CORES) && (SB_HAS(1_CORE) || SB_HAS(2_CORES) || \
SB_HAS(4_CORES) || SB_HAS(6_CORES))) || \
@@ -541,8 +519,6 @@
#error "Your platform must define SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER."
#endif
-#if SB_API_VERSION >= 4
-
#if !defined(SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND)
#error \
"Your platform must define SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND."
@@ -553,10 +529,6 @@
"Your platform must define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND."
#endif // !defined(SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND)
-#endif // SB_API_VERSION >= 4
-
-#if SB_API_VERSION >= 4
-
#if defined(SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT)
#error "SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT is deprecated."
#error "Use gyp variable |cobalt_media_buffer_non_video_budget| instead."
@@ -576,8 +548,6 @@
#error "SB_MEDIA_GPU_BUFFER_BUDGET is deprecated."
#endif // defined(SB_MEDIA_GPU_BUFFER_BUDGET)
-#endif // SB_API_VERSION >= 4
-
#if SB_API_VERSION >= 5
#if !defined(SB_HAS_SPEECH_RECOGNIZER)
#error "Your platform must define SB_HAS_SPEECH_RECOGNIZER."
diff --git a/src/starboard/cryptography.h b/src/starboard/cryptography.h
index 8f700a9..2aefa7e 100644
--- a/src/starboard/cryptography.h
+++ b/src/starboard/cryptography.h
@@ -50,8 +50,6 @@
#include "starboard/export.h"
#include "starboard/types.h"
-#if SB_API_VERSION >= 4
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -212,6 +210,4 @@
} // extern "C"
#endif
-#endif // SB_API_VERSION >= 4
-
#endif // STARBOARD_CRYPTOGRAPHY_H_
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
index c437c96..40a680f 100644
--- a/src/starboard/decode_target.h
+++ b/src/starboard/decode_target.h
@@ -96,15 +96,8 @@
#include "starboard/log.h"
#include "starboard/types.h"
-#if SB_API_VERSION >= 3
-
#if SB_HAS(BLITTER)
#include "starboard/blitter.h"
-#elif SB_HAS(GLES2) // SB_HAS(BLITTER)
-#if SB_API_VERSION < 4
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-#endif // SB_API_VERSION < 4
#endif // SB_HAS(BLITTER)
#ifdef __cplusplus
@@ -180,7 +173,6 @@
kSbDecodeTargetPlaneV = 2,
} SbDecodeTargetPlane;
-#if SB_API_VERSION >= 4
#if SB_HAS(GLES2)
struct SbDecodeTargetGraphicsContextProvider;
@@ -236,35 +228,6 @@
#endif // SB_HAS(BLITTER)
} SbDecodeTargetGraphicsContextProvider;
-#else // SB_API_VERSION >= 4
-// A function that can produce an SbDecodeTarget of the given |format|, |width|,
-// and |height|.
-typedef SbDecodeTarget (*SbDecodeTargetAcquireFunction)(
- void* context,
- SbDecodeTargetFormat format,
- int width,
- int height);
-
-// A function that can reclaim an SbDecodeTarget allocated with an
-// SbDecodeTargetAcquireFunction.
-typedef void (*SbDecodeTargetReleaseFunction)(void* context,
- SbDecodeTarget decode_target);
-
-// An object that can acquire and release SbDecodeTarget instances.
-typedef struct SbDecodeTargetProvider {
- // The function to acquire a new SbDecodeTarget from the provider.
- SbDecodeTargetAcquireFunction acquire;
-
- // The function to release an acquired SbDecodeTarget back to the provider.
- SbDecodeTargetReleaseFunction release;
-
- // |context| will be passed into every call to |acquire| and |release|.
- void* context;
-} SbDecodeTargetProvider;
-#endif // SB_API_VERSION >= 4
-
-#if SB_API_VERSION >= 4
-
// Defines a rectangular content region within a SbDecodeTargetInfoPlane
// structure.
typedef struct SbDecodeTargetInfoContentRegion {
@@ -342,8 +305,6 @@
SbDecodeTargetInfoPlane planes[3];
} SbDecodeTargetInfo;
-#endif // SB_API_VERSION >= 4
-
// --- Constants -------------------------------------------------------------
// Well-defined value for an invalid decode target handle.
@@ -384,101 +345,6 @@
}
#endif // SB_API_VERSION >= SB_DECODE_TARGET_PLANES_FOR_FORMAT
-#if SB_API_VERSION < 4
-
-#if SB_HAS(BLITTER)
-
-// Creates a new SbBlitter-compatible SbDecodeTarget from one or more |planes|
-// created from |display|.
-//
-// |format|: The format of the decode target being created.
-// |planes|: An array of SbBlitterSurface handles to be bundled into an
-// SbDecodeTarget. Must not be NULL. Is expected to have the same number of
-// entries as the number of planes for |format|, in the order and size expected
-// by that format.
-SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat format,
- SbBlitterSurface* planes);
-
-// Gets the surface that represents the given plane.
-SB_EXPORT SbBlitterSurface SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
- SbDecodeTargetPlane plane);
-
-#elif SB_HAS(GLES2) // SB_HAS(BLITTER)
-
-// Creates a new EGL/GLES2-compatible SbDecodeTarget from one or more |planes|
-// owned by |context|, created from |display|. Must be called from a thread
-// where |context| is current.
-//
-// |display|: The EGLDisplay being targeted.
-// |context|: The EGLContext used for this operation, or EGL_NO_CONTEXT if a
-// context is not required.
-// |format|: The format of the decode target being created.
-// |planes|: An array of GLES Texture handles to be bundled into an
-// SbDecodeTarget. Must not be NULL. Is expected to have the same number of
-// entries as the number of planes for |format|, in the order and size expected
-// by that format.
-SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(EGLDisplay display,
- EGLContext context,
- SbDecodeTargetFormat format,
- GLuint* planes);
-
-// Gets the texture that represents the given plane.
-SB_EXPORT GLuint SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
- SbDecodeTargetPlane plane);
-
-#else // SB_HAS(BLITTER)
-
-// Stub function for when graphics aren't enabled. Always creates
-// kSbDecodeTargetInvalid.
-// TODO: Who is calling this when graphics aren't enabled?
-static SB_C_INLINE SbDecodeTarget
-SbDecodeTargetCreate(SbDecodeTargetFormat format) {
- SB_UNREFERENCED_PARAMETER(format);
- return kSbDecodeTargetInvalid;
-}
-
-#endif // SB_HAS(BLITTER)
-
-// Destroys the given SbDecodeTarget and all its associated surfaces.
-SB_EXPORT void SbDecodeTargetDestroy(SbDecodeTarget decode_target);
-
-// Gets the format that |decode_target| was created with.
-SB_EXPORT SbDecodeTargetFormat
-SbDecodeTargetGetFormat(SbDecodeTarget decode_target);
-
-// Gets whether |decode_target| is opaque or not. The underlying source of
-// this value is expected to be properly maintained by the Starboard
-// implementation. So, for example, if an opaque only image type were decoded
-// into an SbDecodeTarget, then the implementation would configure things in
-// such a way that this function would return true. By opaque, it is meant
-// that all alpha values are guaranteed to be 255, if |decode_target| is of a
-// format that has alpha values. If |decode_target| is of a format that does
-// not have alpha values, then this function should return |true|.
-SB_EXPORT bool SbDecodeTargetIsOpaque(SbDecodeTarget decode_target);
-
-// Inline convenience function to acquire an SbDecodeTarget of type |format|,
-// |width|, and |height| from |provider|.
-static SB_C_INLINE SbDecodeTarget
-SbDecodeTargetAcquireFromProvider(SbDecodeTargetProvider* provider,
- SbDecodeTargetFormat format,
- int width,
- int height) {
- return provider->acquire(provider->context, format, width, height);
-}
-
-// Inline convenience function to release |decode_target| back to |provider|.
-static SB_C_INLINE void SbDecodeTargetReleaseToProvider(
- SbDecodeTargetProvider* provider,
- SbDecodeTarget decode_target) {
- provider->release(provider->context, decode_target);
-}
-
-#else // SB_API_VERSION < 4
-
-// SBCHANGELOG: Rename SbDecodeTargetDestroy() to SbDecodeTargetRelease() to
-// more accurately reflect its potential semantics as a reference
-// count decrementer.
-
// Returns ownership of |decode_target| to the Starboard implementation.
// This function will likely result in the destruction of the SbDecodeTarget and
// all its associated surfaces, though in some cases, platforms may simply
@@ -486,10 +352,6 @@
// must be called on a thread with the context
SB_EXPORT void SbDecodeTargetRelease(SbDecodeTarget decode_target);
-// SBCHANGELOG: Remove all SbDecodeTarget "get" functions and replace them with
-// a SbDecodeTargetGetInfo() function that returns all information
-// about a decode target at once.
-
// Writes all information about |decode_target| into |out_info|. Returns false
// if the provided |out_info| structure is not zero initialized.
SB_EXPORT bool SbDecodeTargetGetInfo(SbDecodeTarget decode_target,
@@ -525,12 +387,8 @@
#endif // SB_HAS(GLES2)
-#endif // SB_API_VERSION < 4
-
#ifdef __cplusplus
} // extern "C"
#endif
-#endif // SB_API_VERSION >= 3
-
#endif // STARBOARD_DECODE_TARGET_H_
diff --git a/src/starboard/drm.h b/src/starboard/drm.h
index cae076d..8149cd8 100644
--- a/src/starboard/drm.h
+++ b/src/starboard/drm.h
@@ -88,17 +88,15 @@
// from a SbDrmSystem. |drm_system| will be the DRM system that
// SbDrmGenerateSessionUpdateRequest() was called on. |context| will be the same
// context that was passed into the call to SbDrmCreateSystem().
-#if SB_API_VERSION >= 4
-// |ticket| will be the same ticket that was passed
-// to SbDrmGenerateSessionUpdateRequest() or |kSbDrmTicketInvalid| if
-// the update request was generated by the DRM system.
-#endif // SB_API_VERSION >= 4
+//
+// |ticket| will be the same ticket that was passed to
+// SbDrmGenerateSessionUpdateRequest() or |kSbDrmTicketInvalid| if the update
+// request was generated by the DRM system.
+//
// |session_id| can be NULL if there was an error generating the request.
typedef void (*SbDrmSessionUpdateRequestFunc)(SbDrmSystem drm_system,
void* context,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const void* session_id,
int session_id_size,
const void* content,
@@ -109,15 +107,13 @@
// encrypted samples are actively ready to be decoded. |drm_system| will be the
// DRM system that SbDrmUpdateSession() was called on. |context| will be the
// same context passed into that call to SbDrmCreateSystem().
-#if SB_API_VERSION >= 4
+//
// |ticket| will be the same ticket that was passed to SbDrmUpdateSession().
-#endif // SB_API_VERSION >= 4
+//
// |succeeded| is whether the session was successfully updated or not.
typedef void (*SbDrmSessionUpdatedFunc)(SbDrmSystem drm_system,
void* context,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const void* session_id,
int session_id_size,
bool succeeded);
@@ -141,10 +137,8 @@
// An invalid SbDrmSystem.
#define kSbDrmSystemInvalid ((SbDrmSystem)NULL)
-#if SB_API_VERSION >= 4
// A ticket for callback invocations initiated by the DRM system.
#define kSbDrmTicketInvalid kSbInvalidInt
-#endif // SB_API_VERSION >= 4
// --- Functions -------------------------------------------------------------
@@ -153,12 +147,10 @@
return drm != kSbDrmSystemInvalid;
}
-#if SB_API_VERSION >= 4
// Indicates whether |ticket| is a valid ticket.
static SB_C_FORCE_INLINE bool SbDrmTicketIsValid(int ticket) {
return ticket != kSbDrmTicketInvalid;
}
-#endif // SB_API_VERSION >= 4
// Creates a new DRM system that can be used when constructing an SbPlayer
// or an SbDecoder.
@@ -217,23 +209,21 @@
// |drm_system|: The DRM system that defines the key system used for the
// session update request payload as well as the callback function that is
// called as a result of the function being called.
-#if SB_API_VERSION >= 4
-// |ticket|: The opaque ID that allows to distinguish callbacks from
-// multiple concurrent calls to SbDrmGenerateSessionUpdateRequest(),
-// which will be passed to |update_request_callback| as-is. It is
-// the responsibility of the caller to establish ticket uniqueness, issuing
-// multiple request with the same ticket may result in undefined behavior.
-// Value |kSbDrmTicketInvalid| must not be used.
-#endif // SB_API_VERSION >= 4
+//
+// |ticket|: The opaque ID that allows to distinguish callbacks from multiple
+// concurrent calls to SbDrmGenerateSessionUpdateRequest(), which will be passed
+// to |update_request_callback| as-is. It is the responsibility of the caller to
+// establish ticket uniqueness, issuing multiple request with the same ticket
+// may result in undefined behavior. The value |kSbDrmTicketInvalid| must not be
+// used.
+//
// |type|: The case-sensitive type of the session update request payload.
// |initialization_data|: The data for which the session update request payload
// is created.
// |initialization_data_size|: The size of the session update request payload.
SB_EXPORT void SbDrmGenerateSessionUpdateRequest(
SbDrmSystem drm_system,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const char* type,
const void* initialization_data,
int initialization_data_size);
@@ -242,13 +232,12 @@
// server response. Calls |session_updated_callback| with |context| and whether
// the update succeeded. |context| may be used to route callbacks back to
// an object instance.
-#if SB_API_VERSION >= 4
+//
// |ticket| is the opaque ID that allows to distinguish callbacks from
// multiple concurrent calls to SbDrmUpdateSession(), which will be passed
// to |session_updated_callback| as-is. It is the responsibility of the caller
// to establish ticket uniqueness, issuing multiple calls with the same ticket
// may result in undefined behavior.
-#endif // SB_API_VERSION >= 4
//
// Once the session is successfully updated, an SbPlayer or SbDecoder associated
// with that DRM key system will be able to decrypt encrypted samples.
@@ -256,9 +245,7 @@
// |drm_system|'s |session_updated_callback| may called either from the
// current thread before this function returns or from another thread.
SB_EXPORT void SbDrmUpdateSession(SbDrmSystem drm_system,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const void* key,
int key_size,
const void* session_id,
diff --git a/src/starboard/event.h b/src/starboard/event.h
index 9788ecd..7bfca2c 100644
--- a/src/starboard/event.h
+++ b/src/starboard/event.h
@@ -199,12 +199,10 @@
// directly. The data type is an internally-defined structure.
kSbEventTypeScheduled,
-#if SB_API_VERSION >= 4
// The platform's accessibility settings have changed. The application should
// query the accessibility settings using the appropriate APIs to get the
// new settings.
kSbEventTypeAccessiblitySettingsChanged,
-#endif
#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
// An optional event that platforms may send to indicate that the application
diff --git a/src/starboard/examples/window/main.cc b/src/starboard/examples/window/main.cc
index c944564..b508e7d 100644
--- a/src/starboard/examples/window/main.cc
+++ b/src/starboard/examples/window/main.cc
@@ -47,11 +47,9 @@
g_window = SbWindowCreate(NULL);
SB_CHECK(SbWindowIsValid(g_window));
-#if SB_API_VERSION >= 4
SB_LOG(INFO) << " F1 - Pause";
SB_LOG(INFO) << " F2 - Unpause";
SB_LOG(INFO) << " F3 - Suspend";
-#endif // SB_API_VERSION >= 4
SB_LOG(INFO) << " F5 - Stop";
break;
}
@@ -91,7 +89,6 @@
}
switch (data->key) {
-#if SB_API_VERSION >= 4
case kSbKeyF1:
SbSystemRequestPause();
break;
@@ -101,7 +98,6 @@
case kSbKeyF3:
SbSystemRequestSuspend();
break;
-#endif // SB_API_VERSION >= 4
case kSbKeyF5:
SbSystemRequestStop(0);
break;
diff --git a/src/starboard/image.h b/src/starboard/image.h
index 721067c..9f9054a 100644
--- a/src/starboard/image.h
+++ b/src/starboard/image.h
@@ -52,8 +52,6 @@
#include "starboard/export.h"
#include "starboard/types.h"
-#if SB_API_VERSION >= 3
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -88,11 +86,7 @@
// kSbDecodeTargetInvalid will be returned, with any intermediate allocations
// being cleaned up in the implementation.
SB_EXPORT SbDecodeTarget SbImageDecode(
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider* context_provider,
-#else
- SbDecodeTargetProvider* provider,
-#endif
void* data,
int data_size,
const char* mime_type,
@@ -102,6 +96,4 @@
} // extern "C"
#endif
-#endif // SB_API_VERSION >= 3
-
#endif // STARBOARD_IMAGE_H_
diff --git a/src/starboard/input.h b/src/starboard/input.h
index 3510230..c3a7dbf 100644
--- a/src/starboard/input.h
+++ b/src/starboard/input.h
@@ -128,17 +128,10 @@
} SbInputEventType;
// A 2-dimensional vector used to represent points and motion vectors.
-#if SB_API_VERSION >= 4
typedef struct SbInputVector {
float x;
float y;
} SbInputVector;
-#else
-typedef struct SbInputVector {
- int x;
- int y;
-} SbInputVector;
-#endif
// Event data for |kSbEventTypeInput| events.
typedef struct SbInputData {
diff --git a/src/starboard/key.h b/src/starboard/key.h
index 9c5a6c1..462dcaf 100644
--- a/src/starboard/key.h
+++ b/src/starboard/key.h
@@ -204,17 +204,22 @@
// Beyond this point are non-Windows key codes that are provided as
// extensions, as they otherwise have no analogs.
- // Some media keys that are used around the industry.
+ // Other supported CEA 2014 keys.
kSbKeyMediaRewind = 0xE3,
kSbKeyMediaFastForward = 0xE4,
-#if SB_API_VERSION >= SB_COLOR_KEYCODES_API_VERSION
- // The colored keys found on most contemporary TV remotes.
+#if SB_API_VERSION >= SB_NEW_KEYCODES_API_VERSION
kSbKeyRed = 0x193,
kSbKeyGreen = 0x194,
kSbKeyYellow = 0x195,
kSbKeyBlue = 0x196,
-#endif // SB_API_VESRION >= SB_COLOR_KEYCODES_API_VERSION
+
+ kSbKeySubtitle = 0x1CC,
+ kSbKeyClosedCaption = kSbKeySubtitle,
+
+ // A button that will directly launch the current application.
+ kSbKeyLaunchThisApplication = 0x3000,
+#endif // SB_API_VERSION >= SB_NEW_KEYCODES_API_VERSION
// Mouse buttons, starting with the left mouse button.
kSbKeyMouse1 = 0x7000,
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index 66579d8..75be40d 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -341,7 +341,6 @@
"//starboard/shared/linux/memory_get_stack_bounds.cc",
"//starboard/shared/linux/page_internal.cc",
"//starboard/shared/linux/socket_get_interface_address.cc",
- "//starboard/shared/linux/socket_get_local_interface_address.cc",
"//starboard/shared/linux/system_get_random_data.cc",
"//starboard/shared/linux/system_get_stack.cc",
"//starboard/shared/linux/system_get_total_cpu_memory.cc",
@@ -512,7 +511,6 @@
"//starboard/shared/starboard/player/player_output_mode_supported.cc",
"//starboard/shared/starboard/player/player_seek.cc",
"//starboard/shared/starboard/player/player_set_bounds.cc",
- "//starboard/shared/starboard/player/player_set_pause.cc",
"//starboard/shared/starboard/player/player_set_playback_rate.cc",
"//starboard/shared/starboard/player/player_set_volume.cc",
"//starboard/shared/starboard/player/player_worker.cc",
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index 6688b18..40fe9ab 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -267,69 +267,6 @@
// supported composition methods below.
#define SB_HAS_PLAYER 1
-#if SB_API_VERSION < 4
-// Specifies whether this platform's player will produce an OpenGL texture that
-// the client must draw every frame with its graphics rendering. It may be that
-// we get a texture handle, but cannot perform operations like GlReadPixels on
-// it if it is DRM-protected.
-#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
-
-// Specifies whether this platform's player is composited with a formal
-// compositor, where the client must specify how video is to be composited into
-// the graphicals scene.
-#define SB_IS_PLAYER_COMPOSITED 0
-
-// Specifies whether this platform's player uses a "punch-out" model, where
-// video is rendered to the far background, and the graphics plane is
-// automatically composited on top of the video by the platform. The client must
-// punch an alpha hole out of the graphics plane for video to show through. In
-// this case, changing the video bounds must be tightly synchronized between the
-// player and the graphics plane.
-#define SB_IS_PLAYER_PUNCHED_OUT 1
-#endif // SB_API_VERSION < 4
-
-#if SB_API_VERSION < 4
-
-// Specifies the maximum amount of memory used by audio buffers of media source
-// before triggering a garbage collection. A large value will cause more memory
-// being used by audio buffers but will also make JavaScript app less likely to
-// re-download audio data. Note that the JavaScript app may experience
-// significant difficulty if this value is too low.
-#define SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT (3U * 1024U * 1024U)
-
-// Specifies the maximum amount of memory used by video buffers of media source
-// before triggering a garbage collection. A large value will cause more memory
-// being used by video buffers but will also make JavaScript app less likely to
-// re-download video data. Note that the JavaScript app may experience
-// significant difficulty if this value is too low.
-#define SB_MEDIA_SOURCE_BUFFER_STREAM_VIDEO_MEMORY_LIMIT (16U * 1024U * 1024U)
-
-// Specifies how much memory to reserve up-front for the main media buffer
-// (usually resides inside the CPU memory) used by media source and demuxers.
-// The main media buffer can work in one of the following two ways:
-// 1. If GPU buffer is used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is non-zero), the
-// main buffer will be used as a cache so a media buffer will be copied from
-// GPU memory to main memory before sending to the decoder for further
-// processing. In this case this macro should be set to a value that is
-// large enough to hold all media buffers being decoded.
-// 2. If GPU buffer is not used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is zero) all
-// media buffers will reside in the main memory buffer. In this case the
-// macro should be set to a value that is greater than the sum of the above
-// source buffer stream memory limits with extra room to take account of
-// fragmentations and memory used by demuxers.
-#define SB_MEDIA_MAIN_BUFFER_BUDGET (24U * 1024U * 1024U)
-
-// Specifies how much GPU memory to reserve up-front for media source buffers.
-// This should only be set to non-zero on system with limited CPU memory and
-// excess GPU memory so the app can store media buffer in GPU memory.
-// SB_MEDIA_MAIN_BUFFER_BUDGET has to be set to a non-zero value to avoid
-// media buffers being decoded when being stored in GPU.
-#define SB_MEDIA_GPU_BUFFER_BUDGET 0U
-
-#endif // SB_API_VERSION < 4
-
-#if SB_API_VERSION >= 4
-
// The maximum audio bitrate the platform can decode. The following value
// equals to 5M bytes per seconds which is more than enough for compressed
// audio.
@@ -340,8 +277,6 @@
// video.
#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
-#endif // SB_API_VERSION >= 4
-
// Specifies whether this platform has webm/vp9 support. This should be set to
// non-zero on platforms with webm/vp9 support.
#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index 311618c..2b37d17 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -96,7 +96,6 @@
'<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
'<(DEPTH)/starboard/shared/linux/page_internal.cc',
'<(DEPTH)/starboard/shared/linux/socket_get_interface_address.cc',
- '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
'<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
'<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
'<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
@@ -267,7 +266,6 @@
'<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
- '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_playback_rate.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
diff --git a/src/starboard/linux/x64x11/mock/configuration_public.h b/src/starboard/linux/x64x11/mock/configuration_public.h
index 0fdbea7..8753319 100644
--- a/src/starboard/linux/x64x11/mock/configuration_public.h
+++ b/src/starboard/linux/x64x11/mock/configuration_public.h
@@ -352,27 +352,6 @@
// supported composition methods below.
#define SB_HAS_PLAYER 1
-#if SB_API_VERSION < 4
-// Specifies whether this platform's player will produce an OpenGL texture that
-// the client must draw every frame with its graphics rendering. It may be that
-// we get a texture handle, but cannot perform operations like GlReadPixels on
-// it if it is DRM-protected.
-#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
-
-// Specifies whether this platform's player is composited with a formal
-// compositor, where the client must specify how video is to be composited into
-// the graphicals scene.
-#define SB_IS_PLAYER_COMPOSITED 0
-
-// Specifies whether this platform's player uses a "punch-out" model, where
-// video is rendered to the far background, and the graphics plane is
-// automatically composited on top of the video by the platform. The client must
-// punch an alpha hole out of the graphics plane for video to show through. In
-// this case, changing the video bounds must be tightly synchronized between the
-// player and the graphics plane.
-#define SB_IS_PLAYER_PUNCHED_OUT 1
-#endif // SB_API_VERSION < 4
-
// After a seek is triggerred, the default behavior is to append video frames
// from the last key frame before the seek time and append audio frames from the
// seek time because usually all audio frames are key frames. On platforms that
@@ -386,8 +365,6 @@
// not available should define the following quirk.
#undef SB_HAS_QUIRK_NO_FFS
-#if SB_API_VERSION >= 4
-
// The maximum audio bitrate the platform can decode. The following value
// equals to 5M bytes per seconds which is more than enough for compressed
// audio.
@@ -398,8 +375,6 @@
// video.
#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
-#endif // SB_API_VERSION >= 4
-
// Specifies whether this platform has webm/vp9 support. This should be set to
// non-zero on platforms with webm/vp9 support.
#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0
diff --git a/src/starboard/linux/x64x11/mock/starboard_platform.gyp b/src/starboard/linux/x64x11/mock/starboard_platform.gyp
index 3b233a6..dd5621a 100644
--- a/src/starboard/linux/x64x11/mock/starboard_platform.gyp
+++ b/src/starboard/linux/x64x11/mock/starboard_platform.gyp
@@ -104,7 +104,6 @@
'<(DEPTH)/starboard/shared/stub/player_output_mode_supported.cc',
'<(DEPTH)/starboard/shared/stub/player_seek.cc',
'<(DEPTH)/starboard/shared/stub/player_set_bounds.cc',
- '<(DEPTH)/starboard/shared/stub/player_set_pause.cc',
'<(DEPTH)/starboard/shared/stub/player_set_playback_rate.cc',
'<(DEPTH)/starboard/shared/stub/player_set_volume.cc',
'<(DEPTH)/starboard/shared/stub/player_write_end_of_stream.cc',
@@ -119,7 +118,6 @@
'<(DEPTH)/starboard/shared/stub/socket_get_interface_address.cc',
'<(DEPTH)/starboard/shared/stub/socket_get_last_error.cc',
'<(DEPTH)/starboard/shared/stub/socket_get_local_address.cc',
- '<(DEPTH)/starboard/shared/stub/socket_get_local_interface_address.cc',
'<(DEPTH)/starboard/shared/stub/socket_is_connected.cc',
'<(DEPTH)/starboard/shared/stub/socket_is_connected_and_idle.cc',
'<(DEPTH)/starboard/shared/stub/socket_join_multicast_group.cc',
@@ -147,7 +145,6 @@
'<(DEPTH)/starboard/shared/stub/speech_recognizer_start.cc',
'<(DEPTH)/starboard/shared/stub/speech_recognizer_stop.cc',
'<(DEPTH)/starboard/shared/stub/speech_synthesis_cancel.cc',
- '<(DEPTH)/starboard/shared/stub/speech_synthesis_set_language.cc',
'<(DEPTH)/starboard/shared/stub/speech_synthesis_speak.cc',
'<(DEPTH)/starboard/shared/stub/storage_close_record.cc',
'<(DEPTH)/starboard/shared/stub/storage_delete_record.cc',
diff --git a/src/starboard/media.h b/src/starboard/media.h
index 4767c4d..a01b109 100644
--- a/src/starboard/media.h
+++ b/src/starboard/media.h
@@ -131,7 +131,6 @@
kSbMediaAudioFrameStorageTypePlanar,
} SbMediaAudioFrameStorageType;
-#if SB_API_VERSION >= 4
// SMPTE 2086 mastering data
// http://ieeexplore.ieee.org/document/7291707/
// This standard specifies the metadata items to specify the color
@@ -379,7 +378,6 @@
// completed as (0, 0, 0, 1).
float custom_primary_matrix[12];
} SbMediaColorMetadata;
-#endif // SB_API_VERSION >= 4
// The set of information required by the decoder or player for each video
// sample.
@@ -398,7 +396,6 @@
// key frames, but may change on any key frame.
int frame_height;
-#if SB_API_VERSION >= 4
// HDR metadata common for HDR10 and WebM/VP9-based HDR formats as
// well as the Color Space, and Color elements: MatrixCoefficients,
// BitsPerChannel, ChromaSubsamplingHorz, ChromaSubsamplingVert,
@@ -408,7 +405,6 @@
// This will only be specified on frames where the HDR metadata and
// color / color space might have changed (e.g. keyframes).
SbMediaColorMetadata* color_metadata;
-#endif
} SbMediaVideoSampleInfo;
// A structure describing the audio configuration parameters of a single audio
@@ -494,38 +490,6 @@
SbMediaAudioCodec audio_codec,
const char* key_system);
-#if SB_API_VERSION < 4
-
-// Indicates whether a given combination of
-// (|frame_width| x |frame_height|) frames at |bitrate| and |fps| is supported
-// on this platform with |video_codec|. If |video_codec| is not supported under
-// any condition, this function returns |false|.
-//
-// Setting any of the parameters to |0| indicates that they shouldn't be
-// considered.
-//
-// |video_codec|: The video codec used in the media content.
-// |frame_width|: The frame width of the media content.
-// |frame_height|: The frame height of the media content.
-// |bitrate|: The bitrate of the media content.
-// |fps|: The number of frames per second in the media content.
-SB_EXPORT bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
- int frame_width,
- int frame_height,
- int64_t bitrate,
- int fps);
-
-// Indicates whether this platform supports |audio_codec| at |bitrate|.
-// If |audio_codec| is not supported under any condition, this function
-// returns |false|.
-//
-// |audio_codec|: The media's audio codec (|SbMediaAudioCodec|).
-// |bitrate|: The media's bitrate.
-SB_EXPORT bool SbMediaIsAudioSupported(SbMediaVideoCodec audio_codec,
- int64_t bitrate);
-
-#endif // SB_API_VERSION < 4
-
// Returns information about whether the playback of the specific media
// described by |mime| and encrypted using |key_system| can be played.
//
diff --git a/src/starboard/microphone.h b/src/starboard/microphone.h
index ae6a02f..4c5ac3d 100644
--- a/src/starboard/microphone.h
+++ b/src/starboard/microphone.h
@@ -43,7 +43,7 @@
#include "starboard/export.h"
#include "starboard/types.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
#ifdef __cplusplus
extern "C" {
@@ -194,6 +194,6 @@
} // extern "C"
#endif
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
#endif // STARBOARD_MICROPHONE_H_
diff --git a/src/starboard/nplb/BUILD.gn b/src/starboard/nplb/BUILD.gn
index 29d0b35..ad3a2bd 100644
--- a/src/starboard/nplb/BUILD.gn
+++ b/src/starboard/nplb/BUILD.gn
@@ -164,7 +164,6 @@
"socket_get_interface_address_test.cc",
"socket_get_last_error_test.cc",
"socket_get_local_address_test.cc",
- "socket_get_local_interface_address_test.cc",
"socket_helpers.cc",
"socket_is_connected_and_idle_test.cc",
"socket_is_connected_test.cc",
diff --git a/src/starboard/nplb/accessibility_get_setting_test.cc b/src/starboard/nplb/accessibility_get_setting_test.cc
index ca0662f..d85fdb1 100644
--- a/src/starboard/nplb/accessibility_get_setting_test.cc
+++ b/src/starboard/nplb/accessibility_get_setting_test.cc
@@ -19,8 +19,6 @@
namespace nplb {
namespace {
-#if SB_API_VERSION >= 4
-
TEST(SbAccessibilityGetSettingTest, CanCallGetTextToSpeechSettings) {
SbAccessibilityTextToSpeechSettings settings = {0};
EXPECT_TRUE(SbAccessibilityGetTextToSpeechSettings(&settings));
@@ -49,8 +47,6 @@
EXPECT_FALSE(SbAccessibilityGetDisplaySettings(NULL));
}
-#endif
-
} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/cryptography_create_transformer_test.cc b/src/starboard/nplb/cryptography_create_transformer_test.cc
index 241d6cf..1f60680 100644
--- a/src/starboard/nplb/cryptography_create_transformer_test.cc
+++ b/src/starboard/nplb/cryptography_create_transformer_test.cc
@@ -16,8 +16,6 @@
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_API_VERSION >= 4
-
namespace starboard {
namespace nplb {
namespace {
@@ -48,5 +46,3 @@
} // namespace
} // namespace nplb
} // namespace starboard
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/nplb/cryptography_transform_gcm_test.cc b/src/starboard/nplb/cryptography_transform_gcm_test.cc
index 6f42daa..0088ce0 100644
--- a/src/starboard/nplb/cryptography_transform_gcm_test.cc
+++ b/src/starboard/nplb/cryptography_transform_gcm_test.cc
@@ -70,8 +70,6 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_API_VERSION >= 4
-
using ::testing::NotNull;
namespace starboard {
@@ -445,5 +443,3 @@
} // namespace
} // namespace nplb
} // namespace starboard
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/nplb/cryptography_transform_test.cc b/src/starboard/nplb/cryptography_transform_test.cc
index 1b5770f..fd267f9 100644
--- a/src/starboard/nplb/cryptography_transform_test.cc
+++ b/src/starboard/nplb/cryptography_transform_test.cc
@@ -19,8 +19,6 @@
#include "starboard/nplb/cryptography_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_API_VERSION >= 4
-
namespace starboard {
namespace nplb {
namespace {
@@ -225,5 +223,3 @@
} // namespace
} // namespace nplb
} // namespace starboard
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/nplb/decode_target_create_test.cc b/src/starboard/nplb/decode_target_create_test.cc
deleted file mode 100644
index d6edb3a..0000000
--- a/src/starboard/nplb/decode_target_create_test.cc
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/window.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// This must come after gtest, because it includes GL, which can include X11,
-// which will define None to be 0L, which conflicts with gtest.
-#include "starboard/decode_target.h" // NOLINT(build/include_order)
-
-#if SB_API_VERSION >= 3 && SB_API_VERSION < 4 && SB_HAS(GRAPHICS)
-
-#if SB_HAS(BLITTER)
-#include "starboard/blitter.h"
-#include "starboard/nplb/blitter_helpers.h"
-#elif SB_HAS(GLES2) // SB_HAS(BLITTER)
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-#endif
-
-namespace starboard {
-namespace nplb {
-namespace {
-
-#if SB_HAS(BLITTER)
-const int kWidth = 128;
-const int kHeight = 128;
-
-TEST(SbDecodeTargetTest, SunnyDayCreate) {
- ContextTestEnvironment env(kWidth, kHeight);
-
- ASSERT_TRUE(SbBlitterSetRenderTarget(env.context(), env.render_target()));
-
-#if SB_API_VERSION >= 4
- SbDecodeTarget target = SbDecodeTargetCreate(
- env.device(), kSbDecodeTargetFormat1PlaneRGBA, kWidth, kHeight);
- if (SbDecodeTargetIsValid(target)) {
- SbDecodeTargetInfo info;
- SbMemorySet(&info, 0, sizeof(info));
- SbDecodeTargetGetInfo(target, &info);
-
- EXPECT_EQ(kWidth, info.width);
- EXPECT_EQ(kHeight, info.height);
- EXPECT_EQ(kSbDecodeTargetFormat1PlaneRGBA, info.format);
- EXPECT_EQ(kWidth, info.planes[kSbDecodeTargetPlaneRGBA].width);
- EXPECT_EQ(kHeight, info.planes[kSbDecodeTargetPlaneRGBA].height);
- EXPECT_TRUE(
- SbBlitterIsSurfaceValid(info.planes[kSbDecodeTargetPlaneRGBA].surface));
- }
- SbDecodeTargetRelease(target);
-#else // SB_API_VERSION >= 4
- SbBlitterSurface surface =
- CreateArbitraryRenderTargetSurface(env.device(), kWidth, kHeight);
-
- SbDecodeTarget target =
- SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA, &surface);
- if (SbDecodeTargetIsValid(target)) {
- SbBlitterSurface plane =
- SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
- EXPECT_TRUE(SbBlitterIsSurfaceValid(plane));
- }
- SbDecodeTargetDestroy(target);
- EXPECT_TRUE(SbBlitterDestroySurface(surface));
-#endif // SB_API_VERSION >= 4
-}
-#elif SB_HAS(GLES2) // SB_HAS(BLITTER)
-// clang-format off
-EGLint const kAttributeList[] = {
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_STENCIL_SIZE, 0,
- EGL_BUFFER_SIZE, 32,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
- EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
- EGL_CONFORMANT, EGL_OPENGL_ES2_BIT,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_NONE,
-};
-// clang-format on
-
-class SbDecodeTargetTest : public testing::Test {
- protected:
- void SetUp() SB_OVERRIDE {
- SbWindowOptions options;
- SbWindowSetDefaultOptions(&options);
- window_ = SbWindowCreate(&options);
-
- display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_DISPLAY, display_);
-
- eglInitialize(display_, NULL, NULL);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- EGLint num_configs = 0;
- eglChooseConfig(display_, kAttributeList, NULL, 0, &num_configs);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(0, num_configs);
-
- // Allocate space to receive the matching configs and retrieve them.
- EGLConfig* configs = new EGLConfig[num_configs];
- eglChooseConfig(display_, kAttributeList, configs, num_configs,
- &num_configs);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- EGLNativeWindowType native_window =
- (EGLNativeWindowType)SbWindowGetPlatformHandle(window_);
- EGLConfig config;
-
- // Find the first config that successfully allow a window surface to be
- // created.
- surface_ = EGL_NO_SURFACE;
- for (int config_number = 0; config_number < num_configs; ++config_number) {
- config = configs[config_number];
- surface_ = eglCreateWindowSurface(display_, config, native_window, NULL);
- if (EGL_SUCCESS == eglGetError())
- break;
- }
- ASSERT_NE(EGL_NO_SURFACE, surface_);
-
- delete[] configs;
-
- // Create the GLES2 or GLEX3 Context.
- context_ = EGL_NO_CONTEXT;
- EGLint context_attrib_list[] = {
- EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE,
- };
-#if defined(GLES3_SUPPORTED)
- // Attempt to create an OpenGL ES 3.0 context.
- context_ =
- eglCreateContext(display_, config, EGL_NO_CONTEXT, context_attrib_list);
-#endif
- if (context_ == EGL_NO_CONTEXT) {
- // Create an OpenGL ES 2.0 context.
- context_attrib_list[1] = 2;
- context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT,
- context_attrib_list);
- }
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_CONTEXT, context_);
-
- // connect the context to the surface
- eglMakeCurrent(display_, surface_, surface_, context_);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- }
-
- void TearDown() SB_OVERRIDE {
- eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- EXPECT_EQ(EGL_SUCCESS, eglGetError());
- eglDestroyContext(display_, context_);
- EXPECT_EQ(EGL_SUCCESS, eglGetError());
- eglDestroySurface(display_, surface_);
- EXPECT_EQ(EGL_SUCCESS, eglGetError());
- eglTerminate(display_);
- EXPECT_EQ(EGL_SUCCESS, eglGetError());
- SbWindowDestroy(window_);
- }
-
- EGLContext context_;
- EGLDisplay display_;
- EGLSurface surface_;
- SbWindow window_;
-};
-
-#if SB_API_VERSION >= 4
-TEST_F(SbDecodeTargetTest, SunnyDayCreate) {
- const int kTextureWidth = 256;
- const int kTextureHeight = 256;
-
- SbDecodeTarget target =
- SbDecodeTargetCreate(display_, context_, kSbDecodeTargetFormat1PlaneRGBA,
- kTextureWidth, kTextureHeight);
- if (SbDecodeTargetIsValid(target)) {
- SbDecodeTargetInfo info;
- SbMemorySet(&info, 0, sizeof(info));
- SbDecodeTargetGetInfo(target, &info);
- EXPECT_EQ(kSbDecodeTargetFormat1PlaneRGBA, info.format);
-
- EXPECT_NE(0, info.planes[kSbDecodeTargetPlaneRGBA].texture);
-
- glBindTexture(info.planes[kSbDecodeTargetPlaneRGBA].gl_texture_target,
- info.planes[kSbDecodeTargetPlaneRGBA].texture);
- EXPECT_TRUE(glGetError() == GL_NO_ERROR);
- glBindTexture(info.planes[kSbDecodeTargetPlaneRGBA].gl_texture_target, 0);
- EXPECT_TRUE(glGetError() == GL_NO_ERROR);
-
- SbDecodeTargetRelease(target);
- }
-}
-#else // #if SB_API_VERSION >= 4
-TEST_F(SbDecodeTargetTest, SunnyDayCreate) {
- // Generate a texture to put in the SbDecodeTarget.
- const int kTextureWidth = 256;
- const int kTextureHeight = 256;
-
- GLuint texture_handle;
- glGenTextures(1, &texture_handle);
- glBindTexture(GL_TEXTURE_2D, texture_handle);
- glTexImage2D(GL_TEXTURE_2D, 0 /*level*/, GL_RGBA, kTextureWidth,
- kTextureHeight, 0 /*border*/, GL_RGBA, GL_UNSIGNED_BYTE,
- NULL /*data*/);
- glBindTexture(GL_TEXTURE_2D, 0);
-
- SbDecodeTarget target =
- SbDecodeTargetCreate(display_, context_, kSbDecodeTargetFormat1PlaneRGBA,
- &texture_handle);
- if (SbDecodeTargetIsValid(target)) {
- GLuint plane = SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
- EXPECT_EQ(texture_handle, plane);
- SbDecodeTargetDestroy(target);
- }
- glDeleteTextures(1, &texture_handle);
-}
-#endif // #if SB_API_VERSION >= 4
-
-#else // SB_HAS(BLITTER)
-
-TEST(SbDecodeTargetTest, SunnyDayCreate) {
- // When graphics are not enabled, we expect to always create a
- // kSbDecodeTargetInvalid, and get NULL back for planes.
- EXPECT_EQ(SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA),
- kSbDecodeTargetInvalid);
- SbDecodeTargetInfo info;
- SbMemorySet(&info, 0, sizeof(info));
- EXPECT_FALSE(SbDecodeTargetGetInfo(kSbDecodeTargetInvalid, &info));
-}
-
-#endif // SB_HAS(BLITTER)
-
-} // namespace
-} // namespace nplb
-} // namespace starboard
-
-#endif // SB_API_VERSION >= 3 && \
- // SB_API_VERSION < 4 && \
- // SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/decode_target_provider_test.cc b/src/starboard/nplb/decode_target_provider_test.cc
deleted file mode 100644
index 1da7057..0000000
--- a/src/starboard/nplb/decode_target_provider_test.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-// This must come after gtest, because it includes GL, which can include X11,
-// which will define None to be 0L, which conflicts with gtest.
-#include "starboard/decode_target.h" // NOLINT(build/include_order)
-
-namespace starboard {
-namespace nplb {
-
-#if SB_HAS(GRAPHICS)
-#if SB_API_VERSION >= 3 && SB_API_VERSION < 4
-
-namespace {
-
-struct ProviderContext {
- static SbDecodeTarget Acquire(void* raw_context,
- SbDecodeTargetFormat format,
- int width,
- int height) {
- ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context);
- ++context->called_acquire;
- return kSbDecodeTargetInvalid;
- }
-
- static void Release(void* raw_context, SbDecodeTarget target) {
- ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context);
- ++context->called_release;
- }
-
- int called_acquire;
- int called_release;
-};
-
-void AcquireFalse(SbDecodeTargetProvider* provider) {
- if (provider) {
- SbDecodeTarget target = SbDecodeTargetAcquireFromProvider(
- provider, kSbDecodeTargetFormat1PlaneRGBA, 16, 16);
- bool valid = SbDecodeTargetIsValid(target);
- EXPECT_FALSE(valid);
- if (valid) {
-#if SB_API_VERSION >= 4
- SbDecodeTargetRelease(target);
-#else // SB_API_VERSION >= 4
- SbDecodeTargetDestroy(target);
-#endif // SB_API_VERSION >= 4
- }
- }
-}
-
-void ReleaseInvalid(SbDecodeTargetProvider* provider) {
- if (provider) {
- SbDecodeTargetReleaseToProvider(provider, kSbDecodeTargetInvalid);
- }
-}
-
-TEST(SbDecodeTargetProviderTest, SunnyDayCallsThroughToProvider) {
- ProviderContext context = {};
- SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
- &ProviderContext::Release,
- reinterpret_cast<void*>(&context)};
-
- EXPECT_EQ(0, context.called_acquire);
- EXPECT_EQ(0, context.called_release);
-
- AcquireFalse(&provider);
- EXPECT_EQ(1, context.called_acquire);
- AcquireFalse(&provider);
- EXPECT_EQ(2, context.called_acquire);
-
- ReleaseInvalid(&provider);
- EXPECT_EQ(1, context.called_release);
- ReleaseInvalid(&provider);
- EXPECT_EQ(2, context.called_release);
-}
-
-TEST(SbDecodeTargetProviderTest, SunnyDayMultipleProviders) {
- ProviderContext context = {};
- SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
- &ProviderContext::Release,
- reinterpret_cast<void*>(&context)};
-
- ProviderContext context2 = {};
- SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire,
- &ProviderContext::Release,
- reinterpret_cast<void*>(&context2)};
-
- AcquireFalse(&provider2);
- EXPECT_EQ(1, context2.called_acquire);
- EXPECT_EQ(0, context.called_acquire);
- AcquireFalse(&provider2);
- EXPECT_EQ(2, context2.called_acquire);
- EXPECT_EQ(0, context.called_acquire);
-
- ReleaseInvalid(&provider2);
- EXPECT_EQ(1, context2.called_release);
- EXPECT_EQ(0, context.called_release);
- ReleaseInvalid(&provider2);
- EXPECT_EQ(2, context2.called_release);
- EXPECT_EQ(0, context.called_release);
-}
-
-TEST(SbDecodeTargetProviderTest, RainyDayAcquireNoProvider) {
- AcquireFalse(NULL);
-}
-
-TEST(SbDecodeTargetProviderTest, RainyDayReleaseNoProvider) {
- ReleaseInvalid(NULL);
-}
-
-TEST(SbDecodeTargetProviderTest, RainyDayMultipleProviders) {
- ProviderContext context = {};
- SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
- &ProviderContext::Release,
- reinterpret_cast<void*>(&context)};
-
- ProviderContext context2 = {};
- SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire,
- &ProviderContext::Release,
- reinterpret_cast<void*>(&context2)};
-
- AcquireFalse(&provider2);
- EXPECT_EQ(1, context2.called_acquire);
- AcquireFalse(&provider2);
- EXPECT_EQ(2, context2.called_acquire);
-
- ReleaseInvalid(&provider2);
- EXPECT_EQ(1, context2.called_release);
- ReleaseInvalid(&provider2);
- EXPECT_EQ(2, context2.called_release);
-
- AcquireFalse(&provider);
- EXPECT_EQ(2, context2.called_acquire);
- AcquireFalse(&provider);
- EXPECT_EQ(2, context2.called_acquire);
-
- ReleaseInvalid(&provider);
- EXPECT_EQ(2, context2.called_release);
- ReleaseInvalid(&provider);
- EXPECT_EQ(2, context2.called_release);
-}
-
-} // namespace
-
-#endif // SB_API_VERSION >= 3 && \
- // SB_API_VERSION < 4
-#endif // SB_HAS(GRAPHICS)
-
-} // namespace nplb
-} // namespace starboard
diff --git a/src/starboard/nplb/flat_map_test.cc b/src/starboard/nplb/flat_map_test.cc
index b5817f4..ea7f32a 100644
--- a/src/starboard/nplb/flat_map_test.cc
+++ b/src/starboard/nplb/flat_map_test.cc
@@ -18,6 +18,7 @@
#include <sstream>
#include <string>
+#include "starboard/system.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -34,11 +35,11 @@
return (std::rand() % 2) == 1;
}
-int Random(int first_includsive, int end_exclusive) {
- size_t range = static_cast<size_t>(end_exclusive - first_includsive);
-
- size_t r = rand() % range;
- return static_cast<int>(r) + first_includsive;
+int Random(int first_inclusive, int end_exclusive) {
+ size_t range = static_cast<size_t>(end_exclusive - first_inclusive);
+ size_t rand = 0;
+ SbSystemGetRandomData(&rand, sizeof(rand));
+ return static_cast<int>(rand % range) + first_inclusive;
}
template <typename MapA_Type, typename MapB_Type>
@@ -89,7 +90,7 @@
}
SbTimeMonotonic GetThreadTimeMonotonicNow() {
-#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
+#if SB_HAS(TIME_THREAD_NOW)
return SbTimeGetMonotonicThreadNow();
#else
return SbTimeGetMonotonicNow();
diff --git a/src/starboard/nplb/key_test.cc b/src/starboard/nplb/key_test.cc
new file mode 100644
index 0000000..cbfd240
--- /dev/null
+++ b/src/starboard/nplb/key_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/key.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+TEST(SbKeyTest, CanReference) {
+ EXPECT_NE(kSbKeyUnknown, kSbKeyMediaRewind);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyMediaFastForward);
+
+#if SB_API_VERSION >= SB_NEW_KEYCODES_API_VERSION
+ EXPECT_NE(kSbKeyUnknown, kSbKeyRed);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyGreen);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyYellow);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyBlue);
+ EXPECT_NE(kSbKeyUnknown, kSbKeySubtitle);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyClosedCaption);
+ EXPECT_NE(kSbKeyUnknown, kSbKeyLaunchThisApplication);
+#endif // SB_API_VERSION >= SB_NEW_KEYCODES_API_VERSION
+}
+
+} // namespace
+} // namespace nplb
+} // namespace starboard
diff --git a/src/starboard/nplb/microphone_close_test.cc b/src/starboard/nplb/microphone_close_test.cc
index b18508a..a059cba 100644
--- a/src/starboard/nplb/microphone_close_test.cc
+++ b/src/starboard/nplb/microphone_close_test.cc
@@ -18,8 +18,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneCloseTest, SunnyDayCloseAreCalledMultipleTimes) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -73,7 +74,8 @@
EXPECT_FALSE(success);
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_create_test.cc b/src/starboard/nplb/microphone_create_test.cc
index 4fbd015..c11fd5f 100644
--- a/src/starboard/nplb/microphone_create_test.cc
+++ b/src/starboard/nplb/microphone_create_test.cc
@@ -18,8 +18,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneCreateTest, SunnyDayOnlyOneMicrophone) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -177,7 +178,8 @@
}
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_destroy_test.cc b/src/starboard/nplb/microphone_destroy_test.cc
index 8f7a69e..67de227 100644
--- a/src/starboard/nplb/microphone_destroy_test.cc
+++ b/src/starboard/nplb/microphone_destroy_test.cc
@@ -17,14 +17,16 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneDestroyTest, DestroyInvalidMicrophone) {
SbMicrophoneDestroy(kSbMicrophoneInvalid);
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_get_available_test.cc b/src/starboard/nplb/microphone_get_available_test.cc
index 5324be0..dc30d4a 100644
--- a/src/starboard/nplb/microphone_get_available_test.cc
+++ b/src/starboard/nplb/microphone_get_available_test.cc
@@ -18,8 +18,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneGetAvailableTest, SunnyDay) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -53,7 +54,8 @@
}
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_helpers.h b/src/starboard/nplb/microphone_helpers.h
index b413f02..280a53a 100644
--- a/src/starboard/nplb/microphone_helpers.h
+++ b/src/starboard/nplb/microphone_helpers.h
@@ -17,7 +17,7 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
namespace starboard {
namespace nplb {
@@ -27,6 +27,6 @@
} // namespace nplb
} // namespace starboard
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
#endif // STARBOARD_NPLB_MICROPHONE_HELPERS_H_
diff --git a/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
index 7b86992..f8fb1fb 100644
--- a/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
+++ b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
@@ -18,8 +18,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneIsSampleRateSupportedTest, SunnyDay) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -49,7 +50,8 @@
kNormallyUsedSampleRateInHz));
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_open_test.cc b/src/starboard/nplb/microphone_open_test.cc
index 273f081..b11916e 100644
--- a/src/starboard/nplb/microphone_open_test.cc
+++ b/src/starboard/nplb/microphone_open_test.cc
@@ -18,8 +18,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneOpenTest, SunnyDay) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -92,7 +93,8 @@
EXPECT_FALSE(success);
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/microphone_read_test.cc b/src/starboard/nplb/microphone_read_test.cc
index 805f035..211a5ef 100644
--- a/src/starboard/nplb/microphone_read_test.cc
+++ b/src/starboard/nplb/microphone_read_test.cc
@@ -19,8 +19,9 @@
namespace starboard {
namespace nplb {
+namespace {
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if SB_HAS(MICROPHONE)
TEST(SbMicrophoneReadTest, SunnyDay) {
SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -235,7 +236,8 @@
EXPECT_LT(read_bytes, 0);
}
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#endif // SB_HAS(MICROPHONE)
+} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 274a67f..eb9b8cd 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -88,8 +88,6 @@
'cryptography_helpers.h',
'cryptography_transform_test.cc',
'cryptography_transform_gcm_test.cc',
- 'decode_target_create_test.cc',
- 'decode_target_provider_test.cc',
'directory_can_open_test.cc',
'directory_close_test.cc',
'directory_create_test.cc',
@@ -114,6 +112,7 @@
'flat_map_test.cc',
'include_all.c',
'include_all_too.c',
+ 'key_test.cc',
'log_flush_test.cc',
'log_format_test.cc',
'log_is_tty_test.cc',
@@ -161,7 +160,6 @@
'socket_get_interface_address_test.cc',
'socket_get_last_error_test.cc',
'socket_get_local_address_test.cc',
- 'socket_get_local_interface_address_test.cc',
'socket_helpers.cc',
'socket_is_connected_and_idle_test.cc',
'socket_is_connected_test.cc',
diff --git a/src/starboard/nplb/optional_test.cc b/src/starboard/nplb/optional_test.cc
index c467d2a..9767f96 100644
--- a/src/starboard/nplb/optional_test.cc
+++ b/src/starboard/nplb/optional_test.cc
@@ -803,7 +803,7 @@
EXPECT_TRUE(optional_int_set.find(0) != optional_int_set.end());
EXPECT_TRUE(optional_int_set.find(optional<int>()) != optional_int_set.end());
- optional_int_set.erase(0);
+ optional_int_set.erase(make_optional(0));
EXPECT_FALSE(optional_int_set.find(0) != optional_int_set.end());
EXPECT_TRUE(optional_int_set.find(optional<int>()) != optional_int_set.end());
diff --git a/src/starboard/nplb/player_create_test.cc b/src/starboard/nplb/player_create_test.cc
index 9523d89..9172d45 100644
--- a/src/starboard/nplb/player_create_test.cc
+++ b/src/starboard/nplb/player_create_test.cc
@@ -18,12 +18,12 @@
#include "starboard/window.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_HAS(PLAYER)
-
namespace starboard {
namespace nplb {
+namespace {
-#if SB_API_VERSION >= 4
+#if SB_HAS(PLAYER)
+
#if SB_HAS(GLES2)
void GlesContextRunner(
SbDecodeTargetGraphicsContextProvider* graphics_context_provider,
@@ -34,16 +34,6 @@
SB_UNREFERENCED_PARAMETER(target_function_context);
}
#endif // SB_HAS(GLES2)
-#elif SB_API_VERSION >= 3
-SbDecodeTarget SbDecodeTargetAcquireStub(void* /*context*/,
- SbDecodeTargetFormat /*format*/,
- int /*width*/,
- int /*height*/) {
- return kSbDecodeTargetInvalid;
-}
-void SbDecodeTargetReleaseStub(void* /*context*/,
- SbDecodeTarget /*decode_target*/) {}
-#endif // SB_API_VERSION >= 3
TEST(SbPlayerTest, SunnyDay) {
SbWindowOptions window_options;
@@ -70,7 +60,6 @@
SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
-#if SB_API_VERSION >= 4
SbPlayerOutputMode output_modes[] = {kSbPlayerOutputModeDecodeToTexture,
kSbPlayerOutputModePunchOut};
@@ -79,9 +68,7 @@
if (!SbPlayerOutputModeSupported(output_mode, kVideoCodec, kDrmSystem)) {
continue;
}
-#endif // SB_API_VERSION >= 4
-#if SB_API_VERSION >= 4
SbDecodeTargetGraphicsContextProvider
decode_target_graphics_context_provider;
#if SB_HAS(BLITTER)
@@ -93,44 +80,25 @@
&GlesContextRunner;
decode_target_graphics_context_provider.gles_context_runner_context = NULL;
#endif // SB_HAS(BLITTER)
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider decode_target_provider;
- decode_target_provider.acquire = &SbDecodeTargetAcquireStub;
- decode_target_provider.release = &SbDecodeTargetReleaseStub;
- decode_target_provider.context = NULL;
-#endif // SB_API_VERSION >= 3
- SbPlayer player =
- SbPlayerCreate(window, kSbMediaVideoCodecH264, kSbMediaAudioCodecAac,
- SB_PLAYER_NO_DURATION, kSbDrmSystemInvalid,
- &audio_header, NULL, NULL, NULL, NULL
-#if SB_API_VERSION >= 4
- ,
- output_mode,
- &decode_target_graphics_context_provider
-#elif SB_API_VERSION >= 3
- ,
- &decode_target_provider
-#endif
- ); // NOLINT
+ SbPlayer player = SbPlayerCreate(
+ window, kSbMediaVideoCodecH264, kSbMediaAudioCodecAac,
+ SB_PLAYER_NO_DURATION, kSbDrmSystemInvalid, &audio_header, NULL, NULL,
+ NULL, NULL, output_mode, &decode_target_graphics_context_provider);
EXPECT_TRUE(SbPlayerIsValid(player));
-#if SB_API_VERSION >= 4
if (output_mode == kSbPlayerOutputModeDecodeToTexture) {
SbDecodeTarget current_frame = SbPlayerGetCurrentFrame(player);
}
-#endif // SB_API_VERSION >= 4
SbPlayerDestroy(player);
-
-#if SB_API_VERSION >= 4
}
-#endif // SB_API_VERSION >= 4
SbWindowDestroy(window);
}
+#endif // SB_HAS(PLAYER)
+
+} // namespace
} // namespace nplb
} // namespace starboard
-
-#endif // SB_HAS(PLAYER)
diff --git a/src/starboard/nplb/player_output_mode_supported_test.cc b/src/starboard/nplb/player_output_mode_supported_test.cc
index ffffb84..29f3e5b 100644
--- a/src/starboard/nplb/player_output_mode_supported_test.cc
+++ b/src/starboard/nplb/player_output_mode_supported_test.cc
@@ -17,10 +17,11 @@
#include "starboard/window.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_HAS(PLAYER) && SB_API_VERSION >= 4
-
namespace starboard {
namespace nplb {
+namespace {
+
+#if SB_HAS(PLAYER)
TEST(SbPlayerOutputModeSupportedTest, SunnyDay) {
// We should support either decode-to-texture or punch-out mode.
@@ -41,8 +42,8 @@
EXPECT_FALSE(result);
}
+#endif // SB_HAS(PLAYER)
+
+} // namespace
} // namespace nplb
} // namespace starboard
-
-#endif // SB_HAS(PLAYER) && \
- SB_API_VERSION >= 4
diff --git a/src/starboard/nplb/socket_bind_test.cc b/src/starboard/nplb/socket_bind_test.cc
index 04b9c5e..f813c0a 100644
--- a/src/starboard/nplb/socket_bind_test.cc
+++ b/src/starboard/nplb/socket_bind_test.cc
@@ -114,11 +114,7 @@
TEST_F(SbSocketBindTest, SunnyDayLocalInterface) {
SbSocketAddress address = {0};
-#if SB_API_VERSION < 4
- EXPECT_TRUE(SbSocketGetLocalInterfaceAddress(&address));
-#else
EXPECT_TRUE(SbSocketGetInterfaceAddress(NULL, &address, NULL));
-#endif // SB_API_VERSION < 4
SbSocket server_socket = CreateServerTcpSocket(address.type);
ASSERT_TRUE(SbSocketIsValid(server_socket));
diff --git a/src/starboard/nplb/socket_get_interface_address_test.cc b/src/starboard/nplb/socket_get_interface_address_test.cc
index a9d3f5a..97ec86e 100644
--- a/src/starboard/nplb/socket_get_interface_address_test.cc
+++ b/src/starboard/nplb/socket_get_interface_address_test.cc
@@ -16,17 +16,12 @@
#include "starboard/socket.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace {
-
-const unsigned char kInvalidByte = 0xFE;
-
-} // namespace
-
namespace starboard {
namespace nplb {
namespace {
-#if SB_API_VERSION >= 4
+const unsigned char kInvalidByte = 0xFE;
+
class SbSocketGetInterfaceAddressTest
: public ::testing::TestWithParam<SbSocketAddressType> {
public:
@@ -190,8 +185,6 @@
::testing::Values(kSbSocketAddressTypeIpv4));
#endif // SB_HAS(IPV6)
-#endif // SB_API_VERSION >= 4
-
} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/socket_get_local_address_test.cc b/src/starboard/nplb/socket_get_local_address_test.cc
index 2021446..781ec5a 100644
--- a/src/starboard/nplb/socket_get_local_address_test.cc
+++ b/src/starboard/nplb/socket_get_local_address_test.cc
@@ -80,11 +80,8 @@
TEST_F(SbSocketGetLocalAddressTest, SunnyDayBoundSpecified) {
SbSocketAddress interface_address = {0};
-#if SB_API_VERSION < 4
- EXPECT_TRUE(SbSocketGetLocalInterfaceAddress(&interface_address));
-#else
EXPECT_TRUE(SbSocketGetInterfaceAddress(NULL, &interface_address, NULL));
-#endif
+
SbSocket server_socket = CreateServerTcpSocket(interface_address.type);
ASSERT_TRUE(SbSocketIsValid(server_socket));
diff --git a/src/starboard/nplb/socket_get_local_interface_address_test.cc b/src/starboard/nplb/socket_get_local_interface_address_test.cc
deleted file mode 100644
index db8e0eb..0000000
--- a/src/starboard/nplb/socket_get_local_interface_address_test.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2015 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/nplb/socket_helpers.h"
-#include "starboard/socket.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace starboard {
-namespace nplb {
-namespace {
-
-#if SB_API_VERSION < 4
-
-TEST(SbSocketGetLocalInterfaceAddressTest, SunnyDay) {
- SbSocketAddress address;
- // Initialize to something invalid.
- SbMemorySet(&address, 0xFE, sizeof(address));
- EXPECT_TRUE(SbSocketGetLocalInterfaceAddress(&address));
- EXPECT_EQ(0, address.port);
- EXPECT_FALSE(IsUnspecified(&address));
- EXPECT_FALSE(IsLocalhost(&address));
-}
-
-TEST(SbSocketGetLocalInterfaceAddressTest, RainyDayNull) {
- EXPECT_FALSE(SbSocketGetLocalInterfaceAddress(NULL));
-}
-
-#endif // SB_API_VERSION < 4
-
-} // namespace
-} // namespace nplb
-} // namespace starboard
diff --git a/src/starboard/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc
index 7dd636f..f9d9b23 100644
--- a/src/starboard/nplb/speech_synthesis_basic_test.cc
+++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -15,11 +15,19 @@
#include "starboard/speech_synthesis.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+namespace starboard {
+namespace nplb {
+namespace {
+
+#if SB_HAS(SPEECH_SYNTHESIS)
TEST(SbSpeechSynthesisBasicTest, Basic) {
SbSpeechSynthesisSpeak("Hello");
SbSpeechSynthesisCancel();
}
-#endif // SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+#endif // SB_HAS(SPEECH_SYNTHESIS)
+
+} // namespace
+} // namespace nplb
+} // namespace starboard
diff --git a/src/starboard/nplb/system_get_path_test.cc b/src/starboard/nplb/system_get_path_test.cc
index 9d2bb59..243a496 100644
--- a/src/starboard/nplb/system_get_path_test.cc
+++ b/src/starboard/nplb/system_get_path_test.cc
@@ -80,10 +80,8 @@
BasicTest(kSbSystemPathTempDirectory, false, false, __LINE__);
BasicTest(kSbSystemPathTestOutputDirectory, false, false, __LINE__);
BasicTest(kSbSystemPathCacheDirectory, false, false, __LINE__);
-#if SB_API_VERSION >= 4
BasicTest(kSbSystemPathFontDirectory, false, false, __LINE__);
BasicTest(kSbSystemPathFontConfigurationDirectory, false, false, __LINE__);
-#endif // SB_API_VERSION >= 4
}
TEST(SbSystemGetPathTest, CanCreateAndRemoveDirectoryInCache) {
@@ -113,6 +111,7 @@
EXPECT_FALSE(SbFileExists(path));
}
}
+
TEST(SbSystemGetPathTest, CanWriteAndReadCache) {
char path[kPathSize];
SbMemorySet(path, 0xCD, kPathSize);
diff --git a/src/starboard/nplb/system_get_property_test.cc b/src/starboard/nplb/system_get_property_test.cc
index cebf148..f866564 100644
--- a/src/starboard/nplb/system_get_property_test.cc
+++ b/src/starboard/nplb/system_get_property_test.cc
@@ -70,9 +70,7 @@
BasicTest(kSbSystemPropertyChipsetModelNumber, false, true, __LINE__);
BasicTest(kSbSystemPropertyFirmwareVersion, false, true, __LINE__);
BasicTest(kSbSystemPropertyNetworkOperatorName, false, true, __LINE__);
-#if SB_API_VERSION >= 2
BasicTest(kSbSystemPropertySpeechApiKey, false, true, __LINE__);
-#endif // SB_API_VERSION >= 2
if (IsCEDevice(SbSystemGetDeviceType())) {
BasicTest(kSbSystemPropertyBrandName, true, true, __LINE__);
diff --git a/src/starboard/player.h b/src/starboard/player.h
index 0226d8b..20b95a5 100644
--- a/src/starboard/player.h
+++ b/src/starboard/player.h
@@ -79,7 +79,6 @@
kSbPlayerStateError,
} SbPlayerState;
-#if SB_API_VERSION >= 4
typedef enum SbPlayerOutputMode {
// Requests for SbPlayer to produce an OpenGL texture that the client must
// draw every frame with its graphics rendering. It may be that we get a
@@ -100,7 +99,6 @@
// An invalid output mode.
kSbPlayerOutputModeInvalid,
} SbPlayerOutputMode;
-#endif // SB_API_VERSION >= 4
// Information about the current media playback state.
typedef struct SbPlayerInfo {
@@ -138,7 +136,6 @@
// the player.
int corrupted_video_frames;
-#if SB_API_VERSION >= 4
// The rate of playback. The video is played back in a speed that is
// proportional to this. By default it is 1.0 which indicates that the
// playback is at normal speed. When it is greater than one, the video is
@@ -146,7 +143,6 @@
// is played in a slower than normal speed. Negative speeds are not
// supported.
double playback_rate;
-#endif // SB_API_VERSION >= 4
} SbPlayerInfo;
// An opaque handle to an implementation-private structure representing a
@@ -186,13 +182,6 @@
void* context,
const void* sample_buffer);
-#if SB_IS(PLAYER_COMPOSITED)
-// A handle that can be used to compose a player's video output with other
-// composition layers.
-// TODO: Define a SbCompositor interface with a composition handle type.
-typedef uint32_t SbPlayerCompositionHandle;
-#endif
-
// --- Constants -------------------------------------------------------------
// The value to pass into SbPlayerCreate's |duration_ptr| argument for cases
@@ -289,35 +278,26 @@
// provider may not always be needed by the player, but if it is needed, and
// the provider is not given, the player will fail by returning
// kSbPlayerInvalid.
-SB_EXPORT SbPlayer SbPlayerCreate(
- SbWindow window,
- SbMediaVideoCodec video_codec,
- SbMediaAudioCodec audio_codec,
- SbMediaTime duration_pts,
- SbDrmSystem drm_system,
- const SbMediaAudioHeader* audio_header,
- SbPlayerDeallocateSampleFunc sample_deallocate_func,
- SbPlayerDecoderStatusFunc decoder_status_func,
- SbPlayerStatusFunc player_status_func,
- void* context
-#if SB_API_VERSION >= 4
- ,
- SbPlayerOutputMode output_mode,
- SbDecodeTargetGraphicsContextProvider* context_provider
-#elif SB_API_VERSION >= 3
- ,
- SbDecodeTargetProvider* provider
-#endif
- ); // NOLINT
+SB_EXPORT SbPlayer
+SbPlayerCreate(SbWindow window,
+ SbMediaVideoCodec video_codec,
+ SbMediaAudioCodec audio_codec,
+ SbMediaTime duration_pts,
+ SbDrmSystem drm_system,
+ const SbMediaAudioHeader* audio_header,
+ SbPlayerDeallocateSampleFunc sample_deallocate_func,
+ SbPlayerDecoderStatusFunc decoder_status_func,
+ SbPlayerStatusFunc player_status_func,
+ void* context,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider* context_provider);
-#if SB_API_VERSION >= 4
// Returns true if the given player output mode is supported by the platform.
// If this function returns true, it is okay to call SbPlayerCreate() with
// the given |output_mode|.
SB_EXPORT bool SbPlayerOutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
SbDrmSystem drm_system);
-#endif // SB_API_VERSION < 4
// Destroys |player|, freeing all associated resources. Each callback must
// receive one more callback to say that the player was destroyed. Callbacks
@@ -360,8 +340,6 @@
SbMediaTime seek_to_pts,
int ticket);
-#if SB_API_VERSION >= 4
-
// Writes a single sample of the given media type to |player|'s input stream.
// Its data may be passed in via more than one buffers. The lifetime of
// |sample_buffers|, |sample_buffer_sizes|, |video_sample_info|, and
@@ -411,41 +389,6 @@
const SbMediaVideoSampleInfo* video_sample_info,
const SbDrmSampleInfo* sample_drm_info);
-#else // SB_API_VERSION >= 4
-
-// Writes a sample of the given media type to |player|'s input stream. The
-// lifetime of |video_sample_info| and |sample_drm_info| (as well as member
-// |subsample_mapping| contained inside it) are not guaranteed past the call
-// to SbPlayerWriteSample. That means that before returning, the implementation
-// must synchronously copy any information it wants to retain from those
-// structures.
-//
-// |player|: The player to which the sample is written.
-// |sample_type|: The type of sample being written. See the |SbMediaType|
-// enum in media.h.
-// |sample_buffer|: A pointer to a buffer with the data for this sample. The
-// buffer is expected to be a portion of a bytestream of the codec type that
-// the player was created with. The buffer should contain a sequence of whole
-// NAL Units for video, or a complete audio frame.
-// |sample_buffer_size|: The number of bytes in the given sample.
-// |sample_pts|: The timestamp of the sample in 90KHz ticks (PTS). Note that
-// samples MAY be written "slightly" out of order.
-// |video_sample_info|: Information about a video sample. This value is
-// required if |sample_type| is |kSbMediaTypeVideo|. Otherwise, it must be
-// |NULL|.
-// |sample_drm_info|: The DRM system for the media sample. This value is
-// required for encrypted samples. Otherwise, it must be |NULL|.
-SB_EXPORT void SbPlayerWriteSample(
- SbPlayer player,
- SbMediaType sample_type,
- const void* sample_buffer,
- int sample_buffer_size,
- SbMediaTime sample_pts,
- const SbMediaVideoSampleInfo* video_sample_info,
- const SbDrmSampleInfo* sample_drm_info);
-
-#endif // SB_API_VERSION >= 4
-
// Writes a marker to |player|'s input stream of |stream_type| indicating that
// there are no more samples for that media type for the remainder of this
// media stream. This marker is invalidated, along with the rest of the stream's
@@ -456,7 +399,6 @@
SB_EXPORT void SbPlayerWriteEndOfStream(SbPlayer player,
SbMediaType stream_type);
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
// Sets the player bounds to the given graphics plane coordinates. The changes
// do not take effect until the next graphics frame buffer swap. The default
// bounds for a player is the full screen. This function is only relevant when
@@ -478,23 +420,11 @@
// |width|: The width of the player, in pixels.
// |height|: The height of the player, in pixels.
SB_EXPORT void SbPlayerSetBounds(SbPlayer player,
-#if SB_API_VERSION >= 4
int z_index,
-#endif // SB_API_VERSION >= 4
int x,
int y,
int width,
int height);
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
-
-#if SB_API_VERSION < 4
-
-// Pauses or unpauses the |player|. If the |player|'s state is
-// |kPlayerStatePrerolling|, this function sets the initial pause state for
-// the current seek target.
-SB_EXPORT void SbPlayerSetPause(SbPlayer player, bool pause);
-
-#else // SB_API_VERSION < 4
// Set the playback rate of the |player|. |rate| is default to 1.0 which
// indicates the playback is at its original speed. A |rate| greater than one
@@ -508,8 +438,6 @@
// |playback_rate| is negative or if it is too high to support.
SB_EXPORT bool SbPlayerSetPlaybackRate(SbPlayer player, double playback_rate);
-#endif // SB_API_VERSION < 4
-
// Sets the player's volume.
//
// |player|: The player in which the volume is being adjusted.
@@ -526,29 +454,6 @@
// |out_player_info|: The information retrieved for the player.
SB_EXPORT void SbPlayerGetInfo(SbPlayer player, SbPlayerInfo* out_player_info);
-#if SB_API_VERSION < 4
-#if SB_IS(PLAYER_COMPOSITED)
-// Gets a handle that represents the player's video output, for the purpose of
-// composing with |SbCompositor|, which is currently undefined.
-//
-// |player|: The player for which the video output handle is retrieved.
-SB_EXPORT SbPlayerCompositionHandle
-SbPlayerGetCompositionHandle(SbPlayer player);
-#endif // SB_IS(PLAYER_COMPOSITED)
-#endif // SB_API_VERSION < 4
-
-#if SB_API_VERSION < 4
-#if SB_IS(PLAYER_PRODUCING_TEXTURE)
-// Gets an OpenGL texture ID that points to the player's video output frame at
-// the time it was called. This function can be called once, and the texture ID
-// will be appropriately remapped to the current video frame when it is drawn.
-//
-// |player|: The player for which the texture ID is retrieved.
-SB_EXPORT uint32_t SbPlayerGetTextureId(SbPlayer player);
-#endif // SB_IS(PLAYER_PRODUCING_TEXTURE)
-#endif // SB_API_VERSION < 4
-
-#if SB_API_VERSION >= 4
// Given a player created with the kSbPlayerOutputModeDecodeToTexture
// output mode, it will return a SbDecodeTarget representing the current frame
// to be rasterized. On GLES systems, this function must be called on a
@@ -557,7 +462,6 @@
// |player| object that was created with an output mode other than
// kSbPlayerOutputModeDecodeToTexture, kSbDecodeTargetInvalid is returned.
SB_EXPORT SbDecodeTarget SbPlayerGetCurrentFrame(SbPlayer player);
-#endif // SB_API_VERSION >= 4
#ifdef __cplusplus
} // extern "C"
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h
index 76ea308..84be82d 100644
--- a/src/starboard/raspi/shared/configuration_public.h
+++ b/src/starboard/raspi/shared/configuration_public.h
@@ -263,67 +263,6 @@
// supported composition methods below.
#define SB_HAS_PLAYER 1
-#if SB_API_VERSION < 4
-// Specifies whether this platform's player will produce an OpenGL texture that
-// the client must draw every frame with its graphics rendering. It may be that
-// we get a texture handle, but cannot perform operations like GlReadPixels on
-// it if it is DRM-protected.
-#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
-
-// Specifies whether this platform's player is composited with a formal
-// compositor, where the client must specify how video is to be composited into
-// the graphicals scene.
-#define SB_IS_PLAYER_COMPOSITED 0
-
-// Specifies whether this platform's player uses a "punch-out" model, where
-// video is rendered to the far background, and the graphics plane is
-// automatically composited on top of the video by the platform. The client must
-// punch an alpha hole out of the graphics plane for video to show through. In
-// this case, changing the video bounds must be tightly synchronized between the
-// player and the graphics plane.
-#define SB_IS_PLAYER_PUNCHED_OUT 1
-#endif // SB_API_VERSION < 4
-
-#if SB_API_VERSION < 4
-
-// Specifies the maximum amount of memory used by audio buffers of media source
-// before triggering a garbage collection. A large value will cause more memory
-// being used by audio buffers but will also make JavaScript app less likely to
-// re-download audio data. Note that the JavaScript app may experience
-// significant difficulty if this value is too low.
-#define SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT (3U * 1024U * 1024U)
-
-// Specifies the maximum amount of memory used by video buffers of media source
-// before triggering a garbage collection. A large value will cause more memory
-// being used by video buffers but will also make JavaScript app less likely to
-// re-download video data. Note that the JavaScript app may experience
-// significant difficulty if this value is too low.
-#define SB_MEDIA_SOURCE_BUFFER_STREAM_VIDEO_MEMORY_LIMIT (16U * 1024U * 1024U)
-
-// Specifies how much memory to reserve up-front for the main media buffer
-// (usually resides inside the CPU memory) used by media source and demuxers.
-// The main media buffer can work in one of the following two ways:
-// 1. If GPU buffer is used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is non-zero), the
-// main buffer will be used as a cache so a media buffer will be copied from
-// GPU memory to main memory before sending to the decoder for further
-// processing. In this case this macro should be set to a value that is
-// large enough to hold all media buffers being decoded.
-// 2. If GPU buffer is not used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is zero) all
-// media buffers will reside in the main memory buffer. In this case the
-// macro should be set to a value that is greater than the sum of the above
-// source buffer stream memory limits with extra room to take account of
-// fragmentations and memory used by demuxers.
-#define SB_MEDIA_MAIN_BUFFER_BUDGET (24U * 1024U * 1024U)
-
-// Specifies how much GPU memory to reserve up-front for media source buffers.
-// This should only be set to non-zero on system with limited CPU memory and
-// excess GPU memory so the app can store media buffer in GPU memory.
-// SB_MEDIA_MAIN_BUFFER_BUDGET has to be set to a non-zero value to avoid
-// media buffers being decoded when being stored in GPU.
-#define SB_MEDIA_GPU_BUFFER_BUDGET 0U
-
-#endif // SB_API_VERSION < 4
-
// The maximum audio bitrate the platform can decode. The following value
// equals to 5M bytes per seconds which is more than enough for compressed
// audio.
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index 030fec1..6c80ea1 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -135,7 +135,6 @@
'<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
'<(DEPTH)/starboard/shared/linux/page_internal.cc',
'<(DEPTH)/starboard/shared/linux/socket_get_interface_address.cc',
- '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
'<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
'<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
'<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
@@ -307,7 +306,6 @@
'<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
'<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
- '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_playback_rate.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
diff --git a/src/starboard/shared/alsa/alsa_audio_sink_type.cc b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
index e0cbd79..def4105 100644
--- a/src/starboard/shared/alsa/alsa_audio_sink_type.cc
+++ b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
@@ -92,12 +92,10 @@
bool IsType(Type* type) SB_OVERRIDE { return type_ == type; }
-#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate) SB_OVERRIDE {
ScopedLock lock(mutex_);
playback_rate_ = playback_rate;
}
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume) SB_OVERRIDE {
ScopedLock lock(mutex_);
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 5c08757..022f843 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -15,7 +15,6 @@
#include "starboard/shared/ffmpeg/ffmpeg_video_decoder.h"
#include "starboard/linux/shared/decode_target_internal.h"
-
#include "starboard/memory.h"
#include "starboard/thread.h"
@@ -360,7 +359,6 @@
namespace player {
namespace filter {
-#if SB_API_VERSION >= 4
// static
bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
@@ -381,7 +379,6 @@
return false;
}
-#endif // SB_API_VERSION >= 4
} // namespace filter
} // namespace player
diff --git a/src/starboard/shared/gcc/atomic_gcc_public.h b/src/starboard/shared/gcc/atomic_gcc_public.h
index 9c7afc6..cdf9fa8 100644
--- a/src/starboard/shared/gcc/atomic_gcc_public.h
+++ b/src/starboard/shared/gcc/atomic_gcc_public.h
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// These definitions were mostly extracted from Chromium's
-// base/atomicops_internals_gcc.h.
-
#ifndef STARBOARD_SHARED_GCC_ATOMIC_GCC_PUBLIC_H_
#define STARBOARD_SHARED_GCC_ATOMIC_GCC_PUBLIC_H_
@@ -28,97 +25,79 @@
SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr,
SbAtomic32 old_value,
SbAtomic32 new_value) {
- SbAtomic32 prev_value;
- do {
- if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
- return old_value;
- prev_value = *ptr;
- } while (prev_value == old_value);
- return prev_value;
-}
-
-static SB_C_FORCE_INLINE SbAtomic32
-SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, SbAtomic32 new_value) {
- SbAtomic32 old_value;
- do {
- old_value = *ptr;
- } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_RELAXED, __ATOMIC_RELAXED);
return old_value;
}
static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, SbAtomic32 new_value) {
+ return __atomic_exchange_n(ptr, new_value, __ATOMIC_RELAXED);
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, SbAtomic32 increment) {
- return SbAtomicBarrier_Increment(ptr, increment);
+ return increment + __atomic_fetch_add(ptr, increment, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
SbAtomic32 increment) {
- for (;;) {
- // Atomic exchange the old value with an incremented one.
- SbAtomic32 old_value = *ptr;
- SbAtomic32 new_value = old_value + increment;
- if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
- // The exchange took place as expected.
- return new_value;
- }
- // Otherwise, *ptr changed mid-loop and we need to retry.
- }
+ return increment + __atomic_fetch_add(ptr, increment, __ATOMIC_SEQ_CST);
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr,
SbAtomic32 old_value,
SbAtomic32 new_value) {
- // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which
- // is a full memory barrier, none is needed here or below in Release.
- return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE);
+ return old_value;
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr,
SbAtomic32 old_value,
SbAtomic32 new_value) {
- return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED);
+ return old_value;
}
static SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() {
- __sync_synchronize();
+ __atomic_thread_fence(__ATOMIC_SEQ_CST);
}
static SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr,
SbAtomic32 value) {
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr,
SbAtomic32 value) {
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELAXED);
SbAtomicMemoryBarrier();
}
static SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr,
SbAtomic32 value) {
- SbAtomicMemoryBarrier();
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELEASE);
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr) {
- return *ptr;
+ return __atomic_load_n(ptr, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicAcquire_Load(volatile const SbAtomic32* ptr) {
- SbAtomic32 value = *ptr;
- SbAtomicMemoryBarrier();
- return value;
+ return __atomic_load_n(ptr, __ATOMIC_ACQUIRE);
}
static SB_C_FORCE_INLINE SbAtomic32
SbAtomicRelease_Load(volatile const SbAtomic32* ptr) {
SbAtomicMemoryBarrier();
- return *ptr;
+ return __atomic_load_n(ptr, __ATOMIC_RELAXED);
}
// 64-bit atomic operations (only available on 64-bit processors).
@@ -127,91 +106,75 @@
SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr,
SbAtomic64 old_value,
SbAtomic64 new_value) {
- SbAtomic64 prev_value;
- do {
- if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
- return old_value;
- prev_value = *ptr;
- } while (prev_value == old_value);
- return prev_value;
-}
-
-static SB_C_FORCE_INLINE SbAtomic64
-SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, SbAtomic64 new_value) {
- SbAtomic64 old_value;
- do {
- old_value = *ptr;
- } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_RELAXED, __ATOMIC_RELAXED);
return old_value;
}
static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, SbAtomic64 new_value) {
+ return __atomic_exchange_n(ptr, new_value, __ATOMIC_RELAXED);
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
- return SbAtomicBarrier_Increment64(ptr, increment);
+ return increment + __atomic_fetch_add(ptr, increment, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
- for (;;) {
- // Atomic exchange the old value with an incremented one.
- SbAtomic64 old_value = *ptr;
- SbAtomic64 new_value = old_value + increment;
- if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
- // The exchange took place as expected.
- return new_value;
- }
- // Otherwise, *ptr changed mid-loop and we need to retry.
- }
+ return increment + __atomic_fetch_add(ptr, increment, __ATOMIC_SEQ_CST);
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr,
SbAtomic64 old_value,
SbAtomic64 new_value) {
- return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE);
+ return old_value;
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr,
SbAtomic64 old_value,
SbAtomic64 new_value) {
- return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
+ __atomic_compare_exchange_n(ptr, &old_value, new_value, false,
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED);
+ return old_value;
}
static SB_C_FORCE_INLINE void
SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr,
SbAtomic64 value) {
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr,
SbAtomic64 value) {
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELAXED);
SbAtomicMemoryBarrier();
}
static SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr,
SbAtomic64 value) {
- SbAtomicMemoryBarrier();
- *ptr = value;
+ __atomic_store_n(ptr, value, __ATOMIC_RELEASE);
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr) {
- return *ptr;
+ return __atomic_load_n(ptr, __ATOMIC_RELAXED);
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr) {
- SbAtomic64 value = *ptr;
- SbAtomicMemoryBarrier();
- return value;
+ return __atomic_load_n(ptr, __ATOMIC_ACQUIRE);
}
static SB_C_FORCE_INLINE SbAtomic64
SbAtomicRelease_Load64(volatile const SbAtomic64* ptr) {
SbAtomicMemoryBarrier();
- return *ptr;
+ return __atomic_load_n(ptr, __ATOMIC_RELAXED);
}
#endif // SB_HAS(64_BIT_ATOMICS)
diff --git a/src/starboard/shared/gcc/atomic_gcc_sync_public.h b/src/starboard/shared/gcc/atomic_gcc_sync_public.h
new file mode 100644
index 0000000..fc5d803
--- /dev/null
+++ b/src/starboard/shared/gcc/atomic_gcc_sync_public.h
@@ -0,0 +1,227 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// An SbAtomic* implementation using the older "__sync_*" GCC intrinsics.
+// These may be useful if, for example, you are using a GCC version prior to
+// 4.7, or your toolchain has a broken implementation of the newer
+// "__atomic_*" intrinsics.
+//
+// These definitions were mostly extracted from Chromium's
+// base/atomicops_internals_gcc.h.
+
+#ifndef STARBOARD_SHARED_GCC_ATOMIC_GCC_SYNC_PUBLIC_H_
+#define STARBOARD_SHARED_GCC_ATOMIC_GCC_SYNC_PUBLIC_H_
+
+#include "starboard/atomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr,
+ SbAtomic32 old_value,
+ SbAtomic32 new_value) {
+ SbAtomic32 prev_value;
+ do {
+ if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
+ return old_value;
+ prev_value = *ptr;
+ } while (prev_value == old_value);
+ return prev_value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, SbAtomic32 new_value) {
+ SbAtomic32 old_value;
+ do {
+ old_value = *ptr;
+ } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
+ return old_value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, SbAtomic32 increment) {
+ return SbAtomicBarrier_Increment(ptr, increment);
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
+ SbAtomic32 increment) {
+ for (;;) {
+ // Atomic exchange the old value with an incremented one.
+ SbAtomic32 old_value = *ptr;
+ SbAtomic32 new_value = old_value + increment;
+ if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
+ // The exchange took place as expected.
+ return new_value;
+ }
+ // Otherwise, *ptr changed mid-loop and we need to retry.
+ }
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr,
+ SbAtomic32 old_value,
+ SbAtomic32 new_value) {
+ // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which
+ // is a full memory barrier, none is needed here or below in Release.
+ return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr,
+ SbAtomic32 old_value,
+ SbAtomic32 new_value) {
+ return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
+}
+
+static SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() {
+ __sync_synchronize();
+}
+
+static SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr,
+ SbAtomic32 value) {
+ *ptr = value;
+}
+
+static SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr,
+ SbAtomic32 value) {
+ *ptr = value;
+ SbAtomicMemoryBarrier();
+}
+
+static SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr,
+ SbAtomic32 value) {
+ SbAtomicMemoryBarrier();
+ *ptr = value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr) {
+ return *ptr;
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicAcquire_Load(volatile const SbAtomic32* ptr) {
+ SbAtomic32 value = *ptr;
+ SbAtomicMemoryBarrier();
+ return value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicRelease_Load(volatile const SbAtomic32* ptr) {
+ SbAtomicMemoryBarrier();
+ return *ptr;
+}
+
+// 64-bit atomic operations (only available on 64-bit processors).
+#if SB_HAS(64_BIT_ATOMICS)
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr,
+ SbAtomic64 old_value,
+ SbAtomic64 new_value) {
+ SbAtomic64 prev_value;
+ do {
+ if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
+ return old_value;
+ prev_value = *ptr;
+ } while (prev_value == old_value);
+ return prev_value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, SbAtomic64 new_value) {
+ SbAtomic64 old_value;
+ do {
+ old_value = *ptr;
+ } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
+ return old_value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
+ return SbAtomicBarrier_Increment64(ptr, increment);
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
+ for (;;) {
+ // Atomic exchange the old value with an incremented one.
+ SbAtomic64 old_value = *ptr;
+ SbAtomic64 new_value = old_value + increment;
+ if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
+ // The exchange took place as expected.
+ return new_value;
+ }
+ // Otherwise, *ptr changed mid-loop and we need to retry.
+ }
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr,
+ SbAtomic64 old_value,
+ SbAtomic64 new_value) {
+ return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr,
+ SbAtomic64 old_value,
+ SbAtomic64 new_value) {
+ return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
+}
+
+static SB_C_FORCE_INLINE void
+SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr,
+ SbAtomic64 value) {
+ *ptr = value;
+}
+
+static SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr,
+ SbAtomic64 value) {
+ *ptr = value;
+ SbAtomicMemoryBarrier();
+}
+
+static SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr,
+ SbAtomic64 value) {
+ SbAtomicMemoryBarrier();
+ *ptr = value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr) {
+ return *ptr;
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr) {
+ SbAtomic64 value = *ptr;
+ SbAtomicMemoryBarrier();
+ return value;
+}
+
+static SB_C_FORCE_INLINE SbAtomic64
+SbAtomicRelease_Load64(volatile const SbAtomic64* ptr) {
+ SbAtomicMemoryBarrier();
+ return *ptr;
+}
+#endif // SB_HAS(64_BIT_ATOMICS)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // STARBOARD_SHARED_GCC_ATOMIC_GCC_SYNC_PUBLIC_H_
diff --git a/src/starboard/shared/linux/socket_get_interface_address.cc b/src/starboard/shared/linux/socket_get_interface_address.cc
index b9bd9c4..36f59bd 100644
--- a/src/starboard/shared/linux/socket_get_interface_address.cc
+++ b/src/starboard/shared/linux/socket_get_interface_address.cc
@@ -14,8 +14,6 @@
#include "starboard/socket.h"
-#if SB_API_VERSION >= 4
-
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <linux/if.h>
@@ -370,5 +368,3 @@
return false;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/linux/socket_get_local_interface_address.cc b/src/starboard/shared/linux/socket_get_local_interface_address.cc
deleted file mode 100644
index 61ef792..0000000
--- a/src/starboard/shared/linux/socket_get_local_interface_address.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2015 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/socket.h"
-
-#if SB_API_VERSION < 4
-
-#include <arpa/inet.h>
-#include <ifaddrs.h>
-
-#include "starboard/log.h"
-#include "starboard/shared/posix/socket_internal.h"
-
-namespace sbposix = starboard::shared::posix;
-
-bool SbSocketGetLocalInterfaceAddress(SbSocketAddress* out_address) {
- if (!out_address) {
- return false;
- }
-
- // Get a list of the local network interfaces.
- struct ifaddrs* ifaddr;
- if (getifaddrs(&ifaddr) == -1) {
- return false;
- }
-
- // Scan the returned interfaces for a non-loopback IPv4 interface.
- for (struct ifaddrs* it = ifaddr; it != NULL; it = it->ifa_next) {
- if (it->ifa_addr == NULL) {
- continue;
- }
-
- // Filter out anything but IPv4.
- if (it->ifa_addr->sa_family != AF_INET) {
- continue;
- }
-
- // We know it is IPV4, so let's cast it over.
- struct sockaddr_in* if_addr =
- reinterpret_cast<struct sockaddr_in*>(it->ifa_addr);
-
- // Filter out the loopback adapter.
- if (if_addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
- continue;
- }
-
- // Copy the address to the destination.
- sbposix::SockAddr sock_addr;
- sock_addr.FromSockaddr(it->ifa_addr);
- if (!sock_addr.ToSbSocketAddress(out_address)) {
- continue;
- }
-
- freeifaddrs(ifaddr);
- return true;
- }
-
- freeifaddrs(ifaddr);
- return false;
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/signal/suspend_signals.cc b/src/starboard/shared/signal/suspend_signals.cc
index 490b0ed..64fc947 100644
--- a/src/starboard/shared/signal/suspend_signals.cc
+++ b/src/starboard/shared/signal/suspend_signals.cc
@@ -65,6 +65,11 @@
starboard::Application::Get()->Unpause(NULL, NULL);
}
+void LowMemory(int signal_id) {
+ LogSignalCaught(signal_id);
+ starboard::Application::Get()->InjectLowMemoryEvent();
+}
+
void Ignore(int signal_id) {
LogSignalCaught(signal_id);
SbLogRawDumpStack(1);
@@ -91,6 +96,8 @@
#endif
SetSignalHandler(SIGUSR1, &Suspend);
UnblockSignal(SIGUSR1);
+ SetSignalHandler(SIGUSR2, &LowMemory);
+ UnblockSignal(SIGUSR2);
SetSignalHandler(SIGCONT, &Resume);
}
@@ -99,6 +106,7 @@
SetSignalHandler(SIGPIPE, SIG_DFL);
#endif
SetSignalHandler(SIGUSR1, SIG_DFL);
+ SetSignalHandler(SIGUSR2, SIG_DFL);
SetSignalHandler(SIGCONT, SIG_DFL);
}
diff --git a/src/starboard/shared/speechd/speech_synthesis_set_language.cc b/src/starboard/shared/speechd/speech_synthesis_set_language.cc
deleted file mode 100644
index fe083f7..0000000
--- a/src/starboard/shared/speechd/speech_synthesis_set_language.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/speech_synthesis.h"
-
-#if SB_API_VERSION < 4
-// DEPRECATED IN API VERSION 4
-
-#include "starboard/shared/speechd/speechd_internal.h"
-
-using starboard::shared::speechd::SpeechDispatcher;
-
-bool SbSpeechSynthesisSetLanguage(const char* lang) {
- SpeechDispatcher* speech_dispatcher = SpeechDispatcher::Get();
- if (!speech_dispatcher) {
- return false;
- }
- return speech_dispatcher->SetLanguage(lang);
-}
-
-#endif
diff --git a/src/starboard/shared/starboard/application.cc b/src/starboard/shared/starboard/application.cc
index 194adc1..d287112 100644
--- a/src/starboard/shared/starboard/application.cc
+++ b/src/starboard/shared/starboard/application.cc
@@ -135,6 +135,12 @@
SbMemoryDeallocate));
}
+void Application::InjectLowMemoryEvent() {
+#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
+ Inject(new Event(kSbEventTypeLowMemory, NULL, NULL));
+#endif // SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
+}
+
SbEventId Application::Schedule(SbEventCallback callback,
void* context,
SbTimeMonotonic delay) {
@@ -147,7 +153,7 @@
CancelTimedEvent(id);
}
-#if SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#if SB_HAS(PLAYER)
void Application::HandleFrame(SbPlayer player,
const scoped_refptr<VideoFrame>& frame,
int x,
@@ -156,7 +162,7 @@
int height) {
AcceptFrame(player, frame, x, y, width, height);
}
-#endif // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#endif // SB_HAS(PLAYER)
void Application::SetStartLink(const char* start_link) {
SB_DCHECK(IsCurrentThread());
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h
index 2d1569d..d4ec732 100644
--- a/src/starboard/shared/starboard/application.h
+++ b/src/starboard/shared/starboard/application.h
@@ -209,6 +209,9 @@
// external thread.
void Link(const char* link_data);
+ // Injects an event of type kSbEventTypeLowMemory to the application.
+ void InjectLowMemoryEvent();
+
// Schedules an event into the event queue. May be called from an external
// thread.
SbEventId Schedule(SbEventCallback callback,
@@ -219,7 +222,7 @@
// external thread.
void Cancel(SbEventId id);
-#if SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#if SB_HAS(PLAYER)
// Handles receiving a new video frame of |player| from the media system. Only
// used when the application needs to composite video frames with punch-out
// video manually (should be rare). Will be called from an external thread.
@@ -229,7 +232,7 @@
int y,
int width,
int height);
-#endif // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#endif // SB_HAS(PLAYER)
// Registers a |callback| function that will be called when |Teardown| is
// called.
@@ -257,7 +260,7 @@
// processed the Resume event.
virtual void OnResume() {}
-#if SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#if SB_HAS(PLAYER)
// Subclasses may override this method to accept video frames from the media
// system. Will be called from an external thread.
virtual void AcceptFrame(SbPlayer /* player */,
@@ -266,7 +269,7 @@
int /* y */,
int /* width */,
int /* height */) {}
-#endif // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
+#endif // SB_HAS(PLAYER)
// Blocks until the next event is available. Subclasses must implement this
// method to provide events for the platform. Gives ownership to the caller.
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
index 4aaeef1..de632e4 100644
--- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
+++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -39,9 +39,7 @@
};
virtual ~SbAudioSinkPrivate() {}
-#if SB_API_VERSION >= 4
virtual void SetPlaybackRate(double playback_rate) = 0;
-#endif // SB_API_VERSION >= 4
virtual void SetVolume(double volume) = 0;
diff --git a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
index f6ccd09..13b4d0d 100644
--- a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
+++ b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
@@ -37,12 +37,10 @@
~StubAudioSink() SB_OVERRIDE;
bool IsType(Type* type) SB_OVERRIDE { return type_ == type; }
-#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate) SB_OVERRIDE {
SB_UNREFERENCED_PARAMETER(playback_rate);
SB_NOTIMPLEMENTED();
}
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume) SB_OVERRIDE {
SB_UNREFERENCED_PARAMETER(volume);
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_create_transformer.cc b/src/starboard/shared/starboard/cryptography/cryptography_create_transformer.cc
index 19c286d..2e09e7b 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_create_transformer.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_create_transformer.cc
@@ -20,10 +20,6 @@
#include "starboard/shared/starboard/cryptography/software_aes.h"
#include "starboard/string.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
using starboard::shared::starboard::cryptography::AES_KEY;
using starboard::shared::starboard::cryptography::AES_gcm128_init;
using starboard::shared::starboard::cryptography::Algorithm;
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_destroy_transformer.cc b/src/starboard/shared/starboard/cryptography/cryptography_destroy_transformer.cc
index fede3c3..5d11036 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_destroy_transformer.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_destroy_transformer.cc
@@ -16,10 +16,6 @@
#include "starboard/cryptography.h"
#include "starboard/shared/starboard/cryptography/cryptography_internal.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
void SbCryptographyDestroyTransformer(SbCryptographyTransformer transformer) {
if (!transformer) {
return;
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_get_tag.cc b/src/starboard/shared/starboard/cryptography/cryptography_get_tag.cc
index ee09892..8f0cc74 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_get_tag.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_get_tag.cc
@@ -17,10 +17,6 @@
#include "starboard/shared/starboard/cryptography/cryptography_internal.h"
#include "starboard/shared/starboard/cryptography/software_aes.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
using starboard::shared::starboard::cryptography::AES_gcm128_tag;
using starboard::shared::starboard::cryptography::kAlgorithmAes128Gcm;
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_set_authenticated_data.cc b/src/starboard/shared/starboard/cryptography/cryptography_set_authenticated_data.cc
index 6178bd6..7dff5e5 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_set_authenticated_data.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_set_authenticated_data.cc
@@ -17,10 +17,6 @@
#include "starboard/shared/starboard/cryptography/cryptography_internal.h"
#include "starboard/shared/starboard/cryptography/software_aes.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
using starboard::shared::starboard::cryptography::AES_gcm128_aad;
using starboard::shared::starboard::cryptography::kAlgorithmAes128Gcm;
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_set_initialization_vector.cc b/src/starboard/shared/starboard/cryptography/cryptography_set_initialization_vector.cc
index f81d293..0b5e4a7 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_set_initialization_vector.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_set_initialization_vector.cc
@@ -21,10 +21,6 @@
#include "starboard/shared/starboard/cryptography/cryptography_internal.h"
#include "starboard/shared/starboard/cryptography/software_aes.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
using starboard::shared::starboard::cryptography::AES_gcm128_setiv;
using starboard::shared::starboard::cryptography::kAlgorithmAes128Gcm;
using starboard::shared::starboard::cryptography::kAlgorithmAes128Ctr;
diff --git a/src/starboard/shared/starboard/cryptography/cryptography_transform.cc b/src/starboard/shared/starboard/cryptography/cryptography_transform.cc
index fc297c4..b22fccf 100644
--- a/src/starboard/shared/starboard/cryptography/cryptography_transform.cc
+++ b/src/starboard/shared/starboard/cryptography/cryptography_transform.cc
@@ -17,10 +17,6 @@
#include "starboard/shared/starboard/cryptography/cryptography_internal.h"
#include "starboard/shared/starboard/cryptography/software_aes.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
using starboard::shared::starboard::cryptography::AES_cbc_encrypt;
using starboard::shared::starboard::cryptography::AES_ctr128_encrypt;
using starboard::shared::starboard::cryptography::AES_decrypt;
diff --git a/src/starboard/shared/starboard/drm/drm_generate_session_update_request.cc b/src/starboard/shared/starboard/drm/drm_generate_session_update_request.cc
index efedc7e..da8f6a2 100644
--- a/src/starboard/shared/starboard/drm/drm_generate_session_update_request.cc
+++ b/src/starboard/shared/starboard/drm/drm_generate_session_update_request.cc
@@ -18,9 +18,7 @@
#include "starboard/shared/starboard/drm/drm_system_internal.h"
void SbDrmGenerateSessionUpdateRequest(SbDrmSystem drm_system,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const char* type,
const void* initialization_data,
int initialization_data_size) {
@@ -29,16 +27,12 @@
return;
}
-#if SB_API_VERSION >= 4
if (ticket == kSbDrmTicketInvalid) {
SB_DLOG(ERROR) << "Ticket must be specified.";
return;
}
-#endif // SB_API_VERSION >= 4
drm_system->GenerateSessionUpdateRequest(
-#if SB_API_VERSION >= 4
ticket,
-#endif // SB_API_VERSION >= 4
type, initialization_data, initialization_data_size);
}
diff --git a/src/starboard/shared/starboard/drm/drm_system_internal.h b/src/starboard/shared/starboard/drm/drm_system_internal.h
index a392fe1..05d06b1 100644
--- a/src/starboard/shared/starboard/drm/drm_system_internal.h
+++ b/src/starboard/shared/starboard/drm/drm_system_internal.h
@@ -27,16 +27,12 @@
virtual ~SbDrmSystemPrivate() {}
virtual void GenerateSessionUpdateRequest(
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const char* type,
const void* initialization_data,
int initialization_data_size) = 0;
virtual void UpdateSession(
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const void* key,
int key_size,
const void* session_id,
diff --git a/src/starboard/shared/starboard/drm/drm_update_session.cc b/src/starboard/shared/starboard/drm/drm_update_session.cc
index 57862d5..f051b82 100644
--- a/src/starboard/shared/starboard/drm/drm_update_session.cc
+++ b/src/starboard/shared/starboard/drm/drm_update_session.cc
@@ -18,9 +18,7 @@
#include "starboard/shared/starboard/drm/drm_system_internal.h"
void SbDrmUpdateSession(SbDrmSystem drm_system,
-#if SB_API_VERSION >= 4
int ticket,
-#endif // SB_API_VERSION >= 4
const void* key,
int key_size,
const void* session_id,
@@ -31,8 +29,6 @@
}
drm_system->UpdateSession(
-#if SB_API_VERSION >= 4
ticket,
-#endif // SB_API_VERSION >= 4
key, key_size, session_id, session_id_size);
}
diff --git a/src/starboard/shared/starboard/link_receiver.cc b/src/starboard/shared/starboard/link_receiver.cc
index e94db87..45b9d3f 100644
--- a/src/starboard/shared/starboard/link_receiver.cc
+++ b/src/starboard/shared/starboard/link_receiver.cc
@@ -276,20 +276,46 @@
LinkReceiver::Impl::~Impl() {
SB_DCHECK(!SbThreadIsEqual(thread_, SbThreadGetCurrent()));
quit_.store(true);
- SbSocketWaiterWakeUp(waiter_);
+ if (SbSocketWaiterIsValid(waiter_)) {
+ SbSocketWaiterWakeUp(waiter_);
+ }
destroy_waiter_.Put();
SbThreadJoin(thread_, NULL);
}
void LinkReceiver::Impl::Run() {
waiter_ = SbSocketWaiterCreate();
- SB_DCHECK(SbSocketWaiterIsValid(waiter_));
+ if (!SbSocketWaiterIsValid(waiter_)) {
+ SB_LOG(WARNING) << "Unable to create SbSocketWaiter.";
+ waiter_initialized_.Put();
+ return;
+ }
+
listen_socket_ =
CreateListeningSocket(kSbSocketAddressTypeIpv4, specified_port_);
- SB_DCHECK(listen_socket_);
+ if (!listen_socket_ || !listen_socket_->IsValid()) {
+ listen_socket_ = CreateListeningSocket(kSbSocketAddressTypeIpv6,
+ specified_port_);
+ }
+ if (!listen_socket_ || !listen_socket_->IsValid()) {
+ SB_LOG(WARNING) << "Unable to start LinkReceiver on port "
+ << specified_port_ << ".";
+ SbSocketWaiterDestroy(waiter_);
+ waiter_ = kSbSocketWaiterInvalid;
+ waiter_initialized_.Put();
+ return;
+ }
+
actual_port_ = 0;
bool result = GetBoundPort(listen_socket_.get(), &actual_port_);
- SB_DCHECK(result);
+ if (!result) {
+ SB_LOG(WARNING) << "Unable to get LinkReceiver bound port.";
+ SbSocketWaiterDestroy(waiter_);
+ waiter_ = kSbSocketWaiterInvalid;
+ waiter_initialized_.Put();
+ return;
+ }
+
SB_LOG(INFO) << "LinkReceiver port: " << actual_port_;
char port_string[32] = {0};
diff --git a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
index bdf679d..5859831 100644
--- a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
+++ b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -16,84 +16,11 @@
#include "starboard/character.h"
#include "starboard/log.h"
-#include "starboard/shared/starboard/media/mime_type.h"
-#include "starboard/string.h"
-
-#if SB_API_VERSION < 4
-
-using starboard::shared::starboard::media::MimeType;
-
-namespace {
-bool HasMultiChannelOutput() {
- int count = SbMediaGetAudioOutputCount();
- for (int output_index = 0; output_index < count; ++output_index) {
- SbMediaAudioConfiguration configuration;
- if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
- continue;
- }
-
- if (configuration.number_of_channels >= 6) {
- return true;
- }
- }
-
- return false;
-}
-} // namespace
-
-SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime,
- const char* key_system) {
- if (mime == NULL) {
- SB_DLOG(WARNING) << "mime cannot be NULL";
- return kSbMediaSupportTypeNotSupported;
- }
- if (key_system == NULL) {
- SB_DLOG(WARNING) << "key_system cannot be NULL";
- return kSbMediaSupportTypeNotSupported;
- }
- if (SbStringGetLength(key_system) != 0) {
- if (!SbMediaIsSupported(kSbMediaVideoCodecNone, kSbMediaAudioCodecNone,
- key_system)) {
- return kSbMediaSupportTypeNotSupported;
- }
- }
- MimeType mime_type(mime);
- if (!mime_type.is_valid()) {
- SB_DLOG(WARNING) << mime << " is not a valid mime type";
- return kSbMediaSupportTypeNotSupported;
- }
-
- if (mime_type.type() == "audio" && mime_type.subtype() == "mp4") {
- // TODO: Base this on the "channels" param, not the codecs param.
- if (mime_type.GetParamStringValue("codecs", "") == "aac51") {
- if (!HasMultiChannelOutput()) {
- return kSbMediaSupportTypeNotSupported;
- }
- }
- return kSbMediaSupportTypeProbably;
- }
- if (mime_type.type() == "video" && mime_type.subtype() == "mp4") {
- return kSbMediaSupportTypeProbably;
- }
-#if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- if (mime_type.type() == "video" && mime_type.subtype() == "webm") {
- if (mime_type.GetCodecs().empty()) {
- return kSbMediaSupportTypeMaybe;
- }
- if (mime_type.GetParamStringValue(0) == "vp9") {
- return kSbMediaSupportTypeProbably;
- }
- return kSbMediaSupportTypeNotSupported;
- }
-#endif // SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
- return kSbMediaSupportTypeNotSupported;
-}
-
-#else // SB_API_VERSION < 4
-
#include "starboard/shared/starboard/media/codec_util.h"
#include "starboard/shared/starboard/media/media_support_internal.h"
#include "starboard/shared/starboard/media/media_util.h"
+#include "starboard/shared/starboard/media/mime_type.h"
+#include "starboard/string.h"
using starboard::shared::starboard::media::GetAudioCodecFromString;
using starboard::shared::starboard::media::GetTransferIdFromString;
@@ -313,5 +240,3 @@
return CanPlayMimeAndKeySystem(mime_type, key_system);
}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/starboard/media/media_support_internal.h b/src/starboard/shared/starboard/media/media_support_internal.h
index ff42fd6..94d78e8 100644
--- a/src/starboard/shared/starboard/media/media_support_internal.h
+++ b/src/starboard/shared/starboard/media/media_support_internal.h
@@ -18,10 +18,6 @@
#include "starboard/media.h"
#include "starboard/shared/internal_only.h"
-#if SB_API_VERSION < 4
-#error "Incorrect SB_API_VERSION to include media_support_internal.h"
-#endif // SB_API_VERSION < 4
-
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/src/starboard/shared/starboard/microphone/microphone_close.cc b/src/starboard/shared/starboard/microphone/microphone_close.cc
index 9519189..ac1cba5 100644
--- a/src/starboard/shared/starboard/microphone/microphone_close.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_close.cc
@@ -14,12 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
bool SbMicrophoneClose(SbMicrophone microphone) {
return SbMicrophoneIsValid(microphone) ? microphone->Close() : false;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_create.cc b/src/starboard/shared/starboard/microphone/microphone_create.cc
index 962af50..c8daf54 100644
--- a/src/starboard/shared/starboard/microphone/microphone_create.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_create.cc
@@ -14,15 +14,15 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
SbMicrophone SbMicrophoneCreate(SbMicrophoneId id,
int sample_rate_in_hz,
int buffer_size) {
return SbMicrophonePrivate::CreateMicrophone(id, sample_rate_in_hz,
buffer_size);
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_destroy.cc b/src/starboard/shared/starboard/microphone/microphone_destroy.cc
index 59ec025..a397600 100644
--- a/src/starboard/shared/starboard/microphone/microphone_destroy.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_destroy.cc
@@ -14,12 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
void SbMicrophoneDestroy(SbMicrophone microphone) {
SbMicrophonePrivate::DestroyMicrophone(microphone);
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_get_available.cc b/src/starboard/shared/starboard/microphone/microphone_get_available.cc
index 58fe474..baacd3b 100644
--- a/src/starboard/shared/starboard/microphone/microphone_get_available.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_get_available.cc
@@ -14,14 +14,14 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array,
int info_array_size) {
return SbMicrophonePrivate::GetAvailableMicrophones(out_info_array,
info_array_size);
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_internal.h b/src/starboard/shared/starboard/microphone/microphone_internal.h
index 4895da5..fd05557 100644
--- a/src/starboard/shared/starboard/microphone/microphone_internal.h
+++ b/src/starboard/shared/starboard/microphone/microphone_internal.h
@@ -18,7 +18,9 @@
#include "starboard/microphone.h"
#include "starboard/shared/internal_only.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to include this file."
+#endif
struct SbMicrophonePrivate {
virtual ~SbMicrophonePrivate() {}
@@ -36,6 +38,4 @@
static void DestroyMicrophone(SbMicrophone microphone);
};
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#endif // STARBOARD_SHARED_STARBOARD_MICROPHONE_MICROPHONE_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc b/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
index 2154a0b..d4ca963 100644
--- a/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
@@ -14,14 +14,14 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
bool SbMicrophoneIsSampleRateSupported(SbMicrophoneId id,
int sample_rate_in_hz) {
return SbMicrophonePrivate::IsMicrophoneSampleRateSupported(
id, sample_rate_in_hz);
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_open.cc b/src/starboard/shared/starboard/microphone/microphone_open.cc
index 4042c76..0f21f7f 100644
--- a/src/starboard/shared/starboard/microphone/microphone_open.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_open.cc
@@ -14,12 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
bool SbMicrophoneOpen(SbMicrophone microphone) {
return SbMicrophoneIsValid(microphone) ? microphone->Open() : false;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_read.cc b/src/starboard/shared/starboard/microphone/microphone_read.cc
index 1fbfce6..0eac3db 100644
--- a/src/starboard/shared/starboard/microphone/microphone_read.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_read.cc
@@ -14,10 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
-
#include "starboard/shared/starboard/microphone/microphone_internal.h"
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
+
int SbMicrophoneRead(SbMicrophone microphone,
void* out_audio_data,
int audio_data_size) {
@@ -25,5 +27,3 @@
? microphone->Read(out_audio_data, audio_data_size)
: -1;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h b/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h
index 97bae5b..b67277d 100644
--- a/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h
+++ b/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h
@@ -17,6 +17,7 @@
#include <queue>
+#include "starboard/common/scoped_ptr.h"
#include "starboard/log.h"
#include "starboard/media.h"
#include "starboard/shared/internal_only.h"
@@ -36,21 +37,18 @@
class AudioFrameTracker {
public:
AudioFrameTracker() { Reset(); }
+ virtual ~AudioFrameTracker() {}
// Reset the class to its initial state. In this state there is no frames
// tracked and the playback frames is 0.
- void Reset() {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
+ virtual void Reset() {
while (!frame_records_.empty()) {
frame_records_.pop();
}
frames_played_adjusted_to_playback_rate_ = 0;
}
- void AddFrames(int number_of_frames, double playback_rate) {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
+ virtual void AddFrames(int number_of_frames, double playback_rate) {
if (number_of_frames == 0) {
return;
}
@@ -65,9 +63,7 @@
}
}
- void RecordPlayedFrames(int number_of_frames) {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
+ virtual void RecordPlayedFrames(int number_of_frames) {
while (number_of_frames > 0 && !frame_records_.empty()) {
FrameRecord& record = frame_records_.front();
if (record.number_of_frames > number_of_frames) {
@@ -86,23 +82,50 @@
<< number_of_frames << " " << frame_records_.size();
}
- SbMediaTime GetFramePlayedAdjustedToPlaybackRate() const {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
+ virtual SbMediaTime GetFramesPlayedAdjustedToPlaybackRate() const {
return frames_played_adjusted_to_playback_rate_;
}
- private:
+ protected:
struct FrameRecord {
int number_of_frames;
double playback_rate;
};
- ThreadChecker thread_checker_;
std::queue<FrameRecord> frame_records_;
int frames_played_adjusted_to_playback_rate_;
};
+// A specialization of |AudioFrameTracker| that can only be called from the
+// thread that it was created on, and will assert this in debug builds.
+class SingleThreadedAudioFrameTracker : public AudioFrameTracker {
+ public:
+ // Reset the class to its initial state. In this state there is no frames
+ // tracked and the playback frames is 0.
+ void Reset() SB_OVERRIDE {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ AudioFrameTracker::Reset();
+ }
+
+ void AddFrames(int number_of_frames, double playback_rate) SB_OVERRIDE {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ AudioFrameTracker::AddFrames(number_of_frames, playback_rate);
+ }
+
+ void RecordPlayedFrames(int number_of_frames) SB_OVERRIDE {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ AudioFrameTracker::RecordPlayedFrames(number_of_frames);
+ }
+
+ SbMediaTime GetFramesPlayedAdjustedToPlaybackRate() const SB_OVERRIDE {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ return AudioFrameTracker::GetFramesPlayedAdjustedToPlaybackRate();
+ }
+
+ private:
+ ThreadChecker thread_checker_;
+};
+
} // namespace filter
} // namespace player
} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
index 9e63967..2ad5415 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -64,8 +64,10 @@
} // namespace
-AudioRendererImpl::AudioRendererImpl(scoped_ptr<AudioDecoder> decoder,
- const SbMediaAudioHeader& audio_header)
+AudioRendererImpl::AudioRendererImpl(
+ scoped_ptr<AudioDecoder> decoder,
+ const SbMediaAudioHeader& audio_header,
+ scoped_ptr<AudioFrameTracker> audio_frame_tracker)
: eos_state_(kEOSNotReceived),
channels_(audio_header.number_of_channels),
sink_sample_type_(GetSinkAudioSampleType()),
@@ -84,7 +86,8 @@
audio_sink_(kSbAudioSinkInvalid),
can_accept_more_data_(true),
process_audio_data_scheduled_(false),
- decoder_needs_full_reset_(false) {
+ decoder_needs_full_reset_(false),
+ audio_frame_tracker_(audio_frame_tracker.Pass()) {
SB_DCHECK(decoder_ != NULL);
frame_buffers_[0] = &frame_buffer_[0];
@@ -165,7 +168,6 @@
paused_.store(true);
}
-#if SB_API_VERSION >= 4
void AudioRendererImpl::SetPlaybackRate(double playback_rate) {
SB_DCHECK(BelongsToCurrentThread());
@@ -177,7 +179,6 @@
audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
}
}
-#endif // SB_API_VERSION >= 4
void AudioRendererImpl::SetVolume(double volume) {
SB_DCHECK(BelongsToCurrentThread());
@@ -209,7 +210,7 @@
frames_consumed_by_sink_.store(0);
frames_consumed_by_sink_since_last_get_current_time_.store(0);
pending_decoder_outputs_ = 0;
- audio_frame_tracker_.Reset();
+ audio_frame_tracker_->Reset();
frames_consumed_set_at_.store(SbTimeGetMonotonicNow());
can_accept_more_data_ = true;
process_audio_data_scheduled_ = false;
@@ -252,11 +253,11 @@
return seeking_to_pts_;
}
- audio_frame_tracker_.RecordPlayedFrames(
+ audio_frame_tracker_->RecordPlayedFrames(
frames_consumed_by_sink_since_last_get_current_time_.exchange(0));
return seeking_to_pts_ +
- audio_frame_tracker_.GetFramePlayedAdjustedToPlaybackRate() *
+ audio_frame_tracker_->GetFramesPlayedAdjustedToPlaybackRate() *
kSbMediaTimeSecond / decoder_->GetSamplesPerSecond();
}
@@ -291,11 +292,9 @@
&AudioRendererImpl::ConsumeFramesFunc, this);
SB_DCHECK(SbAudioSinkIsValid(audio_sink_));
-#if SB_API_VERSION >= 4
// TODO: Remove SetPlaybackRate() support from audio sink as it only need to
// support play/pause.
audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
-#endif // SB_API_VERSION >= 4
audio_sink_->SetVolume(volume_);
}
@@ -458,8 +457,8 @@
if (decoded_audio->frames() == 0 && eos_state_.load() == kEOSDecoded) {
eos_state_.store(kEOSSentToSink);
}
- audio_frame_tracker_.AddFrames(decoded_audio->frames(),
- playback_rate_to_fill);
+ audio_frame_tracker_->AddFrames(decoded_audio->frames(),
+ playback_rate_to_fill);
// TODO: Support kSbMediaAudioFrameStorageTypePlanar.
decoded_audio->SwitchFormatTo(sink_sample_type_,
kSbMediaAudioFrameStorageTypeInterleaved);
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
index a469037..f1b7730 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
@@ -48,8 +48,12 @@
// exceeds kPrerollTime or if EOS is reached.
static const size_t kPrerollTime = kSbTimeSecond / 4;
- AudioRendererImpl(scoped_ptr<AudioDecoder> decoder,
- const SbMediaAudioHeader& audio_header);
+ AudioRendererImpl(
+ scoped_ptr<AudioDecoder> decoder,
+ const SbMediaAudioHeader& audio_header,
+ scoped_ptr<AudioFrameTracker> audio_frame_tracker =
+ scoped_ptr<AudioFrameTracker>(new SingleThreadedAudioFrameTracker())
+ .Pass());
~AudioRendererImpl() SB_OVERRIDE;
void WriteSample(const scoped_refptr<InputBuffer>& input_buffer) SB_OVERRIDE;
@@ -57,9 +61,7 @@
void Play() SB_OVERRIDE;
void Pause() SB_OVERRIDE;
-#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate) SB_OVERRIDE;
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume) SB_OVERRIDE;
void Seek(SbMediaTime seek_to_pts) SB_OVERRIDE;
@@ -71,6 +73,21 @@
bool IsSeekingInProgress() const SB_OVERRIDE;
SbMediaTime GetCurrentTime() SB_OVERRIDE;
+ protected:
+ atomic_bool paused_;
+ atomic_bool seeking_;
+ SbMediaTime seeking_to_pts_;
+ scoped_ptr<AudioFrameTracker> audio_frame_tracker_;
+
+ atomic_int64_t frames_sent_to_sink_;
+ atomic_int64_t frames_consumed_by_sink_;
+ atomic_int32_t frames_consumed_by_sink_since_last_get_current_time_;
+
+ scoped_ptr<AudioDecoder> decoder_;
+
+ atomic_int64_t frames_consumed_set_at_;
+ double playback_rate_;
+
private:
enum EOSState {
kEOSNotReceived,
@@ -117,25 +134,14 @@
scoped_ptr<AudioResampler> resampler_;
AudioTimeStretcher time_stretcher_;
- double playback_rate_;
double volume_;
- atomic_bool paused_;
- atomic_bool seeking_;
- SbMediaTime seeking_to_pts_;
-
std::vector<uint8_t> frame_buffer_;
uint8_t* frame_buffers_[1];
- atomic_int64_t frames_sent_to_sink_;
- atomic_int64_t frames_consumed_by_sink_;
- atomic_int32_t frames_consumed_by_sink_since_last_get_current_time_;
int32_t pending_decoder_outputs_;
- AudioFrameTracker audio_frame_tracker_;
- atomic_int64_t frames_consumed_set_at_;
Closure log_frames_consumed_closure_;
- scoped_ptr<AudioDecoder> decoder_;
SbAudioSink audio_sink_;
bool can_accept_more_data_;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
index a6ca64b..7f0ae7f 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -35,9 +35,7 @@
virtual void WriteEndOfStream() = 0;
virtual void Play() = 0;
virtual void Pause() = 0;
-#if SB_API_VERSION >= 4
virtual void SetPlaybackRate(double playback_rate) = 0;
-#endif // SB_API_VERSION >= 4
virtual void SetVolume(double volume) = 0;
virtual void Seek(SbMediaTime seek_to_pts) = 0;
virtual bool IsEndOfStreamWritten() const = 0;
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index c373d90..6882f4a 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -42,16 +42,9 @@
SbMediaVideoCodec video_codec,
SbMediaAudioCodec audio_codec,
SbDrmSystem drm_system,
- const SbMediaAudioHeader& audio_header
-#if SB_API_VERSION >= 4
- ,
+ const SbMediaAudioHeader& audio_header,
SbPlayerOutputMode output_mode,
- SbDecodeTargetGraphicsContextProvider* provider
-#elif SB_API_VERSION >= 3
- ,
- SbDecodeTargetProvider* provider
-#endif // SB_API_VERSION >= 4
- )
+ SbDecodeTargetGraphicsContextProvider* provider)
: player_worker_(NULL),
job_queue_(NULL),
player_(kSbPlayerInvalid),
@@ -63,19 +56,10 @@
drm_system_(drm_system),
audio_header_(audio_header),
paused_(false),
-#if SB_API_VERSION >= 4
playback_rate_(1.0),
-#endif // SB_API_VERSION >= 4
- volume_(1.0)
-#if SB_API_VERSION >= 4
- ,
+ volume_(1.0),
output_mode_(output_mode),
- decode_target_graphics_context_provider_(provider)
-#elif SB_API_VERSION >= 3
- ,
- decode_target_provider_(provider)
-#endif // SB_API_VERSION >= 4
-{
+ decode_target_graphics_context_provider_(provider) {
#if SB_API_VERSION >= SB_AUDIO_SPECIFIC_CONFIG_AS_POINTER
if (audio_header_.audio_specific_config_size > 0) {
audio_specific_config_.reset(
@@ -92,11 +76,7 @@
}
bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {
-#if SB_API_VERSION >= 4
return (output_mode_ == kSbPlayerOutputModePunchOut);
-#else
- return true;
-#endif // SB_API_VERSION >= 4
}
bool FilterBasedPlayerWorkerHandler::Init(
@@ -127,16 +107,9 @@
AudioParameters audio_parameters = {audio_codec_, audio_header_, drm_system_,
job_queue_};
- VideoParameters video_parameters = {
- video_codec_,
- drm_system_,
- job_queue_
-#if SB_API_VERSION >= 4
- ,
- output_mode_,
- decode_target_graphics_context_provider_
-#endif // SB_API_VERSION >= 4
- }; // NOLINT(whitespace/parens)
+ VideoParameters video_parameters = {video_codec_, drm_system_, job_queue_,
+ output_mode_,
+ decode_target_graphics_context_provider_};
scoped_ptr<PlayerComponents> media_components =
PlayerComponents::Create(audio_parameters, video_parameters);
@@ -148,9 +121,7 @@
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
media_components->GetRenderers(&audio_renderer_, &video_renderer_);
-#if SB_API_VERSION >= 4
audio_renderer_->SetPlaybackRate(playback_rate_);
-#endif // SB_API_VERSION >= 4
audio_renderer_->SetVolume(volume_);
job_queue_->Schedule(update_closure_, kUpdateInterval);
@@ -281,7 +252,6 @@
return true;
}
-#if SB_API_VERSION >= 4
bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -294,7 +264,6 @@
audio_renderer_->SetPlaybackRate(playback_rate_);
return true;
}
-#endif // SB_API_VERSION >= 4
void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -362,8 +331,6 @@
void FilterBasedPlayerWorkerHandler::Stop() {
job_queue_->Remove(update_closure_);
- audio_renderer_.reset();
-
scoped_ptr<VideoRenderer> video_renderer;
{
// Set |video_renderer_| to null with the lock, but we actually destroy
@@ -375,6 +342,8 @@
}
video_renderer.reset();
+ audio_renderer_.reset();
+
if (IsPunchoutMode()) {
// Clear the video frame as we terminate.
shared::starboard::Application::Get()->HandleFrame(
@@ -382,7 +351,6 @@
}
}
-#if SB_API_VERSION >= 4
SbDecodeTarget FilterBasedPlayerWorkerHandler::GetCurrentDecodeTarget() {
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
@@ -392,7 +360,6 @@
return kSbDecodeTargetInvalid;
}
}
-#endif // SB_API_VERSION >= 4
} // namespace filter
} // namespace player
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
index 20655ca..efceeaa 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -17,9 +17,7 @@
#include "starboard/common/scoped_ptr.h"
#include "starboard/configuration.h"
-#if SB_API_VERSION >= 3
#include "starboard/decode_target.h"
-#endif
#include "starboard/media.h"
#include "starboard/player.h"
#include "starboard/shared/starboard/player/closure.h"
@@ -38,19 +36,13 @@
class FilterBasedPlayerWorkerHandler : public PlayerWorker::Handler {
public:
- FilterBasedPlayerWorkerHandler(SbMediaVideoCodec video_codec,
- SbMediaAudioCodec audio_codec,
- SbDrmSystem drm_system,
- const SbMediaAudioHeader& audio_header
-#if SB_API_VERSION >= 4
- ,
- SbPlayerOutputMode output_mode,
- SbDecodeTargetGraphicsContextProvider* provider
-#elif SB_API_VERSION >= 3
- ,
- SbDecodeTargetProvider* provider
-#endif // SB_API_VERSION >= 4
- ); // NOLINT(whitespace/parens)
+ FilterBasedPlayerWorkerHandler(
+ SbMediaVideoCodec video_codec,
+ SbMediaAudioCodec audio_codec,
+ SbDrmSystem drm_system,
+ const SbMediaAudioHeader& audio_header,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider* provider);
private:
bool IsPunchoutMode() const;
@@ -65,18 +57,14 @@
bool* written) SB_OVERRIDE;
bool WriteEndOfStream(SbMediaType sample_type) SB_OVERRIDE;
bool SetPause(bool pause) SB_OVERRIDE;
-#if SB_API_VERSION >= 4
bool SetPlaybackRate(double playback_rate) SB_OVERRIDE;
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume) SB_OVERRIDE;
bool SetBounds(const PlayerWorker::Bounds& bounds) SB_OVERRIDE;
void Stop() SB_OVERRIDE;
void Update();
-#if SB_API_VERSION >= 4
SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
-#endif // SB_API_VERSION >= 4
PlayerWorker* player_worker_;
JobQueue* job_queue_;
@@ -99,9 +87,7 @@
scoped_ptr<VideoRenderer> video_renderer_;
bool paused_;
-#if SB_API_VERSION >= 4
double playback_rate_;
-#endif // SB_API_VERSION >= 4
double volume_;
PlayerWorker::Bounds bounds_;
Closure update_closure_;
@@ -116,13 +102,9 @@
// accesses are happening from the same thread.
::starboard::Mutex video_renderer_existence_mutex_;
-#if SB_API_VERSION >= 4
SbPlayerOutputMode output_mode_;
SbDecodeTargetGraphicsContextProvider*
decode_target_graphics_context_provider_;
-#elif SB_API_VERSION >= 3
- SbDecodeTargetProvider* decode_target_provider_;
-#endif // SB_API_VERSION >= 4
};
} // namespace filter
diff --git a/src/starboard/shared/starboard/player/filter/player_components.h b/src/starboard/shared/starboard/player/filter/player_components.h
index cf2a60d..7f55b72 100644
--- a/src/starboard/shared/starboard/player/filter/player_components.h
+++ b/src/starboard/shared/starboard/player/filter/player_components.h
@@ -38,11 +38,9 @@
SbMediaVideoCodec video_codec;
SbDrmSystem drm_system;
JobQueue* job_queue;
-#if SB_API_VERSION >= 4
SbPlayerOutputMode output_mode;
SbDecodeTargetGraphicsContextProvider*
decode_target_graphics_context_provider;
-#endif // SB_API_VERSION >= 4
};
// All the platform specific components that a
diff --git a/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc b/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
index f4bc6f6..7e4e0bd 100644
--- a/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
@@ -180,14 +180,12 @@
Host* host_;
};
-#if SB_API_VERSION >= 4
// static
bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
SbDrmSystem drm_system) {
return output_mode == kSbPlayerOutputModePunchOut;
}
-#endif // SB_API_VERSION >= 4
// static
scoped_ptr<PlayerComponents> PlayerComponents::Create(
diff --git a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
index a8c8952..215a50f 100644
--- a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
@@ -49,20 +49,16 @@
// is called again.
virtual void Reset() = 0;
-#if SB_API_VERSION >= 3
// May be called from an arbitrary thread (e.g. a renderer thread).
virtual SbDecodeTarget GetCurrentDecodeTarget() {
return kSbDecodeTargetInvalid;
}
-#endif // SB_API_VERSION >= 3
-#if SB_API_VERSION >= 4
// Individual implementations must implement this function to indicate which
// output modes they support.
static bool OutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
SbDrmSystem drm_system);
-#endif // SB_API_VERSION >= 3
};
// An extended |VideoDecoder| that is capable of providing |VideoFrame|s to
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
index 685927f..badd1f1 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
@@ -151,7 +151,6 @@
need_more_input_ = (status == VideoDecoder::kNeedMoreInput);
}
-#if SB_API_VERSION >= 4
SbDecodeTarget VideoRendererImpl::GetCurrentDecodeTarget() {
if (decoder_) {
return decoder_->GetCurrentDecodeTarget();
@@ -160,8 +159,6 @@
}
}
-#endif // SB_API_VERSION >= 4
-
::starboard::scoped_refptr<VideoFrame>
VideoRendererImpl::GetLastDisplayedFrame() {
ScopedLock lock(mutex_);
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
index 77a1d31..c69aa8b 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
@@ -58,9 +58,7 @@
bool CanAcceptMoreData() const SB_OVERRIDE;
bool IsSeekingInProgress() const SB_OVERRIDE;
-#if SB_API_VERSION >= 4
SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
-#endif // SB_API_VERSION >= 4
scoped_refptr<VideoFrame> GetLastDisplayedFrame();
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
index cffa6de..85906bf 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
@@ -44,9 +44,7 @@
virtual bool IsEndOfStreamPlayed() const = 0;
virtual bool CanAcceptMoreData() const = 0;
virtual bool IsSeekingInProgress() const = 0;
-#if SB_API_VERSION >= 4
virtual SbDecodeTarget GetCurrentDecodeTarget() = 0;
-#endif // SB_API_VERSION >= 4
};
} // namespace filter
diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc
index 25dfe9b..3a948db 100644
--- a/src/starboard/shared/starboard/player/player_create.cc
+++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -18,9 +18,7 @@
#include "starboard/decode_target.h"
#include "starboard/log.h"
#include "starboard/shared/media_session/playback_state.h"
-#if SB_API_VERSION >= 4
#include "starboard/shared/starboard/media/media_support_internal.h"
-#endif // SB_API_VERSION >= 4
#include "starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h"
#include "starboard/shared/starboard/player/player_internal.h"
#include "starboard/shared/starboard/player/player_worker.h"
@@ -41,19 +39,11 @@
SbPlayerDeallocateSampleFunc sample_deallocate_func,
SbPlayerDecoderStatusFunc decoder_status_func,
SbPlayerStatusFunc player_status_func,
- void* context
-#if SB_API_VERSION >= 4
- ,
+ void* context,
SbPlayerOutputMode output_mode,
- SbDecodeTargetGraphicsContextProvider* provider
-#elif SB_API_VERSION >= 3
- ,
- SbDecodeTargetProvider* provider
-#endif // SB_API_VERSION >= 4
- ) {
+ SbDecodeTargetGraphicsContextProvider* provider) {
SB_UNREFERENCED_PARAMETER(window);
-#if SB_API_VERSION >= 4
const int64_t kDefaultBitRate = 0;
if (!SbMediaIsAudioSupported(audio_codec, kDefaultBitRate)) {
SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec;
@@ -69,45 +59,22 @@
SB_LOG(ERROR) << "Unsupported video codec " << video_codec;
return kSbPlayerInvalid;
}
-#else // SB_API_VERSION >= 4
- if (audio_codec != kSbMediaAudioCodecAac) {
- SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec;
- return kSbPlayerInvalid;
- }
-
- if (video_codec != kSbMediaVideoCodecH264) {
- SB_LOG(ERROR) << "Unsupported video codec " << video_codec;
- return kSbPlayerInvalid;
- }
-#endif // SB_API_VERSION >= 4
if (!audio_header) {
SB_LOG(ERROR) << "SbPlayerCreate() requires a non-NULL SbMediaAudioHeader";
return kSbPlayerInvalid;
}
-#if SB_API_VERSION >= 4
if (!SbPlayerOutputModeSupported(output_mode, video_codec, drm_system)) {
SB_LOG(ERROR) << "Unsupported player output mode " << output_mode;
return kSbPlayerInvalid;
}
-#endif // SB_API_VERSION >= 4
UpdateActiveSessionPlatformPlaybackState(kPlaying);
-#if SB_API_VERSION >= 4
starboard::scoped_ptr<PlayerWorker::Handler> handler(
new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
*audio_header, output_mode, provider));
-#elif SB_API_VERSION >= 3
- starboard::scoped_ptr<PlayerWorker::Handler> handler(
- new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
- *audio_header, provider));
-#else // SB_API_VERSION >= 4
- starboard::scoped_ptr<PlayerWorker::Handler> handler(
- new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
- *audio_header));
-#endif // SB_API_VERSION >= 4
return new SbPlayerPrivate(duration_pts, sample_deallocate_func,
decoder_status_func, player_status_func, context,
diff --git a/src/starboard/shared/starboard/player/player_get_current_frame.cc b/src/starboard/shared/starboard/player/player_get_current_frame.cc
index 47eb30f..a40c4b7 100644
--- a/src/starboard/shared/starboard/player/player_get_current_frame.cc
+++ b/src/starboard/shared/starboard/player/player_get_current_frame.cc
@@ -18,8 +18,6 @@
#include "starboard/log.h"
#include "starboard/shared/starboard/player/player_internal.h"
-#if SB_API_VERSION >= 4
-
SbDecodeTarget SbPlayerGetCurrentFrame(SbPlayer player) {
if (!SbPlayerIsValid(player)) {
SB_DLOG(WARNING) << "player is invalid.";
@@ -28,5 +26,3 @@
return player->GetCurrentDecodeTarget();
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc
index 3b46a5d..7e017cc 100644
--- a/src/starboard/shared/starboard/player/player_internal.cc
+++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -44,9 +44,7 @@
frame_width_(0),
frame_height_(0),
is_paused_(true),
-#if SB_API_VERSION >= 4
playback_rate_(1.0),
-#endif // SB_API_VERSION >= 4
volume_(1.0),
total_video_frames_(0),
dropped_video_frames_(0),
@@ -116,21 +114,17 @@
out_player_info->total_video_frames = total_video_frames_;
out_player_info->dropped_video_frames = dropped_video_frames_;
out_player_info->corrupted_video_frames = 0;
-#if SB_API_VERSION >= 4
out_player_info->playback_rate = playback_rate_;
-#endif // SB_API_VERSION >= 4
}
void SbPlayerPrivate::SetPause(bool pause) {
worker_->SetPause(pause);
}
-#if SB_API_VERSION >= 4
void SbPlayerPrivate::SetPlaybackRate(double playback_rate) {
playback_rate_ = playback_rate;
worker_->SetPlaybackRate(playback_rate);
}
-#endif // SB_API_VERSION >= 4
void SbPlayerPrivate::SetVolume(double volume) {
volume_ = volume;
@@ -151,8 +145,6 @@
dropped_video_frames_ = dropped_video_frames;
}
-#if SB_API_VERSION >= 4
SbDecodeTarget SbPlayerPrivate::GetCurrentDecodeTarget() {
return worker_->GetCurrentDecodeTarget();
}
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/starboard/player/player_internal.h b/src/starboard/shared/starboard/player/player_internal.h
index fdb3d09..67ce3bb 100644
--- a/src/starboard/shared/starboard/player/player_internal.h
+++ b/src/starboard/shared/starboard/player/player_internal.h
@@ -50,14 +50,10 @@
void GetInfo(SbPlayerInfo* out_player_info);
void SetPause(bool pause);
-#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate);
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume);
-#if SB_API_VERSION >= 4
SbDecodeTarget GetCurrentDecodeTarget();
-#endif // SB_API_VERSION >= 4
private:
// PlayerWorker::Host methods.
@@ -75,9 +71,7 @@
int frame_width_;
int frame_height_;
bool is_paused_;
-#if SB_API_VERSION >= 4
double playback_rate_;
-#endif // SB_API_VERSION >= 4
double volume_;
int total_video_frames_;
int dropped_video_frames_;
diff --git a/src/starboard/shared/starboard/player/player_output_mode_supported.cc b/src/starboard/shared/starboard/player/player_output_mode_supported.cc
index 4d34cce..25dcfe6 100644
--- a/src/starboard/shared/starboard/player/player_output_mode_supported.cc
+++ b/src/starboard/shared/starboard/player/player_output_mode_supported.cc
@@ -18,13 +18,9 @@
#include "starboard/log.h"
#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
-#if SB_API_VERSION >= 4
-
bool SbPlayerOutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
SbDrmSystem drm_system) {
return starboard::shared::starboard::player::filter::VideoDecoder::
OutputModeSupported(output_mode, codec, drm_system);
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/starboard/player/player_set_bounds.cc b/src/starboard/shared/starboard/player/player_set_bounds.cc
index c4ac286..c275c82 100644
--- a/src/starboard/shared/starboard/player/player_set_bounds.cc
+++ b/src/starboard/shared/starboard/player/player_set_bounds.cc
@@ -17,12 +17,8 @@
#include "starboard/log.h"
#include "starboard/shared/starboard/player/player_internal.h"
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
-
void SbPlayerSetBounds(SbPlayer player,
-#if SB_API_VERSION >= 4
int /*z_index*/,
-#endif // SB_API_VERSION >= 4
int x,
int y,
int width,
@@ -33,5 +29,3 @@
}
player->SetBounds(x, y, width, height);
}
-
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
diff --git a/src/starboard/shared/starboard/player/player_set_pause.cc b/src/starboard/shared/starboard/player/player_set_pause.cc
deleted file mode 100644
index ae5cd95..0000000
--- a/src/starboard/shared/starboard/player/player_set_pause.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/player.h"
-
-#include "starboard/log.h"
-#include "starboard/shared/starboard/player/player_internal.h"
-
-#if SB_API_VERSION < 4
-
-void SbPlayerSetPause(SbPlayer player, bool pause) {
- if (!SbPlayerIsValid(player)) {
- SB_DLOG(WARNING) << "player is invalid.";
- return;
- }
- player->SetPause(pause);
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/starboard/player/player_set_playback_rate.cc b/src/starboard/shared/starboard/player/player_set_playback_rate.cc
index aaf96d1..7b6288a 100644
--- a/src/starboard/shared/starboard/player/player_set_playback_rate.cc
+++ b/src/starboard/shared/starboard/player/player_set_playback_rate.cc
@@ -23,8 +23,6 @@
using starboard::shared::media_session::
UpdateActiveSessionPlatformPlaybackState;
-#if SB_API_VERSION >= 4
-
bool SbPlayerSetPlaybackRate(SbPlayer player, double playback_rate) {
if (!SbPlayerIsValid(player)) {
SB_DLOG(WARNING) << "player is invalid.";
@@ -40,5 +38,3 @@
UpdateActiveSessionPlatformPlaybackState(paused ? kPaused : kPlaying);
return true;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index 24e834c..7d9494d 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -243,14 +243,12 @@
}
}
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void PlayerWorker::DoSetBounds(Bounds bounds) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!handler_->SetBounds(bounds)) {
UpdatePlayerState(kSbPlayerStateError);
}
}
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void PlayerWorker::DoSetPause(bool pause) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -260,7 +258,6 @@
}
}
-#if SB_API_VERSION >= 4
void PlayerWorker::DoSetPlaybackRate(double playback_rate) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -268,7 +265,6 @@
UpdatePlayerState(kSbPlayerStateError);
}
}
-#endif // SB_API_VERSION >= 4
void PlayerWorker::DoSetVolume(double volume) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h
index 84be839..30da877 100644
--- a/src/starboard/shared/starboard/player/player_worker.h
+++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -80,9 +80,7 @@
bool* written) = 0;
virtual bool WriteEndOfStream(SbMediaType sample_type) = 0;
virtual bool SetPause(bool pause) = 0;
-#if SB_API_VERSION >= 4
virtual bool SetPlaybackRate(double playback_rate) = 0;
-#endif // SB_API_VERSION >= 4
virtual void SetVolume(double volume) = 0;
virtual bool SetBounds(const Bounds& bounds) = 0;
@@ -92,9 +90,7 @@
// after and is no longer safe to access.
virtual void Stop() = 0;
-#if SB_API_VERSION >= 4
virtual SbDecodeTarget GetCurrentDecodeTarget() = 0;
-#endif // SB_API_VERSION >= 4
};
PlayerWorker(Host* host,
@@ -120,17 +116,14 @@
Bind(&PlayerWorker::DoWriteEndOfStream, this, sample_type));
}
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void SetBounds(Bounds bounds) {
job_queue_->Schedule(Bind(&PlayerWorker::DoSetBounds, this, bounds));
}
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void SetPause(bool pause) {
job_queue_->Schedule(Bind(&PlayerWorker::DoSetPause, this, pause));
}
-#if SB_API_VERSION >= 4
void SetPlaybackRate(double playback_rate) {
// Arbitrary values to give the playback rate a valid range. A particular
// implementation may have stricter or looser requirement, or even only
@@ -147,7 +140,6 @@
job_queue_->Schedule(
Bind(&PlayerWorker::DoSetPlaybackRate, this, playback_rate));
}
-#endif // SB_API_VERSION >= 4
void SetVolume(double volume) {
job_queue_->Schedule(Bind(&PlayerWorker::DoSetVolume, this, volume));
@@ -157,11 +149,9 @@
host_->UpdateDroppedVideoFrames(dropped_video_frames);
}
-#if SB_API_VERSION >= 4
SbDecodeTarget GetCurrentDecodeTarget() {
return handler_->GetCurrentDecodeTarget();
}
-#endif // SB_API_VERSION >= 4
private:
void UpdateMediaTime(SbMediaTime time);
@@ -176,13 +166,9 @@
void DoWriteSample(const scoped_refptr<InputBuffer>& input_buffer);
void DoWritePendingSamples();
void DoWriteEndOfStream(SbMediaType sample_type);
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void DoSetBounds(Bounds bounds);
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void DoSetPause(bool pause);
-#if SB_API_VERSION >= 4
void DoSetPlaybackRate(double rate);
-#endif // SB_API_VERSION >= 4
void DoSetVolume(double volume);
void DoStop();
diff --git a/src/starboard/shared/starboard/player/player_write_sample.cc b/src/starboard/shared/starboard/player/player_write_sample.cc
index 5fd3ca5..770b981 100644
--- a/src/starboard/shared/starboard/player/player_write_sample.cc
+++ b/src/starboard/shared/starboard/player/player_write_sample.cc
@@ -17,8 +17,6 @@
#include "starboard/log.h"
#include "starboard/shared/starboard/player/player_internal.h"
-#if SB_API_VERSION >= 4
-
void SbPlayerWriteSample(SbPlayer player,
SbMediaType sample_type,
#if SB_API_VERSION >= SB_PLAYER_WRITE_SAMPLE_EXTRA_CONST_API_VERSION
@@ -57,23 +55,3 @@
number_of_sample_buffers, sample_pts, video_sample_info,
sample_drm_info);
}
-
-#else // SB_API_VERSION >= 4
-
-void SbPlayerWriteSample(SbPlayer player,
- SbMediaType sample_type,
- const void* sample_buffer,
- int sample_buffer_size,
- SbMediaTime sample_pts,
- const SbMediaVideoSampleInfo* video_sample_info,
- const SbDrmSampleInfo* sample_drm_info) {
- if (!SbPlayerIsValid(player)) {
- SB_DLOG(WARNING) << "player is invalid.";
- return;
- }
-
- player->WriteSample(sample_type, sample_buffer, sample_buffer_size,
- sample_pts, video_sample_info, sample_drm_info);
-}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/starboard/system_request_pause.cc b/src/starboard/shared/starboard/system_request_pause.cc
index 98feeb4..a7f37d5 100644
--- a/src/starboard/shared/starboard/system_request_pause.cc
+++ b/src/starboard/shared/starboard/system_request_pause.cc
@@ -16,10 +16,6 @@
#include "starboard/shared/starboard/application.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestPause requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestPause() {
starboard::shared::starboard::Application::Get()->Pause(NULL, NULL);
}
diff --git a/src/starboard/shared/starboard/system_request_suspend.cc b/src/starboard/shared/starboard/system_request_suspend.cc
index 779ea2d..8c4da78 100644
--- a/src/starboard/shared/starboard/system_request_suspend.cc
+++ b/src/starboard/shared/starboard/system_request_suspend.cc
@@ -16,10 +16,6 @@
#include "starboard/shared/starboard/application.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestSuspend requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestSuspend() {
starboard::shared::starboard::Application::Get()->Suspend(NULL, NULL);
}
diff --git a/src/starboard/shared/starboard/system_request_unpause.cc b/src/starboard/shared/starboard/system_request_unpause.cc
index c9baab5..7817eb2 100644
--- a/src/starboard/shared/starboard/system_request_unpause.cc
+++ b/src/starboard/shared/starboard/system_request_unpause.cc
@@ -16,10 +16,6 @@
#include "starboard/shared/starboard/application.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestUnpause requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestUnpause() {
starboard::shared::starboard::Application::Get()->Unpause(NULL, NULL);
}
diff --git a/src/starboard/shared/stub/cryptography_create_transformer.cc b/src/starboard/shared/stub/cryptography_create_transformer.cc
index 502a006..469f06b 100644
--- a/src/starboard/shared/stub/cryptography_create_transformer.cc
+++ b/src/starboard/shared/stub/cryptography_create_transformer.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
SbCryptographyTransformer SbCryptographyCreateTransformer(
const char* algorithm,
int block_size_bits,
diff --git a/src/starboard/shared/stub/cryptography_destroy_transformer.cc b/src/starboard/shared/stub/cryptography_destroy_transformer.cc
index 0547106..2d8b7a0 100644
--- a/src/starboard/shared/stub/cryptography_destroy_transformer.cc
+++ b/src/starboard/shared/stub/cryptography_destroy_transformer.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
void SbCryptographyDestroyTransformer(SbCryptographyTransformer transformer) {
SB_UNREFERENCED_PARAMETER(transformer);
}
diff --git a/src/starboard/shared/stub/cryptography_get_tag.cc b/src/starboard/shared/stub/cryptography_get_tag.cc
index 5ca1cbf..f204586 100644
--- a/src/starboard/shared/stub/cryptography_get_tag.cc
+++ b/src/starboard/shared/stub/cryptography_get_tag.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
bool SbCryptographyGetTag(
SbCryptographyTransformer transformer,
void* out_tag,
diff --git a/src/starboard/shared/stub/cryptography_set_authenticated_data.cc b/src/starboard/shared/stub/cryptography_set_authenticated_data.cc
index 93968dd..8be46c9 100644
--- a/src/starboard/shared/stub/cryptography_set_authenticated_data.cc
+++ b/src/starboard/shared/stub/cryptography_set_authenticated_data.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
bool SbCryptographySetAuthenticatedData(
SbCryptographyTransformer transformer,
const void* data,
diff --git a/src/starboard/shared/stub/cryptography_set_initialization_vector.cc b/src/starboard/shared/stub/cryptography_set_initialization_vector.cc
index a070c26..0ec6a27 100644
--- a/src/starboard/shared/stub/cryptography_set_initialization_vector.cc
+++ b/src/starboard/shared/stub/cryptography_set_initialization_vector.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
void SbCryptographySetInitializationVector(
SbCryptographyTransformer transformer,
const void* initialization_vector,
diff --git a/src/starboard/shared/stub/cryptography_transform.cc b/src/starboard/shared/stub/cryptography_transform.cc
index 4488a1f..785050a 100644
--- a/src/starboard/shared/stub/cryptography_transform.cc
+++ b/src/starboard/shared/stub/cryptography_transform.cc
@@ -15,10 +15,6 @@
#include "starboard/configuration.h"
#include "starboard/cryptography.h"
-#if SB_API_VERSION < 4
-#error "SbCryptography requires SB_API_VERSION >= 4."
-#endif
-
int SbCryptographyTransform(SbCryptographyTransformer transformer,
const void* in_data,
int in_data_size,
diff --git a/src/starboard/shared/stub/decode_target_create_blitter.cc b/src/starboard/shared/stub/decode_target_create_blitter.cc
deleted file mode 100644
index 5d807a7..0000000
--- a/src/starboard/shared/stub/decode_target_create_blitter.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat /*format*/,
- SbBlitterSurface* /*planes*/) {
- return kSbDecodeTargetInvalid;
-}
-
-#else // SB_API_VERSION < 4
-
-SbDecodeTarget SbDecodeTargetCreate(SbBlitterDevice device,
- SbDecodeTargetFormat format,
- int width,
- int height) {
- return kSbDecodeTargetInvalid;
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_create_egl.cc b/src/starboard/shared/stub/decode_target_create_egl.cc
deleted file mode 100644
index 82f151f..0000000
--- a/src/starboard/shared/stub/decode_target_create_egl.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-SbDecodeTarget SbDecodeTargetCreate(EGLDisplay /*display*/,
- EGLContext /*context*/,
- SbDecodeTargetFormat /*format*/,
- GLuint* /*planes*/) {
- return kSbDecodeTargetInvalid;
-}
-#else // SB_API_VERSION < 4
-SbDecodeTarget SbDecodeTargetCreate(void* /*display*/,
- void* /*context*/,
- SbDecodeTargetFormat /*format*/,
- int /*width*/,
- int /*height*/) {
- return kSbDecodeTargetInvalid;
-}
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_get_format.cc b/src/starboard/shared/stub/decode_target_get_format.cc
deleted file mode 100644
index 28532f3..0000000
--- a/src/starboard/shared/stub/decode_target_get_format.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-SbDecodeTargetFormat SbDecodeTargetGetFormat(SbDecodeTarget /*decode_target*/) {
- return kSbDecodeTargetFormatInvalid;
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_get_info.cc b/src/starboard/shared/stub/decode_target_get_info.cc
index c8fe44c..fa34fc0 100644
--- a/src/starboard/shared/stub/decode_target_get_info.cc
+++ b/src/starboard/shared/stub/decode_target_get_info.cc
@@ -14,11 +14,7 @@
#include "starboard/decode_target.h"
-#if SB_API_VERSION >= 4
-
bool SbDecodeTargetGetInfo(SbDecodeTarget /*decode_target*/,
SbDecodeTargetInfo* /*out_info*/) {
return false;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/decode_target_get_plane_blitter.cc b/src/starboard/shared/stub/decode_target_get_plane_blitter.cc
deleted file mode 100644
index eac0538..0000000
--- a/src/starboard/shared/stub/decode_target_get_plane_blitter.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-#if SB_HAS(BLITTER)
-SbBlitterSurface SbDecodeTargetGetPlane(SbDecodeTarget /*decode_target*/,
- SbDecodeTargetPlane /*plane*/) {
- return kSbBlitterInvalidSurface;
-}
-#endif // SB_HAS(BLITTER)
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_get_plane_egl.cc b/src/starboard/shared/stub/decode_target_get_plane_egl.cc
deleted file mode 100644
index dec9300..0000000
--- a/src/starboard/shared/stub/decode_target_get_plane_egl.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-#if SB_HAS(GLES2)
-GLuint SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
- SbDecodeTargetPlane plane) {
- return 0;
-}
-#endif // SB_HAS(GLES2)
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_get_size.cc b/src/starboard/shared/stub/decode_target_get_size.cc
deleted file mode 100644
index 09ee7a5..0000000
--- a/src/starboard/shared/stub/decode_target_get_size.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-void SbDecodeTargetGetSize(SbDecodeTarget decode_target,
- int* out_width,
- int* out_height) {
- SB_UNREFERENCED_PARAMETER(decode_target);
- SB_UNREFERENCED_PARAMETER(out_width);
- SB_UNREFERENCED_PARAMETER(out_height);
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/decode_target_is_opaque.cc b/src/starboard/shared/stub/decode_target_is_opaque.cc
deleted file mode 100644
index 16301d9..0000000
--- a/src/starboard/shared/stub/decode_target_is_opaque.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/configuration.h"
-#include "starboard/decode_target.h"
-
-#if SB_API_VERSION < 4
-
-#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
-#error "Requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
-#endif
-
-bool SbDecodeTargetIsOpaque(SbDecodeTarget decode_target) {
- return false;
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/drm_generate_session_update_request.cc b/src/starboard/shared/stub/drm_generate_session_update_request.cc
index 7506e11..789e2f4 100644
--- a/src/starboard/shared/stub/drm_generate_session_update_request.cc
+++ b/src/starboard/shared/stub/drm_generate_session_update_request.cc
@@ -15,9 +15,7 @@
#include "starboard/drm.h"
void SbDrmGenerateSessionUpdateRequest(SbDrmSystem /*drm_system*/,
-#if SB_API_VERSION >= 4
int /*ticket*/,
-#endif // SB_API_VERSION >= 4
const char* /*type*/,
const void* /*initialization_data*/,
int /*initialization_data_size*/) {
diff --git a/src/starboard/shared/stub/drm_update_session.cc b/src/starboard/shared/stub/drm_update_session.cc
index 5c7a9d6..ea24954 100644
--- a/src/starboard/shared/stub/drm_update_session.cc
+++ b/src/starboard/shared/stub/drm_update_session.cc
@@ -15,9 +15,7 @@
#include "starboard/drm.h"
void SbDrmUpdateSession(SbDrmSystem /*drm_system*/,
-#if SB_API_VERSION >= 4
int /*ticket*/,
-#endif // SB_API_VERSION >= 4
const void* /*key*/,
int /*key_size*/,
const void* /*session_id*/,
diff --git a/src/starboard/shared/stub/image_decode.cc b/src/starboard/shared/stub/image_decode.cc
index d0d7782..f139ed5 100644
--- a/src/starboard/shared/stub/image_decode.cc
+++ b/src/starboard/shared/stub/image_decode.cc
@@ -15,11 +15,10 @@
#include "starboard/configuration.h"
#include "starboard/image.h"
-#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
-#error "SbImageDecode requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
+#if !SB_HAS(GRAPHICS)
+#error "SbImageDecode requires SB_HAS(GRAPHICS)."
#endif
-#if SB_API_VERSION >= 4
SbDecodeTarget SbImageDecode(SbDecodeTargetGraphicsContextProvider* provider,
void* data,
int data_size,
@@ -32,17 +31,3 @@
SB_UNREFERENCED_PARAMETER(provider);
return kSbDecodeTargetInvalid;
}
-#else // SB_API_VERSION >= 4
-SbDecodeTarget SbImageDecode(SbDecodeTargetProvider* provider,
- void* data,
- int data_size,
- const char* mime_type,
- SbDecodeTargetFormat format) {
- SB_UNREFERENCED_PARAMETER(data);
- SB_UNREFERENCED_PARAMETER(data_size);
- SB_UNREFERENCED_PARAMETER(format);
- SB_UNREFERENCED_PARAMETER(mime_type);
- SB_UNREFERENCED_PARAMETER(provider);
- return kSbDecodeTargetInvalid;
-}
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/image_is_decode_supported.cc b/src/starboard/shared/stub/image_is_decode_supported.cc
index c4426fd..fc03956 100644
--- a/src/starboard/shared/stub/image_is_decode_supported.cc
+++ b/src/starboard/shared/stub/image_is_decode_supported.cc
@@ -15,8 +15,8 @@
#include "starboard/configuration.h"
#include "starboard/image.h"
-#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
-#error "Requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
+#if !SB_HAS(GRAPHICS)
+#error "Requires SB_HAS(GRAPHICS)."
#endif
bool SbImageIsDecodeSupported(const char* mime_type,
diff --git a/src/starboard/shared/stub/microphone_close.cc b/src/starboard/shared/stub/microphone_close.cc
index 9246202..7589ec4 100644
--- a/src/starboard/shared/stub/microphone_close.cc
+++ b/src/starboard/shared/stub/microphone_close.cc
@@ -14,10 +14,10 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
bool SbMicrophoneClose(SbMicrophone microphone) {
return false;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_create.cc b/src/starboard/shared/stub/microphone_create.cc
index bdc4166..832d97a 100644
--- a/src/starboard/shared/stub/microphone_create.cc
+++ b/src/starboard/shared/stub/microphone_create.cc
@@ -14,12 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
SbMicrophone SbMicrophoneCreate(SbMicrophoneId id,
int sample_rate_in_hz,
int buffer_size) {
return kSbMicrophoneInvalid;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_destroy.cc b/src/starboard/shared/stub/microphone_destroy.cc
index b0bbdab..f7759d5 100644
--- a/src/starboard/shared/stub/microphone_destroy.cc
+++ b/src/starboard/shared/stub/microphone_destroy.cc
@@ -14,8 +14,8 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
void SbMicrophoneDestroy(SbMicrophone microphone) {}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_get_available.cc b/src/starboard/shared/stub/microphone_get_available.cc
index 264b2d0..bddbf91 100644
--- a/src/starboard/shared/stub/microphone_get_available.cc
+++ b/src/starboard/shared/stub/microphone_get_available.cc
@@ -14,11 +14,11 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array,
int info_array_size) {
return 0;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc b/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
index 82cfbc1..c09f83d 100644
--- a/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
+++ b/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
@@ -14,11 +14,11 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
bool SbMicrophoneIsSampleRateSupported(SbMicrophoneId id,
int sample_rate_in_hz) {
return false;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_open.cc b/src/starboard/shared/stub/microphone_open.cc
index 19fedbf..6f36949 100644
--- a/src/starboard/shared/stub/microphone_open.cc
+++ b/src/starboard/shared/stub/microphone_open.cc
@@ -14,10 +14,10 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
bool SbMicrophoneOpen(SbMicrophone microphone) {
return false;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_read.cc b/src/starboard/shared/stub/microphone_read.cc
index d798d57..3a16b73 100644
--- a/src/starboard/shared/stub/microphone_read.cc
+++ b/src/starboard/shared/stub/microphone_read.cc
@@ -14,12 +14,12 @@
#include "starboard/microphone.h"
-#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
+#if !SB_HAS(MICROPHONE)
+#error "SB_HAS_MICROPHONE must be set to build this file."
+#endif
int SbMicrophoneRead(SbMicrophone microphone,
void* out_audio_data,
int audio_data_size) {
return -1;
}
-
-#endif // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/player_create.cc b/src/starboard/shared/stub/player_create.cc
index a91e304..35b05fd 100644
--- a/src/starboard/shared/stub/player_create.cc
+++ b/src/starboard/shared/stub/player_create.cc
@@ -27,15 +27,8 @@
SbPlayerDeallocateSampleFunc /*sample_deallocate_func*/,
SbPlayerDecoderStatusFunc /*decoder_status_func*/,
SbPlayerStatusFunc /*player_status_func*/,
- void* /*context*/
-#if SB_API_VERSION >= 4
- ,
+ void* /*context*/,
SbPlayerOutputMode /*output_mode*/,
- SbDecodeTargetGraphicsContextProvider* /*provider*/
-#elif SB_API_VERSION >= 3
- ,
- SbDecodeTargetProvider* /*provider*/
-#endif
- ) {
+ SbDecodeTargetGraphicsContextProvider* /*provider*/) {
return kSbPlayerInvalid;
}
diff --git a/src/starboard/shared/stub/player_get_current_frame.cc b/src/starboard/shared/stub/player_get_current_frame.cc
index 917e0f3..8f94d1e 100644
--- a/src/starboard/shared/stub/player_get_current_frame.cc
+++ b/src/starboard/shared/stub/player_get_current_frame.cc
@@ -14,10 +14,10 @@
#include "starboard/player.h"
-#if SB_API_VERSION >= 4
+#if !SB_HAS(PLAYER)
+#error "This file requires SB_HAS(PLAYER)."
+#endif
SbDecodeTarget SbPlayerGetCurrentFrame(SbPlayer /*player*/) {
return kSbDecodeTargetInvalid;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/player_output_mode_supported.cc b/src/starboard/shared/stub/player_output_mode_supported.cc
index 437d5e6..f81a7bc 100644
--- a/src/starboard/shared/stub/player_output_mode_supported.cc
+++ b/src/starboard/shared/stub/player_output_mode_supported.cc
@@ -14,12 +14,12 @@
#include "starboard/player.h"
-#if SB_API_VERSION >= 4
+#if !SB_HAS(PLAYER)
+#error "This file requires SB_HAS(PLAYER)."
+#endif
bool SbPlayerOutputModeSupported(SbPlayerOutputMode /*output_mode*/,
SbMediaVideoCodec /*codec*/,
SbDrmSystem /*drm_system*/) {
return false;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/player_set_bounds.cc b/src/starboard/shared/stub/player_set_bounds.cc
index 5fbee67..5008bd8 100644
--- a/src/starboard/shared/stub/player_set_bounds.cc
+++ b/src/starboard/shared/stub/player_set_bounds.cc
@@ -14,16 +14,14 @@
#include "starboard/player.h"
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
+#if !SB_HAS(PLAYER)
+#error "This file requires SB_HAS(PLAYER)."
+#endif
void SbPlayerSetBounds(SbPlayer /*player*/,
-#if SB_API_VERSION >= 4
int /*z_index*/,
-#endif // SB_API_VERSION >= 4
int /*x*/,
int /*y*/,
int /*width*/,
int /*height*/) {
}
-
-#endif // SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
diff --git a/src/starboard/shared/stub/player_set_pause.cc b/src/starboard/shared/stub/player_set_pause.cc
deleted file mode 100644
index c2f4d60..0000000
--- a/src/starboard/shared/stub/player_set_pause.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/player.h"
-
-#if SB_API_VERSION < 4
-
-void SbPlayerSetPause(SbPlayer /*player*/, bool /*pause*/) {}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/player_set_playback_rate.cc b/src/starboard/shared/stub/player_set_playback_rate.cc
index c9103aa..6937cb6 100644
--- a/src/starboard/shared/stub/player_set_playback_rate.cc
+++ b/src/starboard/shared/stub/player_set_playback_rate.cc
@@ -14,10 +14,10 @@
#include "starboard/player.h"
-#if SB_API_VERSION >= 4
+#if !SB_HAS(PLAYER)
+#error "This file requires SB_HAS(PLAYER)."
+#endif
bool SbPlayerSetPlaybackRate(SbPlayer /*player*/, double /*playback_rate*/) {
return false;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/player_write_sample.cc b/src/starboard/shared/stub/player_write_sample.cc
index 0450efd..e08b4b4 100644
--- a/src/starboard/shared/stub/player_write_sample.cc
+++ b/src/starboard/shared/stub/player_write_sample.cc
@@ -14,7 +14,9 @@
#include "starboard/player.h"
-#if SB_API_VERSION >= 4
+#if !SB_HAS(PLAYER)
+#error "This file requires SB_HAS(PLAYER)."
+#endif
void SbPlayerWriteSample(SbPlayer /*player*/,
SbMediaType /*sample_type*/,
@@ -30,15 +32,3 @@
const SbMediaVideoSampleInfo* /*video_sample_info*/,
const SbDrmSampleInfo* /*sample_drm_info*/) {
}
-
-#else // SB_API_VERSION >= 4
-
-void SbPlayerWriteSample(SbPlayer /*player*/,
- SbMediaType /*sample_type*/,
- const void* /*sample_buffer*/,
- int /*sample_buffer_size*/,
- SbMediaTime /*sample_pts*/,
- const SbMediaVideoSampleInfo* /*video_sample_info*/,
- const SbDrmSampleInfo* /*sample_drm_info*/) {}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/socket_get_interface_address.cc b/src/starboard/shared/stub/socket_get_interface_address.cc
index c31cd84..936e434 100644
--- a/src/starboard/shared/stub/socket_get_interface_address.cc
+++ b/src/starboard/shared/stub/socket_get_interface_address.cc
@@ -14,12 +14,8 @@
#include "starboard/socket.h"
-#if SB_API_VERSION >= 4
-
bool SbSocketGetInterfaceAddress(const SbSocketAddress* const /*destination*/,
SbSocketAddress* /*out_source_address*/,
SbSocketAddress* /*out_netmask*/) {
return false;
}
-
-#endif // SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/socket_get_local_interface_address.cc b/src/starboard/shared/stub/socket_get_local_interface_address.cc
deleted file mode 100644
index 17e4ab5..0000000
--- a/src/starboard/shared/stub/socket_get_local_interface_address.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/socket.h"
-
-#if SB_API_VERSION < 4
-
-bool SbSocketGetLocalInterfaceAddress(SbSocketAddress* /*out_address*/) {
- return false;
-}
-
-#endif // SB_API_VERSION < 4
diff --git a/src/starboard/shared/stub/speech_synthesis_cancel.cc b/src/starboard/shared/stub/speech_synthesis_cancel.cc
index 5071394..9413b32 100644
--- a/src/starboard/shared/stub/speech_synthesis_cancel.cc
+++ b/src/starboard/shared/stub/speech_synthesis_cancel.cc
@@ -14,7 +14,7 @@
#include "starboard/speech_synthesis.h"
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+#if !SB_HAS(SPEECH_SYNTHESIS)
#error If speech synthesis not enabled on this platform, please exclude it\
from the build
#endif
diff --git a/src/starboard/shared/stub/speech_synthesis_set_language.cc b/src/starboard/shared/stub/speech_synthesis_set_language.cc
deleted file mode 100644
index 8cc1042..0000000
--- a/src/starboard/shared/stub/speech_synthesis_set_language.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#if SB_API_VERSION < 4
-
-#include "starboard/speech_synthesis.h"
-
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
-#error If speech synthesis not enabled on this platform, please exclude it\
- from the build
-#endif
-
-bool SbSpeechSynthesisSetLanguage(const char* lang) {
- return false;
-}
-
-#endif
diff --git a/src/starboard/shared/stub/speech_synthesis_speak.cc b/src/starboard/shared/stub/speech_synthesis_speak.cc
index a06c9cf..0cd2163 100644
--- a/src/starboard/shared/stub/speech_synthesis_speak.cc
+++ b/src/starboard/shared/stub/speech_synthesis_speak.cc
@@ -14,7 +14,7 @@
#include "starboard/speech_synthesis.h"
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+#if !SB_HAS(SPEECH_SYNTHESIS)
#error If speech synthesis not enabled on this platform, please exclude it\
from the build
#endif
diff --git a/src/starboard/shared/stub/system_request_pause.cc b/src/starboard/shared/stub/system_request_pause.cc
index 618cfb8..7ab8359 100644
--- a/src/starboard/shared/stub/system_request_pause.cc
+++ b/src/starboard/shared/stub/system_request_pause.cc
@@ -14,9 +14,5 @@
#include "starboard/system.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestPause requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestPause() {
}
diff --git a/src/starboard/shared/stub/system_request_suspend.cc b/src/starboard/shared/stub/system_request_suspend.cc
index f9e28b0..ae6680c 100644
--- a/src/starboard/shared/stub/system_request_suspend.cc
+++ b/src/starboard/shared/stub/system_request_suspend.cc
@@ -14,9 +14,5 @@
#include "starboard/system.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestSuspend requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestSuspend() {
}
diff --git a/src/starboard/shared/stub/system_request_unpause.cc b/src/starboard/shared/stub/system_request_unpause.cc
index b3820dc..ee6b42a 100644
--- a/src/starboard/shared/stub/system_request_unpause.cc
+++ b/src/starboard/shared/stub/system_request_unpause.cc
@@ -14,9 +14,5 @@
#include "starboard/system.h"
-#if SB_API_VERSION < 4
-#error "SbSystemRequestUnpause requires SB_API_VERSION >= 4."
-#endif
-
void SbSystemRequestUnpause() {
}
diff --git a/src/starboard/shared/uwp/application_uwp_key_event.cc b/src/starboard/shared/uwp/application_uwp_key_event.cc
index abc3ef3..73b7d27 100644
--- a/src/starboard/shared/uwp/application_uwp_key_event.cc
+++ b/src/starboard/shared/uwp/application_uwp_key_event.cc
@@ -207,8 +207,8 @@
case VirtualKey::GamepadDPadDown: return kSbKeyGamepadDPadDown;
case VirtualKey::GamepadDPadLeft: return kSbKeyGamepadDPadLeft;
case VirtualKey::GamepadDPadRight: return kSbKeyGamepadDPadRight;
- case VirtualKey::GamepadMenu: return kSbKeyMediaLaunchMediaSelect;
- case VirtualKey::GamepadView: return kSbKeyF1;
+ case VirtualKey::GamepadMenu: return kSbKeyGamepad6;
+ case VirtualKey::GamepadView: return kSbKeyGamepad5;
case VirtualKey::GamepadLeftThumbstickButton:
return kSbKeyGamepadLeftStick;
case VirtualKey::GamepadRightThumbstickButton:
diff --git a/src/starboard/shared/uwp/system_get_property.cc b/src/starboard/shared/uwp/system_get_property.cc
index 0c48971..1f4924a 100644
--- a/src/starboard/shared/uwp/system_get_property.cc
+++ b/src/starboard/shared/uwp/system_get_property.cc
@@ -110,15 +110,24 @@
case kSbSystemPropertyModelName: {
EasClientDeviceInformation^ current_device_info =
ref new EasClientDeviceInformation();
- // TODO: Use SystemSku and map to friendly names instead.
- std::string product_name =
- platformStringToString(current_device_info->SystemProductName);
- product_name.erase(
- std::remove(product_name.begin(), product_name.end(), ' '),
- product_name.end());
+ std::string sku =
+ platformStringToString(current_device_info->SystemSku);
+
+ std::string friendly_name;
+
+ // TODO: Move this logic into xb1 specific directory.
+ if (sku == "XBOX_ONE_DU") {
+ friendly_name = "XboxOne";
+ } else if (sku == "XBOX_ONE_ED") {
+ friendly_name = "XboxOne S";
+ } else if (sku == "XBOX_ONE_CH") {
+ friendly_name = "XboxOne X";
+ } else {
+ friendly_name = "XboxOne " + sku;
+ }
return CopyStringAndTestIfSuccess(out_value, value_length,
- product_name.c_str());
+ friendly_name.c_str());
}
case kSbSystemPropertyFriendlyName: {
EasClientDeviceInformation^ current_device_info =
diff --git a/src/starboard/shared/win32/decrypting_decoder.cc b/src/starboard/shared/win32/decrypting_decoder.cc
index 0b25085..ca590f9 100644
--- a/src/starboard/shared/win32/decrypting_decoder.cc
+++ b/src/starboard/shared/win32/decrypting_decoder.cc
@@ -116,7 +116,7 @@
const scoped_refptr<InputBuffer>& input_buffer,
int bytes_to_skip_in_sample) {
SB_DCHECK(input_buffer);
- SB_DCHECK(bytes_to_skip_in_sample > 0);
+ SB_DCHECK(bytes_to_skip_in_sample >= 0);
ComPtr<IMFSample> input_sample;
diff --git a/src/starboard/shared/win32/drm_system_playready.cc b/src/starboard/shared/win32/drm_system_playready.cc
index 8856937..38fb628 100644
--- a/src/starboard/shared/win32/drm_system_playready.cc
+++ b/src/starboard/shared/win32/drm_system_playready.cc
@@ -118,7 +118,6 @@
}
iter->second->UpdateLicense(key, key_size);
- SB_DCHECK(iter->second->usable());
if (iter->second->usable()) {
SB_LOG_IF(INFO, kEnablePlayreadyLog)
<< "Successfully add key for key id "
@@ -180,6 +179,15 @@
ScopedLock lock(mutex_);
for (auto& item : successful_requests_) {
if (item.second->key_id() == key_id) {
+ if (buffer->sample_type() == kSbMediaTypeAudio) {
+ return kSuccess;
+ }
+
+ if (item.second->IsHDCPRequired()) {
+ // TODO: Enforce HDCP
+ // if (!is_hdcp_enabled()) { return kFailure; }
+ }
+
return kSuccess;
}
}
diff --git a/src/starboard/shared/win32/drm_system_playready.h b/src/starboard/shared/win32/drm_system_playready.h
index 48d608e..673500e 100644
--- a/src/starboard/shared/win32/drm_system_playready.h
+++ b/src/starboard/shared/win32/drm_system_playready.h
@@ -47,6 +47,7 @@
virtual std::string license_challenge() const = 0;
virtual Microsoft::WRL::ComPtr<IMFTransform> decryptor() = 0;
virtual void UpdateLicense(const void* license, int license_size) = 0;
+ virtual bool IsHDCPRequired() = 0;
};
SbDrmSystemPlayready(
diff --git a/src/starboard/shared/win32/sdk_configuration.py b/src/starboard/shared/win32/sdk_configuration.py
index 6b21f39..bed103d 100644
--- a/src/starboard/shared/win32/sdk_configuration.py
+++ b/src/starboard/shared/win32/sdk_configuration.py
@@ -70,7 +70,7 @@
if not os.path.exists(self.vs_host_tools_path):
logging.critical('Expected Visual Studio path \"%s\" not found.',
- self.vs_cl_path)
+ self.vs_host_tools_path)
self._CheckWindowsSdkVersion()
def _CheckWindowsSdkVersion(self):
diff --git a/src/starboard/shared/win32/system_get_connection_type.cc b/src/starboard/shared/win32/system_get_connection_type.cc
index 399e2b2..bced05d 100644
--- a/src/starboard/shared/win32/system_get_connection_type.cc
+++ b/src/starboard/shared/win32/system_get_connection_type.cc
@@ -32,7 +32,10 @@
!sbwin32::IsIfTypeEthernet(adapter->IfType)) {
continue;
}
- if (adapter->IfType == IF_TYPE_IEEE80211) {
+ // Some devices do not report IfType correctly.
+ // So, an extra attempt at determining if an interface is wireless is made.
+ if (adapter->IfType == IF_TYPE_IEEE80211 ||
+ (wcsstr(adapter->Description, L"WiFi") != nullptr)) {
return kSbSystemConnectionTypeWireless;
}
return kSbSystemConnectionTypeWired;
diff --git a/src/starboard/shared/win32/win32_audio_decoder.cc b/src/starboard/shared/win32/win32_audio_decoder.cc
index 01ff7ac..811e125 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.cc
+++ b/src/starboard/shared/win32/win32_audio_decoder.cc
@@ -93,7 +93,8 @@
SbMediaAudioSampleType sample_type,
const SbMediaAudioHeader& audio_header,
SbDrmSystem drm_system)
- : codec_(codec),
+ : thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
+ codec_(codec),
audio_frame_fmt_(audio_frame_fmt),
sample_type_(sample_type),
audio_header_(audio_header),
@@ -241,6 +242,16 @@
thread_checker_.Detach();
}
+ // The object is single-threaded and is driven by a dedicated thread.
+ // However the thread may gets destroyed and re-created over the life time of
+ // this object. We enforce that certain member functions can only called
+ // from one thread while still allows this object to be driven by different
+ // threads by:
+ // 1. The |thread_checker_| is initially created without attaching to any
+ // thread.
+ // 2. When a thread is destroyed, Reset() will be called which in turn calls
+ // Detach() on the |thread_checker_| to allow the object to attach to a
+ // new thread.
::starboard::shared::starboard::ThreadChecker thread_checker_;
const SbMediaAudioCodec codec_;
const SbMediaAudioHeader audio_header_;
diff --git a/src/starboard/shared/win32/win32_video_decoder.cc b/src/starboard/shared/win32/win32_video_decoder.cc
index d475a79..921be3e 100644
--- a/src/starboard/shared/win32/win32_video_decoder.cc
+++ b/src/starboard/shared/win32/win32_video_decoder.cc
@@ -268,6 +268,16 @@
thread_checker_.Detach();
}
+ // The object is single-threaded and is driven by a dedicated thread.
+ // However the thread may gets destroyed and re-created over the life time of
+ // this object. We enforce that certain member functions can only called
+ // from one thread while still allows this object to be driven by different
+ // threads by:
+ // 1. The |thread_checker_| is initially created without attaching to any
+ // thread.
+ // 2. When a thread is destroyed, Reset() will be called which in turn calls
+ // Detach() on the |thread_checker_| to allow the object to attach to a
+ // new thread.
::starboard::shared::starboard::ThreadChecker thread_checker_;
std::queue<VideoFramePtr> output_queue_;
const SbMediaVideoCodec codec_;
diff --git a/src/starboard/shared/x11/window_get_platform_handle.cc b/src/starboard/shared/x11/window_get_platform_handle.cc
index 267c9bd..3cd0229 100644
--- a/src/starboard/shared/x11/window_get_platform_handle.cc
+++ b/src/starboard/shared/x11/window_get_platform_handle.cc
@@ -23,10 +23,6 @@
}
Window handle = None;
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
handle = window->gl_window;
-#else
- handle = window->window;
-#endif
return reinterpret_cast<void*>(handle);
}
diff --git a/src/starboard/shared/x11/window_internal.cc b/src/starboard/shared/x11/window_internal.cc
index 108da65..a8d42e4 100644
--- a/src/starboard/shared/x11/window_internal.cc
+++ b/src/starboard/shared/x11/window_internal.cc
@@ -14,6 +14,8 @@
#include "starboard/shared/x11/window_internal.h"
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xrender.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/Xlib.h>
@@ -26,32 +28,22 @@
#include "starboard/log.h"
#include "starboard/player.h"
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
-#include <X11/extensions/Xcomposite.h>
-#include <X11/extensions/Xrender.h>
-#endif // SB_API_VERSION >= 4 ||
- // SB_IS(PLAYER_PUNCHED_OUT)
-
namespace {
const int kWindowWidth = 1920;
const int kWindowHeight = 1080;
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
bool HasNeededExtensionsForPunchedOutVideo(Display* display) {
int dummy;
return (XRenderQueryExtension(display, &dummy, &dummy) &&
XCompositeQueryExtension(display, &dummy, &dummy));
}
-#endif // SB_API_VERSION >= 4 ||
- // SB_IS(PLAYER_PUNCHED_OUT)
+
} // namespace
SbWindowPrivate::SbWindowPrivate(Display* display,
const SbWindowOptions* options)
- : window(None)
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
- ,
+ : window(None),
window_picture(None),
composition_pixmap(None),
composition_picture(None),
@@ -61,10 +53,7 @@
video_pixmap_gc(None),
video_picture(None),
gl_window(None),
- gl_picture(None)
-#endif // SB_API_VERSION >= 4 ||
- // SB_IS(PLAYER_PUNCHED_OUT)
- ,
+ gl_picture(None),
display(display) {
// Request a 32-bit depth visual for our Window.
XVisualInfo x_visual_info = {0};
@@ -104,7 +93,6 @@
Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", True);
XSetWMProtocols(display, window, &wm_delete, 1);
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
SB_CHECK(HasNeededExtensionsForPunchedOutVideo(display));
gl_window = XCreateSimpleWindow(display, window, 0, 0, width, height, 0,
WhitePixel(display, DefaultScreen(display)),
@@ -128,8 +116,6 @@
XRenderFindVisualFormat(display, x_visual_info.visual);
window_picture = XRenderCreatePicture(display, window, pict_format, 0, NULL);
SB_CHECK(window_picture != None);
-#endif // SB_API_VERSION >= 4 ||
- // SB_IS(PLAYER_PUNCHED_OUT)
XSelectInput(display, window,
VisibilityChangeMask | ExposureMask | FocusChangeMask |
@@ -139,7 +125,6 @@
}
SbWindowPrivate::~SbWindowPrivate() {
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
if (composition_pixmap != None) {
XFreePixmap(display, composition_pixmap);
XRenderFreePicture(display, composition_picture);
@@ -152,12 +137,9 @@
XRenderFreePicture(display, gl_picture);
XDestroyWindow(display, gl_window);
XRenderFreePicture(display, window_picture);
-#endif // SB_API_VERSION >= 4 || \
- SB_IS(PLAYER_PUNCHED_OUT)
XDestroyWindow(display, window);
}
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
void SbWindowPrivate::Composite(
int bounds_x,
int bounds_y,
@@ -278,5 +260,3 @@
window_picture, 0, 0, 0, 0, 0, 0, width, height);
XSynchronize(display, False);
}
-#endif // SB_API_VERSION >= 4 || \
- SB_IS(PLAYER_PUNCHED_OUT)
diff --git a/src/starboard/shared/x11/window_internal.h b/src/starboard/shared/x11/window_internal.h
index 3c96af9..5dcc153 100644
--- a/src/starboard/shared/x11/window_internal.h
+++ b/src/starboard/shared/x11/window_internal.h
@@ -22,9 +22,9 @@
#include "starboard/shared/starboard/player/video_frame_internal.h"
#include "starboard/window.h"
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
+#if SB_TRUE || SB_IS(PLAYER_PUNCHED_OUT)
#include <X11/extensions/Xrender.h>
-#endif // SB_API_VERSION >= 4 ||
+#endif // SB_TRUE ||
// SB_IS(PLAYER_PUNCHED_OUT)
struct SbWindowPrivate {
@@ -33,7 +33,7 @@
Window window;
-#if SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT)
+#if SB_TRUE || SB_IS(PLAYER_PUNCHED_OUT)
typedef ::starboard::shared::starboard::player::VideoFrame VideoFrame;
// Composites graphics and the given video frame video for this window. In
@@ -80,7 +80,7 @@
// A cached XRender Picture wrapper for |gl_window|.
Picture gl_picture;
-#endif // SB_API_VERSION >= 4 ||
+#endif // SB_TRUE ||
// SB_IS(PLAYER_PUNCHED_OUT)
// The display that this window was created from.
diff --git a/src/starboard/socket.h b/src/starboard/socket.h
index ba5600d..e44ddc0 100644
--- a/src/starboard/socket.h
+++ b/src/starboard/socket.h
@@ -227,7 +227,6 @@
SB_EXPORT bool SbSocketGetLocalAddress(SbSocket socket,
SbSocketAddress* out_address);
-#if SB_API_VERSION >= 4
// Gets the source address and the netmask that would be used to connect to the
// destination. The netmask parameter is optional, and only populated if a
// non-NULL parameter is passed in. To determine which source IP will be used,
@@ -267,17 +266,6 @@
SbSocketAddress* out_source_address,
SbSocketAddress* out_netmask);
-#else
-
-// Gets the address of the local IPv4 network interface. The return value
-// indicates whether the address was retrieved successfully.
-//
-// |out_address|: The retrieved address. The address does not include loopback
-// (or IPv6) addresses.
-SB_EXPORT bool SbSocketGetLocalInterfaceAddress(SbSocketAddress* out_address);
-
-#endif // SB_API_VERSION >= 4
-
// Reads up to |data_size| bytes from |socket| into |out_data| and places the
// source address of the packet in |out_source| if out_source is not NULL.
// Returns the number of bytes read, or a negative number if there is an error,
diff --git a/src/starboard/speech_synthesis.h b/src/starboard/speech_synthesis.h
index a8a46d6..5c0d177 100644
--- a/src/starboard/speech_synthesis.h
+++ b/src/starboard/speech_synthesis.h
@@ -29,22 +29,12 @@
#include "starboard/export.h"
#include "starboard/types.h"
-#if SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+#if SB_HAS(SPEECH_SYNTHESIS)
#ifdef __cplusplus
extern "C" {
#endif
-#if SB_API_VERSION < 4
-// DEPRECATED IN API VERSION 4
-// Must be called before any other function in this module,
-// or subsequent calls are allowed to fail silently.
-//
-// |lang| should be a BCP 47 language string, eg "en-US".
-// Return true if language is supported, false if not.
-SB_EXPORT bool SbSpeechSynthesisSetLanguage(const char* lang);
-#endif
-
// Enqueues |text|, a UTF-8 string, to be spoken.
// Returns immediately.
//
@@ -64,6 +54,6 @@
} // extern "C"
#endif
-#endif // SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
+#endif // SB_HAS(SPEECH_SYNTHESIS)
#endif // STARBOARD_SPEECH_SYNTHESIS_H_
diff --git a/src/starboard/stub/BUILD.gn b/src/starboard/stub/BUILD.gn
index 18ad876..dedc7f5 100644
--- a/src/starboard/stub/BUILD.gn
+++ b/src/starboard/stub/BUILD.gn
@@ -276,7 +276,6 @@
"//starboard/shared/stub/player_output_mode_supported.cc",
"//starboard/shared/stub/player_seek.cc",
"//starboard/shared/stub/player_set_bounds.cc",
- "//starboard/shared/stub/player_set_pause.cc",
"//starboard/shared/stub/player_set_playback_rate.cc",
"//starboard/shared/stub/player_set_volume.cc",
"//starboard/shared/stub/player_write_end_of_stream.cc",
@@ -291,7 +290,6 @@
"//starboard/shared/stub/socket_get_interface_address.cc",
"//starboard/shared/stub/socket_get_last_error.cc",
"//starboard/shared/stub/socket_get_local_address.cc",
- "//starboard/shared/stub/socket_get_local_interface_address.cc",
"//starboard/shared/stub/socket_is_connected.cc",
"//starboard/shared/stub/socket_is_connected_and_idle.cc",
"//starboard/shared/stub/socket_join_multicast_group.cc",
@@ -319,7 +317,6 @@
"//starboard/shared/stub/speech_recognizer_start.cc",
"//starboard/shared/stub/speech_recognizer_stop.cc",
"//starboard/shared/stub/speech_synthesis_cancel.cc",
- "//starboard/shared/stub/speech_synthesis_set_language.cc",
"//starboard/shared/stub/speech_synthesis_speak.cc",
"//starboard/shared/stub/storage_close_record.cc",
"//starboard/shared/stub/storage_delete_record.cc",
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h
index 5786638..1336a8f 100644
--- a/src/starboard/stub/configuration_public.h
+++ b/src/starboard/stub/configuration_public.h
@@ -356,27 +356,6 @@
// supported composition methods below.
#define SB_HAS_PLAYER 1
-#if SB_API_VERSION < 4
-// Specifies whether this platform's player will produce an OpenGL texture that
-// the client must draw every frame with its graphics rendering. It may be that
-// we get a texture handle, but cannot perform operations like GlReadPixels on
-// it if it is DRM-protected.
-#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
-
-// Specifies whether this platform's player is composited with a formal
-// compositor, where the client must specify how video is to be composited into
-// the graphicals scene.
-#define SB_IS_PLAYER_COMPOSITED 0
-
-// Specifies whether this platform's player uses a "punch-out" model, where
-// video is rendered to the far background, and the graphics plane is
-// automatically composited on top of the video by the platform. The client must
-// punch an alpha hole out of the graphics plane for video to show through. In
-// this case, changing the video bounds must be tightly synchronized between the
-// player and the graphics plane.
-#define SB_IS_PLAYER_PUNCHED_OUT 1
-#endif // SB_API_VERSION < 4
-
// After a seek is triggerred, the default behavior is to append video frames
// from the last key frame before the seek time and append audio frames from the
// seek time because usually all audio frames are key frames. On platforms that
@@ -390,8 +369,6 @@
// not available should define the following quirk.
#undef SB_HAS_QUIRK_NO_FFS
-#if SB_API_VERSION >= 4
-
// The maximum audio bitrate the platform can decode. The following value
// equals to 5M bytes per seconds which is more than enough for compressed
// audio.
@@ -402,8 +379,6 @@
// video.
#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
-#endif // SB_API_VERSION >= 4
-
// Specifies whether this platform has webm/vp9 support. This should be set to
// non-zero on platforms with webm/vp9 support.
#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0
diff --git a/src/starboard/stub/starboard_platform.gyp b/src/starboard/stub/starboard_platform.gyp
index a95ca89..754909d 100644
--- a/src/starboard/stub/starboard_platform.gyp
+++ b/src/starboard/stub/starboard_platform.gyp
@@ -134,7 +134,6 @@
'<(DEPTH)/starboard/shared/stub/player_output_mode_supported.cc',
'<(DEPTH)/starboard/shared/stub/player_seek.cc',
'<(DEPTH)/starboard/shared/stub/player_set_bounds.cc',
- '<(DEPTH)/starboard/shared/stub/player_set_pause.cc',
'<(DEPTH)/starboard/shared/stub/player_set_playback_rate.cc',
'<(DEPTH)/starboard/shared/stub/player_set_volume.cc',
'<(DEPTH)/starboard/shared/stub/player_write_end_of_stream.cc',
@@ -149,7 +148,6 @@
'<(DEPTH)/starboard/shared/stub/socket_get_interface_address.cc',
'<(DEPTH)/starboard/shared/stub/socket_get_last_error.cc',
'<(DEPTH)/starboard/shared/stub/socket_get_local_address.cc',
- '<(DEPTH)/starboard/shared/stub/socket_get_local_interface_address.cc',
'<(DEPTH)/starboard/shared/stub/socket_is_connected.cc',
'<(DEPTH)/starboard/shared/stub/socket_is_connected_and_idle.cc',
'<(DEPTH)/starboard/shared/stub/socket_join_multicast_group.cc',
@@ -177,7 +175,6 @@
'<(DEPTH)/starboard/shared/stub/speech_recognizer_start.cc',
'<(DEPTH)/starboard/shared/stub/speech_recognizer_stop.cc',
'<(DEPTH)/starboard/shared/stub/speech_synthesis_cancel.cc',
- '<(DEPTH)/starboard/shared/stub/speech_synthesis_set_language.cc',
'<(DEPTH)/starboard/shared/stub/speech_synthesis_speak.cc',
'<(DEPTH)/starboard/shared/stub/storage_close_record.cc',
'<(DEPTH)/starboard/shared/stub/storage_delete_record.cc',
diff --git a/src/starboard/system.h b/src/starboard/system.h
index dd08f44..cfa9eed 100644
--- a/src/starboard/system.h
+++ b/src/starboard/system.h
@@ -45,7 +45,6 @@
// screenshots) can be written into.
kSbSystemPathDebugOutputDirectory,
-#if SB_API_VERSION >= 4
// Path to a directory where system font files can be found. Should only be
// specified on platforms that provide fonts usable by Starboard applications.
kSbSystemPathFontDirectory,
@@ -55,7 +54,6 @@
// necessarily. Should only be specified on platforms that provide fonts
// usable by Starboard applications.
kSbSystemPathFontConfigurationDirectory,
-#endif // SB_API_VERSION >= 4
// Path to the directory containing the root of the source tree.
kSbSystemPathSourceDirectory,
@@ -109,13 +107,12 @@
// A universally-unique ID for the current user.
kSbSystemPropertyPlatformUuid,
-#if SB_API_VERSION >= 2
// The Google Speech API key. The platform manufacturer is responsible
// for registering a Google Speech API key for their products. In the API
// Console (http://developers.google.com/console), you can enable the
// Speech APIs and generate a Speech API key.
kSbSystemPropertySpeechApiKey,
-#endif // SB_VERSION(2)
+
#if SB_API_VERSION >= 5
// A field that, if available, is appended to the user agent
kSbSystemPropertyUserAgentAuxField,
@@ -145,10 +142,8 @@
// Desktop PC.
kSbSystemDeviceTypeDesktopPC,
-#if SB_API_VERSION >= 4
// An Android TV Device.
kSbSystemDeviceTypeAndroidTV,
-#endif // SB_API_VERSION >= 4
// Unknown device.
kSbSystemDeviceTypeUnknown,
@@ -281,10 +276,7 @@
// Breaks the current program into the debugger, if a debugger is attached.
// If a debugger is not attached, this function aborts the program.
-#if SB_API_VERSION >= 4
-SB_NORETURN
-#endif
-SB_EXPORT void SbSystemBreakIntoDebugger();
+SB_NORETURN SB_EXPORT void SbSystemBreakIntoDebugger();
// Attempts to determine whether the current program is running inside or
// attached to a debugger. The function returns |false| if neither of those
@@ -454,7 +446,6 @@
char* out_buffer,
int buffer_size);
-#if SB_API_VERSION >= 4
// Requests that the application move into the Paused state at the next
// convenient point. This should roughly correspond to "unfocused application"
// in a traditional window manager, where the application may be partially
@@ -489,7 +480,6 @@
// running. The expectation is that an external system event will bring the
// application out of the Suspended state.
SB_EXPORT void SbSystemRequestSuspend();
-#endif // SB_API_VERSION >= 4
// Requests that the application be terminated gracefully at the next
// convenient point. In the meantime, some work may continue to be done, and
diff --git a/src/starboard/time.h b/src/starboard/time.h
index c9d2a3f..cdbb194 100644
--- a/src/starboard/time.h
+++ b/src/starboard/time.h
@@ -91,7 +91,7 @@
// Gets a monotonically increasing time representing right now.
SB_EXPORT SbTimeMonotonic SbTimeGetMonotonicNow();
-#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
+#if SB_HAS(TIME_THREAD_NOW)
// Gets a monotonically increasing time representing how long the current
// thread has been in the executing state (i.e. not pre-empted nor waiting
// on an event). This is not necessarily total time and is intended to allow
diff --git a/src/starboard/tizen/armv7l/starboard_common.gyp b/src/starboard/tizen/armv7l/starboard_common.gyp
index 1837af9..86c0acf 100644
--- a/src/starboard/tizen/armv7l/starboard_common.gyp
+++ b/src/starboard/tizen/armv7l/starboard_common.gyp
@@ -74,7 +74,6 @@
'<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
'<(DEPTH)/starboard/shared/linux/page_internal.cc',
'<(DEPTH)/starboard/shared/linux/socket_get_interface_address.cc',
- '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
'<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
'<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
'<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
@@ -210,13 +209,6 @@
'<(DEPTH)/starboard/shared/starboard/media/media_get_audio_configuration_stereo_only.cc',
'<(DEPTH)/starboard/shared/starboard/media/media_get_audio_output_count_stereo_only.cc',
'<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_close.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_create.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_destroy.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_get_available.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_is_sample_rate_supported.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_open.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_read.cc',
'<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
'<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
'<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
diff --git a/src/starboard/tizen/armv7l/starboard_platform.gyp b/src/starboard/tizen/armv7l/starboard_platform.gyp
index 01aa556..515966a 100644
--- a/src/starboard/tizen/armv7l/starboard_platform.gyp
+++ b/src/starboard/tizen/armv7l/starboard_platform.gyp
@@ -71,7 +71,6 @@
'<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
'<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
- '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_playback_rate.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
'<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
index 9ccc5ce..c898592 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -297,7 +297,6 @@
namespace player {
namespace filter {
-#if SB_API_VERSION >= 4
// static
bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaVideoCodec codec,
@@ -307,7 +306,6 @@
return output_mode == kSbPlayerOutputModePunchOut;
}
-#endif // SB_API_VERSION >= 4
} // namespace filter
} // namespace player
diff --git a/src/starboard/tools/example/__init__.py b/src/starboard/tools/example/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/tools/example/__init__.py
diff --git a/src/starboard/tools/example/app_launcher_client.py b/src/starboard/tools/example/app_launcher_client.py
new file mode 100644
index 0000000..b240697
--- /dev/null
+++ b/src/starboard/tools/example/app_launcher_client.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+#
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Client to launch executables via the new launcher logic."""
+
+import importlib
+import os
+import sys
+
+if "environment" in sys.modules:
+ environment = sys.modules["environment"]
+else:
+ env_path = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir))
+ if env_path not in sys.path:
+ sys.path.append(env_path)
+ environment = importlib.import_module("environment")
+
+import argparse
+
+from starboard.tools import abstract_launcher
+
+arg_parser = argparse.ArgumentParser(
+ description="Runs application/tool executables.")
+arg_parser.add_argument(
+ "-p",
+ "--platform",
+ help="Device platform, eg 'linux-x64x11'.")
+arg_parser.add_argument(
+ "-t",
+ "--target_name",
+ help="Name of executable.")
+arg_parser.add_argument(
+ "-c",
+ "--config",
+ choices=["debug", "devel", "qa", "gold"],
+ help="Build config (eg, 'qa' or 'devel')")
+arg_parser.add_argument(
+ "-d",
+ "--device_id",
+ help="Devkit or IP address for the target device.")
+arg_parser.add_argument(
+ "--target_params",
+ help="Command line arguments to pass to the executable."
+ " Because different executables could have differing command"
+ " line syntax, list all arguments exactly as you would to the"
+ " executable between a set of double quotation marks.")
+
+
+def main():
+ args = arg_parser.parse_args()
+ extra_args = {}
+
+ if not args.device_id:
+ args.device_id = None
+
+ extra_args = {}
+ if args.target_params:
+ extra_args["target_params"] = args.target_params.split(" ")
+
+ launcher = abstract_launcher.LauncherFactory(args.platform,
+ args.target_name, args.config,
+ args.device_id, extra_args)
+ return launcher.Run()
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/starboard/user.h b/src/starboard/user.h
index 6e66ed4..05883bc 100644
--- a/src/starboard/user.h
+++ b/src/starboard/user.h
@@ -56,29 +56,6 @@
kSbUserPropertyUserId,
} SbUserPropertyId;
-#if SB_API_VERSION < 4
-#if SB_HAS(USER_APPLICATION_LINKING_SUPPORT)
-// Information about an application-specific authorization token.
-typedef struct SbUserApplicationTokenResults {
- // The size of the buffer pointed to by |token_buffer|. Call
- // SbUserMaxAuthenticationTokenSizeInBytes() to get an appropriate size.
- // |token_buffer_size| must be set to a value greater than zero.
- size_t token_buffer_size;
-
- // Pointer to a buffer into which the token will be copied.
- // |token_buffer| must not be NULL.
- char* token_buffer;
-
- // If true, |expiry| will be set. If false, the token never expires.
- bool has_expiry;
-
- // The absolute time that this token expires. It is valid to use the value of
- // |expiry| only if |has_expiry| is true.
- SbTime expiry;
-} SbUserApplicationTokenResults;
-#endif
-#endif
-
// Well-defined value for an invalid user.
#define kSbUserInvalid (SbUser) NULL
diff --git a/src/starboard/win/lib/gyp_configuration.gypi b/src/starboard/win/lib/gyp_configuration.gypi
index e491c37..f530aed 100644
--- a/src/starboard/win/lib/gyp_configuration.gypi
+++ b/src/starboard/win/lib/gyp_configuration.gypi
@@ -23,6 +23,7 @@
'final_executable_type': 'shared_library',
'default_renderer_options_dependency': '<(DEPTH)/cobalt/renderer/rasterizer/lib/lib.gyp:external_rasterizer',
'sb_enable_lib': 1,
+ 'enable_map_to_mesh': 1,
'angle_build_winrt': 0,
'winrt': 0,
'enable_d3d11_feature_level_11': 1,
diff --git a/src/starboard/win/lib/starboard_platform.gyp b/src/starboard/win/lib/starboard_platform.gyp
index 04fd716..986d8b6 100644
--- a/src/starboard/win/lib/starboard_platform.gyp
+++ b/src/starboard/win/lib/starboard_platform.gyp
@@ -32,10 +32,10 @@
'<(DEPTH)/starboard/shared/starboard/system_request_suspend.cc',
'<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
'<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
- '<(DEPTH)/starboard/shared/stub/decode_target_get_info.cc',
- '<(DEPTH)/starboard/shared/stub/decode_target_release.cc',
'<@(uwp_incompatible_win32)',
- '<@(stub_media_player)'
+ '<@(win32_media_player_files)',
+ '<@(win32_shared_drm_files)',
+ '<@(win32_shared_media_player_files)',
],
},
}
diff --git a/src/starboard/win/shared/configuration_public.h b/src/starboard/win/shared/configuration_public.h
index 5bcf077..138ca41 100644
--- a/src/starboard/win/shared/configuration_public.h
+++ b/src/starboard/win/shared/configuration_public.h
@@ -347,27 +347,6 @@
// supported composition methods below.
#define SB_HAS_PLAYER 1
-#if SB_API_VERSION < 4
-// Specifies whether this platform's player will produce an OpenGL texture that
-// the client must draw every frame with its graphics rendering. It may be that
-// we get a texture handle, but cannot perform operations like GlReadPixels on
-// it if it is DRM-protected.
-#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
-
-// Specifies whether this platform's player is composited with a formal
-// compositor, where the client must specify how video is to be composited into
-// the graphicals scene.
-#define SB_IS_PLAYER_COMPOSITED 0
-
-// Specifies whether this platform's player uses a "punch-out" model, where
-// video is rendered to the far background, and the graphics plane is
-// automatically composited on top of the video by the platform. The client must
-// punch an alpha hole out of the graphics plane for video to show through. In
-// this case, changing the video bounds must be tightly synchronized between the
-// player and the graphics plane.
-#define SB_IS_PLAYER_PUNCHED_OUT 1
-#endif // SB_API_VERSION < 4
-
// After a seek is triggerred, the default behavior is to append video frames
// from the last key frame before the seek time and append audio frames from the
// seek time because usually all audio frames are key frames. On platforms that
@@ -381,8 +360,6 @@
// not available should define the following quirk.
#undef SB_HAS_QUIRK_NO_FFS
-#if SB_API_VERSION >= 4
-
// The maximum audio bitrate the platform can decode. The following value
// equals to 5M bytes per seconds which is more than enough for compressed
// audio.
@@ -393,8 +370,6 @@
// video.
#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
-#endif // SB_API_VERSION >= 4
-
// Specifies whether this platform has webm/vp9 support. This should be set to
// non-zero on platforms with webm/vp9 support.
#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi
index 6a20b46..42dacc9 100644
--- a/src/starboard/win/shared/starboard_platform.gypi
+++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -247,13 +247,6 @@
'<(DEPTH)/starboard/shared/stub/image_decode.cc',
'<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc',
'<(DEPTH)/starboard/shared/stub/media_set_output_protection.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_close.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_create.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_destroy.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_get_available.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_is_sample_rate_supported.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_open.cc',
- '<(DEPTH)/starboard/shared/stub/microphone_read.cc',
'<(DEPTH)/starboard/shared/stub/system_get_stack.cc',
'<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
'<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
diff --git a/src/starboard/win/win32/lib/starboard_platform.gyp b/src/starboard/win/win32/lib/starboard_platform.gyp
index 7216017..52063bb 100644
--- a/src/starboard/win/win32/lib/starboard_platform.gyp
+++ b/src/starboard/win/win32/lib/starboard_platform.gyp
@@ -18,6 +18,9 @@
'variables': {
'starboard_platform_dependent_files': [
'<@(base_win32_starboard_platform_dependent_files)',
+ '<@(win32_media_player_files)',
+ '<@(win32_shared_drm_files)',
+ '<@(win32_shared_media_player_files)',
]
},
}