본문 바로가기
외부 라이브러리/FFmpeg

Ffmpeg 학습 & UE에서 사용

by 공작사 2023. 8. 19.

FFmpeg 을 처음 다룬다면 다음 사이트가 큰 도움이 된다.

 

FFmpeg I

 

FFmpeg 강좌I

FFmpeg은 멀티미디어 디코딩, 인코딩에 대한 모든 기능을 제공하는 오픈 소스 라이브러리입니다. 이 강좌는 FFmpeg에 대한 기본적인 사용 방법과 프로젝트 구성 방법을 설명하고 간단한 동영상 플

soen.kr

 

위에서 기본적인 사용법을 익힌 후, 파일 관리 시스템 에 덧붙였다. 파일 뷰어에서 선택한 파일이 영상 파일일 경우, 썸네일과 메타 데이터를 출력하도록 했다.

ULibraryManager widget

언리얼에 붙이기 위해 [모듈이름].build.cs 를 다음처럼 작성한다.

.build.cs

더보기
// Copyright Epic Games, Inc. All Rights Reserved.

using System.IO;
using UnrealBuildTool;

public class LoadScenePlugin : ModuleRules
{
	public LoadScenePlugin(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicIncludePaths.AddRange(
			new string[] {
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] {
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				"UMG",
				"InputCore",
				// ... add other public dependencies that you statically link with here ...
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
                "RenderCore",
                "RHI",
				// ... add private dependencies that you statically link with here ...	
			}
			);
		
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				// ... add any modules that your module loads dynamically here ...
			}
			);

        string thirdPartyPath = Path.Combine(ModuleDirectory, "../../ThirdParty/Ffmpeg");

        PublicIncludePaths.AddRange(
            new string[]
            {
                Path.Combine(thirdPartyPath, "include")
            });

        PublicAdditionalLibraries.AddRange(
             new string[]
             {
                Path.Combine(thirdPartyPath, "lib", "avcodec.lib"),
                Path.Combine(thirdPartyPath, "lib", "avdevice.lib"),
                Path.Combine(thirdPartyPath, "lib", "avfilter.lib"),
                Path.Combine(thirdPartyPath, "lib", "avformat.lib"),
                Path.Combine(thirdPartyPath, "lib", "avutil.lib"),
                Path.Combine(thirdPartyPath, "lib", "swresample.lib"),
                Path.Combine(thirdPartyPath, "lib", "swscale.lib"),
             });

        // Ensure that the DLLs are staged along with the executable
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/avcodec-59.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/avdevice-59.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/avfilter-8.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/avformat-59.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/avutil-57.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/swresample-4.dll");
        RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/swscale-6.dll");
    }
}

 

ULibraryManager.h

더보기
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"

#include "Components/ScrollBox.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Components/Button.h"
#include "Engine/TextureRenderTarget2D.h"

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
}

#include "Windows/AllowWindowsPlatformTypes.h"
#include <windows.h>
#include "Windows/HideWindowsPlatformTypes.h"

#include "LibraryManager.generated.h"

UCLASS()
class LOADSCENEPLUGIN_API ULibraryManager : public UUserWidget
{
	GENERATED_BODY()
	
public:
	ULibraryManager(const FObjectInitializer& obj);
	virtual void NativeConstruct() override;

	void ShowMediaFileProperty(FString path, FText filename);

	UFUNCTION()
		void RenderTexture();
	UFUNCTION(BlueprintCallable, Category = "Set Thumbnail")
		void SetRenderTexture(UTextureRenderTarget2D* texture);

	TArray<UMyExpandableArea*> Directories;
	TArray<UFile_Descriptor*> Files;

private:
	UTextureRenderTarget2D* Thumbnail;

public:
	UPROPERTY(BlueprintReadWrite)
		FString root_path;

	//-----Media Property-------//
	UPROPERTY(meta = (bindwidget))
		UTextBlock* TextBlock_width;
	UPROPERTY(meta = (bindwidget))
		UTextBlock* TextBlock_height;
	UPROPERTY(meta = (bindwidget))
		UTextBlock* TextBlock_Time;
	UPROPERTY(meta = (bindwidget))
		UImage* Image_icon;
	UPROPERTY(meta = (bindwidget))
		UTextBlock* TextBlock_filename;
	UPROPERTY(meta = (bindwidget))
		UImage* Image_thumbnail;


private:
	AVFormatContext* fmtCtx;
	const AVCodec* vCodec;
	AVCodecContext* vCtx;
	AVFrame vFrame;

	SwsContext* swsCtx;
	AVFrame RGBFrame;
	uint8_t* rgbbuf;

	FTimerHandle RenderTimer;
};

ULibraryManager.cpp

더보기
// Fill out your copyright notice in the Description page of Project Settings.


#include "LibraryManager.h"

#include "MyExpandableArea.h"
#include "File_Descriptor.h"
#include "Kismet/KismetSystemLibrary.h"

ULibraryManager::ULibraryManager(const FObjectInitializer& obj) : Super(obj), ClickedDir(nullptr), ClickedFile(nullptr)
{
	//...
}

void ULibraryManager::NativeConstruct()
{
	//...
}

