<commit_before>//=============================================================================
// ■ init.cpp
//-----------------------------------------------------------------------------
//   所有初始化相关的代码都被放置在这里。
//=============================================================================

#include "global.hpp"

//-----------------------------------------------------------------------------
// ● 设置视口
//-----------------------------------------------------------------------------
void setup_viewport() {
	int width, height;
	glfwGetFramebufferSize(window, &width, &height);
	glViewport(0, 0, width, height);
}

//-----------------------------------------------------------------------------
// ● GL数据查询
//-----------------------------------------------------------------------------
void GL_info() {
	log("==========================");
	log("OpenGL Info:");
	GLint a, b;
	glGetIntegerv(GL_MAJOR_VERSION, &a);
	glGetIntegerv(GL_MINOR_VERSION, &b);
	log("Supproted GL Version %d.%d", a, b);
	log("=====================================================");
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &a);
	log("Maximum vertex attributes: %d", a);
	glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, &a);
	if (VMDE->gl_ver == GL_43) {
		log("Maximum vertex output components: %d", a);
		glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &a);
	}
	log("Maximum fragment shader storage blocks: %d", a);
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &a);
	log("Maximum texture size: %d", a);
	glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &a);
	log("Maximum texture unit: %d", a);
	check_gl_error;
	log("=====================================================");
	log(
		"EXT_packed_stencil_depth: %s",
		glfwExtensionSupported("EXT_packed_stencil_depth") ? "Supported" : "Unsupported"
	);
	check_gl_error;
	log("=====================================================");
}

//-----------------------------------------------------------------------------
// ● 初始化图形
//-----------------------------------------------------------------------------
void init_graphics(int w, int h, const char* title) {
	init_vmde(w, h);

	// GLFW库初始化
	glfwSetErrorCallback(glfw_error_callback);
	if (!glfwInit()) {
		log("glfwInit() failed");
		return;
	}

	// OpenGL 向前&向后兼容，使用GL 4.3 Core Profile，窗口大小不可变
	// 如果有更高版本就利用
	// 指定版本后便无需再检查是否支持指定版本，因为GLFW会处理此问题
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	window = glfwCreateWindow(VMDE->width, VMDE->height, title, NULL, NULL);
	if (!window) {
		// 如果GL 4.3不行就回退到GL 3.3
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
		window = glfwCreateWindow(VMDE->width, VMDE->height, title, NULL, NULL);
		VMDE->gl_ver = GL_33;
		if (!window) {
			glfwTerminate();
			error("glfwCreateWindow() (GLFW Window Creation) failed. Your computer need OpenGL 3.3.");
			return;
		}
		log("OpenGL API: 3.3");
	} else {
		VMDE->gl_ver = GL_43;
		log("OpenGL API: 4.3");
	}

	VMSC::init_graphics_state();

	setup_viewport();

	// Query GL info
	GL_info();

	// Setup API constants
	PostProcessingManager::init();
	glDepthRange(0.0f, 1.0f);
	glDepthMask(GL_TRUE);
	glClearDepth(1.0f);

	glDepthFunc(GL_LEQUAL);

	glFrontFace(GL_CCW);
	VMStateControl::enable_depth_test();
	VMStateControl::enable_stencil_test();
	VMStateControl::enable_cullface();
}

//-----------------------------------------------------------------------------
// ● 初始化引擎
//-----------------------------------------------------------------------------
void init_engine(int w, int h, const char* title) {
	Util::init();
	log("initializing the engine");
	srand(time(NULL));
	log("The system is %s-endian.", is_little_endian() ? "little" : "big");

	init_vmde(w, h);
	init_graphics(w, h, title);
	Audio::init();

	log("initialized the engine");
}

//-----------------------------------------------------------------------------
// ● 初始化VMDE结构体
//-----------------------------------------------------------------------------
void init_vmde(int w, int h) {
	VMDE = new struct VMDE;
	VMDE->state.frozen = false;
	VMDE->state.brightness = 1.0;
	VMDE->frame_count = 0;
	VMDE->fps = 0;
	VMDE->width = w;
	VMDE->height = h;
	VMDE->done = false;
}
<commit_msg>修复等号数量不一致<commit_after>//=============================================================================
// ■ init.cpp
//-----------------------------------------------------------------------------
//   所有初始化相关的代码都被放置在这里。
//=============================================================================

#include "global.hpp"

//-----------------------------------------------------------------------------
// ● 设置视口
//-----------------------------------------------------------------------------
void setup_viewport() {
	int width, height;
	glfwGetFramebufferSize(window, &width, &height);
	glViewport(0, 0, width, height);
}

//-----------------------------------------------------------------------------
// ● GL数据查询
//-----------------------------------------------------------------------------
void GL_info() {
	log("=====================================================");
	log("OpenGL Info:");
	GLint a, b;
	glGetIntegerv(GL_MAJOR_VERSION, &a);
	glGetIntegerv(GL_MINOR_VERSION, &b);
	log("Supproted GL Version %d.%d", a, b);
	log("=====================================================");
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &a);
	log("Maximum vertex attributes: %d", a);
	glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, &a);
	if (VMDE->gl_ver == GL_43) {
		log("Maximum vertex output components: %d", a);
		glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &a);
	}
	log("Maximum fragment shader storage blocks: %d", a);
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &a);
	log("Maximum texture size: %d", a);
	glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &a);
	log("Maximum texture unit: %d", a);
	check_gl_error;
	log("=====================================================");
	log(
		"EXT_packed_stencil_depth: %s",
		glfwExtensionSupported("EXT_packed_stencil_depth") ? "Supported" : "Unsupported"
	);
	check_gl_error;
	log("=====================================================");
}

//-----------------------------------------------------------------------------
// ● 初始化图形
//-----------------------------------------------------------------------------
void init_graphics(int w, int h, const char* title) {
	init_vmde(w, h);

	// GLFW库初始化
	glfwSetErrorCallback(glfw_error_callback);
	if (!glfwInit()) {
		log("glfwInit() failed");
		return;
	}

	// OpenGL 向前&向后兼容，使用GL 4.3 Core Profile，窗口大小不可变
	// 如果有更高版本就利用
	// 指定版本后便无需再检查是否支持指定版本，因为GLFW会处理此问题
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	window = glfwCreateWindow(VMDE->width, VMDE->height, title, NULL, NULL);
	if (!window) {
		// 如果GL 4.3不行就回退到GL 3.3
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
		window = glfwCreateWindow(VMDE->width, VMDE->height, title, NULL, NULL);
		VMDE->gl_ver = GL_33;
		if (!window) {
			glfwTerminate();
			error("glfwCreateWindow() (GLFW Window Creation) failed. Your computer need OpenGL 3.3.");
			return;
		}
		log("OpenGL API: 3.3");
	} else {
		VMDE->gl_ver = GL_43;
		log("OpenGL API: 4.3");
	}

	VMSC::init_graphics_state();

	setup_viewport();

	// Query GL info
	GL_info();

	// Setup API constants
	PostProcessingManager::init();
	glDepthRange(0.0f, 1.0f);
	glDepthMask(GL_TRUE);
	glClearDepth(1.0f);

	glDepthFunc(GL_LEQUAL);

	glFrontFace(GL_CCW);
	VMStateControl::enable_depth_test();
	VMStateControl::enable_stencil_test();
	VMStateControl::enable_cullface();
}

//-----------------------------------------------------------------------------
// ● 初始化引擎
//-----------------------------------------------------------------------------
void init_engine(int w, int h, const char* title) {
	Util::init();
	log("initializing the engine");
	srand(time(NULL));
	log("The system is %s-endian.", is_little_endian() ? "little" : "big");

	init_vmde(w, h);
	init_graphics(w, h, title);
	Audio::init();

	log("initialized the engine");
}

//-----------------------------------------------------------------------------
// ● 初始化VMDE结构体
//-----------------------------------------------------------------------------
void init_vmde(int w, int h) {
	VMDE = new struct VMDE;
	VMDE->state.frozen = false;
	VMDE->state.brightness = 1.0;
	VMDE->frame_count = 0;
	VMDE->fps = 0;
	VMDE->width = w;
	VMDE->height = h;
	VMDE->done = false;
}
<|endoftext|>


<commit_before>// 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 "remoting/host/screen_recorder.h"

#include <algorithm>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util-inl.h"
#include "base/task.h"
#include "base/time.h"
#include "remoting/base/capture_data.h"
#include "remoting/base/tracer.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/message_decoder.h"
#include "remoting/protocol/util.h"

using remoting::protocol::ConnectionToClient;

namespace remoting {

// By default we capture 20 times a second. This number is obtained by
// experiment to provide good latency.
static const double kDefaultCaptureRate = 20.0;

// Maximum number of frames that can be processed similtaneously.
// TODO(sergeyu): Should this be set to 1? Or should we change
// dynamically depending on how fast network and CPU are? Experement
// with it.
static const int kMaxRecordings = 2;

ScreenRecorder::ScreenRecorder(
    MessageLoop* capture_loop,
    MessageLoop* encode_loop,
    MessageLoop* network_loop,
    Capturer* capturer,
    Encoder* encoder)
    : capture_loop_(capture_loop),
      encode_loop_(encode_loop),
      network_loop_(network_loop),
      capturer_(capturer),
      encoder_(encoder),
      is_recording_(false),
      network_stopped_(false),
      recordings_(0),
      frame_skipped_(false),
      max_rate_(kDefaultCaptureRate) {
  DCHECK(capture_loop_);
  DCHECK(encode_loop_);
  DCHECK(network_loop_);
}

ScreenRecorder::~ScreenRecorder() {
}

// Public methods --------------------------------------------------------------

void ScreenRecorder::Start() {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoStart));
}

void ScreenRecorder::Stop(Task* done_task) {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoStop, done_task));
}

void ScreenRecorder::SetMaxRate(double rate) {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoSetMaxRate, rate));
}

void ScreenRecorder::AddConnection(
    scoped_refptr<ConnectionToClient> connection) {
  ScopedTracer tracer("AddConnection");

  capture_loop_->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &ScreenRecorder::DoInvalidateFullScreen));

  // Add the client to the list so it can receive update stream.
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoAddConnection, connection));
}

void ScreenRecorder::RemoveConnection(
    scoped_refptr<ConnectionToClient> connection) {
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoRemoveClient, connection));
}

void ScreenRecorder::RemoveAllConnections() {
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoRemoveAllClients));
}

