티스토리 뷰

카메라 스트리밍 모듈을 만들 일이 있어서 조사하던 도중 ffmpeg를 이용해 영상을 압축하고 스트리밍 할 수 있다는 것을 알아 만들어보고 정리한다.


*RTP


ffmpeg는 멀티미디어 분야에서 많이 쓰는 라이브러리다. 원격지로 영상 전송을 위해 RTP(Realtime Transport Protocol)을 사용하였는데 간단히 알아보자


1. 개요

RTP는 멀티미디어 실시간 스트리밍을 위해 만들어진 응용층 프로토콜이다. 또한 IP 네트워크에서 영상/오디오 전송 표준으로 여겨진다. 

RTP는 원래 전송층으로 TCP를 기준으로 하도록 만들어졌으나, TCP는 데이터 전송 딜레이 같은 시간에 대한 요소보다 신뢰성을 더 중시하기 때문에 스트리밍에 적합하지 않아 UDP를 많이 사용한다. 

멀티미디어 스트리밍을 하려는 어플리케이션은 정해진 시간(FPS)마다 데이터를 전송해야하고, 데이터 손실에 대한 처리, 데이터 순서에 대한 처리가 있어야만 스트리밍을 할 수 있다. RTP는 이러한 요구들을 다 처리해준다.


2. RTP의 요소들

RTP는 실시간 전송을 위해 다음과 같은 요소들을 포함한다.

1) 타임스탬프(timestamp)

동기화를 위해 타임스탬프를 포함한다.

2) 순서 (sequence number)

TCP는 알아서 해주겠지만 UDP를 사용하는 경우 패킷 손실 처리나, 데이터 순서를 재배열 해야할 때 데이터의 순서가 필요하다.

3) 데이터 인코딩 정보

인코딩된 데이터의 경우 디코딩이 필요하기 때문에 이러한 정보를 포함해야한다.


이 외에는 아래 헤더에 자세히 나와있다.


3. RTP 헤더

RTP 패킷의 헤더는 아래 그림 1과 같이 구성된다.

[그림 1] RTP packet header (출처: wikipedia)


RTP 헤더는 최소 12바이트를 요구한다. 이 최소 헤더 다음에는 확장 헤더가 필요할 경우 확장 헤더가 붙는다.

헤더의 필드들은 아래와 같다.

1) Version / 2비트: 프로토콜의 버전을 나타낸다. 현재 버전은 2이다.

2) P(Padding) / 1비트: RTP 패킷 끝에 padding byte가 있을 경우 사용된다. padding byte는 특정한 크기의 블록으로 채워지는데, 암호화 알고리즘등에 사용된다. padding byte의 마지막 byte는 padding byte들의 갯수를 나타낸다.

3) X(Extension / 1비트: 최소 헤더와 데이터 사이에 확장 헤더가 존재함을 나타낸다. 

4) CC(CSRC count) / 4비트: 그림 1의 CSRC identifiers의 갯수를 나타낸다.

5) M(Maker) / 1비트: profile에 의해 값이 매겨지고 응용층에서 사용된다. 이 비트가 설정되면 데이터가 어플리케이션에 뭔가 특별한 관련성이 있다는 것을 의미한다.

6) PT(Payload type) / 7비트: payload의 타입을 나타낸다. RTP가 전송하고 있는 데이터가 MPEG4를 사용하는지 H264를 사용하는지 등을 나타낸다. 수신측 어플리케이션에서 해석을 하는데, 수신측 프로필로 알 수가 없다면 수신측 어플리케이션은 수신하지 않는다.

7) Sequence number / 16비트: 송신측이 RTP 데이터 패킷을 하나 보낼때마다 하나씩 증가하는 숫자인데, 수신측에서는 패킷 손실 감지와 패킷 순서 복구를 위해 사용한다.

RTP는 패킷 손실에 대해 아무런 처리를 하지 않는다. 그래서 손실에 대한 처리를 하고싶으면 어플리케이션 레벨에서 처리를 해야한다.

