성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)</h1> <p> 지난 글에 이어,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12924'>https://www.sysnet.pe.kr/2/0/12924</a> </pre> <br /> 이번에는 <a target='tab' href='https://ffmpeg.org/doxygen/trunk/examples.html'>ffmpeg 예제</a> 중 "<a target='tab' href='https://ffmpeg.org/doxygen/trunk/decode_audio_8c-example.html'>decode_audio.c</a>" 파일을 포팅하겠습니다.<br /> <br /> 예제를 가만 보니, 코덱으로 AV_CODEC_ID_MP2를 쓰고 있는데요, 따라서 이 예제가 동작하려면 MP2 파일이 필요합니다. 없다면 아래와 같은 명령어로 기존 mp3 파일로부터 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>ffmpeg -i "test.mp3" -acodec mp2 "test.mp2"</span> </pre> <br /> 변환된 파일을 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>ffprobe "test.mp2"</span> 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: <span style='color: blue; font-weight: bold'>Audio: mp2</span>, 44100 Hz, stereo, fltp, 384 kb/s </pre> <br /> "Input #0, mp3"라고는 하지만 내부 Stream은 mp2라고 나옵니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그래서 결국 이렇게 포팅을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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; } } } </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1885&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> (<a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/decode_audio'>이 글의 소스 코드는 github에 올려</a>져 있습니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 해당 파일들의 크기를 비교하면 이렇습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 원본 mp3: 5,752KB mp2: 11,267KB decode_audio.c: 41,405KB </pre> <br /> 그리고 출력된 파일은 나름 포맷을 가지고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\media_sample> <span style='color: blue; font-weight: bold'>ffprobe C:\temp\output\test_cs2.dat</span> 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: <span style='color: blue; font-weight: bold'>Audio: adpcm_dtk</span>, 48000 Hz, stereo, s16p </pre> <br /> 재미있는 것은, 예제(decode_audio)를 실행하면 어떻게 음악을 재생할 수 있는지도 출력을 해줍니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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: <span style='color: blue; font-weight: bold'>ffplay -f s16le -ac 2 -ar 44100 c:\temp\output\test_cs2.dat</span> </pre> <br /> 실제로 저 명령으로 실행하면 다음과 같은 식의 창이 뜨고,<br /> <br /> <img alt='decode_audio_1.png' src='/SysWebRes/bbs/decode_audio_1.png' /><br /> <br /> 음악이 재생됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 혹시나 싶어 MP3 파일로 해당 예제를 실행했더니 다음과 같은 오류 메시지가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [mp2 @ 0000015BA400C000] Header missing Error submitting the packet to the decoder </pre> <br /> 그렇다면, 저 소스 코드 그대로 단지 codec ID 값만 MP3로 바꾸면 어떨까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > codec = ffmpeg.avcodec_find_decoder(AVCodecID.<span style='color: blue; font-weight: bold'>AV_CODEC_ID_MP3</span>); </pre> <br /> 아쉽게도, 여전히 "Header missing" 오류가 발생합니다. 뭐랄까... 추상화를 잘 했다면 분명히 코덱 ID만 바꿔 전달하는 것으로 동작을 했어야 하지 않을까요? ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1282
(왼쪽의 숫자를 입력해야 합니다.)