Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 24개 있습니다.)
.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c)
; https://www.sysnet.pe.kr/2/0/12898

.NET Framework: 1134. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

.NET Framework: 1135. C# - ffmpeg(FFmpeg.AutoGen)로 하드웨어 가속기를 이용한 비디오 디코딩 예제(hw_decode.c)
; https://www.sysnet.pe.kr/2/0/12932

.NET Framework: 1136. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)
; https://www.sysnet.pe.kr/2/0/12933

.NET Framework: 1137. ffmpeg의 파일 해시 예제(ffhash.c)를 C#으로 포팅
; https://www.sysnet.pe.kr/2/0/12935

.NET Framework: 1138. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 멀티미디어 파일의 메타데이터를 보여주는 예제(metadata.c)
; https://www.sysnet.pe.kr/2/0/12936

.NET Framework: 1139. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 오디오(mp2) 인코딩하는 예제(encode_audio.c)
; https://www.sysnet.pe.kr/2/0/12937

.NET Framework: 1147. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터링 예제(filtering_audio.c)
; https://www.sysnet.pe.kr/2/0/12951

.NET Framework: 1148. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터 예제(filter_audio.c)
; https://www.sysnet.pe.kr/2/0/12952

.NET Framework: 1150. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12959

개발 환경 구성: 637. ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/12960

.NET Framework: 1151. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 프레임의 크기 및 포맷 변경 예제(scaling_video.c)
; https://www.sysnet.pe.kr/2/0/12961

.NET Framework: 1153. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 avio_reading.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12964

.NET Framework: 1157. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 muxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12971

.NET Framework: 1159. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 qsvdec.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12975

.NET Framework: 1161. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 resampling_audio.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12978

.NET Framework: 1166. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 filtering_video.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12984

.NET Framework: 1169. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 demuxing_decoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12987

.NET Framework: 1170. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcode_aac.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12991

.NET Framework: 1178. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 http_multiclient.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13002

.NET Framework: 1180. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 remuxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13006

.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13023

.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13025

.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드
; https://www.sysnet.pe.kr/2/0/13026




C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)

지난 글에 이어,

C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

이번에는 ffmpeg 예제 중 "decode_audio.c" 파일을 포팅하겠습니다.

예제를 가만 보니, 코덱으로 AV_CODEC_ID_MP2를 쓰고 있는데요, 따라서 이 예제가 동작하려면 MP2 파일이 필요합니다. 없다면 아래와 같은 명령어로 기존 mp3 파일로부터 구할 수 있습니다.

c:\temp> ffmpeg -i "test.mp3" -acodec mp2 "test.mp2"

변환된 파일을 살펴보면,

c:\temp> ffprobe "test.mp2"
ffprobe version 4.4.1 Copyright (c) 2007-2021 the FFmpeg developers
  built with Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30705 for x64
  configuration: ...[생략]... --extra-cflags=-MD --extra-cxxflags=-MD
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
[mp3 @ 0000014BC8BF0740] Estimating duration from bitrate, this may be inaccurate
Input #0, mp3, from 'test.mp2':
  Duration: 00:04:00.35, start: 0.000000, bitrate: 383 kb/s
  Stream #0:0: Audio: mp2, 44100 Hz, stereo, fltp, 384 kb/s

"Input #0, mp3"라고는 하지만 내부 Stream은 mp2라고 나옵니다.




그래서 결국 이렇게 포팅을 할 수 있습니다.