// Private accessors -----------------------------------------------------------

Capturer* ScreenRecorder::capturer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  DCHECK(capturer_);
  return capturer_;
}

Encoder* ScreenRecorder::encoder() {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  DCHECK(encoder_.get());
  return encoder_.get();
}

// Capturer thread -------------------------------------------------------------

void ScreenRecorder::DoStart() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (is_recording_) {
    NOTREACHED() << "Record session already started.";
    return;
  }

  is_recording_ = true;
  StartCaptureTimer();

  // Capture first frame immedately.
  DoCapture();
}

void ScreenRecorder::DoStop(Task* done_task) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  // We might have not started when we receive a stop command, simply run the
  // task and then return.
  if (!is_recording_) {
    DoCompleteStop(done_task);
    return;
  }

  capture_timer_.Stop();
  is_recording_ = false;

  DCHECK_GE(recordings_, 0);
  if (recordings_) {
     network_loop_->PostTask(
        FROM_HERE,
        NewTracedMethod(this,
                        &ScreenRecorder::DoStopOnNetworkThread, done_task));
    return;
  }

  DoCompleteStop(done_task);
}

void ScreenRecorder::DoCompleteStop(Task* done_task) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (done_task) {
    done_task->Run();
    delete done_task;
  }
}

void ScreenRecorder::DoSetMaxRate(double max_rate) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  // TODO(hclam): Should also check for small epsilon.
  DCHECK_GT(max_rate, 0.0) << "Rate is too small.";

  max_rate_ = max_rate;

  // Restart the timer with the new rate.
  if (is_recording_) {
    capture_timer_.Stop();
    StartCaptureTimer();
  }
}

void ScreenRecorder::StartCaptureTimer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
      static_cast<int>(base::Time::kMillisecondsPerSecond / max_rate_));
  capture_timer_.Start(interval, this, &ScreenRecorder::DoCapture);
}

void ScreenRecorder::DoCapture() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  // Make sure we have at most two oustanding recordings. We can simply return
  // if we can't make a capture now, the next capture will be started by the
  // end of an encode operation.
  if (recordings_ >= kMaxRecordings || !is_recording_) {
    frame_skipped_ = true;
    return;
  }

  if (frame_skipped_) {
    frame_skipped_ = false;
    capture_timer_.Reset();
  }

  TraceContext::tracer()->PrintString("Capture Started");

  // At this point we are going to perform one capture so save the current time.
  ++recordings_;
  DCHECK_LE(recordings_, kMaxRecordings);

  // And finally perform one capture.
  capture_start_time_ = base::Time::Now();
  capturer()->CaptureInvalidRects(
      NewCallback(this, &ScreenRecorder::CaptureDoneCallback));
}

void ScreenRecorder::CaptureDoneCallback(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  TraceContext::tracer()->PrintString("Capture Done");
  int capture_time =
      (base::Time::Now() - capture_start_time_).InMilliseconds();
  capture_data->set_capture_time_ms(capture_time);
  encode_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoEncode, capture_data));
}

void ScreenRecorder::DoFinishOneRecording() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  // Decrement the number of recording in process since we have completed
  // one cycle.
  --recordings_;
  DCHECK_GE(recordings_, 0);

  // Try to do a capture again only if |frame_skipped_| is set to true by
  // capture timer.
  if (frame_skipped_)
    DoCapture();
}

void ScreenRecorder::DoInvalidateFullScreen() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capturer_->InvalidateFullScreen();
}

// Network thread --------------------------------------------------------------

void ScreenRecorder::DoSendVideoPacket(VideoPacket* packet) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  TraceContext::tracer()->PrintString("DoSendVideoPacket");

  bool last = (packet->flags() & VideoPacket::LAST_PARTITION) != 0;

  if (network_stopped_ || connections_.empty()) {
    delete packet;
    return;
  }

  for (ConnectionToClientList::const_iterator i = connections_.begin();
       i < connections_.end(); ++i) {
    Task* done_task = NULL;

    // Call FrameSentCallback() only for the last packet in the first
    // connection.
    if (last && i == connections_.begin()) {
      done_task = NewTracedMethod(this, &ScreenRecorder::FrameSentCallback,
                                  packet);
    } else {
      // TODO(hclam): Fix this code since it causes multiple deletion if there's
      // more than one connection.
      done_task = new DeleteTask<VideoPacket>(packet);
    }

    (*i)->video_stub()->ProcessVideoPacket(packet, done_task);
  }

  TraceContext::tracer()->PrintString("DoSendVideoPacket done");
}

void ScreenRecorder::FrameSentCallback(VideoPacket* packet) {
  delete packet;

  if (network_stopped_)
    return;

  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoFinishOneRecording));
}

void ScreenRecorder::DoAddConnection(
    scoped_refptr<ConnectionToClient> connection) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  connections_.push_back(connection);
}

void ScreenRecorder::DoRemoveClient(
    scoped_refptr<ConnectionToClient> connection) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  ConnectionToClientList::iterator it =
      std::find(connections_.begin(), connections_.end(), connection);
  if (it != connections_.end()) {
    connections_.erase(it);
  }
}

void ScreenRecorder::DoRemoveAllClients() {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  // Clear the list of connections.
  connections_.clear();
}

void ScreenRecorder::DoStopOnNetworkThread(Task* done_task) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  // There could be tasks on the network thread when this method is being
  // executed. By setting the flag we'll not post anymore tasks from network
  // thread.
  //
  // After that a task is posted on encode thread to continue the stop
  // sequence.
  network_stopped_ = true;

  encode_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoStopOnEncodeThread, done_task));
}

// Encoder thread --------------------------------------------------------------

void ScreenRecorder::DoEncode(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  TraceContext::tracer()->PrintString("DoEncode called");

  // Early out if there's nothing to encode.
  if (!capture_data->dirty_rects().size()) {
    capture_loop_->PostTask(
        FROM_HERE,
        NewTracedMethod(this, &ScreenRecorder::DoFinishOneRecording));
    return;
  }

  TraceContext::tracer()->PrintString("Encode start");
  encode_start_time_ = base::Time::Now();
  encoder()->Encode(
      capture_data, false,
      NewCallback(this, &ScreenRecorder::EncodedDataAvailableCallback));
  TraceContext::tracer()->PrintString("Encode Done");
}

void ScreenRecorder::DoStopOnEncodeThread(Task* done_task) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  // When this method is being executed there are no more tasks on encode thread
  // for this object. We can then post a task to capture thread to finish the
  // stop sequence.
  capture_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoCompleteStop, done_task));
}

void ScreenRecorder::EncodedDataAvailableCallback(VideoPacket* packet) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  bool last = packet->flags() & VideoPacket::LAST_PACKET;
  if (last) {
    int encode_time =
        (base::Time::Now() - encode_start_time_).InMilliseconds();
    packet->set_encode_time_ms(encode_time);
  }

  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoSendVideoPacket, packet));
}

}  // namespace remoting
<commit_msg>Fix build breaks<commit_after>// 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 "remoting/host/screen_recorder.h"

#include <algorithm>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util-inl.h"
#include "base/task.h"
#include "base/time.h"
#include "remoting/base/capture_data.h"
#include "remoting/base/tracer.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/message_decoder.h"
#include "remoting/protocol/util.h"

using remoting::protocol::ConnectionToClient;

namespace remoting {

// By default we capture 20 times a second. This number is obtained by
// experiment to provide good latency.
static const double kDefaultCaptureRate = 20.0;

// Maximum number of frames that can be processed similtaneously.
// TODO(sergeyu): Should this be set to 1? Or should we change
// dynamically depending on how fast network and CPU are? Experement
// with it.
static const int kMaxRecordings = 2;

ScreenRecorder::ScreenRecorder(
    MessageLoop* capture_loop,
    MessageLoop* encode_loop,
    MessageLoop* network_loop,
    Capturer* capturer,
    Encoder* encoder)
    : capture_loop_(capture_loop),
      encode_loop_(encode_loop),
      network_loop_(network_loop),
      capturer_(capturer),
      encoder_(encoder),
      is_recording_(false),
      network_stopped_(false),
      recordings_(0),
      frame_skipped_(false),
      max_rate_(kDefaultCaptureRate) {
  DCHECK(capture_loop_);
  DCHECK(encode_loop_);
  DCHECK(network_loop_);
}

ScreenRecorder::~ScreenRecorder() {
}

// Public methods --------------------------------------------------------------

void ScreenRecorder::Start() {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoStart));
}

void ScreenRecorder::Stop(Task* done_task) {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoStop, done_task));
}

void ScreenRecorder::SetMaxRate(double rate) {
  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoSetMaxRate, rate));
}

void ScreenRecorder::AddConnection(
    scoped_refptr<ConnectionToClient> connection) {
  ScopedTracer tracer("AddConnection");

  capture_loop_->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &ScreenRecorder::DoInvalidateFullScreen));

  // Add the client to the list so it can receive update stream.
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoAddConnection, connection));
}

void ScreenRecorder::RemoveConnection(
    scoped_refptr<ConnectionToClient> connection) {
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoRemoveClient, connection));
}

void ScreenRecorder::RemoveAllConnections() {
  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoRemoveAllClients));
}

// Private accessors -----------------------------------------------------------

Capturer* ScreenRecorder::capturer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  DCHECK(capturer_);
  return capturer_;
}

Encoder* ScreenRecorder::encoder() {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  DCHECK(encoder_.get());
  return encoder_.get();
}

// Capturer thread -------------------------------------------------------------

void ScreenRecorder::DoStart() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (is_recording_) {
    NOTREACHED() << "Record session already started.";
    return;
  }

  is_recording_ = true;
  StartCaptureTimer();

  // Capture first frame immedately.
  DoCapture();
}

void ScreenRecorder::DoStop(Task* done_task) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  // We might have not started when we receive a stop command, simply run the
  // task and then return.
  if (!is_recording_) {
    DoCompleteStop(done_task);
    return;
  }

  capture_timer_.Stop();
  is_recording_ = false;

  DCHECK_GE(recordings_, 0);
  if (recordings_) {
     network_loop_->PostTask(
        FROM_HERE,
        NewTracedMethod(this,
                        &ScreenRecorder::DoStopOnNetworkThread, done_task));
    return;
  }

  DoCompleteStop(done_task);
}

void ScreenRecorder::DoCompleteStop(Task* done_task) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (done_task) {
    done_task->Run();
    delete done_task;
  }
}

