성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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)를 이용한 오디오 필터링 예제(filtering_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)를 이용해 오디오(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 /> 이번에는 <a target='tab' href='https://ffmpeg.org/doxygen/trunk/examples.html'>ffmpeg 예제</a> 중 "<a target='tab' href='https://ffmpeg.org/doxygen/trunk/filtering_audio_8c-example.html'>filtering_audio.c</a>" 파일을 FFmpeg.AutoGen으로 포팅하겠습니다.<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; namespace FFmpegApp1 { internal unsafe class Program { [Flags] public enum AV_BUFFERSRC_FLAG { NO_CHECK_FORMAT = 1, FLAG_PUSH = 4, FLAG_KEEP_REF = 8, } 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(); AVFormatContext* fmt_ctx = null; AVCodecContext* dec_ctx = null; int audio_stream_index = -1; AVFilterGraph* filter_graph = null; string filter_descr = "aresample=8000,aformat=sample_fmts=s16:channel_layouts=mono"; AVFilterContext* buffersrc_ctx = null; AVFilterContext* buffersink_ctx = null; int ret; AVPacket* packet = ffmpeg.av_packet_alloc(); AVFrame* frame = ffmpeg.av_frame_alloc(); AVFrame* filter_frame = ffmpeg.av_frame_alloc(); if (packet == null || frame == null || filter_frame == null) { Console.WriteLine("Could not allocate frame or packet"); return; } string filePath = @"D:\media_sample\test.m4a"; do { if ((ret = open_input_file(filePath, &fmt_ctx, out audio_stream_index, &dec_ctx)) < 0) { Console.WriteLine("Could not allocate frame or packet"); break; } if ((ret = init_filters(filter_descr, fmt_ctx, audio_stream_index, &filter_graph, dec_ctx, &buffersrc_ctx, &buffersink_ctx)) < 0) { break; } while (true) { if ((ret = ffmpeg.av_read_frame(fmt_ctx, packet)) < 0) { break; } if (packet->stream_index == audio_stream_index) { ret = ffmpeg.avcodec_send_packet(dec_ctx, packet); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Error while sending a packet to the decoder\n"); break; } while (ret >= 0) { ret = ffmpeg.avcodec_receive_frame(dec_ctx, frame); if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF) { break; } else if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Error while receiving a frame from the decoder\n"); goto End_Of_Process; } if (ret >= 0) { if (ffmpeg.av_buffersrc_add_frame_flags(buffersrc_ctx, frame, (int)AV_BUFFERSRC_FLAG.FLAG_KEEP_REF) < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Error while feeding the audio filtergraph\n"); break; } while (true) { ret = ffmpeg.av_buffersink_get_frame(buffersink_ctx, filter_frame); if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF) { break; } if (ret < 0) { goto End_Of_Process; } print_frame(filter_frame); ffmpeg.av_frame_unref(filter_frame); } ffmpeg.av_frame_unref(frame); } } } ffmpeg.av_packet_unref(packet); } } while (false); End_Of_Process: if (filter_graph != null) { ffmpeg.avfilter_graph_free(&filter_graph); } if (dec_ctx != null) { ffmpeg.avcodec_free_context(&dec_ctx); } if (fmt_ctx != null) { ffmpeg.avformat_close_input(&fmt_ctx); } if (packet != null) { ffmpeg.av_packet_free(&packet); } if (frame != null) { ffmpeg.av_frame_free(&frame); } if (filter_frame != null) { ffmpeg.av_frame_free(&filter_frame); } } private static unsafe void print_frame(AVFrame* frame) { int n = frame->nb_samples * ffmpeg.av_get_channel_layout_nb_channels(frame->channel_layout); ushort* p = (ushort*)frame->data[0]; ushort* p_end = p + n; while (p < p_end) { Console.Write(*p & 0xFF); Console.Write((*p >> 8) & 0xFF); p++; } } private static unsafe int init_filters(string filters_descr, AVFormatContext* fmt_ctx, int audio_stream_index , AVFilterGraph** filter_graph, AVCodecContext* dec_ctx, AVFilterContext** buffersrc_ctx, AVFilterContext** buffersink_ctx) { string args = null; int ret = 0; AVFilter* aBufferSrc = ffmpeg.avfilter_get_by_name("abuffer"); AVFilter* aBufferSink = ffmpeg.avfilter_get_by_name("abuffersink"); AVFilterInOut* outputs = ffmpeg.avfilter_inout_alloc(); AVFilterInOut* inputs = ffmpeg.avfilter_inout_alloc(); AVSampleFormat[] out_sample_fmts = new AVSampleFormat[] { AVSampleFormat.AV_SAMPLE_FMT_S16, AVSampleFormat.AV_SAMPLE_FMT_NONE }; long[] out_channel_layouts = { ffmpeg.AV_CH_LAYOUT_MONO, -1 }; int[] out_sample_rates = { 8000, -1 }; AVFilterLink* outlink = null; AVRational time_base = fmt_ctx->streams[audio_stream_index]->time_base; do { *filter_graph = ffmpeg.avfilter_graph_alloc(); if (outputs == null || inputs == null || *filter_graph == null) { ret = ffmpeg.AVERROR(ffmpeg.ENOMEM); break; } if (dec_ctx->channel_layout == 0) { dec_ctx->channel_layout = (ulong)ffmpeg.av_get_default_channel_layout(dec_ctx->channels); } args = $"time_base={time_base.num}/{time_base.den}:sample_rate={dec_ctx->sample_rate}:sample_fmt={ffmpeg.av_get_sample_fmt_name(dec_ctx->sample_fmt)}:channel_layout=0x{dec_ctx->channel_layout.ToString("x")}"; ret = ffmpeg.avfilter_graph_create_filter(buffersrc_ctx, aBufferSrc, "in", args, null, *filter_graph); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot create audio buffer source\n"); break; } ret = ffmpeg.avfilter_graph_create_filter(buffersink_ctx, aBufferSink, "out", null, null, *filter_graph); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot create audio buffer sink\n"); break; } ret = av_opt_set_int_list(*buffersink_ctx, "sample_fmts", out_sample_fmts, -1, ffmpeg.AV_OPT_SEARCH_CHILDREN); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot set output sample format\n"); break; } ret = av_opt_set_int_list(*buffersink_ctx, "channel_layouts", out_channel_layouts, -1, ffmpeg.AV_OPT_SEARCH_CHILDREN); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot set output channel layout\n"); break; } ret = av_opt_set_int_list(*buffersink_ctx, "sample_rates", out_sample_rates, -1, ffmpeg.AV_OPT_SEARCH_CHILDREN); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot set output sample rate\n"); break; } outputs->name = ffmpeg.av_strdup("in"); outputs->filter_ctx = *buffersrc_ctx; outputs->pad_idx = 0; outputs->next = null; inputs->name = ffmpeg.av_strdup("out"); inputs->filter_ctx = *buffersink_ctx; inputs->pad_idx = 0; inputs->next = null; if ((ret = ffmpeg.avfilter_graph_parse_ptr(*filter_graph, filters_descr, &inputs, &outputs, null)) < 0) { break; } if ((ret = ffmpeg.avfilter_graph_config(*filter_graph, null)) < 0) { break; } outlink = (*buffersink_ctx)->inputs[0]; #pragma warning disable CA2014 // Do not use stackalloc in loops byte* buffer = stackalloc byte[512]; #pragma warning restore CA2014 // Do not use stackalloc in loops ffmpeg.av_get_channel_layout_string(buffer, 512, -1, outlink->channel_layout); string channel_layout_string = new string((sbyte*)buffer, 0, getStringSize(buffer, 512), System.Text.Encoding.ASCII); ffmpeg.av_log(null, ffmpeg.AV_LOG_INFO, $"Output: srate: {outlink->sample_rate}Hz fmt: {ffmpeg.av_get_sample_fmt_name((AVSampleFormat)outlink->format) ?? "?"} chlayout: {channel_layout_string}"); } while (false); if (inputs != null) { ffmpeg.avfilter_inout_free(&inputs); } if (outputs != null) { ffmpeg.avfilter_inout_free(&outputs); } return ret; } private static int getStringSize(byte* buffer, int length) { for (int i = 0; i < length; i++) { if (buffer[i] == 0) { return i; } } return 0; } private unsafe static int av_opt_set_int_list(AVFilterContext* obj, string name, long[] val, int term, int flags) { fixed (void* ptr = &val[0]) { return av_opt_set_int_list(obj, 8, name, ptr, term, flags); } } private unsafe static int av_opt_set_int_list(AVFilterContext* obj, string name, int[] val, int term, int flags) { fixed (void* ptr = &val[0]) { return av_opt_set_int_list(obj, 4, name, ptr, term, flags); } } private unsafe static int av_opt_set_int_list(AVFilterContext* obj, string name, AVSampleFormat[] val, int term, int flags) { fixed (void* ptr = &val[0]) { return av_opt_set_int_list(obj, 4, name, ptr, term, flags); } } private unsafe static int av_opt_set_int_list(AVFilterContext* obj, uint elementSize, string name, void* ptr, int term, int flags) { uint size = ffmpeg.av_int_list_length_for_size(elementSize, ptr, (ulong)term); if (size > (int.MaxValue / elementSize)) { return ffmpeg.AVERROR(ffmpeg.EINVAL); } else { return ffmpeg.av_opt_set_bin(obj, name, (byte*)ptr, (int)(size * elementSize), flags); } } private unsafe static int open_input_file(string filePath, AVFormatContext** fmt_ctx, out int audio_stream_index , AVCodecContext** dec_ctx) { AVCodec* decoder = null; int ret; audio_stream_index = -1; if ((ret = ffmpeg.avformat_open_input(fmt_ctx, filePath, null, null)) < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot open input file\n"); return ret; } if ((ret = ffmpeg.avformat_find_stream_info(*fmt_ctx, null)) < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot find stream information\n"); return ret; } ret = ffmpeg.av_find_best_stream(*fmt_ctx, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0); if (ret < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot find an audio stream in the input file"); return ret; } audio_stream_index = ret; *dec_ctx = ffmpeg.avcodec_alloc_context3(decoder); if (*dec_ctx == null) { return ffmpeg.AVERROR(ffmpeg.ENOMEM); } ffmpeg.avcodec_parameters_to_context(*dec_ctx, (*fmt_ctx)->streams[audio_stream_index]->codecpar); if ((ret = ffmpeg.avcodec_open2(*dec_ctx, decoder, null)) < 0) { ffmpeg.av_log(null, ffmpeg.AV_LOG_ERROR, "Cannot open audio decoder"); return ret; } return 0; } } } </pre> <br /> 위의 코드를 실행하면, print_frame의 영향으로 화면에는 ushort 숫자가 잔뜩 출력됩니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Output: srate: 8000Hz fmt: s16 chlayout: mono<br /> 0000000000...[생략]...000000000000000000<br /> </div><br /> <br /> filter_graph라는 단어가 Direct Show에서만 쓰는 것이 아니군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Direct Show를 사용하는 다른 프로그램의 필터 그래프를 graphedt.exe에서 확인하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1652'>https://www.sysnet.pe.kr/2/0/1652</a> </pre> <br /> 그러니까, Direct Show에서 PIN을 연결하듯이 필터를 구성했던 방식과 유사한 듯합니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1894&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> (<a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/filtering_audio'>이 글의 소스 코드는 github에 올려</a>져 있습니다.)<br /> <br /> <hr style='width: 50%' /><br /> <a name='get_decoder'></a> <br /> filtering_video.c를 보면, AVCodec을 더 편하게 얻는 방법이 나옵니다. 지난 글에서는,<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) AVFormatContext를 이용해 ffprobe처럼 정보 출력 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12948'>https://www.sysnet.pe.kr/2/0/12948</a> </pre> <br /> av_find_best_stream을 스트림 타입에 해당하는 최적의 index 값을 반환받아,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int videoIndex = -1; int audioIndex = -1; { videoIndex = ffmpeg.av_find_best_stream(av_context, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, null, 0); audioIndex = ffmpeg.av_find_best_stream(av_context, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1 /* 또는 videoIndex */, null, 0); ffmpeg.av_dump_format(av_context, videoIndex, filePath, 0); } </pre> <br /> 그다음 이에 대한 코덱을 열려고 다음과 같이 streams를 열거하면서 했는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > for (int i = 0; i < av_context->nb_streams; i++) { if (videoIndex == i) { AVStream* stream = av_context->streams[i]; AVCodecParameters* codecpar = stream->codecpar; AVCodec* decoder = ffmpeg.avcodec_find_decoder(codecpar->codec_id); } } </pre> <br /> open_input_file을 보면, 그럴 필요 없이 그냥 바로 av_find_best_stream에서 코덱 인스턴스를 받고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AVCodec* decoder = null; ret = ffmpeg.av_find_best_stream(fmt_ctx, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, <span style='color: blue; font-weight: bold'>&decoder</span>, 0); </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1925
(왼쪽의 숫자를 입력해야 합니다.)