using FFmpeg.AutoGen;
using FFmpeg.AutoGen.Example;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace FFmpegApp1
{
    internal unsafe class Program
    {
        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
        static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

        const int AUDIO_INBUF_SIZE = 20480;
        const int AUDIO_REFILL_THRESH = 4096;

        static void Main(string[] args)
        {
            FFmpegBinariesHelper.RegisterFFmpegBinaries();

#if DEBUG
            Console.WriteLine("Current directory: " + Environment.CurrentDirectory);
            Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32");
            Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}");
#endif
            Console.WriteLine();

            Console.WriteLine($"LIBAVFORMAT Version: {ffmpeg.LIBAVFORMAT_VERSION_MAJOR}.{ffmpeg.LIBAVFORMAT_VERSION_MINOR}");

            string outputPath = @"c:\temp\output";
            try
            {
                Directory.Delete(outputPath, true);
                Directory.CreateDirectory(outputPath);
            }
            catch { }

            string outputFilePath = Path.Combine(outputPath, "test_cs2.dat");

            decode_audio(@"D:\media_sample\test.mp3", outputFilePath);
        }

        static unsafe void decode_audio(string inputFileName, string outputFileName)
        {
            AVCodec* codec = null;
            AVPacket* packet = null;
            AVCodecParserContext* parser = null;
            AVCodecContext* c = null;
            AVFrame* decoded_frame = null;

            int ret;

            do
            {
                packet = ffmpeg.av_packet_alloc();

                codec = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_MP2);
                if (codec == null)
                {
                    Console.WriteLine("Codec not found");
                    break;
                }

                parser = ffmpeg.av_parser_init((int)codec->id);
                if (parser == null)
                {
                    Console.WriteLine("Parser not found");
                    break;
                }

                c = ffmpeg.avcodec_alloc_context3(codec);
                if (c == null)
                {
                    Console.WriteLine("Could not allocate audio codec context");
                    break;
                }

                if (ffmpeg.avcodec_open2(c, codec, null) < 0)
                {
                    Console.WriteLine("Could not open codec");
                    break;
                }

                using FileStream f = File.OpenRead(inputFileName);
                using FileStream outfile = File.OpenWrite(outputFileName);

                byte[] inbuf = new byte[AUDIO_INBUF_SIZE + ffmpeg.AV_INPUT_BUFFER_PADDING_SIZE];
                fixed (byte* ptr = &inbuf[0])
                {
                    byte* data = ptr;
                    int data_size = f.Read(inbuf, 0, AUDIO_INBUF_SIZE);

                    while (data_size > 0)
                    {
                        if (decoded_frame == null)
                        {
                            decoded_frame = ffmpeg.av_frame_alloc();
                            if (decoded_frame == null)
                            {
                                Console.WriteLine("Could not allocate audio frame");
                                break;
                            }
                        }

                        ret = ffmpeg.av_parser_parse2(parser, c, &packet->data, &packet->size,
                            data, data_size, ffmpeg.AV_NOPTS_VALUE, ffmpeg.AV_NOPTS_VALUE, 0);

                        if (ret < 0)
                        {
                            Console.WriteLine("Error while parsing");
                            break;
                        }

                        data += ret;
                        data_size -= ret;

                        if (packet->size != 0)
                        {
                            if (decode(c, packet, decoded_frame, outfile) == false)
                            {
                                break;
                            }
                        }

                        if (data_size < AUDIO_REFILL_THRESH)
                        {
                            fixed (byte* temp_ptr = &inbuf[0])
                            {
                                MoveMemory(new IntPtr(temp_ptr), new IntPtr(data), data_size);
                                data = temp_ptr;

                                int len = f.Read(inbuf, data_size, AUDIO_INBUF_SIZE - data_size);
                                if (len > 0)
                                {
                                    data_size += len;
                                }
                            }
                        }
                    }
                }

                packet->data = null;
                packet->size = 0;

                if (decode(c, packet, decoded_frame, outfile) == false)
                {
                    break;
                }

                AVSampleFormat sfmt = c->sample_fmt;

                if (ffmpeg.av_sample_fmt_is_planar(sfmt) != 0)
                {
                    string packed = ffmpeg.av_get_sample_fmt_name(sfmt);
                    Console.WriteLine($"Warning: the sample format the decoder produced is planar {0}. This example will output the first channel only.",
                        packed == null ? "?" : packed);
                    sfmt = ffmpeg.av_get_packed_sample_fmt(sfmt);
                }

                int n_channels = c->channels;
                string fmt = get_format_from_sample_fmt(sfmt);
                if (fmt == null)
                {
                    break;
                }

                Console.WriteLine("Play the output audio file with the command:\n" +
                    $"ffplay -f {fmt} -ac {n_channels} -ar {c->sample_rate} {outputFileName}\n");

            } while (false);

            if (c != null)
            {
                ffmpeg.avcodec_free_context(&c);
            }

            if (parser != null)
            {
                ffmpeg.av_parser_close(parser);
            }

            if (decoded_frame != null)
            {
                ffmpeg.av_frame_free(&decoded_frame);
            }

            if (packet != null)
            {
                ffmpeg.av_packet_free(&packet);
            }
        }

        private unsafe static bool decode(AVCodecContext* dec_ctx, AVPacket* packet, AVFrame* frame, FileStream outfile)
        {
            int i;
            int ch;
            int ret;
            int data_size;

            ret = ffmpeg.avcodec_send_packet(dec_ctx, packet);
            if (ret < 0)
            {
                Console.WriteLine("Error submitting the packet to the decoder");
                return false;
            }

            while (ret >= 0)
            {
                ret = ffmpeg.avcodec_receive_frame(dec_ctx, frame);
                if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)
                {
                    return true;
                } else if (ret < 0)
                {
                    Console.WriteLine("Error during decoding");
                    return false;
                }

                data_size = ffmpeg.av_get_bytes_per_sample(dec_ctx->sample_fmt);
                if (data_size < 0)
                {
                    Console.WriteLine("Failed to calculate data size");
                    return false;
                }

                for (i = 0; i < frame->nb_samples; i ++)
                {
                    for (ch = 0; ch < dec_ctx->channels; ch ++)
                    {
                        byte *ptr = frame->data[0];
                        ptr += (data_size * i);

                        ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(ptr, data_size);
                        outfile.Write(data);
                    }
                }
            }

            return true;
        }

        static unsafe string get_format_from_sample_fmt(AVSampleFormat sample_fmt)
        {
            sample_fmt_entry[] sample_fmt_entries = {
                new sample_fmt_entry(AVSampleFormat.AV_SAMPLE_FMT_U8, "u8", "u8"),
                new sample_fmt_entry(AVSampleFormat.AV_SAMPLE_FMT_S16, "s16be", "s16le"),
                new sample_fmt_entry(AVSampleFormat.AV_SAMPLE_FMT_S32, "s32be", "s32le"),
                new sample_fmt_entry(AVSampleFormat.AV_SAMPLE_FMT_FLT, "f32be", "f32le"),
                new sample_fmt_entry(AVSampleFormat.AV_SAMPLE_FMT_DBL, "f64be", "f64le"),
            };

            foreach (var entry in sample_fmt_entries)
            {
                if (entry.SampleFormat == sample_fmt)
                {
                    if (BitConverter.IsLittleEndian == true)
                    {
                        return entry.FormatLE;
                    }

                    return entry.FoamtBE;
                }
            }

            Console.WriteLine($"sample format {ffmpeg.av_get_sample_fmt_name(sample_fmt)} is not supported as output format");
            return null;
        }
    }

    struct sample_fmt_entry
    {
        AVSampleFormat sample_fmt;
        public AVSampleFormat SampleFormat => sample_fmt;

        string fmt_be;
        public string FoamtBE => fmt_be;

        string fmt_le;
        public string FormatLE => fmt_le;

        public sample_fmt_entry(AVSampleFormat fmt, string be, string le)
        {
            sample_fmt = fmt;
            fmt_be = be;
            fmt_le = le;
        }
    }
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)
(이 글의 소스 코드는 github에 올려져 있습니다.)