8) Timestamp / 32비트: 수신측이 데이터를 받았을 때 그 데이터를 적절한 간격으로 재생할 수 있도록 하기위해 사용된다. 스트림이 여러개 있으면 서로 독립적인 값을 갖는다. 사용하고 있는 코덱에 따라서 샘플링 레이트에 맞게 증가된다.

9) SSRC / 32비트: 스트림의 소스를 식별하는 동기화 소스(Synchronization source)이다. RTP 내에서 동기화 소스는 고유성을 지니고 있어야한다.

10) CSRC / 32비트: 다수의 스트림이 존재할 때, 모든 스트림의 SSRC를 나타낸다. (예를 들어 다수의 오디오 스트림이 하나로 합쳐질 때)


4. 경험으로 알게된 RTP

처음에 RTSP(Real Time Streaming Protocol)을 이용해 개발을 했는데, RTP와의 차이점은 RTSP는 요청 메시지가 존재한다는 것이다. RTSP는 클라이언트에서 서버로 데이터를 전송한다. 또한 RTSP는 데이터를 전송하는 포트외에도 요청 메시지를 위한 포트(554)를 사용한다. (스트림을 위한 포트, 요청을 위한 포트)

나는 PLAY, PAUSE 같은 요청 메시지가 필요없고, 단순히 실시간 영상 전송만이 필요하기때문에 RTP를 사용했다.



*FFMPEG for Windows C++


https://ffmpeg.zeranoe.com/builds/


위 링크는 ffmpeg windows C++ 버전을 위해 빌드된 파일을 제공해주는 사이트이다.

ffmpeg는 리눅스 기반으로 개발된 라이브러리라 윈도우즈에서 빌드하기가 쉽지가 않다. 따라서 보통 위 링크에서 빌드한 것을 이용해 ffmpeg 라이브러리를 사용한다. 

개발을 위해 홈페이지에서 dev, shared 두가지를 다운로드 하자. dev는 header, lib를 위해, shared는 dll을 위해 다운로드 받는 것이다. 

나는 개발 환경 설정을 위해 아래 그림 2와 같이 C드라이브에 dev에 shared의 bin폴더를 합친 내용을 ffmpeg라는 폴더 이름으로 저장했다.

[그림 2] include, lib, bin 폴더를 합친 ffmpeg 폴더


그 다음으로는 여타 라이브러리를 사용할 때와 마찬가지로 bin폴더(DLL)를 path환경변수에 설정한다. (그림 3)

[그림 3] Path 환경변수 설정


그럼 이제 DLL을 위한 세팅은 끝났고 프로젝트 세팅에서 header, lib를 위해 include, lib 폴더를 링크해야한다.

Visual Studio 2013을 개발환경으로 사용하고 있다. (그림 4, 그림 5)


[그림 4] include 폴더 세팅


[그림 5] lib 폴더 세팅



이제 ffmpeg 라이브러리를 Windows C++ 환경에서 사용할 준비가 되었다.

RTP를 이용해서 스트리밍을 하기 위해 ffmpeg API를 어떻게 사용하는지 알아보자.



*Streaming from Cam using FFMPEG & OpenCV


먼저 아래 함수를 호출해 코덱을 등록시키고, 네트워크를 초기화 해야한다.


av_register_all();
avformat_network_init();


그다음 output context를 할당해야하는데, format name은 "rtp", filename은 "rtp://ip:port/나머지"로 등록해야한다.


std::string tempUrl("");
tempUrl.append("rtp://");
tempUrl.append(ip + ":");
tempUrl.append(std::to_string(port));
//tempUrl.append("/live.sdp");

// 맨 마지막 파라미터의 rtp url로 내보내는 context를 할당한다.
AVFormatContext* oc;
avformat_alloc_output_context2(&oc, NULL, "rtp", tempUrl.c_str());
if (!oc)
{
	// ERROR
	return false;
}


output context의 format에 codec을 설정하고 stream을 생성한다.

