# 화면 녹화 #3 - 논리계층 구현

아래는 Job Flow라는 제가 오래 전부터 사용하던 설계 형식입니다.

  • 화면을 녹화하는 DesktopRecorder는 네 개의 클래스로 나뉘어서 구현되었습니다.
    • VideoCreater
      • 화면의 스크린샷과 오디오 패킷을 압축하고 mp4로 저장한다.
    • Scheduler
      • 별도의 스레드로 프로세스를 진행하여 UI와 비동기로 처리된다.
      • 외부의 요청과 내부의 반복처리가 단일 스레드로 이루어져 임계영역 없이 동기화되는 이점이 있다.
    • DesktopCapture
      • 지정된 영역의 화면을 캡쳐한다.
    • AudioCapture
      • 기본으로 설정된 오디오 입력 장치에서 오디오를 캡쳐한다.
  • DesktopRecorder의 네 개의 클래스는 자신의 할 일에만 집중하며 서로 필요한 최소한의 메시지를 주고 받도록 설계되었습니다.

# 다이어그램을 코드로 표현하기

# DesktopRecorder

class DesktopRecorder {
public:
	DesktopRecorder()
	{
		scheduler_.setOnRepeat([&]() {
			do_encode();
		});

		scheduler_.setOnTask([&](int task, const string text, const void* data, int, int size) {
			switch (task) {
				case TASK_START: do_start(); break;
				case TASK_STOP:  do_stop(); break;
			}
		});
	}

	void terminate()
	{
		scheduler_.terminateAndWait();
		do_stop();
	}

	void start()
	{
		scheduler_.add(TASK_START);
	}

	void stop()
	{
		scheduler_.add(TASK_STOP);
	}

private:
	VideoCreater* videoCreater_ = nullptr;
	Scheduler scheduler_;
	DesktopCapture desktopCapture_;
	AudioCapture audioCapture_;

	void do_start(Task* task)
	{
		if (videoCreater_ != nullptr) {
			throw "이미 녹화 중입니다.";
		}

		videoCreater_ = new VideoCreater();
		desktopCapture_.start();
		audioCapture_.start();
		scheduler_.start();
	}

	void do_stop()
	{
		scheduler_.stop();
		audioCapture_.stop();

		if (videoCreater_ != nullptr) {
			delete videoCreater_;
			videoCreater_ = nullptr;
		}
	}

	void do_encode()
	{
		if (videoCreater_ == nullptr) return false;

		if (videoCreater_->isVideoTurn()) {
			void* bitmap = desktopCapture_.getBitmap();
			videoCreater_->writeBitmap(bitmap);
		} else {
			void* audio = audioCapture_.getAudioData();
			int audio_size = audioCapture_.getAduioDataSize();
			videoCreater_->writeAudioPacket(audio, audio_size);
			free(audio);
		}
	}
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# DesktopCapture

다이어그램에서 DesktopCapture 부분을 보시면 화살표로 들어오는 곳이 3개 입니다. 이것은 외부에서 의존하는 기능이기 때문에 public 인터페이스에 해당하게 됩니다. 따라서 DesktopCapture는 3개의 메소드 이외에 나머지들은 캡슐화하여 내부에서 처리해도 전체 시스템은 문제 없다는 의미입니다.

TIP

  • 설계대로 항상 진행되지는 않기에 드러나지 않은 의존성이 추가로 발견될 수도 있습니다.
  • 다이어그램을 통해서 시스템을 구성하는 각 객체들이 최소한의 의존성을 갖을 수 있도록 고민하는 것이 중요합니다.
  • 시스템을 구성하는 객체들이 단 하나의 책임을 가지고 최대한 독립적인 수행을 하도록 하는 것도 중요합니다.

다이어그램을 코드로 나타내보면 다음과 같습니다.

class DesktopCapture {
public:
	void start()
	{
	}

	void stop()
	{
	}

	void* getBitmap()
	{
		return nullptr;
	}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# AudioCapture

class AudioCapture {
public:
	void start()
	{
	}

	void stop()
	{
	}

	void* getAudioData()
	{
		return nullptr;
	}
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16