void ULibraryManager::ShowMediaFileProperty(FString path, FText filename)
{
	SetVisibility_MediaProperties(ESlateVisibility::Visible);

	char* result = TCHAR_TO_UTF8(*path);
	//파일 오픈
	int flag1 = avformat_open_input(&fmtCtx, result, NULL, NULL);
	//스트림 찾기
	int flag2 = avformat_find_stream_info(fmtCtx, NULL);
	if (flag1 == 0 && flag2 == 0)
	{
		//비디오 스트림 찾기
		int vidx = -1;
		vidx = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
		
		AVStream* vStream;
		AVCodecParameters* vPara;
		vStream = fmtCtx->streams[vidx];
		vPara = vStream->codecpar;

		//시간 파싱
		int32 duration_sec = (fmtCtx->duration / AV_TIME_BASE);
		int32 hour = duration_sec / 3600;
		int32 min = duration_sec / 60;
		int32 seconds = duration_sec % 60;
		FString s_hour, s_min, s_sec;

		s_hour = FString::FromInt(hour);
		s_min = FString::FromInt(min);
		s_sec = FString::FromInt(seconds);

		if (hour < 10)
			s_hour = "0" + s_hour;
		if (min < 10)
			s_min = "0" + s_min;
		if (seconds < 10)
			s_sec = "0" + s_sec;

		//위젯에 출력.
		FString width = "W : " + FString::FromInt(vPara->width);
		FString height = "H : " + FString::FromInt(vPara->height);
		FString duration = "Time : " + s_hour + " : " + s_min + " : " + s_sec;
		TextBlock_width->SetText(FText::FromString(width));
		TextBlock_height->SetText(FText::FromString(height));
		TextBlock_Time->SetText(FText::FromString(duration));
		TextBlock_filename->SetText(filename);


		//썸네일 생성
		int ret;
		AVPacket packet = { 0, };
		vFrame = { 0, };
		vCodec = avcodec_find_decoder(vPara->codec_id);
		vCtx = avcodec_alloc_context3(vCodec);
		avcodec_parameters_to_context(vCtx, vPara);
		avcodec_open2(vCtx, vCodec, NULL);

		while (av_read_frame(fmtCtx, &packet) == 0)
		{
			//비디오 스트림이면
			if (packet.stream_index == vidx)
			{
				ret = avcodec_send_packet(vCtx, &packet);
				if (ret != 0) { continue; }

				ret = avcodec_receive_frame(vCtx, &vFrame);
				if (ret == 0)
				{
					if (Thumbnail == nullptr)
						break;

					if (Thumbnail->GetSurfaceWidth() != vFrame.width || Thumbnail->GetSurfaceHeight() != vFrame.height)
					{
						Thumbnail->InitCustomFormat(vFrame.width, vFrame.height, EPixelFormat::PF_R8G8B8A8, true);
						Thumbnail->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
						Thumbnail->UpdateResource();

						RenderTimer = UKismetSystemLibrary::K2_SetTimer(this, FString(TEXT("RenderTexture")), 0.1f, false);
					}
					else
					{
						RenderTexture();
					}
					break;
				}
				av_packet_unref(&packet);
			}
		}
		//av_frame_unref(&vFrame);
	}
	if (vCtx) { avcodec_free_context(&vCtx); }
	if (fmtCtx) { avformat_close_input(&fmtCtx); }
}

void ULibraryManager::RenderTexture()
{
	// 스케일 컨텍스트 생성
	if (swsCtx == NULL)
	{
		swsCtx = sws_getContext(
			vFrame.width, vFrame.height, AVPixelFormat(vFrame.format),
			vFrame.width, vFrame.height, AV_PIX_FMT_RGB24,
			SWS_BICUBIC, NULL, NULL, NULL);

		// 변환 결과를 저장할 프레임 버퍼 할당
			//프레임 크기와 포맷에 따른 바이트 계산.
		int rasterbufsize = av_image_get_buffer_size(AV_PIX_FMT_RGB24,
			vFrame.width, vFrame.height, 1);
			//계산한 크기로 메모리 할당.
		rgbbuf = (uint8_t*)av_malloc(rasterbufsize);
			//픽셀 포맷과 크기에 맞게 data와 linesize를 'rgbbuf' 에서 설정한다.
		av_image_fill_arrays(RGBFrame.data, RGBFrame.linesize, rgbbuf,
			AV_PIX_FMT_RGB24, vFrame.width, vFrame.height, 1);
	}

	// 변환을 수행한다.
	sws_scale(swsCtx, vFrame.data, vFrame.linesize, 0, vFrame.height,
		RGBFrame.data, RGBFrame.linesize);

	// 변환된 결과를 출력한다.
	unsigned char* raster = RGBFrame.data[0];
	TArray<uint8> SrcData;
	SrcData.Reset(vFrame.width * vFrame.height * 4);
	for (int y = 0; y < vFrame.height; y++)
	{
		for (int x = 0; x < vFrame.width; x++) {
			
			int offset = (y * vFrame.width + x) * 3;

			// 버퍼에 담는다.역워드 현상 없음.
			SrcData.Push(raster[offset + 0]);
			SrcData.Push(raster[offset + 1]);
			SrcData.Push(raster[offset + 2]);
			SrcData.Push(0xFF);
		}
	}

	FTextureResource* TextureResource = Thumbnail->Resource;
	auto Region = FUpdateTextureRegion2D(0, 0, 0, 0, vFrame.width, vFrame.height);

	ENQUEUE_RENDER_COMMAND(UpdateTextureData)(
		[TextureResource, Region, SrcData](FRHICommandListImmediate& RHICmdList) {
			FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
	if (!Texture2D)
	{
		return;
	}
	RHIUpdateTexture2D(Texture2D, 0, Region, 4 * Region.Width, SrcData.GetData());

		});


	if (rgbbuf) { av_free(rgbbuf); rgbbuf = NULL; }
	if (swsCtx) { sws_freeContext(swsCtx); swsCtx = NULL; }
}

void ULibraryManager::SetRenderTexture(UTextureRenderTarget2D* texture)
{
	Thumbnail = texture;
}

 

끝.

'외부 라이브러리 > FFmpeg' 카테고리의 다른 글

FFmpeg 빌드하기.  (0) 2023.08.15

댓글