해당 파일들의 크기를 비교하면 이렇습니다.

원본 mp3: 5,752KB
mp2: 11,267KB
decode_audio.c: 41,405KB

그리고 출력된 파일은 나름 포맷을 가지고 있습니다.

D:\media_sample> ffprobe C:\temp\output\test_cs2.dat
ffprobe version 4.4.1 Copyright (c) 2007-2021 the FFmpeg developers
  built with Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30705 for x64
  configuration: ...[생략]...
  libpostproc    55.  9.100 / 55.  9.100
[adp @ 000002C510D30000] Format adp detected only with low score of 25, misdetection possible!
Input #0, adp, from 'C:\temp\output\test_cs2.dat':
  Duration: 00:12:52.88, start: 0.000000, bitrate: 438 kb/s
  Stream #0:0: Audio: adpcm_dtk, 48000 Hz, stereo, s16p

재미있는 것은, 예제(decode_audio)를 실행하면 어떻게 음악을 재생할 수 있는지도 출력을 해줍니다. ^^

Warning: the sample format the decoder produced is planar 0. This example will output the first channel only.
Play the output audio file with the command:
ffplay -f s16le -ac 2 -ar 44100 c:\temp\output\test_cs2.dat

실제로 저 명령으로 실행하면 다음과 같은 식의 창이 뜨고,

decode_audio_1.png

음악이 재생됩니다.




혹시나 싶어 MP3 파일로 해당 예제를 실행했더니 다음과 같은 오류 메시지가 발생합니다.

[mp2 @ 0000015BA400C000] Header missing
Error submitting the packet to the decoder

그렇다면, 저 소스 코드 그대로 단지 codec ID 값만 MP3로 바꾸면 어떨까요?

codec = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_MP3);

아쉽게도, 여전히 "Header missing" 오류가 발생합니다. 뭐랄까... 추상화를 잘 했다면 분명히 코덱 ID만 바꿔 전달하는 것으로 동작을 했어야 하지 않을까요? ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/13/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...
NoWriterDateCnt.TitleFile(s)
13281정성태3/12/20233858Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/20234610개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/20234129오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/20234160개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/20234814개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/20234489.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
13275정성태3/3/20234796.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/20234358.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/20234097.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234360오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/20234281오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
13270정성태2/24/20233876.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/20234443스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/20234995개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/20234911.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/20234525오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/20234437.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/20235957스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/20234106개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/20235183디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/20234426Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/20234234오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234391.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234267오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234356.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235196개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...