AVOutputFormat* fmt = oc->oformat;
if (!fmt)
{
	// ERROR
	return false;
}

// set codec (여기선 MPEG1 사용)
fmt->video_codec = AV_CODEC_ID_MPEG1VIDEO;

AVStream* video_st;
AVCodec* video_codec;
if (fmt->video_codec != AV_CODEC_ID_NONE)
	video_st = add_stream(oc, &video_codec, fmt->video_codec, img_width, img_height);

#define STREAM_FPS 30
#define STREAM_PIX_FMT	AV_PIX_FMT_YUV420P
AVStream* add_stream(AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id,
							int img_width, int img_height)
{
	AVCodecContext *c;
	AVStream *st;

	/* find the encoder */
	*codec = avcodec_find_encoder(codec_id);
	if (!(*codec)) {
		fprintf(stderr, "Could not find encoder for '%s'\n",
			avcodec_get_name(codec_id));
		exit(1);
	}

	// output context에 대한 스트림을 생성한다.
	st = avformat_new_stream(oc, *codec);
	if (!st) {
		fprintf(stderr, "Could not allocate stream\n");
		exit(1);
	}
	st->id = oc->nb_streams - 1;
	c = st->codec;

	// 오디오 전송을 안할거면 AVMEDIA_TYPE_VIDEO만 신경쓰면 된다.
	switch ((*codec)->type) {
	case AVMEDIA_TYPE_AUDIO:
		c->sample_fmt = (*codec)->sample_fmts ?
			(*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
		c->bit_rate = 64000;
		c->sample_rate = 44100;
		c->channels = 2;
		break;

	// st->codec에 비디오 코덱, 전송률, 이미지 해상도, FPS등을 설정한다.
	case AVMEDIA_TYPE_VIDEO:
		c->codec_id = codec_id;
		c->bit_rate = 3500000;
		/* Resolution must be a multiple of two. */
		c->width = img_width;
		c->height = img_height;
		/* timebase: This is the fundamental unit of time (in seconds) in terms
		* of which frame timestamps are represented. For fixed-fps content,
		* timebase should be 1/framerate and timestamp increments should be
		* identical to 1. */
		c->time_base.den = STREAM_FPS;
		c->time_base.num = 1;
		c->gop_size = 12; /* emit one intra frame every twelve frames at most */
		c->pix_fmt = STREAM_PIX_FMT;
		if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
			/* just for testing, we also add B frames */
			c->max_b_frames = 2;
		}
		if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
			/* Needed to avoid using macroblocks in which some coeffs overflow.
			* This does not happen with normal video, it just happens here as
			* the motion of the chroma plane does not match the luma plane. */
			c->mb_decision = 2;
		}
		break;

	default:
		break;
	}

	/* Some formats want stream headers to be separate. */
	if (oc->oformat->flags & AVFMT_GLOBALHEADER)
		c->flags |= CODEC_FLAG_GLOBAL_HEADER;

	return st;
}


위에 과정을 거쳐서 rtp output context, video codec, stream 이렇게 세가지가 생성이 된다.

이제 할당한 스트림을 열고 frame을 카메라로 부터 얻어 스트리밍을 해야한다.

// 비디오 코덱을 열고, 필요한 버퍼(frame)을 할당하는 함수이다.
if (video_st)
	open_video(oc, video_codec, video_st);

// 전송률, 코덱 정보등 output format에 대한 자세한 정보를 보낸다.
av_dump_format(oc, 0, tempUrl.c_str(), 1);

char errorBuff[80];
// rtp가 제대로 열렸는지 체크
if (!(fmt->flags & AVFMT_NOFILE)) {
	ret = avio_open(&oc->pb, tempUrl.c_str(), AVIO_FLAG_WRITE);
	if (ret < 0) {
		// ERROR
		fprintf(stderr, "Could not open outfile '%s': %s", tempUrl.c_str(), av_make_error_string(errorBuff, 80, ret));
		return false;
	}
}

