성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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)를 이용해 MP3 오디오 파일 인코딩/디코딩하는 예제</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)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12933'>https://www.sysnet.pe.kr/2/0/12933</a> C# - ffmpeg(FFmpeg.AutoGen)를 이용해 오디오(mp2) 인코딩하는 예제(encode_audio.c) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12937'>https://www.sysnet.pe.kr/2/0/12937</a> </pre> <br /> MP2 파일을 인코딩/디코딩했는데요, 그렇다면 MP3 파일은 어떨까요? 일단 인코딩 먼저 볼까요? ^^<br /> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12937'>지난 소스 코드</a>를 참고해, 단순히 인코더만 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_encoder(AVCodecID.<span style='color: blue; font-weight: bold'>AV_CODEC_ID_MP3</span>); </pre> <br /> 이후 ffmpeg.av_frame_get_buffer 호출에서 -22를 반환합니다. (소스 코드 내에서의 오류 메시지: "Could not allocate audio data buffers")<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [mp3_mf @ 00000288337D7B00] MFT name: 'MP3 Encoder ACM Wrapper MFT' Could not allocate audio data buffers </pre> <br /> 함께 출력된 로그를 보면 "MP3 Encoder ACM Wrapper MFT"라고 나오는데, 검색해 보면 MFT는 Media Foundation Transform의 약자로, 아마도 마이크로소프트의 운영체제에 포함된 내장 인코더로 보입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MP3 Audio Encoder ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/medfound/mp3-audio-encoder'>https://docs.microsoft.com/en-us/windows/win32/medfound/mp3-audio-encoder</a> </pre> <br /> 음... 저 인코더는 뭔가 특별한 게 있는 걸까요? ^^;<br /> <br /> <hr style='width: 50%' /><br /> <br /> 혹시나 싶어 AV_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;' > ffmpeg-toys/avcodec.c ; <a target='tab' href='https://github.com/joelanders/ffmpeg-toys/blob/master/avcodec.c'>https://github.com/joelanders/ffmpeg-toys/blob/master/avcodec.c</a> </pre> <br /> 예제가 나옵니다. 하지만 이 예제 코드는 av_frame_get_buffer 호출까지 가기도 전에 이미 check_sample_fmt에서 걸립니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c->sample_fmt = AVSampleFormat.<span style='color: blue; font-weight: bold'>AV_SAMPLE_FMT_S16P</span>; if (check_sample_fmt(codec, c->sample_fmt) == 0) { Console.WriteLine($"Encoder does not support sample format {ffmpeg.av_get_sample_fmt_name(c->sample_fmt)}"); break; } </pre> <br /> check_sample_fmt 함수의 내부를 디버깅해 보면 ("MP3 Encoder ACM Wrapper MFT" 코덱이) AV_SAMPLE_FMT_S16만 지원한다는 것을 알 수 있습니다. (어쨌든, 저 코드를 작성한 개발자의 PC에서는 잘 동작했을 것입니다.)<br /> <br /> 게다가 그 외의 "<a target='tab' href='https://github.com/joelanders/ffmpeg-toys/blob/master/avcodec.c'>ffmpeg-toys/avcodec.c</a>" 코드가 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12937'>이전 mp2 인코딩</a>과 다른 걸로 봐서는 아마도 deprecated 함수로 구성이 된 게 아닌가 싶습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 좀 뚝딱거리다 보니, 해당 코덱의 경우 avcodec_open2 호출 이후에 c->frame_size가 0이라는 점이 독특합니다. 왜냐하면, MP2 예제에서는 1152 값이 나왔기 때문입니다.<br /> <br /> 아마도 이것 때문에 이후 av_samples_get_buffer_size, av_frame_get_buffer 등의 호출에서 반환값이 -22가 나온 듯합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffmpeg.AVERROR(ffmpeg.EAGAIN); // -11 ffmpeg.AVERROR_EOF; // -541478725 <span style='color: blue; font-weight: bold'>ffmpeg.AVERROR(ffmpeg.EINVAL); // -22</span> ffmpeg.AVERROR(ffmpeg.ENOMEM); // -12 ffmpeg.AVERROR(ffmpeg.EPIPE); // -32 </pre> <br /> 실제로 av_samples_get_buffer_size의 소스 코드를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://ffmpeg.org/doxygen/trunk/samplefmt_8c_source.html#l00119'>https://ffmpeg.org/doxygen/trunk/samplefmt_8c_source.html#l00119</a> int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) { int line_size; int sample_size = av_get_bytes_per_sample(sample_fmt); int planar = av_sample_fmt_is_planar(sample_fmt); /* validate parameter ranges */ if (!sample_size || <span style='color: blue; font-weight: bold'>nb_samples <= 0</span> || nb_channels <= 0) <span style='color: blue; font-weight: bold'>return AVERROR(EINVAL);</span> ...[생략]... } </pre> <br /> (frame_size가 담긴) nb_samples가 0인 경우 AVERROR(EINVAL)을 반환합니다. 그리고 av_frame_get_buffer의 경우에도 frame->nb_samples의 값이 0보다 크지 않으면 AVERROR(EINVAL)을 반환합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://ffmpeg.org/doxygen/trunk/frame_8c_source.html#l00243'>https://ffmpeg.org/doxygen/trunk/frame_8c_source.html#l00243</a> int av_frame_get_buffer(AVFrame *frame, int align) { if (frame->format < 0) return AVERROR(EINVAL); if (frame->width > 0 && frame->height > 0) return get_video_buffer(frame, align); else if (<span style='color: blue; font-weight: bold'>frame->nb_samples > 0</span> && (frame->channel_layout || frame->channels > 0)) return get_audio_buffer(frame, align); <span style='color: blue; font-weight: bold'>return AVERROR(EINVAL);</span> } </pre> <br /> 이와 관련해 Q&A에 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffmpeg audio encoder frame size ; <a target='tab' href='https://stackoverflow.com/questions/60997236/ffmpeg-audio-encoder-frame-size'>https://stackoverflow.com/questions/60997236/ffmpeg-audio-encoder-frame-size</a> </pre> <br /> 코덱에 AV_CODEC_CAP_VARIABLE_FRAME_SIZE가 설정돼 있으면 avcodec_open2 호출에도 frame_size가 0으로 나온다고 합니다.<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;' > if ((codec->capabilities & ffmpeg.AV_CODEC_CAP_VARIABLE_FRAME_SIZE) == ffmpeg.AV_CODEC_CAP_VARIABLE_FRAME_SIZE) { Console.WriteLine("Codec: ffmpeg.AV_CODEC_CAP_VARIABLE_FRAME_SIZE"); } </pre> <br /> 저 조건이 걸리지 않습니다. 왜냐하면, codec->capabilities 값이 0x00080022이고, AV_CODEC_CAP_VARIABLE_FRAME_SIZE는 0x00010000이기 때문입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 밑져야 본전이라는 생각으로 ^^ 일부러 (mp2에서 구했던) 1152를 c->frame_size에 대입해 봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (c->frame_size == 0) { <span style='color: blue; font-weight: bold'>c->frame_size = 1152;</span> } <span style='color: blue; font-weight: bold'>frame->nb_samples = c->frame_size;</span> frame->format = (int)c->sample_fmt; frame->channel_layout = c->channel_layout; /* allocate the data buffers */ <span style='color: blue; font-weight: bold'>ret = ffmpeg.av_frame_get_buffer(frame, 0);</span> if (ret < 0) { Console.WriteLine("Could not allocate audio data buffers"); break; } </pre> <br /> 그랬더니 잘 동작합니다. ^^; 출력된 mp3 파일을 ffprobe로도 이렇게 확인할 수 있습니다.<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\output> <span style='color: blue; font-weight: bold'>ffprobe test.mp3</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-cxxflags=-MD libavutil 56. 70.100 / 56. 70.100 ...[생략]... libpostproc 55. 9.100 / 55. 9.100 [mp3 @ 000001EA6C3B0380] Estimating duration from bitrate, this may be inaccurate Input #0, mp3, from 'test.mp3': Duration: <span style='color: blue; font-weight: bold'>00:00:05.27</span>, start: 0.000000, <span style='color: blue; font-weight: bold'>bitrate: 96 kb/s</span> Stream #0:0: <span style='color: blue; font-weight: bold'>Audio: mp3</span>, 44100 Hz, stereo, fltp, 96 kb/s </pre> <br /> 그런데 좀 이상하군요, 분명히 소스 코드에서는 "c->bit_rate = 64000;"라고 지정했는데 위의 출력에서는 96kb/s로 나옵니다. (<a target='tab' href='https://www.sysnet.pe.kr/2/0/12937#mp2_probe'>MP2 인코딩 예제에서는 64kb/s</a>로 나옵니다.) 이와 함께 Duration도 mp2 인코딩에서는 5.22가 나왔지만 위의 경우 5.27로 나옵니다.<br /> <br /> 한 가지 더 이상한 것은, MP2의 경우 "c = ffmpeg.avcodec_alloc_context3(codec);" 실행 시 c->bit_rate가 0으로 나오기 때문에 명시적으로 64000을 설정하는 것이 의미가 있지만, MP3의 경우에는 동일한 코드에서 c->bit_rate가 이미 128000으로 초기화가 되어 있습니다. 그걸 강제로 64000으로 설정하는 것이 의미가 없는 걸까요? (그렇다고 해도 상식적으로, 동일한 데이터에 bitrate가 높게 설정되면 시간이 줄어야 할 듯한데... ^^;)<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;' > BASIC4MCU | 하드웨어 | AUDIO | MPEG1 Audio Layer3 / mp3 파일구조 ; <a target='tab' href='https://www.basic4mcu.com/bbs/board.php?bo_table=gesiyo8&wr_id=480'>https://www.basic4mcu.com/bbs/board.php?bo_table=gesiyo8&wr_id=480</a> </pre> <br /> "mp3는 프레임당 1152 비트가 할당되기 때문에 바이트 계산하면 144바이트가 된다."라는 설명이 나옵니다. 비디오의 경우에는 프레임이 하나의 영상 크기라는 것을 알겠지만, 오디오의 경우 frame_size가 명확히 다가오지 않습니다. 비디오 영상의 30fps라는 의미가 오디오에서는 보통 샘플링 주파수인 44100 Hz 등과 비교될 수 있을 것 같습니다. 그런 의미에서 어쩌면 오디오에서는 채널 수(스테레오 2채널)와 샘플링 비트(16비트 PCM)가 frame_size의 의미가 될 듯하지만, 1152와 32라는 숫자 간에 괴리감이 있습니다.<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;' > <span style='color: blue; font-weight: bold'>mp3는 프레임당 1152 비트가 할당</span>되기 때문에...[생략]... <span style='color: blue; font-weight: bold'>프레임당 약 9558바이트의 Size</span>가 된다. </pre> <br /> 음... 아무리 제가 이 분야로 초보라지만... ^^;<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 어쨌든 우여곡절 끝에 인코딩을 성공했으니 그럼 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12933'>지난번 예제에서 못했던 MP3 디코딩</a>을 해볼까요? ^^<br /> <br /> 그 글에서도 언급했지만, 해당 예제를 AV_CODEC_ID_MP3 식별자로 바꿔 avcodec_find_decoder를 호출하면 이후 avcodec_send_packet에서 ret 값이 -1094995529로 오류를 반환합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ret = ffmpeg.avcodec_send_packet(dec_ctx, packet); if (ret < 0) { Console.WriteLine($"Error submitting the packet to the decoder: {ret}"); return false; } </pre> <br /> 원래의 C 언어 예제에서는 위의 코드가 "return false"가 아닌 exit(1)로 되어 있기 때문에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ret = avcodec_send_packet(dec_ctx, pkt); if (ret < 0) { fprintf(stderr, "Error submitting the packet to the decoder\n"); exit(1); } </pre> <br /> 저도 C#으로 포팅하며 false를 반환한 경우 더 이상 디코딩 작업을 하지 않도록 변경한 것인데요. 재미있는 것은, 그냥 avcodec_send_packet에서 오류가 발생해도 그걸 무시하고 진행해도 된다는 점입니다. 즉, 그냥 true를 반환하도록 해서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ret = ffmpeg.avcodec_send_packet(dec_ctx, packet); if (ret < 0) { Console.WriteLine($"Error submitting the packet to the decoder: {ret}"); <span style='color: blue; font-weight: bold'>return true;</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;' > [mp3float @ 0000023F7FAD32C0] Header missing Error submitting the packet to the decoder: -1094995529 [mp3float @ 0000023F7FAD32C0] Header missing Error submitting the packet to the decoder: -1094995529 [mp3float @ 0000023F7FAD32C0] Header missing Error submitting the packet to the decoder: -1094995529 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 f32le -ac 2 -ar 44100 c:\temp\output\test_cs2.dat </pre> <br /> 3번의 "Header missing"이 나왔지만 그것에 상관없이 출력된 "test_cs2.dat" 파일은 정상적으로 ffplay로 재생이 됩니다.<br /> <br /> 대신 MP2 파일을 디코딩한 파일은 41,405KB 크기로 ffprobe의 결과가 adpcm_dtk였지만, MP3 파일의 경우에는 그 2배인 82,809KB에 ffprobe로는 결과가 나오지 않고,<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\output> <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: ...[생략]... --extra-cxxflags=-MD libavutil 56. 70.100 / 56. 70.100 ...[생략]... libpostproc 55. 9.100 / 55. 9.100 [luodat @ 0000026A8014D640] Format luodat detected only with low score of 1, misdetection possible! Input #0, luodat, from 'c:\temp\output\test_cs2.dat': Duration: N/A, bitrate: N/A </pre> <br /> 대신 ffplay로 재생할 때 pcm_f32le라고 나옵니다.<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\output> <span style='color: blue; font-weight: bold'>ffplay -f f32le -ac 2 -ar 44100 c:\temp\output\test_cs2.dat</span> ffplay version 4.4.1 Copyright (c) 2003-2021 the FFmpeg developers built with Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30705 for x64 configuration: ...[생략]... --extra-cxxflags=-MD libavutil 56. 70.100 / 56. 70.100 ...[생략]... libpostproc 55. 9.100 / 55. 9.100 [f32le @ 000001FFFE6A5340] Estimating duration from bitrate, this may be inaccurate Input #0, f32le, from 'c:\temp\output\test_cs2.dat': Duration: 00:04:00.35, bitrate: 2822 kb/s Stream #0:0: <span style='color: blue; font-weight: bold'>Audio: pcm_f32le</span>, 44100 Hz, 2 channels, flt, 2822 kb/s 49.46 M-A: 0.000 fd= 0 aq= 349KB vq= 0KB sq= 0B f=0/0 </pre> <br /> 어쨌든, 뭔가 석연치 않은 점들이 있지만 일단 MP3 파일에 대한 디코딩/인코딩은 완료했습니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1889&boardid=331301885'>첨부 파일은 MP3 디코딩/인코딩 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1467
(왼쪽의 숫자를 입력해야 합니다.)