보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

반응형

1개 이상의 동영상 파일을 로드하고 재생해 보자.

 

아래 코드는 각각의 영상을 독립적으로 처리하기 때문에 시간 표시도 개별적으로 되고 앞뒤 이동도 제한된다.

 

#include <iostream>
#include <vector>
#include <filesystem>
#include <opencv2/opencv.hpp>

int main()
{
	std::vector<std::string> videoFiles;
	std::string folderPath = "./";

	for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() && entry.path().extension() == ".mkv")
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout << "Found " << videoFiles.size() << " .mkv files in the folder:" << std::endl;
	for (const std::string& file : videoFiles)
	{
		std::cout << file << std::endl;
	}

	std::vector<cv::VideoCapture> videoCaptures;
	for (const std::string& file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr << "Error opening video file: " << file << std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout << "Successfully opened " << videoCaptures.size() << " video files." << std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	int key = 0;

	for (size_t i = 0; i < videoCaptures.size(); ++i)
	{
		std::cout << "Playing video: " << videoFiles[i] << std::endl;
		videoCaptures[i] >> frame;

		while (!frame.empty())
		{
			timestamp = videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(".") + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow("Video", frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
				// 5초 앞으로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp + 5.0 * 1000.0);
			else if (key == 'b')
				 // 5초 뒤로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp - 5.0 * 1000.0);

			videoCaptures[i] >> frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture& cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}

 

f: 5초 앞으로

b: 5초 뒤로

 

 

아래 코드는 각각의 영상을 하나의 영상인 것처럼 시간을 표시하고 5초 앞뒤로 이동할 때도 각 영상을 자연스럽게 이동한다.

 

#include <iostream>
#include <vector>
#include <filesystem>
#include <opencv2/opencv.hpp>

int main()
{
	std::vector<std::string> videoFiles;
	std::string folderPath = "./";

	for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() && entry.path().extension() == ".mkv")
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout << "Found " << videoFiles.size() << " .mkv files in the folder:" << std::endl;
	for (const std::string& file : videoFiles)
	{
		std::cout << file << std::endl;
	}

	std::vector<cv::VideoCapture> videoCaptures;
	for (const std::string& file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr << "Error opening video file: " << file << std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout << "Successfully opened " << videoCaptures.size() << " video files." << std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	double duration = 0.0;
	double move = 5000.0; // 5 seconds in milliseconds
	int key = 0;

	for (size_t i = 0; i < videoCaptures.size(); ++i)
	{
		std::cout << "Playing video: " << videoFiles[i] << std::endl;
		videoCaptures[i] >> frame;

		duration = 0.0;
		for (int j = 0; j < i; j++) {
			duration += (videoCaptures[j].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[j].get(cv::CAP_PROP_FPS));
		}
		duration *= 1000.0; // Convert to milliseconds

		while (!frame.empty())
		{
			timestamp = duration + videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(".") + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow("Video", frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
			{
				// 현재 영상에서 5초 앞으로 가는 것이 가능한 경우, 5초 앞으로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move < videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move);
				// 현재 영상에서 5초 앞으로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 다음 영상으로 이동해서 남은 시간만큼 앞으로 이동
				else if (i < videoCaptures.size() - 1)
				{
					videoCaptures[i + 1].set(cv::CAP_PROP_POS_MSEC, move - (videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0 - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 나중에 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화하는건 불필요.
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					break;
				}
				else
				{
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0);
				}

			}
			else if (key == 'b')
			{
				// 현재 영상에서 5초 뒤로 가는 것이 가능한 경우, 5초 뒤로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) > move)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) - move);
				// 현재 영상에서 5초 뒤로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 이전 영상으로 이동해서 남은 시간만큼 뒤로 이동
				else if (i > 0)
				{
					videoCaptures[i - 1].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i - 1].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i - 1].get(cv::CAP_PROP_FPS) * 1000.0 - (move - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 곧(5초 이내) 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화.
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					i -= 2; // 다음 루프에서 i++ 되므로 -2

					break;
				}
				else
				{
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);
				}
			}

			videoCaptures[i] >> frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture& cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}

 

반응형
Posted by J-sean
:
반응형

FFmpeg로 IP Camera에서 스트리밍 되는 영상을 저장해 보자.

 

ffmpeg -rtsp_transport tcp -i rtsp://admin:123456@192.168.0.56:554/stream1 -b:a 4k -t 10 -y output.mp4

 

-rtsp_transport = RTSP 전송 프로토콜 설정.

FFmpeg Protocols Documentation

 

-b:a = audio bitrate. 너무 높으면 아래와 같은 경고가 출력된다. audio sampling frequency(-ar)를 높이거나 bitrate(-b:a or -ab)를 낮춰야 한다.

[aac @ 000001b64b5b7c00] Too many bits 8832.000000 > 6144 per frame requested, clamping to max

 

-t = duration

 

※ 참고

HikVision Camera RTSP Stream

HikVision Camera RTSP with Authentication
rtsp://<username>:<password>@<IP address of device>:<RTSP port>/Streaming/channels/<channel number><stream number>
NOTE: <stream number> represents main stream (01), or the sub stream (02)
Example:
rtsp://192.168.0.100:554/Streaming/channels/101 – get the main stream of the 1st channel
rtsp://192.168.0.100:554/Streaming/channels/102 – get the sub stream of the 1st channel

 

https://stackoverflow.com/questions/56423581/save-rtsp-stream-continuously-into-multi-mp4-files-with-specific-length-10-minu

https://butteryoon.github.io/dev/2019/04/10/using_ffmpeg.html

 

 

FFmpeg Formats Documentation

4.68 segment , stream-segment, ssegment 참고

 

반응형
Posted by J-sean
:
반응형

C#에서 Windows Media Player를 이용해 오디오 파일을 플레이 해 보자.

 

아래 링크를 참고해 Windows Media Player COM Component를 가져오고 빌드시 메세지가 발생하지 않도록 하자.

(Windows Media Player 컴포넌트를 폼에 배치할 필요는 없다)

2021.11.21 - [C#] - C# Windows Media Player Audio/Video Play #1

 

폼과 버튼을 적당히 배치한다.

 

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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using WMPLib;
 
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private string time;
 
        WindowsMediaPlayer wmp;
 
        public Form1()
        {
            InitializeComponent();
            
            wmp = new WindowsMediaPlayer();
 
            Timer T = new Timer();
            T.Interval = 1000;
            T.Tick += new EventHandler(Form1_Timer);
            T.Start();
        }
 
        private void Form1_Timer(object sender, System.EventArgs e)
        {
            if (wmp.playState == WMPPlayState.wmppsPlaying)
            {
                //time = wmp.controls.currentPositionString + " / " + wmp.controls.currentItem.durationString;
                //time = wmp.controls.currentPosition.ToString() + " / " + wmp.controls.currentItem.duration.ToString();
                time = TimeSpan.FromSeconds((int)wmp.controls.currentPosition).ToString() + " / "
                    + TimeSpan.FromSeconds((int)wmp.controls.currentItem.duration);
 
                Graphics G = CreateGraphics();
                //G.DrawString(time, Font, System.Drawing.Brushes.Black, 20, 70); // 글자가 겹친다.
                TextRenderer.DrawText(G, time, Font, new Point(2070), ForeColor, BackColor);
                G.Dispose();
            }
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    wmp.URL = dlg.FileName;
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message);
            }
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            if (wmp.playState == WMPPlayState.wmppsPlaying)
            {
                wmp.controls.stop();
            }
        }
    }
}
 

 

소스를 입력한다.

 

오디오/비디오 파일을 플레이 할 수 있다. (비디오 파일은 오디오만 출력된다)

실행 파일과 함께 Interop.WMPLib.dll 파일이 존재해야 한다.

 

Using the Windows Media Player Control in a .NET Framework Solution

 

반응형
Posted by J-sean
:
반응형

C#에서 Windows Media Player를 이용해 오디오/비디오 파일을 플레이해 보자.

 

Toolbox의 원하는 곳에서 우클릭 - Choose Items...

 

COM Components - Windows Media Player 선택.

 

폼, 버튼, Windows Media Player를 적당히 배치한다.

 

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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using WMPLib;
 
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    axWindowsMediaPlayer1.URL = dlg.FileName;
                    axWindowsMediaPlayer1.Ctlcontrols.stop();   // 자동 재생 방지.
                    // The managed-code wrapper for the Windows Media Player control exposes
                    // the Controls object as Ctlcontrols to avoid collision with the Controls
                    // property inherited from System.Windows.Forms.Control.
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message);
            }
        }
    }
}
 

 

