1. ftyp 읽기
2. mdat 건너띄기 (mdat는 영상 데이터 부분)
3. moov의 mvhd duration 구하기
참고 : https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf
인터넷에서는 ffprobe를 통해 읽는 것이 많으나 mp4 metadata를 통해 읽어왔다.
ffmpeg -f flv rtmp를 통해 송출을 하면 형식은 [결과] 사진에 있는 ed97dad20ef370904c790102d2fec112949ce8e987f10817ab7773c7ffc9b669.mp4 처럼 f4v가 된다.
만약 송출 시간이 길다면 용량은 많이 늘어나게 되어서 isom 형식으로 변경하였다.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.IO;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
namespace VideoEncoding_console
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var test1 = await GetMetaDataAsync(@"C:\Users\tjdsk\Desktop\ed97dad20ef370904c790102d2fec112949ce8e987f10817ab7773c7ffc9b669.mp4");
stopwatch.Stop();
Console.WriteLine(test1);
Console.WriteLine(stopwatch.ElapsedMilliseconds + Environment.NewLine);
var test2 = await GetMetaDataAsync(@"C:\Users\tjdsk\Desktop\tested97dad20ef370904c790102d2fec112949ce8e987f10817ab7773c7ffc9b669.mp4");
Console.WriteLine(test2 + Environment.NewLine);
Console.ReadLine();
}
static async Task<MetaData> GetMetaDataAsync(string fileFullPath)
{
// https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf
// https://videocube.tistory.com/entry/MP4-%EB%B6%84%EC%84%9D-%ED%95%98%EA%B8%B0-MPEG4-%ED%8C%8C%ED%8A%B8-14
if (!System.IO.File.Exists(fileFullPath))
return null;
MetaData result = new MetaData();
using FileStream fileStream = new FileStream(fileFullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
if (fileStream.Length >= 4)
{
byte[] ftypbytesSize = new byte[4];
await fileStream.ReadAsync(ftypbytesSize.AsMemory(0, 4)); // ftyp
if (BitConverter.IsLittleEndian)
Array.Reverse(ftypbytesSize);
int ftypBoxSize = BitConverter.ToInt32(ftypbytesSize, 0); // ftyp 데이터 사이즈
if (ftypBoxSize > 0)
{
byte[] ftypbytes = new byte[ftypBoxSize];
int ftypReadbytes = await fileStream.ReadAsync(ftypbytes.AsMemory(0, ftypBoxSize)); // ftyp 데이터 읽기
string fileFormat = string.Empty;
string compatibleBrands = string.Empty;
string duration = string.Empty;
if (ftypReadbytes > 0)
{
fileFormat = Encoding.UTF8.GetString(ftypbytes, 4, 4).Trim(); // f4v 인지 isom 인지
compatibleBrands = Encoding.UTF8.GetString(ftypbytes, 12, 16).Replace("\0", "").Replace("\b", "").Trim(); // isommp42m4v 인지 isomiso2avc1mp41
byte[] mdatbytesSize = new byte[4];
fileStream.Position = fileStream.Position + 4; // mdat 파일 사이즈 부분으로 이동.
await fileStream.ReadAsync(mdatbytesSize.AsMemory(0, 4)); // mdat 사이즈 읽기
if (BitConverter.IsLittleEndian)
Array.Reverse(mdatbytesSize);
int mdatBoxSize = BitConverter.ToInt32(mdatbytesSize, 0);
if (mdatBoxSize > 0)
{
fileStream.Position = fileStream.Position + mdatBoxSize + 4 + 4 + 4; // mdat 데이터 부분 + moov + mvhd 사이즈 + mvhd
byte[] mvhdVersionbytes = new byte[1];
await fileStream.ReadAsync(mvhdVersionbytes.AsMemory(0, 1)); // Movie Header Atom 의 버전, 1: 64bits 0: 32: bits(시간)
if (mvhdVersionbytes[0] == 0) // 32비트 시간
{
fileStream.Position = fileStream.Position + 3 + 4 + 4; // Flags + Creation-time + Modification-tim
byte[] timescalebytes = new byte[4];
await fileStream.ReadAsync(timescalebytes.AsMemory(0, 4)); // timescale
byte[] durationbytes = new byte[4];
await fileStream.ReadAsync(durationbytes.AsMemory(0, 4)); // duration
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timescalebytes);
Array.Reverse(durationbytes);
}
int timeScaleValue = BitConverter.ToInt32(timescalebytes, 0);
int durationValue = BitConverter.ToInt32(durationbytes, 0);
duration = $"{durationValue / timeScaleValue}";
}
else if (mvhdVersionbytes[0] == 1) // 64비트 시간
{
fileStream.Position = fileStream.Position + 3 + 8 + 8; // Flags + Creation-time + Modification-tim
byte[] timescalebytes = new byte[4];
await fileStream.ReadAsync(timescalebytes.AsMemory(0, 4)); // timescale
byte[] durationbytes = new byte[8];
await fileStream.ReadAsync(durationbytes.AsMemory(0, 8)); // duration
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timescalebytes);
Array.Reverse(durationbytes);
}
int timeScaleValue = BitConverter.ToInt32(timescalebytes, 0);
long durationValue = BitConverter.ToInt64(durationbytes, 0);
duration = $"{durationValue / timeScaleValue}";
}
result.Filename = fileStream.Name;
result.FileSize = fileStream.Length.ToString();
result.Duration = duration;
result.FileFormat = fileFormat;
result.CompatibleBrands = compatibleBrands;
}
}
}
}
return string.IsNullOrEmpty(result.Filename) ? null : result;
}
}
public class MetaData
{
public string Filename { get; set; }
public string Duration { get; set; }
public string FileSize { get; set; }
public string FileFormat { get; set; }
public string CompatibleBrands { get; set; }
public override string ToString() => $"Filename : {Filename}{Environment.NewLine}Duration: {Duration}{Environment.NewLine}Filesize: {FileSize}{Environment.NewLine}FileFormat: {FileFormat}{Environment.NewLine}CompatibleBrands: {CompatibleBrands}";
}
}
'C#' 카테고리의 다른 글
[C#] 건강보험심사평가원_병원평가정보서비스 Rest Api 사용 (0) | 2021.11.13 |
---|---|
[C#] ffmpeg 를 process 실행시 인코딩 예상 시간 구하기 (0) | 2021.11.03 |
[C#-Linux] 워커 서비스 프로그램 서비스로 등록하기 (0) | 2021.10.21 |
[C#-Linux] WSL 및 리눅스 시스템에 .NET 5 설치 (0) | 2021.10.21 |
[C#] Webview2 - not working Input type when it run as administrator (2) | 2021.10.20 |