void ScreenRecorder::DoSetMaxRate(double max_rate) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  // TODO(hclam): Should also check for small epsilon.
  DCHECK_GT(max_rate, 0.0) << "Rate is too small.";

  max_rate_ = max_rate;

  // Restart the timer with the new rate.
  if (is_recording_) {
    capture_timer_.Stop();
    StartCaptureTimer();
  }
}

void ScreenRecorder::StartCaptureTimer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
      static_cast<int>(base::Time::kMillisecondsPerSecond / max_rate_));
  capture_timer_.Start(interval, this, &ScreenRecorder::DoCapture);
}

void ScreenRecorder::DoCapture() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  // Make sure we have at most two oustanding recordings. We can simply return
  // if we can't make a capture now, the next capture will be started by the
  // end of an encode operation.
  if (recordings_ >= kMaxRecordings || !is_recording_) {
    frame_skipped_ = true;
    return;
  }

  if (frame_skipped_) {
    frame_skipped_ = false;
    capture_timer_.Reset();
  }

  TraceContext::tracer()->PrintString("Capture Started");

  // At this point we are going to perform one capture so save the current time.
  ++recordings_;
  DCHECK_LE(recordings_, kMaxRecordings);

  // And finally perform one capture.
  capture_start_time_ = base::Time::Now();
  capturer()->CaptureInvalidRects(
      NewCallback(this, &ScreenRecorder::CaptureDoneCallback));
}

void ScreenRecorder::CaptureDoneCallback(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  TraceContext::tracer()->PrintString("Capture Done");
  int capture_time = static_cast<int>(
      (base::Time::Now() - capture_start_time_).InMilliseconds());
  capture_data->set_capture_time_ms(capture_time);
  encode_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoEncode, capture_data));
}

void ScreenRecorder::DoFinishOneRecording() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  // Decrement the number of recording in process since we have completed
  // one cycle.
  --recordings_;
  DCHECK_GE(recordings_, 0);

  // Try to do a capture again only if |frame_skipped_| is set to true by
  // capture timer.
  if (frame_skipped_)
    DoCapture();
}

void ScreenRecorder::DoInvalidateFullScreen() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capturer_->InvalidateFullScreen();
}

// Network thread --------------------------------------------------------------

void ScreenRecorder::DoSendVideoPacket(VideoPacket* packet) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  TraceContext::tracer()->PrintString("DoSendVideoPacket");

  bool last = (packet->flags() & VideoPacket::LAST_PARTITION) != 0;

  if (network_stopped_ || connections_.empty()) {
    delete packet;
    return;
  }

  for (ConnectionToClientList::const_iterator i = connections_.begin();
       i < connections_.end(); ++i) {
    Task* done_task = NULL;

    // Call FrameSentCallback() only for the last packet in the first
    // connection.
    if (last && i == connections_.begin()) {
      done_task = NewTracedMethod(this, &ScreenRecorder::FrameSentCallback,
                                  packet);
    } else {
      // TODO(hclam): Fix this code since it causes multiple deletion if there's
      // more than one connection.
      done_task = new DeleteTask<VideoPacket>(packet);
    }

    (*i)->video_stub()->ProcessVideoPacket(packet, done_task);
  }

  TraceContext::tracer()->PrintString("DoSendVideoPacket done");
}

void ScreenRecorder::FrameSentCallback(VideoPacket* packet) {
  delete packet;

  if (network_stopped_)
    return;

  capture_loop_->PostTask(
      FROM_HERE, NewTracedMethod(this, &ScreenRecorder::DoFinishOneRecording));
}

void ScreenRecorder::DoAddConnection(
    scoped_refptr<ConnectionToClient> connection) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  connections_.push_back(connection);
}

void ScreenRecorder::DoRemoveClient(
    scoped_refptr<ConnectionToClient> connection) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  ConnectionToClientList::iterator it =
      std::find(connections_.begin(), connections_.end(), connection);
  if (it != connections_.end()) {
    connections_.erase(it);
  }
}

void ScreenRecorder::DoRemoveAllClients() {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  // Clear the list of connections.
  connections_.clear();
}

void ScreenRecorder::DoStopOnNetworkThread(Task* done_task) {
  DCHECK_EQ(network_loop_, MessageLoop::current());

  // There could be tasks on the network thread when this method is being
  // executed. By setting the flag we'll not post anymore tasks from network
  // thread.
  //
  // After that a task is posted on encode thread to continue the stop
  // sequence.
  network_stopped_ = true;

  encode_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoStopOnEncodeThread, done_task));
}

// Encoder thread --------------------------------------------------------------

void ScreenRecorder::DoEncode(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  TraceContext::tracer()->PrintString("DoEncode called");

  // Early out if there's nothing to encode.
  if (!capture_data->dirty_rects().size()) {
    capture_loop_->PostTask(
        FROM_HERE,
        NewTracedMethod(this, &ScreenRecorder::DoFinishOneRecording));
    return;
  }

  TraceContext::tracer()->PrintString("Encode start");
  encode_start_time_ = base::Time::Now();
  encoder()->Encode(
      capture_data, false,
      NewCallback(this, &ScreenRecorder::EncodedDataAvailableCallback));
  TraceContext::tracer()->PrintString("Encode Done");
}

void ScreenRecorder::DoStopOnEncodeThread(Task* done_task) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  // When this method is being executed there are no more tasks on encode thread
  // for this object. We can then post a task to capture thread to finish the
  // stop sequence.
  capture_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoCompleteStop, done_task));
}

void ScreenRecorder::EncodedDataAvailableCallback(VideoPacket* packet) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  bool last = (packet->flags() & VideoPacket::LAST_PACKET) != 0;
  if (last) {
    int encode_time = static_cast<int>(
        (base::Time::Now() - encode_start_time_).InMilliseconds());
    packet->set_encode_time_ms(encode_time);
  }

  network_loop_->PostTask(
      FROM_HERE,
      NewTracedMethod(this, &ScreenRecorder::DoSendVideoPacket, packet));
}

}  // namespace remoting
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/screen_recorder.h"

#include <algorithm>

#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/stl_util.h"
#include "base/sys_info.h"
#include "base/time.h"
#include "remoting/base/capture_data.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/message_decoder.h"
#include "remoting/protocol/util.h"

using remoting::protocol::ConnectionToClient;

namespace remoting {

// Maximum number of frames that can be processed similtaneously.
// TODO(hclam): Move this value to CaptureScheduler.
static const int kMaxRecordings = 2;

ScreenRecorder::ScreenRecorder(
    MessageLoop* capture_loop,
    MessageLoop* encode_loop,
    base::MessageLoopProxy* network_loop,
    Capturer* capturer,
    Encoder* encoder)
    : capture_loop_(capture_loop),
      encode_loop_(encode_loop),
      network_loop_(network_loop),
      capturer_(capturer),
      encoder_(encoder),
      is_recording_(false),
      network_stopped_(false),
      encoder_stopped_(false),
      max_recordings_(kMaxRecordings),
      recordings_(0),
      frame_skipped_(false),
      sequence_number_(0) {
  DCHECK(capture_loop_);
  DCHECK(encode_loop_);
  DCHECK(network_loop_);
}

ScreenRecorder::~ScreenRecorder() {
}

// Public methods --------------------------------------------------------------

void ScreenRecorder::Start() {
  capture_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoStart, this));
}

void ScreenRecorder::Stop(const base::Closure& done_task) {
  if (MessageLoop::current() != capture_loop_) {
    capture_loop_->PostTask(FROM_HERE, base::Bind(
        &ScreenRecorder::Stop, this, done_task));
    return;
  }

  DCHECK(!done_task.is_null());

  capture_timer_.Stop();
  is_recording_ = false;

  network_loop_->PostTask(FROM_HERE, base::Bind(
      &ScreenRecorder::DoStopOnNetworkThread, this, done_task));
}

void ScreenRecorder::AddConnection(ConnectionToClient* connection) {
  DCHECK(network_loop_->BelongsToCurrentThread());
  connections_.push_back(connection);

  capture_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoInvalidateFullScreen, this));
}

void ScreenRecorder::RemoveConnection(ConnectionToClient* connection) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  ConnectionToClientList::iterator it =
      std::find(connections_.begin(), connections_.end(), connection);
  if (it != connections_.end()) {
    connections_.erase(it);
  }
}

void ScreenRecorder::RemoveAllConnections() {
  DCHECK(network_loop_->BelongsToCurrentThread());
  connections_.clear();
}

void ScreenRecorder::UpdateSequenceNumber(int64 sequence_number) {
  // Sequence number is used and written only on the capture thread.
  if (MessageLoop::current() != capture_loop_) {
    capture_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::UpdateSequenceNumber,
                              this, sequence_number));
    return;
  }

  sequence_number_ = sequence_number;
}

// Private accessors -----------------------------------------------------------

Capturer* ScreenRecorder::capturer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  DCHECK(capturer_);
  return capturer_;
}

Encoder* ScreenRecorder::encoder() {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  DCHECK(encoder_.get());
  return encoder_.get();
}

// Capturer thread -------------------------------------------------------------

void ScreenRecorder::DoStart() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (is_recording_) {
    NOTREACHED() << "Record session already started.";
    return;
  }

  is_recording_ = true;

  // Capture first frame immedately.
  DoCapture();
}

void ScreenRecorder::StartCaptureTimer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capture_timer_.Start(FROM_HERE,
                       scheduler_.NextCaptureDelay(),
                       this,
                       &ScreenRecorder::DoCapture);
}

void ScreenRecorder::DoCapture() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  // Make sure we have at most two oustanding recordings. We can simply return
  // if we can't make a capture now, the next capture will be started by the
  // end of an encode operation.
  if (recordings_ >= max_recordings_ || !is_recording_) {
    frame_skipped_ = true;
    return;
  }

  if (frame_skipped_)
    frame_skipped_ = false;

  // At this point we are going to perform one capture so save the current time.
  ++recordings_;
  DCHECK_LE(recordings_, max_recordings_);

  // Before doing a capture schedule for the next one.
  capture_timer_.Stop();
  capture_timer_.Start(FROM_HERE,
                       scheduler_.NextCaptureDelay(),
                       this,
                       &ScreenRecorder::DoCapture);

  // And finally perform one capture.
  capture_start_time_ = base::Time::Now();
  capturer()->CaptureInvalidRegion(
      base::Bind(&ScreenRecorder::CaptureDoneCallback, this));
}

