FFmpeg 을 처음 다룬다면 다음 사이트가 큰 도움이 된다.
FFmpeg 강좌I
FFmpeg은 멀티미디어 디코딩, 인코딩에 대한 모든 기능을 제공하는 오픈 소스 라이브러리입니다. 이 강좌는 FFmpeg에 대한 기본적인 사용 방법과 프로젝트 구성 방법을 설명하고 간단한 동영상 플
soen.kr
위에서 기본적인 사용법을 익힌 후, 파일 관리 시스템 에 덧붙였다. 파일 뷰어에서 선택한 파일이 영상 파일일 경우, 썸네일과 메타 데이터를 출력하도록 했다.
언리얼에 붙이기 위해 [모듈이름].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 |
---|
댓글