소스를 입력한다.

 

 

이대로 빌드해도 문제는 없지만 위와 같은 메세지가 나온다.

 

References - WMPLib - Properties - Embed Interop Types - False 선택

다시 빌드하면 아무 메세지도 나오지 않는다.

 

MP3등 오디오 파일 재생.

 

AVI, MP4등 비디오 파일 재생.

 

 

이번엔 실행하면 아무것도 보이지 않다가 오디오/비디오 파일을 선택하면 UI없이 플레이 하는 프로그램을 만들어 보자.

 

폼, 버튼, Windows Media Player를 배치한다.

 

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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using WMPLib;
 
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            axWindowsMediaPlayer1.Visible = false;
            axWindowsMediaPlayer1.uiMode = "none";
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    axWindowsMediaPlayer1.URL = dlg.FileName;
                    axWindowsMediaPlayer1.Visible = true;
                }
            } catch (Exception exc)
            {
                MessageBox.Show(exc.Message);
            }
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            if (axWindowsMediaPlayer1.playState == WMPPlayState.wmppsPlaying)
            {
                axWindowsMediaPlayer1.Ctlcontrols.stop();
                // The managed-code wrapper for the Windows Media Player control exposes
                // the Controls object as Ctlcontrols to avoid collision with the Controls
                // property inherited from System.Windows.Forms.Control.
                
                axWindowsMediaPlayer1.Visible = false;
            }            
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