void ScreenRecorder::CaptureDoneCallback(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  if (capture_data) {
    base::TimeDelta capture_time = base::Time::Now() - capture_start_time_;
    int capture_time_ms =
        static_cast<int>(capture_time.InMilliseconds());
    capture_data->set_capture_time_ms(capture_time_ms);
    scheduler_.RecordCaptureTime(capture_time);

    // The best way to get this value is by binding the sequence number to
    // the callback when calling CaptureInvalidRects(). However the callback
    // system doesn't allow this. Reading from the member variable is
    // accurate as long as capture is synchronous as the following statement
    // will obtain the most recent sequence number received.
    capture_data->set_client_sequence_number(sequence_number_);
  }

  encode_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoEncode, this, capture_data));
}

void ScreenRecorder::DoFinishOneRecording() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  // Decrement the number of recording in process since we have completed
  // one cycle.
  --recordings_;
  DCHECK_GE(recordings_, 0);

  // Try to do a capture again only if |frame_skipped_| is set to true by
  // capture timer.
  if (frame_skipped_)
    DoCapture();
}

void ScreenRecorder::DoInvalidateFullScreen() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capturer_->InvalidateFullScreen();
}

// Network thread --------------------------------------------------------------

void ScreenRecorder::DoSendVideoPacket(scoped_ptr<VideoPacket> packet) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  if (network_stopped_ || connections_.empty())
    return;

  // TODO(sergeyu): Currently we send the data only to the first
  // connection. Send it to all connections if necessary.
  connections_.front()->video_stub()->ProcessVideoPacket(
      packet.get(), base::Bind(
          &ScreenRecorder::VideoPacketSentCallback, this,
          base::Passed(packet.Pass())));
}

void ScreenRecorder::VideoPacketSentCallback(scoped_ptr<VideoPacket> packet) {
  if (network_stopped_)
    return;

  if ((packet->flags() & VideoPacket::LAST_PARTITION) != 0) {
    // Post DoFinishOneRecording() if that was the last packet for the
    // frame.
    capture_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::DoFinishOneRecording, this));
  }
}

void ScreenRecorder::DoStopOnNetworkThread(const base::Closure& done_task) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  // There could be tasks on the network thread when this method is being
  // executed. By setting the flag we'll not post anymore tasks from network
  // thread.
  //
  // After that a task is posted on encode thread to continue the stop
  // sequence.
  network_stopped_ = true;

  encode_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoStopOnEncodeThread,
                            this, done_task));
}

// Encoder thread --------------------------------------------------------------

void ScreenRecorder::DoEncode(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  // Early out if there's nothing to encode.
  if (!capture_data || capture_data->dirty_region().isEmpty()) {
    // Send an empty video packet to keep network active.
    scoped_ptr<VideoPacket> packet(new VideoPacket());
    packet->set_flags(VideoPacket::LAST_PARTITION);
    network_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::DoSendVideoPacket,
                              this, base::Passed(packet.Pass())));
    return;
  }

  encode_start_time_ = base::Time::Now();
  encoder()->Encode(
      capture_data, false,
      base::Bind(&ScreenRecorder::EncodedDataAvailableCallback, this));
}

void ScreenRecorder::DoStopOnEncodeThread(const base::Closure& done_task) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  encoder_stopped_ = true;

  // When this method is being executed there are no more tasks on encode thread
  // for this object. We can then post a task to capture thread to finish the
  // stop sequence.
  capture_loop_->PostTask(FROM_HERE, done_task);
}

void ScreenRecorder::EncodedDataAvailableCallback(
    scoped_ptr<VideoPacket> packet) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  if (encoder_stopped_)
    return;

  bool last = (packet->flags() & VideoPacket::LAST_PACKET) != 0;
  if (last) {
    base::TimeDelta encode_time = base::Time::Now() - encode_start_time_;
    int encode_time_ms =
        static_cast<int>(encode_time.InMilliseconds());
    packet->set_encode_time_ms(encode_time_ms);
    scheduler_.RecordEncodeTime(encode_time);
  }

  network_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoSendVideoPacket, this,
                            base::Passed(packet.Pass())));
}

}  // namespace remoting
<commit_msg>Fix crash caused by r127767 due to undefined order of argument parsing.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/screen_recorder.h"

#include <algorithm>

#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/stl_util.h"
#include "base/sys_info.h"
#include "base/time.h"
#include "remoting/base/capture_data.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/message_decoder.h"
#include "remoting/protocol/util.h"

using remoting::protocol::ConnectionToClient;

namespace remoting {

// Maximum number of frames that can be processed similtaneously.
// TODO(hclam): Move this value to CaptureScheduler.
static const int kMaxRecordings = 2;

ScreenRecorder::ScreenRecorder(
    MessageLoop* capture_loop,
    MessageLoop* encode_loop,
    base::MessageLoopProxy* network_loop,
    Capturer* capturer,
    Encoder* encoder)
    : capture_loop_(capture_loop),
      encode_loop_(encode_loop),
      network_loop_(network_loop),
      capturer_(capturer),
      encoder_(encoder),
      is_recording_(false),
      network_stopped_(false),
      encoder_stopped_(false),
      max_recordings_(kMaxRecordings),
      recordings_(0),
      frame_skipped_(false),
      sequence_number_(0) {
  DCHECK(capture_loop_);
  DCHECK(encode_loop_);
  DCHECK(network_loop_);
}

ScreenRecorder::~ScreenRecorder() {
}

// Public methods --------------------------------------------------------------

void ScreenRecorder::Start() {
  capture_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoStart, this));
}

void ScreenRecorder::Stop(const base::Closure& done_task) {
  if (MessageLoop::current() != capture_loop_) {
    capture_loop_->PostTask(FROM_HERE, base::Bind(
        &ScreenRecorder::Stop, this, done_task));
    return;
  }

  DCHECK(!done_task.is_null());

  capture_timer_.Stop();
  is_recording_ = false;

  network_loop_->PostTask(FROM_HERE, base::Bind(
      &ScreenRecorder::DoStopOnNetworkThread, this, done_task));
}

void ScreenRecorder::AddConnection(ConnectionToClient* connection) {
  DCHECK(network_loop_->BelongsToCurrentThread());
  connections_.push_back(connection);

  capture_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoInvalidateFullScreen, this));
}

void ScreenRecorder::RemoveConnection(ConnectionToClient* connection) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  ConnectionToClientList::iterator it =
      std::find(connections_.begin(), connections_.end(), connection);
  if (it != connections_.end()) {
    connections_.erase(it);
  }
}

void ScreenRecorder::RemoveAllConnections() {
  DCHECK(network_loop_->BelongsToCurrentThread());
  connections_.clear();
}

void ScreenRecorder::UpdateSequenceNumber(int64 sequence_number) {
  // Sequence number is used and written only on the capture thread.
  if (MessageLoop::current() != capture_loop_) {
    capture_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::UpdateSequenceNumber,
                              this, sequence_number));
    return;
  }

  sequence_number_ = sequence_number;
}

// Private accessors -----------------------------------------------------------

Capturer* ScreenRecorder::capturer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  DCHECK(capturer_);
  return capturer_;
}

Encoder* ScreenRecorder::encoder() {
  DCHECK_EQ(encode_loop_, MessageLoop::current());
  DCHECK(encoder_.get());
  return encoder_.get();
}

// Capturer thread -------------------------------------------------------------

void ScreenRecorder::DoStart() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (is_recording_) {
    NOTREACHED() << "Record session already started.";
    return;
  }

  is_recording_ = true;

  // Capture first frame immedately.
  DoCapture();
}

void ScreenRecorder::StartCaptureTimer() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capture_timer_.Start(FROM_HERE,
                       scheduler_.NextCaptureDelay(),
                       this,
                       &ScreenRecorder::DoCapture);
}

void ScreenRecorder::DoCapture() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());
  // Make sure we have at most two oustanding recordings. We can simply return
  // if we can't make a capture now, the next capture will be started by the
  // end of an encode operation.
  if (recordings_ >= max_recordings_ || !is_recording_) {
    frame_skipped_ = true;
    return;
  }

  if (frame_skipped_)
    frame_skipped_ = false;

  // At this point we are going to perform one capture so save the current time.
  ++recordings_;
  DCHECK_LE(recordings_, max_recordings_);

  // Before doing a capture schedule for the next one.
  capture_timer_.Stop();
  capture_timer_.Start(FROM_HERE,
                       scheduler_.NextCaptureDelay(),
                       this,
                       &ScreenRecorder::DoCapture);

  // And finally perform one capture.
  capture_start_time_ = base::Time::Now();
  capturer()->CaptureInvalidRegion(
      base::Bind(&ScreenRecorder::CaptureDoneCallback, this));
}

void ScreenRecorder::CaptureDoneCallback(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  if (capture_data) {
    base::TimeDelta capture_time = base::Time::Now() - capture_start_time_;
    int capture_time_ms =
        static_cast<int>(capture_time.InMilliseconds());
    capture_data->set_capture_time_ms(capture_time_ms);
    scheduler_.RecordCaptureTime(capture_time);

    // The best way to get this value is by binding the sequence number to
    // the callback when calling CaptureInvalidRects(). However the callback
    // system doesn't allow this. Reading from the member variable is
    // accurate as long as capture is synchronous as the following statement
    // will obtain the most recent sequence number received.
    capture_data->set_client_sequence_number(sequence_number_);
  }

  encode_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoEncode, this, capture_data));
}

void ScreenRecorder::DoFinishOneRecording() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  if (!is_recording_)
    return;

  // Decrement the number of recording in process since we have completed
  // one cycle.
  --recordings_;
  DCHECK_GE(recordings_, 0);

  // Try to do a capture again only if |frame_skipped_| is set to true by
  // capture timer.
  if (frame_skipped_)
    DoCapture();
}

void ScreenRecorder::DoInvalidateFullScreen() {
  DCHECK_EQ(capture_loop_, MessageLoop::current());

  capturer_->InvalidateFullScreen();
}

// Network thread --------------------------------------------------------------

void ScreenRecorder::DoSendVideoPacket(scoped_ptr<VideoPacket> packet) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  if (network_stopped_ || connections_.empty())
    return;

  VideoPacket* packet_ptr = packet.get();

  // TODO(sergeyu): Currently we send the data only to the first
  // connection. Send it to all connections if necessary.
  connections_.front()->video_stub()->ProcessVideoPacket(
      packet_ptr, base::Bind(
          &ScreenRecorder::VideoPacketSentCallback, this,
          base::Passed(packet.Pass())));
}

void ScreenRecorder::VideoPacketSentCallback(scoped_ptr<VideoPacket> packet) {
  if (network_stopped_)
    return;

  if ((packet->flags() & VideoPacket::LAST_PARTITION) != 0) {
    // Post DoFinishOneRecording() if that was the last packet for the
    // frame.
    capture_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::DoFinishOneRecording, this));
  }
}

void ScreenRecorder::DoStopOnNetworkThread(const base::Closure& done_task) {
  DCHECK(network_loop_->BelongsToCurrentThread());

  // There could be tasks on the network thread when this method is being
  // executed. By setting the flag we'll not post anymore tasks from network
  // thread.
  //
  // After that a task is posted on encode thread to continue the stop
  // sequence.
  network_stopped_ = true;

  encode_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoStopOnEncodeThread,
                            this, done_task));
}

// Encoder thread --------------------------------------------------------------

void ScreenRecorder::DoEncode(
    scoped_refptr<CaptureData> capture_data) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  // Early out if there's nothing to encode.
  if (!capture_data || capture_data->dirty_region().isEmpty()) {
    // Send an empty video packet to keep network active.
    scoped_ptr<VideoPacket> packet(new VideoPacket());
    packet->set_flags(VideoPacket::LAST_PARTITION);
    network_loop_->PostTask(
        FROM_HERE, base::Bind(&ScreenRecorder::DoSendVideoPacket,
                              this, base::Passed(packet.Pass())));
    return;
  }

  encode_start_time_ = base::Time::Now();
  encoder()->Encode(
      capture_data, false,
      base::Bind(&ScreenRecorder::EncodedDataAvailableCallback, this));
}

void ScreenRecorder::DoStopOnEncodeThread(const base::Closure& done_task) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  encoder_stopped_ = true;

  // When this method is being executed there are no more tasks on encode thread
  // for this object. We can then post a task to capture thread to finish the
  // stop sequence.
  capture_loop_->PostTask(FROM_HERE, done_task);
}

void ScreenRecorder::EncodedDataAvailableCallback(
    scoped_ptr<VideoPacket> packet) {
  DCHECK_EQ(encode_loop_, MessageLoop::current());

  if (encoder_stopped_)
    return;

  bool last = (packet->flags() & VideoPacket::LAST_PACKET) != 0;
  if (last) {
    base::TimeDelta encode_time = base::Time::Now() - encode_start_time_;
    int encode_time_ms =
        static_cast<int>(encode_time.InMilliseconds());
    packet->set_encode_time_ms(encode_time_ms);
    scheduler_.RecordEncodeTime(encode_time);
  }

  network_loop_->PostTask(
      FROM_HERE, base::Bind(&ScreenRecorder::DoSendVideoPacket, this,
                            base::Passed(packet.Pass())));
}

}  // namespace remoting
<|endoftext|>


<commit_before>/*
    Copyright 2008 Brain Research Institute, Melbourne, Australia

    Written by Robert E. Smith, 06/02/14.

    This file is part of MRtrix.

    MRtrix is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    MRtrix is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MRtrix.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <vector>

#include <gsl/gsl_fit.h>

#include "app.h"
#include "bitset.h"
#include "command.h"
#include "datatype.h"
#include "progressbar.h"
#include "ptr.h"

#include "image/buffer.h"
#include "image/buffer_scratch.h"
#include "image/header.h"
#include "image/loop.h"
#include "image/utils.h"
#include "image/voxel.h"

#include "math/SH.h"

using namespace MR;
using namespace App;

void usage ()
{

  AUTHOR = "Robert E. Smith (r.smith@brain.org.au)";

  DESCRIPTION
    + "examine the values in spherical harmonic images to estimate (and optionally change) the SH basis used."

    + "In previous versions of MRtrix, the convention used for storing spherical harmonic "
      "coefficients was a non-orthonormal basis (the m!=0 coefficients were a factor of "
      "sqrt(2) too large). This error has been rectified in the new MRtrix (assuming that "
      "compilation was performed without the USE_NON_ORTHONORMAL_SH_BASIS symbol defined), "
      "but will cause issues if processing SH data that was generated using an older version "
      "of MRtrix (or vice-versa)."

    + "This command provides a mechanism for testing the basis used in storage of image data "
      "representing a spherical harmonic series per voxel, and allows the user to forcibly "
      "modify the raw image data to conform to the desired basis.";

  ARGUMENTS
    + Argument ("SH", "the input image of SH coefficients.").allow_multiple().type_image_in();

  OPTIONS
    + Option ("force_old", "force the image data to use the old (i.e. non-orthonormal) basis")
    + Option ("force_new", "force the image data to use the new (i.e. orthonormal) basis")
    + Option ("force_native", "force the image data to use the basis under which MRtrix is compiled");

}

// Perform a linear regression on the power ratio in each order
// Omit l=2 - tends to be abnormally small due to non-isotropic brain-wide fibre distribution
// Use this to project the power ratio at l=0; better predictor for poor data
// Also, if the regression has a substantial gradient, warn the user
// Threshold on gradient will depend on the basis of the image
//
std::pair<float, float> get_regression (const std::vector<float>& ratios)
{
  const size_t n = ratios.size() - 1;
  double x[n], y[n];
  for (size_t i = 1; i != ratios.size(); ++i) {
    x[i-1] = (2*i)+2;
    y[i-1] = ratios[i];
  }
  double c0, c1, cov00, cov01, cov11, sumsq;
  gsl_fit_linear (x, 1, y, 1, n, &c0, &c1, &cov00, &cov01, &cov11, &sumsq);
  return std::make_pair (c0, c1);
}

template <typename value_type>
void check_and_update (Image::Header& H, const bool force_old, const bool force_new)
{

  const size_t lmax = Math::SH::LforN (H.dim(3));

  // Flag which volumes are m==0 and which are not
  const ssize_t N = H.dim(3);
  BitSet mzero_terms (N, false);
  for (size_t l = 2; l <= lmax; l += 2)
    mzero_terms[Math::SH::index (l, 0)] = true;

  typename Image::Buffer<value_type> buffer (H, (force_old || force_new));
  typename Image::Buffer<value_type>::voxel_type v (buffer);

  // Need to mask out voxels where the DC term is zero
  Image::Info info_mask (H);
  info_mask.set_ndim (3);
  info_mask.datatype() = DataType::Bit;
  Image::BufferScratch<bool> mask (info_mask);
  Image::BufferScratch<bool>::voxel_type v_mask (mask);
  size_t voxel_count = 0;
  {
    Image::LoopInOrder loop (v, "Masking image based on DC term...", 0, 3);
    for (loop.start (v, v_mask); loop.ok(); loop.next (v, v_mask)) {
      const value_type value = v.value();
      if (value && std::isfinite (value)) {
        v_mask.value() = true;
        ++voxel_count;
      } else {
        v_mask.value() = false;
      }
    }
  }

  // Get sums independently for each l

  // Each order has a different power, and a different number of m!=0 volumes.
  // Therefore, calculate the mean-square intensity for the m==0 and m!=0
  // volumes independently, and report ratio for each harmonic order
  Ptr<ProgressBar> progress;
  if (App::log_level > 0 && App::log_level < 2)
    progress = new ProgressBar ("Evaluating SH basis of image " + H.name() + "...", N-1);

  std::vector<float> ratios;

  for (size_t l = 2; l <= lmax; l += 2) {

    double mzero_sum = 0.0, mnonzero_sum = 0.0;
    Image::LoopInOrder loop (v, 0, 3);
    for (v[3] = ssize_t (Math::SH::NforL(l-2)); v[3] != ssize_t (Math::SH::NforL(l)); ++v[3]) {
      double sum = 0.0;
      for (loop.start (v, v_mask); loop.ok(); loop.next (v, v_mask)) {
        if (v_mask.value())
          sum += Math::pow2 (value_type(v.value()));
      }
      if (mzero_terms[v[3]]) {
        mzero_sum += sum;
        DEBUG ("Volume " + str(v[3]) + ", m==0, sum " + str(sum));
      } else {
        mnonzero_sum += sum;
        DEBUG ("Volume " + str(v[3]) + ", m!=0, sum " + str(sum));
      }
      if (progress)
      ++*progress;
    }

    const double mnonzero_MSoS = mnonzero_sum / (2.0 * l);
    const float power_ratio = mnonzero_MSoS/mzero_sum;
    ratios.push_back (power_ratio);

    INFO ("SH order " + str(l) + ", ratio of m!=0 to m==0 power: " + str(power_ratio) +
        ", overall m=0 power: " + str (mzero_sum));

  }

  if (progress)
    progress = NULL;

  // First is ratio to be used for SH basis decision, second is gradient of regression
  std::pair<float, float> regression;
  size_t l_for_decision;

  // The gradient will change depending on the current basis, so the threshold needs to also
  // The gradient is as a function of l, not of even orders
  float grad_threshold = -0.02;

  switch (lmax) {

    // Lmax == 2: only one order to use
    case 2:
      regression = std::make_pair (ratios.front(), 0.0);
      l_for_decision = 2;
      break;

    // Lmax = 4: Use l=4 order to determine SH basis, can't check gradient since l=2 is untrustworthy
    case 4:
      regression = std::make_pair (ratios.back(), 0.0);
      l_for_decision = 4;
      break;

    // Lmax = 6: Use l=4 order to determine SH basis, but checking the gradient is not reliable:
    //   artificially double the threshold so the power ratio at l=6 needs to be substantially
    //   smaller than l=4 to throw a warning
    case 6:
      regression = std::make_pair (ratios[1], 0.5 * (ratios[2] - ratios[1]));
      l_for_decision = 4;
      grad_threshold *= 2.0;
      break;

    // Lmax >= 8: Do a linear regression from l=4 to l=lmax, project back to l=0
    // (this is a more reliable quantification on poor data than l=4 alone)
    default:
      regression = get_regression (ratios);
      l_for_decision = 0;
      break;

  }

  DEBUG ("Power ratio for assessing SH basis is " + str(regression.first) + " as derived from l=" + str(l_for_decision));
  if (regression.second)
    DEBUG ("Gradient of regression is " + str(regression.second) + "; threshold is " + str(grad_threshold));

  // Threshold to make decision on what basis is being used, if unambiguous
  value_type multiplier = 1.0;
  if ((regression.first > (5.0/3.0)) && (regression.first < (7.0/3.0))) {
    CONSOLE ("Image " + str(H.name()) + " appears to be in the old non-orthonormal basis");
    if (force_new)
      multiplier = 1.0 / M_SQRT2;
    grad_threshold *= 2.0;
  } else if ((regression.first > (2.0/3.0)) && (regression.first < (4.0/3.0))) {
    CONSOLE ("Image " + str(H.name()) + " appears to be in the new orthonormal basis");
    if (force_old)
      multiplier = M_SQRT2;
  } else {
    multiplier = 0.0;
    WARN ("Cannot make unambiguous decision on SH basis of image " + H.name()
        + " (power ratio " + (l_for_decision ? ("in l=" + str(l_for_decision)) : ("regressed to l=0")) + " is " + str(regression.first) + ")");
  }

  // Decide whether the user needs to be warned about a poor diffusion encoding scheme
  if (regression.second < grad_threshold) {
    WARN ("Image " + H.name() + " may have been derived from poor directional encoding");
    WARN ("(m==0 to m!=0 power ratio decreasing by " + str(-2.0*regression.second) + " per even order)");
  }

  // Adjust the image data in-place if necessary
  if (multiplier && (multiplier != 1.0)) {

    Image::LoopInOrder loop (v, 0, 3);
    ProgressBar progress ("Modifying SH basis of image " + H.name() + "...", N-1);
    for (v[3] = 1; v[3] != N; ++v[3]) {
      if (!mzero_terms[v[3]]) {
        for (loop.start (v); loop.ok(); loop.next (v))
          v.value() *= multiplier;
      }
      ++progress;
    }

  } else if (multiplier && (force_old || force_new)) {
    INFO ("Image " + H.name() + " already in desired basis; nothing to do");
  }

}

void run ()
{
  bool force_old = get_options ("force_old").size();
  bool force_new = get_options ("force_new").size();
  if (force_old && force_new)
    throw Exception ("Options -force_old and -force_new are mutually exclusive");
  if (get_options ("force_native").size()) {
    if (force_old || force_new)
      throw Exception ("Option -force_native cannot be used in conjunction with one of the other -force options");
#ifndef USE_NON_ORTHONORMAL_SH_BASIS
    INFO ("Forcing to new orthonormal basis (native)");
    force_new = true;
#else
    INFO ("Forcing to old non-orthonormal basis (native)");
    force_old = true;
#endif
  }

  for (std::vector<ParsedArgument>::const_iterator i = argument.begin(); i != argument.end(); ++i) {

    const std::string path = *i;
    Image::Header H (path);
    if (H.ndim() != 4)
      throw Exception ("Image " + H.name() + " is not 4D and therefore cannot be an SH image");
    const size_t lmax = Math::SH::LforN (H.dim(3));
    if (!lmax)
      throw Exception ("Image " + H.name() + " does not contain enough volumes to be an SH image");
    if (Math::SH::NforL (lmax) != size_t(H.dim(3)))
      throw Exception ("Image " + H.name() + " does not contain a number of volumes appropriate for an SH image");
    if (!H.datatype().is_floating_point())
      throw Exception ("Image " + H.name() + " does not use a floating-point format and therefore cannot be an SH image");

    if (H.datatype().bytes() == 4)
      check_and_update<float> (H, force_old, force_new);
    else
      check_and_update<double> (H, force_old, force_new);

  }

};

<commit_msg>shbasis: Changes to permit conversion of erroneous data<commit_after>/*
    Copyright 2008 Brain Research Institute, Melbourne, Australia

    Written by Robert E. Smith, 06/02/14.

    This file is part of MRtrix.

    MRtrix is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    MRtrix is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MRtrix.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <vector>

#include <gsl/gsl_fit.h>

#include "app.h"
#include "bitset.h"
#include "command.h"
#include "datatype.h"
#include "progressbar.h"
#include "ptr.h"

#include "image/buffer.h"
#include "image/buffer_scratch.h"
#include "image/header.h"
#include "image/loop.h"
#include "image/utils.h"
#include "image/voxel.h"

#include "math/SH.h"

using namespace MR;
using namespace App;

const char* conversions[] = { "old", "new", "native", "force_oldtonew", "force_newtoold", NULL };
enum conv_t { NONE, OLD, NEW, FORCE_OLDTONEW, FORCE_NEWTOOLD };

void usage ()
{

  AUTHOR = "Robert E. Smith (r.smith@brain.org.au)";

  DESCRIPTION
    + "examine the values in spherical harmonic images to estimate (and optionally change) the SH basis used."

    + "In previous versions of MRtrix, the convention used for storing spherical harmonic "
      "coefficients was a non-orthonormal basis (the m!=0 coefficients were a factor of "
      "sqrt(2) too large). This error has been rectified in the new MRtrix (assuming that "
      "compilation was performed without the USE_NON_ORTHONORMAL_SH_BASIS symbol defined), "
      "but will cause issues if processing SH data that was generated using an older version "
      "of MRtrix (or vice-versa)."

    + "This command provides a mechanism for testing the basis used in storage of image data "
      "representing a spherical harmonic series per voxel, and allows the user to forcibly "
      "modify the raw image data to conform to the desired basis.";

  ARGUMENTS
    + Argument ("SH", "the input image of SH coefficients.").allow_multiple().type_image_in();

  OPTIONS
    + Option ("convert", "convert the image data in-place to the desired basis (if necessary). "
                         "Options are: old, new, native (whichever basis MRtrix is compiled for; "
                         "most likely the new orthonormal basis), force_oldtonew, force_newtoold. "
                         "Note that for the \"force_*\" choices should ideally only be used in "
                         "cases where the command is unable to automatically determine the SH basis "
                         "using the existing image data.")
      + Argument ("mode").type_choice (conversions);

}

// Perform a linear regression on the power ratio in each order
// Omit l=2 - tends to be abnormally small due to non-isotropic brain-wide fibre distribution
// Use this to project the power ratio at l=0; better predictor for poor data
// Also, if the regression has a substantial gradient, warn the user
// Threshold on gradient will depend on the basis of the image
//
std::pair<float, float> get_regression (const std::vector<float>& ratios)
{
  const size_t n = ratios.size() - 1;
  double x[n], y[n];
  for (size_t i = 1; i != ratios.size(); ++i) {
    x[i-1] = (2*i)+2;
    y[i-1] = ratios[i];
  }
  double c0, c1, cov00, cov01, cov11, sumsq;
  gsl_fit_linear (x, 1, y, 1, n, &c0, &c1, &cov00, &cov01, &cov11, &sumsq);
  return std::make_pair (c0, c1);
}

template <typename value_type>
void check_and_update (Image::Header& H, const conv_t conversion)
{

  const size_t lmax = Math::SH::LforN (H.dim(3));

  // Flag which volumes are m==0 and which are not
  const ssize_t N = H.dim(3);
  BitSet mzero_terms (N, false);
  for (size_t l = 2; l <= lmax; l += 2)
    mzero_terms[Math::SH::index (l, 0)] = true;

  // Open in read-write mode if there's a chance of modification
  typename Image::Buffer<value_type> buffer (H, (conversion != NONE));
  typename Image::Buffer<value_type>::voxel_type v (buffer);

  // Need to mask out voxels where the DC term is zero
  Image::Info info_mask (H);
  info_mask.set_ndim (3);
  info_mask.datatype() = DataType::Bit;
  Image::BufferScratch<bool> mask (info_mask);
  Image::BufferScratch<bool>::voxel_type v_mask (mask);
  size_t voxel_count = 0;
  {
    Image::LoopInOrder loop (v, "Masking image based on DC term...", 0, 3);
    for (loop.start (v, v_mask); loop.ok(); loop.next (v, v_mask)) {
      const value_type value = v.value();
      if (value && std::isfinite (value)) {
        v_mask.value() = true;
        ++voxel_count;
      } else {
        v_mask.value() = false;
      }
    }
  }

  // Get sums independently for each l

  // Each order has a different power, and a different number of m!=0 volumes.
  // Therefore, calculate the mean-square intensity for the m==0 and m!=0
  // volumes independently, and report ratio for each harmonic order
  Ptr<ProgressBar> progress;
  if (App::log_level > 0 && App::log_level < 2)
    progress = new ProgressBar ("Evaluating SH basis of image \"" + H.name() + "\"...", N-1);

  std::vector<float> ratios;

  for (size_t l = 2; l <= lmax; l += 2) {

    double mzero_sum = 0.0, mnonzero_sum = 0.0;
    Image::LoopInOrder loop (v, 0, 3);
    for (v[3] = ssize_t (Math::SH::NforL(l-2)); v[3] != ssize_t (Math::SH::NforL(l)); ++v[3]) {
      double sum = 0.0;
      for (loop.start (v, v_mask); loop.ok(); loop.next (v, v_mask)) {
        if (v_mask.value())
          sum += Math::pow2 (value_type(v.value()));
      }
      if (mzero_terms[v[3]]) {
        mzero_sum += sum;
        DEBUG ("Volume " + str(v[3]) + ", m==0, sum " + str(sum));
      } else {
        mnonzero_sum += sum;
        DEBUG ("Volume " + str(v[3]) + ", m!=0, sum " + str(sum));
      }
      if (progress)
      ++*progress;
    }

    const double mnonzero_MSoS = mnonzero_sum / (2.0 * l);
    const float power_ratio = mnonzero_MSoS/mzero_sum;
    ratios.push_back (power_ratio);

    INFO ("SH order " + str(l) + ", ratio of m!=0 to m==0 power: " + str(power_ratio) +
        ", m==0 power: " + str (mzero_sum));

  }

  if (progress)
    progress = NULL;

  // First is ratio to be used for SH basis decision, second is gradient of regression
  std::pair<float, float> regression;
  size_t l_for_decision;

  // The gradient will change depending on the current basis, so the threshold needs to also
  // The gradient is as a function of l, not of even orders
  float grad_threshold = -0.02;

  switch (lmax) {

    // Lmax == 2: only one order to use
    case 2:
      regression = std::make_pair (ratios.front(), 0.0);
      l_for_decision = 2;
      break;

    // Lmax = 4: Use l=4 order to determine SH basis, can't check gradient since l=2 is untrustworthy
    case 4:
      regression = std::make_pair (ratios.back(), 0.0);
      l_for_decision = 4;
      break;

    // Lmax = 6: Use l=4 order to determine SH basis, but checking the gradient is not reliable:
    //   artificially double the threshold so the power ratio at l=6 needs to be substantially
    //   smaller than l=4 to throw a warning
    case 6:
      regression = std::make_pair (ratios[1], 0.5 * (ratios[2] - ratios[1]));
      l_for_decision = 4;
      grad_threshold *= 2.0;
      break;

    // Lmax >= 8: Do a linear regression from l=4 to l=lmax, project back to l=0
    // (this is a more reliable quantification on poor data than l=4 alone)
    default:
      regression = get_regression (ratios);
      l_for_decision = 0;
      break;

  }

  DEBUG ("Power ratio for assessing SH basis is " + str(regression.first) + " as derived from l=" + str(l_for_decision));
  if (regression.second)
    DEBUG ("Gradient of regression is " + str(regression.second) + "; threshold is " + str(grad_threshold));

  // Threshold to make decision on what basis is being used, if unambiguous
  value_type multiplier = 1.0;
  if ((regression.first > (5.0/3.0)) && (regression.first < (7.0/3.0))) {

    CONSOLE ("Image \"" + str(H.name()) + "\" appears to be in the old non-orthonormal basis");
    switch (conversion) {
      case NONE: break;
      case OLD: break;
      case NEW: multiplier = 1.0 / M_SQRT2; break;
      case FORCE_OLDTONEW: multiplier = 1.0 / M_SQRT2; break;
      case FORCE_NEWTOOLD: WARN ("Refusing to convert image \"" + H.name() + "\" from new to old basis, as data appear to already be in the old non-orthonormal basis"); return;
    }
    grad_threshold *= 2.0;

  } else if ((regression.first > (2.0/3.0)) && (regression.first < (4.0/3.0))) {

    CONSOLE ("Image \"" + str(H.name()) + "\" appears to be in the new orthonormal basis");
    switch (conversion) {
      case NONE: break;
      case OLD: multiplier = M_SQRT2; break;
      case NEW: break;
      case FORCE_OLDTONEW: WARN ("Refusing to convert image \"" + H.name() + "\" from old to new basis, as data appear to already be in the new orthonormal basis"); return;
      case FORCE_NEWTOOLD: multiplier = M_SQRT2; break;
    }

  } else {

    multiplier = 0.0;
    WARN ("Cannot make unambiguous decision on SH basis of image \"" + H.name()
        + "\" (power ratio " + (l_for_decision ? ("in l=" + str(l_for_decision)) : ("regressed to l=0")) + " is " + str(regression.first) + ")");

    if (conversion == FORCE_OLDTONEW) {
      WARN ("Forcing conversion of image \"" + H.name() + "\" from old to new SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this conversion!");
      multiplier = 1.0 / M_SQRT2;
    } else if (conversion == FORCE_NEWTOOLD) {
      WARN ("Forcing conversion of image \"" + H.name() + "\" from new to old SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this conversion!");
      multiplier = M_SQRT2;
    }

  }

  // Decide whether the user needs to be warned about a poor diffusion encoding scheme
  if (regression.second < grad_threshold) {
    WARN ("Image \"" + H.name() + "\" may have been derived from poor directional encoding");
    WARN ("(m==0 to m!=0 power ratio decreasing by " + str(-2.0*regression.second) + " per even order)");
  }

  // Adjust the image data in-place if necessary
  if (multiplier && (multiplier != 1.0)) {

    Image::LoopInOrder loop (v, 0, 3);
    ProgressBar progress ("Modifying SH basis of image \"" + H.name() + "\"...", N-1);
    for (v[3] = 1; v[3] != N; ++v[3]) {
      if (!mzero_terms[v[3]]) {
        for (loop.start (v); loop.ok(); loop.next (v))
          v.value() *= multiplier;
      }
      ++progress;
    }

  } else if (multiplier && (conversion != NONE)) {
    INFO ("Image \"" + H.name() + "\" already in desired basis; nothing to do");
  }

}

void run ()
{
  conv_t conversion = NONE;
  Options opt = get_options ("convert");
  if (opt.size()) {
    switch (int(opt[0][0])) {
      case 0: conversion = OLD; break;
      case 1: conversion = NEW; break;
      case 2:
#ifndef USE_NON_ORTHONORMAL_SH_BASIS
        conversion = NEW;
#else
        conversion = OLD;
#endif
        break;
      case 3: conversion = FORCE_OLDTONEW; break;
      case 4: conversion = FORCE_NEWTOOLD; break;
      default: assert (0); break;
    }
  }

  for (std::vector<ParsedArgument>::const_iterator i = argument.begin(); i != argument.end(); ++i) {

    const std::string path = *i;
    Image::Header H (path);
    if (H.ndim() != 4) {
      WARN ("Image \"" + H.name() + "\" is not 4D and therefore cannot be an SH image");
      continue;
    }
    const size_t lmax = Math::SH::LforN (H.dim(3));
    if (!lmax) {
      WARN ("Image \"" + H.name() + "\" does not contain enough volumes to be an SH image");
      continue;
    }
    if (Math::SH::NforL (lmax) != size_t(H.dim(3))) {
      WARN ("Image \"" + H.name() + "\" does not contain a number of volumes appropriate for an SH image");
      continue;
    }
    if (!H.datatype().is_floating_point()) {
      WARN ("Image \"" + H.name() + "\" does not use a floating-point data type and therefore cannot be an SH image");
      continue;
    }

    if (H.datatype().bytes() == 4)
      check_and_update<float>  (H, conversion);
    else
      check_and_update<double> (H, conversion);

  }

};

<|endoftext|>


<commit_before>// -*- mode: c++; coding: utf-8 -*-
/*! @file gridsearch.cpp
    @brief Implementation of GridSearch class
*/
#include "gridsearch.hpp"
#include "util.hpp"

#include <wtl/exception.hpp>
#include <wtl/debug.hpp>
#include <wtl/iostr.hpp>
#include <wtl/zfstream.hpp>
#include <wtl/numeric.hpp>
#include <wtl/math.hpp>
#include <wtl/concurrent.hpp>

#include <chrono>

namespace likeligrid {

void GridSearch::init(const std::pair<size_t, size_t>& epistasis_pair) {HERE;
    if (epistasis_pair.first != epistasis_pair.second) {
        model_.set_epistasis(epistasis_pair);
    }
    mle_params_.resize(model_.names().size());
    mle_params_ = 1.0;
}

void GridSearch::run(const bool writing) {HERE;
    while (stage_ < STEPS.size()) {
        if (writing) {run_fout();} else {run_cout();}
    }
    --stage_;
    search_limits();
}

void GridSearch::run_fout() {HERE;
    const std::string outfile = init_meta();
    std::cerr << "mle_params_: " << mle_params_ << std::endl;
    if (outfile.empty()) return;
    const auto axes = make_vicinity(mle_params_, BREAKS.at(stage_), radius(stage_));
    for (size_t j=0; j<model_.names().size(); ++j) {
        std::cerr << model_.names()[j] << ": " << axes[j] << std::endl;
    }
    {
        wtl::ozfstream fout(outfile, std::ios::out | std::ios::app);
        std::cerr << "Writing: " << outfile << std::endl;
        run_impl(fout, wtl::itertools::product(axes));
    }
}

void GridSearch::run_cout() {HERE;
    const auto axes = make_vicinity(mle_params_, BREAKS.at(stage_), radius(stage_));
    for (size_t j=0; j<model_.names().size(); ++j) {
        std::cerr << model_.names()[j] << ": " << axes[j] << std::endl;
    }
    {
        std::stringstream sst;
        run_impl(sst, wtl::itertools::product(axes));
        std::cout << sst.str();
        read_results(sst);
    }
    std::cerr << "mle_params_: " << mle_params_ << std::endl;
    ++stage_;
}

void GridSearch::search_limits() {HERE;
    namespace bmath = boost::math;
    bmath::chi_squared_distribution<> chisq(1.0);
    const double diff95 = 0.5 * bmath::quantile(bmath::complement(chisq, 0.05));
    auto axis = wtl::round(wtl::lin_spaced(200, 2.0, 0.01), 100);
    axis = (axis * 100.0).apply(std::round) / 100.0;
    std::map<std::string, std::valarray<double>> intersections;
    for (size_t i=0; i<model_.names().size(); ++i) {
        const std::string outfile = "uniaxis-" + model_.names()[i] + ".tsv.gz";
        std::cerr << outfile << std::endl;
        std::stringstream sst;
        run_impl(sst, wtl::itertools::uniaxis(axis, mle_params_, i));
        wtl::ozfstream(outfile) << sst.str();
        const auto logliks = read_loglik(sst, axis.size());
        const double threshold = logliks.max() - diff95;
        const std::valarray<double> range = axis[logliks > threshold];
        auto bound_params = mle_params_;
        bound_params[i] = std::max(range.min() - 0.01, 0.01);
        intersections.emplace(model_.names()[i] + "_L", bound_params);
        bound_params[i] = std::min(range.max() + 0.01, 2.00);
        intersections.emplace(model_.names()[i] + "_U", bound_params);
    }
    for (const auto& p: intersections) {
        const std::string outfile = "limit-" + p.first + ".tsv.gz";
        std::cerr << outfile << ": " << p.second << std::endl;
        const auto axes = make_vicinity(p.second, 5, 0.02);
        wtl::ozfstream fout(outfile);
        //TODO: if exists
        run_impl(fout, wtl::itertools::product(axes));
    }
}

void GridSearch::run_impl(std::ostream& ost, wtl::itertools::Generator<std::valarray<double>>&& gen) {HERE;
    std::cerr << skip_ << " to " << gen.max_count() << std::endl;
    if (skip_ == 0) {
        write_header(ost, gen.max_count());
    }

    wtl::Semaphore semaphore(concurrency_);
    auto task = [this,&semaphore](const std::valarray<double> th_path) {
        std::lock_guard<wtl::Semaphore> scope_unlock(semaphore, std::adopt_lock);
        // argument and model are copied for each thread
        auto buffer = wtl::make_oss();
        auto model_copy = this->model_;
        buffer << model_copy.calc_loglik(th_path) << "\t"
               << wtl::str_join(th_path, "\t") << "\n";
        return buffer.str();
    };

    std::deque<std::future<std::string>> futures;
    const auto min_interval = std::chrono::seconds(1);
    auto next_time = std::chrono::system_clock::now() + min_interval;
    size_t stars = 0;
    for (const auto& th_path: gen(skip_)) {
        semaphore.lock();
        futures.push_back(std::async(std::launch::async, task, th_path));
        auto now = std::chrono::system_clock::now();
        if (now > next_time) {
            next_time = now + min_interval;
            while (!futures.empty() && wtl::is_ready(futures.front())) {
                ost << futures.front().get();
                futures.pop_front();
            }
            for (size_t n= 0.2 * gen.percent(); stars<n; ++stars) {
                std::cerr << "*";
            }
        }
        if (wtl::SIGINT_RAISED()) {throw wtl::KeyboardInterrupt();}
    }
    for (auto& ftr: futures) {
        ost << ftr.get();
    }
}

std::string GridSearch::init_meta() {HERE;
    if (stage_ >= STEPS.size()) return "";
    auto oss = wtl::make_oss(2, std::ios::fixed);
    oss << "grid-" << STEPS.at(stage_) << ".tsv.gz";
    std::string outfile = oss.str();
    try {
        wtl::izfstream ist(outfile);
        std::cerr << "Reading: " << outfile << std::endl;
        read_results(ist);
        if (skip_ == 0) {
            ++stage_;
            outfile = init_meta();
        }
    } catch (std::ios::failure& e) {
        if (errno != 2) throw;
    }
    return outfile;
}

void GridSearch::read_results(std::istream& ist) {HERE;
    size_t max_count;
    double step;
    std::tie(std::ignore, std::ignore, max_count, step) = read_metadata(ist);
    stage_ = guess_stage(step);
    std::vector<std::string> colnames;
    std::valarray<double> mle_params;
    std::tie(skip_, colnames, mle_params) = read_body(ist);
    if (skip_ == max_count) {  // is complete file
        skip_ = 0;
        mle_params_.swap(mle_params);
    }
}

void GridSearch::read_results(const std::string& infile) {
    wtl::izfstream ist(infile);
    read_results(ist);
}

void GridSearch::write_header(std::ostream& ost, const size_t max_count) const {
    ost << "##genotype_file=" << model_.filename() << "\n";
    ost << "##max_sites=" << model_.max_sites() << "\n";
    ost << "##max_count=" << max_count << "\n";
    ost << "##step=" << STEPS.at(stage_) << "\n";
    ost << "loglik\t" << wtl::join(model_.names(), "\t") << "\n";
}

void GridSearch::test() {HERE;
    std::stringstream sst;
    sst <<
R"({
  "pathway": ["A", "B"],
  "annotation": ["0011", "1100"],
  "sample": ["0011", "0101", "1001", "0110", "1010", "1100"]
})";
    GridSearch searcher(sst, 4, {0, 1});
    searcher.run_cout();
}

} // namespace likeligrid
<commit_msg>Put newline after progress stars<commit_after>// -*- mode: c++; coding: utf-8 -*-
/*! @file gridsearch.cpp
    @brief Implementation of GridSearch class
*/
#include "gridsearch.hpp"
#include "util.hpp"

#include <wtl/exception.hpp>
#include <wtl/debug.hpp>
#include <wtl/iostr.hpp>
#include <wtl/zfstream.hpp>
#include <wtl/numeric.hpp>
#include <wtl/math.hpp>
#include <wtl/concurrent.hpp>

#include <chrono>

namespace likeligrid {

void GridSearch::init(const std::pair<size_t, size_t>& epistasis_pair) {HERE;
    if (epistasis_pair.first != epistasis_pair.second) {
        model_.set_epistasis(epistasis_pair);
    }
    mle_params_.resize(model_.names().size());
    mle_params_ = 1.0;
}

void GridSearch::run(const bool writing) {HERE;
    while (stage_ < STEPS.size()) {
        if (writing) {run_fout();} else {run_cout();}
    }
    --stage_;
    search_limits();
}

void GridSearch::run_fout() {HERE;
    const std::string outfile = init_meta();
    std::cerr << "mle_params_: " << mle_params_ << std::endl;
    if (outfile.empty()) return;
    const auto axes = make_vicinity(mle_params_, BREAKS.at(stage_), radius(stage_));
    for (size_t j=0; j<model_.names().size(); ++j) {
        std::cerr << model_.names()[j] << ": " << axes[j] << std::endl;
    }
    {
        wtl::ozfstream fout(outfile, std::ios::out | std::ios::app);
        std::cerr << "Writing: " << outfile << std::endl;
        run_impl(fout, wtl::itertools::product(axes));
    }
}

void GridSearch::run_cout() {HERE;
    const auto axes = make_vicinity(mle_params_, BREAKS.at(stage_), radius(stage_));
    for (size_t j=0; j<model_.names().size(); ++j) {
        std::cerr << model_.names()[j] << ": " << axes[j] << std::endl;
    }
    {
        std::stringstream sst;
        run_impl(sst, wtl::itertools::product(axes));
        std::cout << sst.str();
        read_results(sst);
    }
    std::cerr << "mle_params_: " << mle_params_ << std::endl;
    ++stage_;
}

void GridSearch::search_limits() {HERE;
    namespace bmath = boost::math;
    bmath::chi_squared_distribution<> chisq(1.0);
    const double diff95 = 0.5 * bmath::quantile(bmath::complement(chisq, 0.05));
    auto axis = wtl::round(wtl::lin_spaced(200, 2.0, 0.01), 100);
    axis = (axis * 100.0).apply(std::round) / 100.0;
    std::map<std::string, std::valarray<double>> intersections;
    for (size_t i=0; i<model_.names().size(); ++i) {
        const std::string outfile = "uniaxis-" + model_.names()[i] + ".tsv.gz";
        std::cerr << outfile << std::endl;
        std::stringstream sst;
        run_impl(sst, wtl::itertools::uniaxis(axis, mle_params_, i));
        wtl::ozfstream(outfile) << sst.str();
        const auto logliks = read_loglik(sst, axis.size());
        const double threshold = logliks.max() - diff95;
        const std::valarray<double> range = axis[logliks > threshold];
        auto bound_params = mle_params_;
        bound_params[i] = std::max(range.min() - 0.01, 0.01);
        intersections.emplace(model_.names()[i] + "_L", bound_params);
        bound_params[i] = std::min(range.max() + 0.01, 2.00);
        intersections.emplace(model_.names()[i] + "_U", bound_params);
    }
    for (const auto& p: intersections) {
        const std::string outfile = "limit-" + p.first + ".tsv.gz";
        std::cerr << outfile << ": " << p.second << std::endl;
        const auto axes = make_vicinity(p.second, 5, 0.02);
        wtl::ozfstream fout(outfile);
        //TODO: if exists
        run_impl(fout, wtl::itertools::product(axes));
    }
}

void GridSearch::run_impl(std::ostream& ost, wtl::itertools::Generator<std::valarray<double>>&& gen) {HERE;
    std::cerr << skip_ << " to " << gen.max_count() << std::endl;
    if (skip_ == 0) {
        write_header(ost, gen.max_count());
    }

    wtl::Semaphore semaphore(concurrency_);
    auto task = [this,&semaphore](const std::valarray<double> th_path) {
        std::lock_guard<wtl::Semaphore> scope_unlock(semaphore, std::adopt_lock);
        // argument and model are copied for each thread
        auto buffer = wtl::make_oss();
        auto model_copy = this->model_;
        buffer << model_copy.calc_loglik(th_path) << "\t"
               << wtl::str_join(th_path, "\t") << "\n";
        return buffer.str();
    };

    std::deque<std::future<std::string>> futures;
    const auto min_interval = std::chrono::seconds(1);
    auto next_time = std::chrono::system_clock::now() + min_interval;
    size_t stars = 0;
    for (const auto& th_path: gen(skip_)) {
        semaphore.lock();
        futures.push_back(std::async(std::launch::async, task, th_path));
        auto now = std::chrono::system_clock::now();
        if (now > next_time) {
            next_time = now + min_interval;
            while (!futures.empty() && wtl::is_ready(futures.front())) {
                ost << futures.front().get();
                futures.pop_front();
            }
            for (size_t n= 0.2 * gen.percent(); stars<n; ++stars) {
                std::cerr << "*";
            }
        }
        if (wtl::SIGINT_RAISED()) {throw wtl::KeyboardInterrupt();}
    }
    std::cerr << "\n";
    for (auto& ftr: futures) {
        ost << ftr.get();
    }
}

std::string GridSearch::init_meta() {HERE;
    if (stage_ >= STEPS.size()) return "";
    auto oss = wtl::make_oss(2, std::ios::fixed);
    oss << "grid-" << STEPS.at(stage_) << ".tsv.gz";
    std::string outfile = oss.str();
    try {
        wtl::izfstream ist(outfile);
        std::cerr << "Reading: " << outfile << std::endl;
        read_results(ist);
        if (skip_ == 0) {
            ++stage_;
            outfile = init_meta();
        }
    } catch (std::ios::failure& e) {
        if (errno != 2) throw;
    }
    return outfile;
}

void GridSearch::read_results(std::istream& ist) {HERE;
    size_t max_count;
    double step;
    std::tie(std::ignore, std::ignore, max_count, step) = read_metadata(ist);
    stage_ = guess_stage(step);
    std::vector<std::string> colnames;
    std::valarray<double> mle_params;
    std::tie(skip_, colnames, mle_params) = read_body(ist);
    if (skip_ == max_count) {  // is complete file
        skip_ = 0;
        mle_params_.swap(mle_params);
    }
}

void GridSearch::read_results(const std::string& infile) {
    wtl::izfstream ist(infile);
    read_results(ist);
}

void GridSearch::write_header(std::ostream& ost, const size_t max_count) const {
    ost << "##genotype_file=" << model_.filename() << "\n";
    ost << "##max_sites=" << model_.max_sites() << "\n";
    ost << "##max_count=" << max_count << "\n";
    ost << "##step=" << STEPS.at(stage_) << "\n";
    ost << "loglik\t" << wtl::join(model_.names(), "\t") << "\n";
}

void GridSearch::test() {HERE;
    std::stringstream sst;
    sst <<
R"({
  "pathway": ["A", "B"],
  "annotation": ["0011", "1100"],
  "sample": ["0011", "0101", "1001", "0110", "1010", "1100"]
})";
    GridSearch searcher(sst, 4, {0, 1});
    searcher.run_cout();
}

} // namespace likeligrid
<|endoftext|>