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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12700정성태7/8/20219071.NET Framework: 1074. RuntimeType의 메모리 누수? [1]
12699정성태7/8/20217884VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
12698정성태7/7/202111849오류 유형: 732. Windows 11 업데이트 시 3% 또는 0%에서 다운로드가 멈춘 경우
12697정성태7/7/20217691개발 환경 구성: 574. Windows 11 (Insider Preview) 설치하는 방법
12696정성태7/6/20218319VC++: 146. 운영체제의 스레드 문맥 교환(Context Switch)을 유사하게 구현하는 방법파일 다운로드2
12695정성태7/3/20218375VC++: 145. C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법파일 다운로드1
12694정성태7/2/202110384Java: 24. Azure - Spring Boot 앱을 Java SE(Embedded Web Server)로 호스팅 시 로그 파일 남기는 방법 [1]
12693정성태6/30/20218056오류 유형: 731. Azure Web App Site Extension - Failed to install web app extension [...]. {1}
12692정성태6/30/20217984디버깅 기술: 180. Azure - Web App의 비정상 종료 시 남겨지는 로그 확인
12691정성태6/30/20218740개발 환경 구성: 573. 테스트 용도이지만 테스트에 적합하지 않은 Azure D1 공유(shared) 요금제
12690정성태6/28/20219591Java: 23. Azure - 자바(Java)로 만드는 Web App Service - Tomcat 호스팅
12689정성태6/25/202110203오류 유형: 730. Windows Forms 디자이너 - The class Form1 can be designed, but is not the first class in the file. [1]
12688정성태6/24/20219864.NET Framework: 1073. C# - JSON 역/직렬화 시 리플렉션 손실을 없애는 JsonSrcGen [2]파일 다운로드1
12687정성태6/22/20217786오류 유형: 729. Invalid data: Invalid artifact, java se app service only supports .jar artifact
12686정성태6/21/202110292Java: 22. Azure - 자바(Java)로 만드는 Web App Service - Java SE (Embedded Web Server) 호스팅
12685정성태6/21/202110530Java: 21. Azure Web App Service에 배포된 Java 프로세스의 메모리 및 힙(Heap) 덤프 뜨는 방법
12684정성태6/19/20218972오류 유형: 728. Visual Studio 2022부터 DTE.get_Properties 속성 접근 시 System.MissingMethodException 예외 발생
12683정성태6/18/202110441VS.NET IDE: 166. Visual Studio 2022 - Windows Forms 프로젝트의 x86 DLL 컨트롤이 Designer에서 오류가 발생하는 문제 [1]파일 다운로드1
12682정성태6/18/20218067VS.NET IDE: 165. Visual Studio 2022를 위한 Extension 마이그레이션
12681정성태6/18/20217421오류 유형: 727. .NET 2.0 ~ 3.5 + x64 환경에서 System.EnterpriseServices 참조 시 CS8012 경고
12680정성태6/18/20218564오류 유형: 726. python2.7.exe 실행 시 0xc000007b 오류
12679정성태6/18/20219085COM 개체 관련: 23. CoInitializeSecurity의 전역 설정을 재정의하는 CoSetProxyBlanket 함수 사용법파일 다운로드1
12678정성태6/17/20218281.NET Framework: 1072. C# - CoCreateInstance 관련 Inteop 오류 정리파일 다운로드1
12677정성태6/17/20219923VC++: 144. 역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석파일 다운로드1
12676정성태6/16/20219817VC++: 143. ionescu007/lxss github repo에 공개된 lxssmanager.dll의 CLSID_LxssUserSession/IID_ILxssSession 사용법파일 다운로드1
12675정성태6/16/20217883Java: 20. maven package 명령어 결과물로 (war가 아닌) jar 생성 방법
... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...