// 헤더 쓰기
ret = avformat_write_header(oc, NULL);
if (ret < 0) {
	// ERROR
	fprintf(stderr, "Error occurred when writing header: %s", av_make_error_string(errorBuff, 80, ret));
	return false;
}

// 전역 변수, 클래스 멤버변수 등으로 재사용할 버퍼를 할당해놓자 
AVFrame* frame;
// 아래 두개 변수는 각각 OpenCV 이미지(BGR24), ffmpeg 이미지(이 예제에선 YUV420)를 위한 변수이다.
AVPicture src_picture, dst_picture;
void open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
{
	int ret;
	AVCodecContext *c = st->codec;

	/* open the codec */
	ret = avcodec_open2(c, codec, NULL);
	if (ret < 0) {
		// ERROR
		fprintf(stderr, "Could not open video codec: ");
		return;
	}

	/* allocate and init a re-usable frame */
	// av_frame_alloc 함수는 frame의 데이터 버퍼를 할당하지 않는다.
	// 데이터를 제외한 나머지 부분만을 할당한다.
	frame = av_frame_alloc();
	if (!frame) {
		// ERROR
		fprintf(stderr, "Could not allocate video frame\n");
		return;
	}
	frame->format = c->pix_fmt;
	frame->width = c->width;
	frame->height = c->height;

	/* Allocate the encoded raw picture. */
	ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height);
	if (ret < 0) {
		fprintf(stderr, "Could not allocate picture: ");
		exit(1);
	}
	ret = avpicture_alloc(&src_picture, AV_PIX_FMT_BGR24, c->width, c->height);
	if (ret < 0) {
		fprintf(stderr, "Could not allocate temporary picture:");
		exit(1);
	}

	/* copy data and linesize picture pointers to frame */
	// AVFrame이 AVPicture의 확장 구조체라고 할 수 있다.
	// AVFrame과 AVPicture 구조체의 정의를 보면 알 수 있겠지만, AVFrame의 처음 두개 멤버(이미지 데이터)가 AVPicture와 같다.
	// 위 주석에서 설명됐듯이 av_frame_alloc함수는 데이터 버퍼를 할당하지 않기 때문에, dst_picture에 할당된 데이터 버퍼를 frame 변수가 쓰게 하는 것이다.
	*((AVPicture *)(frame)) = dst_picture;
}


버퍼를 할당하고 스트림을 열었으면 RTP로 송신할 준비가 되었다.

이제 아래와 같은 방법으로 frame을 보내면 된다.

int video_is_eof = 0;
// cv_img는 전송할 이미지 데이터이다.
bool StreamImage(cv::Mat cv_img, bool is_end)
{
	if (video_st && !video_is_eof)
	{
		write_video_frame(oc, video_st, cv_img, is_end);
		return true;
	}
	else
		return false;
}

int frame_count = 0;
void write_video_frame(AVFormatContext *oc, AVStream *st, cv::Mat cv_img, int flush)
{
	int ret;
	static struct SwsContext *sws_ctx;
	AVCodecContext *c = st->codec;

	if (!flush) {
		// BGR opencv image to AV_PIX_FMT_YUV420P
		cv::resize(cv_img, cv_img, cv::Size(c->width, c->height));

		// BGR24 이미지를 YUV420 이미지로 변환하기 위한 context를 할당받는다.
		if (!sws_ctx) {
			sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_BGR24,
				c->width, c->height, c->pix_fmt,
				SWS_BICUBIC, NULL, NULL, NULL);
			if (!sws_ctx) {
				fprintf(stderr,
					"Could not initialize the conversion context\n");
				exit(1);
			}
		}

		// OpenCV Mat 데이터를 ffmpeg 이미지 데이터로 복사
		avpicture_fill(&src_picture, cv_img.data, AV_PIX_FMT_BGR24, c->width, c->height);

		// BGR24 이미지를 YUV420이미지로 복사
		sws_scale(sws_ctx,
			(const uint8_t * const *)(src_picture.data), src_picture.linesize,
			0, c->height, dst_picture.data, dst_picture.linesize);
	}

	AVPacket pkt = { 0 };
	int got_packet;
	av_init_packet(&pkt);

	/* encode the image */
	// 스트림의 코덱에 맞게 인코딩하여 pkt변수에 할당된다.
	frame->pts = frame_count;
	ret = avcodec_encode_video2(c, &pkt, flush ? NULL : frame, &got_packet);
	if (ret < 0) {
		fprintf(stderr, "Error encoding video frame:");
		exit(1);
	}
	/* If size is zero, it means the image was buffered. */
	if (got_packet) {
		// 제대로 이미지가 인코딩 됐으면 스트림에 이미지를 쓴다.
		ret = write_frame(oc, &c->time_base, st, &pkt);
	}
	else {
		if (flush)
			video_is_eof = 1;
		ret = 0;
	}

	if (ret < 0) {
		fprintf(stderr, "Error while writing video frame: ");
		exit(1);
	}
	frame_count++;
}