WMP가 보이지 않는다. Play 버튼을 클릭하고 비디오 파일을 선택한다.

 

비디오가 UI없이 플레이된다.

 

실행 파일과 함께 Interop.WMPLib.dll 파일이 존재해야 한다.

 

Using the Windows Media Player Control in a .NET Framework Solution

2026.03.28 - [FFmpeg, GStreamer, VLC] - [VLC] LibVLCSharp 영상 재생 1

2021.11.21 - [C#] - C# Windows Media Player Audio/Video Play #2

 

반응형
Posted by J-sean
:
반응형

레트로파이에 한글 폰트, 게임 마키, 이미지, 플레이 영상, 설명등을 넣어 보자.

 

2020/02/13 - [Raspberry Pi & Arduino] - Raspberry Pi Desktop(Debian), Ubuntu에서 RetroPie 설치하기

 

특별한 설정을 하지 않았다면 레트로파이(아케이드)에 게임 이름 정도만 표시된다. 등록된 게임을 잘 아는 사람은 문제 없겠지만 모르는 사람은 무슨 게임인지 알 수가 없다. 처음 하는 사람도 어떤 게임인지 알 수 있도록 이미지, 플레이 영상, 설명등을 넣어 보자.

 

Cabin-Bold.ttf 가 기본(영문) 폰트다. 한글이 표시 될 수 있도록 원하는 한글 폰트(KATURI.TTF)를 /etc/emulationstation/themes/carbon/art/ 에 복사한다.

 

/etc/emulationstation/themes/carbon/ 에 있는 'carbon.xml', 'theme.xml' 파일에 설정된 폰트를 변경한다.

 

두 파일을 열고 'Cabin-Bold.ttf'가 나오는 부분을 모두 한글 폰트 이름(KATURI.TTF)으로 바꾼다. (원래 있던 'Cabin-Bold.ttf' 파일을 지우고 한글 폰트 이름을 'Cabin-Bold.ttf'로 바꾸면 두 파일의 내용을 바꾸지 않아도 된다)

 

 

RetroPie를 실행해 보면 바뀐 폰트가 적용되어 있다.

 

/opt/retropie/configs/all/emulationstation/gamelists/arcade/gamelist.xml 에 게임 이름, 이미지, 플레이 영상, 설명등을 등록 할 수 있다.

 

gamelist.xml
5.73MB

 

아래와 같은 형식으로 작성 한다.

 

<?xml version="1.0"?>

<gameList>

<game>

<path>게임 파일 경로</path>

<name>게임 이름</name>

<image>게임 이미지 파일 경로</image>

<video>게임 플레이 영상 파일 경로</video>

<marquee>게임 마키 파일 경로</marquee>

<developer>게임 제작사</developer>

<publisher>게임 판매사</publisher>

<genre>게임 장르</genre>

<players>게임 플레이어 수</players>

<releasedate>게임 발매일</releasedate>

<desc>게임 설명</desc>

</game>

<game>

...

</game>

...

</gameList>

 

'gamelist.xml'에 등록한 파일 경로 및 이름대로 marquee, snap, video 디렉토리를 /home/pi/RetroPie/roms/arcade/ 에 생성하고 각 디렉토리에 맞는 파일을 복사한다.

 

 

마키(marquee)

 

이미지(image)/스냅(snap)

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 

 

예를 들어 marquee 디렉토리의 내용은 위와 같다.

 

RetroPie를 실행해 보자. ARCADE - 메탈 슬러그의 마키, 플레이 영상, 게임 설명등이 디스플레이 된다.

 

스트리트 파이터 2의 내용도 잘 디스플레이 된다.

 

반응형
Posted by J-sean
:
반응형

리눅스(우분투) 서버 같은 콘솔 환경에서도 비디오 플레이어를 사용할 수 있다.


mpv를 설치한다.


--vo=drm 옵션, 영상 파일 이름과 함께 실행한다.


자막 파일이 있다면 자막도 표시된다. 주요 단축키 목록은 아래와 같다.


  • LEFT and RIGHT: Seek backward/forward 5 seconds. Shift+arrow does a 1 second exact seek

  • UP and DOWN: Seek forward/backward 1 minute. Shift+arrow does a 5 second exact seek

  • p / SPACE: Pause (pressing again unpauses).

  • q: Stop playing and quit.

  • Q: Like q, but store the current playback position. Playing the same file later will resume at the old playback position if possible.

  • 9 and 0: Decrease/increase volume.

  • m: Mute sound.

  • o (also P): Show progression bar, elapsed time and total duration on the OSD.

  • O: Toggle OSD states between normal and playback time/duration.

  • v: Toggle subtitle visibility.

  • z and Z: Adjust subtitle delay by +/- 0.1 seconds. The x key does the same as Z currently, but use is discouraged.

  • r and R: Move subtitles up/down. The t key does the same as R currently, but use is discouraged.

  • s: Take a screenshot.

  • S: Take a screenshot, without subtitles. (Whether this works depends on VO driver support.)

  • Shift+PGUP and Shift+PGDWN: Seek backward or forward by 10 minutes. (This used to be mapped to PGUP/PGDWN without Shift.)

  • 1 and 2: Adjust contrast.

  • 3 and 4: Adjust brightness.

  • 5 and 6: Adjust gamma.

  • 7 and 8: Adjust saturation.


반응형
Posted by J-sean
:
반응형

RetroPie installed on a PC can be too fast to play retro games. You can adjust(slow down) its speed by changing video_frame_delay value.

 

라즈베리 파이가 아닌 PC에 레트로파이를 설치한 경우 성능 차이로 인해 레트로 게임들이 너무 빨라 진행 할 수 가 없다. video frame delay 설정을 변경해 속도를 조절해 보자.

 

RetroPie Configuration을 선택 한다.

 

Configuration Editor를 선택 한다.

 

Advanced Configuration을 선택 한다.

 

Configure Libretro options를 선택 한다.

 

 

 

all/retroarch.cfg를 선택 한다.

 

video_frame_delay를 선택 한다.

 

Edit를 선택 한다.

 

15 이하의 적당한 숫자를 입력 하고 OK를 선택 한다. 다시 게임을 진행해 보면 속도가 적당히 변경되어 있다.

 

 

 

Configure Libretro options가 아닌 Manually edit RetroArch configurations에서도 변경 가능 하다.

 

all/retroarch.cfg를 선택 한다.

 

원래는 video_frame_delay가 주석처리(#)되어 있는데 위와 같이 주석을 삭제하고 적당한 값으로 바꾸면 된다.

 

특정 게임에서 잠시 video frame delay 값을 변경하고 싶다면 Hotkey(Select) + X 를 누른다.

 

 

 

Quick Menu가 표시 된다. 라즈베리파이(라즈비안)가 한국어로 설정되어 있다면 Quick Menu의 글자가 제대로 표시되지 않는다. 언어 설정을 영어로 바꾸면 제대로 표시된다.

 

Latency를 선택 한다.

 

Frame Delay를 선택 한다.

 

원하는 값을 선택 하고 게임으로 돌아가면 변경된 값이 적용 된다. 이 방법은 일회성이며 게임을 다시 시작하면 원래 값으로 돌아 간다. 이 상태를 저장하고 싶다면 Quick Menu에서 Save State를 선택 한다.

 

반응형
Posted by J-sean
: