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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11484정성태4/11/201824684.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법파일 다운로드1
11483정성태4/10/201827990개발 환경 구성: 358. "Let's Encrypt"에서 제공하는 무료 SSL 인증서를 IIS에 적용하는 방법 (2) [1]
11482정성태4/10/201820451VC++: 126. CUDA Core 수를 알아내는 방법
11481정성태4/10/201832062개발 환경 구성: 357. CUDA의 인덱싱 관련 용어 - blockIdx, threadIdx, blockDim, gridDim
11480정성태4/9/201822089.NET Framework: 736. C# - API를 사용해 Azure에 접근하는 방법 [2]파일 다운로드1
11479정성태4/9/201817737.NET Framework: 735. Azure - PowerShell로 Access control(IAM)에 새로운 계정 만드는 방법
11478정성태11/8/201919961디버깅 기술: 115. windbg - 덤프 파일로부터 PID와 환경변수 등의 정보를 구하는 방법 [1]
11477정성태4/8/201817442오류 유형: 460. windbg - sos 명령어 수행 시 c0000006 오류 발생
11476정성태4/8/201819005디버깅 기술: 114. windbg - !threads 출력 결과로부터 닷넷 관리 스레드(System.Threading.Thread) 객체를 구하는 방법
11475정성태3/28/201821289디버깅 기술: 113. windbg - Thread.Suspend 호출 시 응용 프로그램 hang 현상에 대한 덤프 분석
11474정성태3/27/201819409오류 유형: 459. xperf: error: TEST.Event: Invalid flags. (0x3ec).
11473정성태3/22/201824584.NET Framework: 734. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상파일 다운로드2
11472정성태3/22/201818543개발 환경 구성: 356. GTX 1070, GTX 960, GT 640M의 cudaGetDeviceProperties 출력 결과
11471정성태3/20/201821926VC++: 125. CUDA로 작성한 RGB2RGBA 성능 [1]파일 다운로드1
11470정성태3/20/201824017오류 유형: 458. Visual Studio - CUDA 프로젝트 빌드 시 오류 C1189, expression must have a constant value
11469정성태3/19/201817039오류 유형: 457. error MSB3103: Invalid Resx file. Could not load file or assembly 'System.Windows.Forms, ...' or one of its dependencies.
11468정성태3/19/201816565오류 유형: 456. 닷넷 응용 프로그램 실행 시 0x80131401 예외 발생
11467정성태3/19/201816046오류 유형: 455. Visual Studio Installer - 업데이트 실패
11466정성태3/18/201817201개발 환경 구성: 355. 한 대의 PC에서 2개 이상의 DirectX 게임을 실행하는 방법
11463정성태3/15/201819543.NET Framework: 733. 스레드 간의 read/write 시에도 lock이 필요 없는 경우파일 다운로드1
11462정성태3/14/201822395개발 환경 구성: 354. HTTPS 호출에 대한 TLS 설정 확인하는 방법 [1]
11461정성태3/13/201825024오류 유형: 454. 윈도우 업데이트 설치 오류 - 0x800705b4 [1]
11460정성태3/13/201817496디버깅 기술: 112. windbg - 닷넷 메모리 덤프에서 전역 객체의 내용을 조사하는 방법
11459정성태3/13/201818305오류 유형: 453. Debug Diagnostic Tool에서 mscordacwks.dll을 찾지 못하는 문제
11458정성태2/21/201819283오류 유형: 452. This share requires the obsolete SMB1 protocol, which is unsafe and could expose your system to attack. [1]
11457정성태2/17/201823987.NET Framework: 732. C# - Task.ContinueWith 설명 [1]파일 다운로드1
... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...