int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
{
	/* rescale output packet timestamp values from codec to stream timebase */
	pkt->pts = av_rescale_q_rnd(pkt->pts, *time_base, st->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
	pkt->dts = av_rescale_q_rnd(pkt->dts, *time_base, st->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
	pkt->duration = av_rescale_q(pkt->duration, *time_base, st->time_base);
	pkt->stream_index = st->index;

	/* Write the compressed frame to the media file. */
	return av_interleaved_write_frame(fmt_ctx, pkt);
}


위의 StreamImage 함수를 호출할 때마다 프레임이 한장씩 보내지는 것이다.

FPS에 맞게 이미지를 카메라로부터 얻어서 StreamImage 함수를 호출하려면 아래와 같이 하면 된다.

#include <mutex>
#include <thread>

// 스레드 멤버
std::thread* sender;
std::mutex mtx_lock;
// 비동기로 스트리밍을 중지하기 위한 변수
bool is_streaming = false;
// 비디오 캡처를 위한 멤버
cv::VideoCapture video_cap;

// ...

video_cap.open(device_id);
if (!video_cap.isOpened())
{
	// ERROR
	return false;
}

mtx_lock.lock();
is_streaming = true;
mtx_lock.unlock();

sender = new std::thread(&SendStream, this);
if (!sender)
{
	// ERROR
	return false;
}

//...

// 이미지를 지속적으로 카메라로부터 얻어 전송하는 스레드 함수
void SendStream()
{
	cv::Mat cam_img;

	while (true)
	{
		// 비동기 스트리밍 중지
		mtx_lock.lock();
		bool thread_end = is_streaming;
		mtx_lock.unlock();

		video_cap >> cam_img;

		// end of video stream
		if (cam_img.empty())
		{
			EndStream();
			break;
		}

		// user finish
		if (!thread_end)
		{
			// write last frame
			if (!ffmpeg.StreamImage(cam_img, true))
			{
				// ERROR
				return;
			}
			break;
		}
		
		// write frame
		if(!ffmpeg.StreamImage(cam_img, false))
		{
			// ERROR
			return;
		}

		// FPS만큼 기다린다.
		std::this_thread::sleep_for(std::chrono::milliseconds(1000 / STREAM_FPS));
	}
}


스트리밍이 끝나면 ffmpeg의 버퍼와 스레드를 release해야한다.

void EndStream()
{
	if (sender)
	{
		mtx_lock.lock();
		is_streaming = false;
		mtx_lock.unlock();

		// wait until finish
		sender->join();

		delete sender;
	}
	if (video_cap.isOpened())
		video_cap.release();
	sender = NULL;

	// 아래 av_write_trailer는 output context를 닫기 전에 호출해야 한다는데 정확히는 몰라서 더 찾아봐야한다..
	/* Write the trailer, if any. The trailer must be written before you
	* close the CodecContexts open when you wrote the header; otherwise
	* av_write_trailer() may try to use memory that was freed on
	* av_codec_close(). */
	if (oc)
		av_write_trailer(oc);

	/* Close each codec. */
	if (video_st)
		close_video(video_st);
	//if (audio_st)
	//	close_audio(audio_st);

	if (fmt && oc)
		if (!(fmt->flags & AVFMT_NOFILE))
			/* Close the output file. */
			avio_close(oc->pb);

	/* free the stream */
	if (oc)
		avformat_free_context(oc);

	oc = NULL;
	fmt = NULL;
	video_st = NULL;
}



127.0.0.1, 포트 9000으로 테스트해보자

ffmpeg shared를 받으면 bin 폴더 안에 ffplay라는 exe파일도 있을것이다.

이것을 cmd를 이용해 아래 그림 6과 같이 수신해 볼 수 있다.

[그림 6] ffplay를 이용한 RTP 스트리밍 수신




다음엔 RTP Streaming을 ffmpeg 라이브러리를 이용해 수신 하는 방법을 포스팅 해야겠다.

'프로그래밍 > C++' 카테고리의 다른 글

Union-Find  (0) 2018.05.23
Sort  (0) 2018.05.14
Const Reference  (0) 2017.04.20
Windows C++ RTP Streaming from Cam using FFMPEG and OpenCV  (22) 2017.03.16
댓글
  • 프로필사진 jun 구현하신 ffmpeg RTP스트리밍 코드는 잘봤습니다.
    님 도움을 받아 저도 구현했는데.. 로컬 PC만 가능하고 공유되어 있는 Pc에서는 스트리밍을 받을수가 없습니다.

    VLC 플레이를 써서 봤는데 로컬 Pc에서는 당연히 스트리밍 받을수 있으며 타피씨에서는 스트리밍을 받을수 없습니다.

    혹시 서버 구현까지 해야하는건가요?(예를 들면 wowza서버 등록이던가...)
    2017.07.10 17:31
  • 프로필사진 국윤창 안녕하세요. 제 블로그 첫 번째 댓글이네요! 감사합니다.
    혹시 수신받는 피씨에서 공유기를 사용하고 계시면 포트포워딩을 하셨나요?
    2017.07.10 17:49 신고
  • 프로필사진 jun 사내 네트워크라서 사내 ip랍니다. 외부 접속이 아니구요 ㅎㅎ

    그러니까 구연한 피씨가 192.168.10.10:32000 이고 vlc를돌릴 피씨가 192.168.10.12 였답니다.

    매우 중요한 글인데 제가 처음 댓글을 달았다니 영광입니다~

    2017.07.11 22:30
  • 프로필사진 국윤창 아 그렇군요

    제가 알기로 RTP 스트리밍은 UDP기반이라 연결이나 따로 서버가 필요 없이 송신측에서 수신측 아이피와 포트만으로 데이터를 송신하는 것으로 알고 있습니다.

    저도 처음에 같은 네트워크로 공유된 피씨에서 테스트를 하였는데 잘 됐습니다.

    제가 지식이 모자라서 사내 네트워크의 경우에는 어떤식으로 네트워크가 구성된 지 잘 모르겠네요 ㅠㅠ..
    2017.07.12 15:37 신고
  • 프로필사진 국윤창 제가 이런 글을 써본 경험이 많지 않아서 글의 설명이 부족한데, 혹시 context를 할당할 때 아이피와 포트를 수신측의 아이피와 포트가 아니라 송신하는 컴퓨터의 아이피와 포트를 할당하셨나요??

    아이피와 포트를 수신측의 아이피와 포트로 할당해야하는데 혹시나 제 글의 설명이 부족해서 오해하셨을까봐 적습니다
    2017.07.12 15:41 신고
  • 프로필사진 seno 안녕하세요. 길지 않은 구글링이었습니다만, 가장 좋은 자료 감사합니다.
    http://nowprogramming.tistory.com/7
    를 보면 캡쳐 함수로 간단히 cv 로 영상을 볼수 있는듣 하네요.
    혹시 오디오전송에 관하여도 이미 포스팅 하셨나요?
    가장 적합해 보이는 자료는 이것입니다만....
    https://stackoverflow.com/questions/8187745/audio-output-with-video-processing-with-opencv
    2017.07.17 17:39
  • 프로필사진 kamchol 안녕하세요

    구글링을 하던중 좋은 자료 올려주신것 감사합니다

    기존 opencv는 TCP 전송만 되고 UDP로는 지원을 하지 않아 ffmpeg를 이용해서 전송하려는데요

    위와 같이 하면 서버에서 udp로 전송 하는 서버가 있다면 ip입력시 해당 서버의 영상과 ntp등을 받아 올 수 있나요?
    2018.03.27 20:05
  • 프로필사진 국윤창 안녕하세요.

    IP를 입력한다는 말씀은 클라이언트에서 수신할 때 RTP 영상송신 서버의 주소를 입력하는 것을 말씀 하시는거죠?

    당연히 RTP로 송신하니 수신부에서도 RTP로 수신하면 영상을 받을 수 있습니다.

    말씀하신 NTP는 제가 지식이 모자라서 어떤 것을 말씀하시는지 모르겠네요..

    제가 수신부 코드를 따로 정리를 못했는데, 대신 제 github에 올려놓은 dll 프로젝트 링크를 남겨드립니다.

    https://github.com/YoonChangKook/KStreamReceivingDll

    도움이 됐으면 좋겠네요 ㅋㅋ
    2018.03.30 03:20 신고
  • 프로필사진 arjen998 정말 좋은 자료입니다. 도움이 많이 됐습니다.
    그런데 혹시 인코딩 코덱을 변경하려면 어떻게 해야되는지 알수 있을까요?
    AV_CODEC_ID 부분의 명칭만 바꿔주면 되나요?
    2018.05.28 14:14
  • 프로필사진 국윤창 안녕하세요. 답변이 늦었네요.
    맞습니다. 그 부분을 아래 나온 링크의
    enum AVCodecID의 enumerator를 참고하여 바꾸시면 코덱이 바뀝니다.

    https://www.ffmpeg.org/doxygen/3.1/group__lavc__core.html#ggaadca229ad2c20e060a14fec08a5cc7cea90ffce5aedda33269b22221916cd8487

    그런데 제가 테스트 했을 때 몇몇 코덱은 description이 필요하다고 런타임에 경고로 떴었는데 어떻게 해결해야 하는지는 확인하지 못했습니다.

    도움이 되셨으면 좋겠습니다.

    감사합니다.
    2018.06.12 22:34 신고
  • 프로필사진 Goodmorning 안녕하십니까. 감사히 글 잘읽었습니다.
    그런데 구현하신 소스로 RTP전송은 확인했는데 RTSP로 변경하니 avformat_write_header 함수에서 더이상 진행이 안됩니다.
    아마 핸드쉐이킹이 없어서 일까요.. 기왕이면 live555를 쓰지않고 ffmpeg으로만 해볼까하는데....쉽지가 않군요 ㅠㅜ 혹시 좋은 방법이 있을까요?
    2019.05.11 09:58
  • 프로필사진 국윤창 안녕하세요^^
    rtsp 컨트롤 송수신 포트인 554는 tcp로 연결돼서, 말씀하신대로 rtsp서버에서 listen 상태여야 테스트가 가능할 것 같습니다.
    2019.05.13 08:18 신고
  • 프로필사진 국윤창 죄송합니다. 답글 내용을 제대로 이해를 못했었네요.
    서버 시작할 때 멈춘다는 말씀이셨군요.
    혹시 output context를 할당할때 인자를 rtsp에 맞게 할당하셨는지 궁금합니다.
    2019.05.13 08:28 신고
  • 프로필사진 칠른 안녕하세요 수신쪽 참고할만한 부탁드려도될까요? 2019.07.22 11:30
  • 프로필사진 국윤창 안녕하세요. 딱히 참고할만한 링크를 저장해놓지 않아서, 제가 이전에 만들어 놓은 DLL 프로젝트의 github 링크를 남겨드립니다. ^^;

    https://github.com/YoonChangKook/KStreamReceivingDll

    보시고 이해가 어려운 부분이 있으시면 댓글 부탁드립니다.
    2019.07.22 11:37 신고
  • 프로필사진 ssangnan 안녕하세요.수신쪽 보다가 궁금한 점이 잇어서 질문드리는 데 MyFFMPEGReceiver::Initialize하는 부분에서 tempUrl에 rtp://127.0.0.1:9000/kstream까지 입력해 주었는데 stream 정보를 못 찾는 것 같더라구요. kstream이라는 것은 sdp 파일인지?그리고 sdp파일이면 경로는 어디로 지정해야 하는지가 궁금하네요. 2019.11.28 10:46
  • 프로필사진 국윤창 안녕하세요!
    rtp://127.0.0.1:9000/kstream은 송신, 수신할 rtp의 주소입니다.
    sdp 파일은 MPEG1이나 MPEG2를 송신할 때 옵션을 따로 변경할 게 없으면 없어도 돼서 따로 프로젝트에 포함시키지 않았습니다.

    https://support.spinetix.com/wiki/SDP_file

    만약 다른 코덱으로 스트리밍 송수신을 하고 싶으시다면, 위 링크 참고하셔서 sdp 파일을 작성하시면 될 것 같습니다.
    위 링크에 따르면 sdp 파일은 프로그램 실행(루트) 경로부터 찾는 것 같습니다. 저는 MPEG1을 이용해 송수신하여 sdp 파일을 따로 작성해보지 않아서 예제는 따로 가지고 있지 않습니다 ㅠㅠ

    도움이 되셨으면 좋겠네요! 감사합니다.
    2019.11.28 14:11 신고
  • 프로필사진 구운몽 안녕하세요. 송신 쪽 구현 하여 영상 송신 하다 보니 다른 문제 점이 생기는 것 같습니다. 영상 전송 시 CPU 점유율이 30%까지 치솟는데 단일 쓰레드 동작인데 이정도로 점유율이 많이 올라가는건지, 안정적으로 사용하고 싶은데 소스에서 어떤 부분을 수정해야 할지 어렵네요.혹시 점유율을 이정도로 차지할만 부분이 소스 코드내에 있을가요? 2020.01.17 16:21
  • 프로필사진 swjeon 올려 주신 코드 잘 참조하겠습니다. 감사합니다. 2020.03.17 12:27
  • 프로필사진 하하 안녕하세요 rtp 전송 이외에도 rtsp 전송도 해보고 싶은데 해당 코드에서 어떤부분을 바꾸어야 할지 혹시 알 수 있을까요? 그리고 avformat_open_input에 rtsp 주소 입력시 인자값의 의미가 정확히 무엇인지 궁금합니다. ex)rtsp://211.189.132.118/nbr-media/media.nmp?ch=T142
    2020.04.22 15:34
  • 프로필사진 국윤창 안녕하세요. rtsp 전송을 원하시면 rtsp 주소의 스키마를 "rtsp://"로 하시고, ouput context를 할당하실때 "rtsp"로 하셔야합니다. 그리고 주소의 경우 sdp 파일의 경로를 주로 사용하는 것 같습니다만, 제가 사용할때는 이 부분을 신경쓰지 않아서 답변드리기가 어렵네요. 위에 다른분 댓글에 sdp에 대한 답글을 달아놓았으니 확인해주세요. 2020.04.27 20:04 신고
  • 프로필사진 ymkang 처음으로 개발 블로그에 글남깁니다.
    좋은 자료 정말 감사합니다.
    영상을 수신할 타겟 IP를 사용해서 ffmpeg 스트림으로 오픈하고,
    opencv 에서 받은 cv::mat 이미지를 동영상으로 인코딩하면
    ffmpeg 내부적으로 타겟 장비로 전송되는 구조군요 !
    2021.06.09 11:44
댓글쓰기 폼