(*
  * Copyright (c) 2010 Nicolas George
  * Copyright (c) 2011 Stefano Sabatini
  * Copyright (c) 2012 Cl¨Śment B?sch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
*)

(* *
  * @file
  * API example for audio decoding and filtering
  * @example filtering_audio.c
*)

program filtering_audio;

{$APPTYPE CONSOLE}
{$POINTERMATH ON}
{$MINENUMSIZE 4} (* use 4-byte enums *)

uses
  Winapi.Windows,
  System.SysUtils,
  System.Math,
  ffmpeg_types,
  libavcodec,
  libavdevice,
  libavfilter,
  libavformat,
  libavutil,
  libpostproc,
  libswresample,
  libswscale;

const
  filter_descr = 'aresample=8000,aformat=sample_fmts=s16:channel_layouts=mono';
  player = 'ffplay -f s16le -ar 8000 -ac 1 -';

var
  fmt_ctx: PAVFormatContext;
  dec_ctx: PAVCodecContext;
  buffersink_ctx: PAVFilterContext;
  buffersrc_ctx: PAVFilterContext;
  filter_graph: PAVFilterGraph;
  audio_stream_index: Integer = -1;

function snprintf(buf: PAnsiChar; size: Cardinal; const fmt: PAnsiChar): Integer;
cdecl varargs;
external 'msvcrt' name '_snprintf';

  function open_input_file(const filename: string): Integer;
  var
    ret: Integer;
    avdec: PAVCodec;
  begin
    ret := avformat_open_input(fmt_ctx, PAnsiChar(AnsiString(filename)), nil, nil);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot open input file'#10);
      Result := ret;
      Exit;
    end;

    ret := avformat_find_stream_info(fmt_ctx, nil);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot find stream information'#10);
      Result := ret;
      Exit;
    end;

    (* select the audio stream *)
    ret := av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, @avdec, 0);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot find an audio stream in the input file'#10);
      Result := ret;
      Exit;
    end;
    audio_stream_index := ret;

    (* create decoding context *)
    dec_ctx := avcodec_alloc_context3(avdec);
    if not Assigned(dec_ctx) then
    begin
      Result := AVERROR_ENOMEM;
      Exit;
    end;
    avcodec_parameters_to_context(dec_ctx, fmt_ctx.streams[audio_stream_index].codecpar);
    av_opt_set_int(dec_ctx, 'refcounted_frames', 1, 0);

    (* init the audio decoder *)
    ret := avcodec_open2(dec_ctx, avdec, nil);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot open audio decoder'#10);
      Result := ret;
      Exit;
    end;

    Result := 0;
  end;

  function init_filters(const filters_descr: PAnsiChar): Integer;
  const
    out_sample_fmts: array [0 .. 1] of AVSampleFormat = (AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE { -1 } );
    out_channel_layouts: array [0 .. 1] of Int64 = (AV_CH_LAYOUT_MONO, -1);
    out_sample_rates: array [0 .. 1] of Integer = (8000, -1);
  var
    args: array [0 .. 512 - 1] of AnsiChar;
    ret: Integer;
    abuffersrc: PAVFilter;
    abuffersink: PAVFilter;
    outputs: PAVFilterInOut;
    inputs: PAVFilterInOut;
    outlink: PAVFilterLink;
    time_base: AVRational;
    I64: Int64;
  label
    the_end;
  begin
    abuffersrc := avfilter_get_by_name('abuffer');
    abuffersink := avfilter_get_by_name('abuffersink');
    outputs := avfilter_inout_alloc();
    inputs := avfilter_inout_alloc();
    time_base := fmt_ctx.streams[audio_stream_index].time_base;

    filter_graph := avfilter_graph_alloc();
    if not Assigned(outputs) or not Assigned(inputs) or not Assigned(filter_graph) then
    begin
      ret := AVERROR_ENOMEM;
      goto the_end;
    end;

    (* buffer audio source: the decoded frames from the decoder will be inserted here. *)
    if dec_ctx.channel_layout = 0 then
      dec_ctx.channel_layout := av_get_default_channel_layout(dec_ctx.channels);
    I64 := dec_ctx.channel_layout;
    snprintf(@args[0], SizeOf(args), 'time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x', time_base.num, time_base.den,
      dec_ctx.sample_rate, av_get_sample_fmt_name(dec_ctx.sample_fmt), I64);
    ret := avfilter_graph_create_filter(buffersrc_ctx, abuffersrc, 'in', @args[0], nil, filter_graph);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot create audio buffer source'#10);
      goto the_end;
    end;

    (* buffer audio sink: to terminate the filter chain. *)
    ret := avfilter_graph_create_filter(buffersink_ctx, abuffersink, 'out', nil, nil, filter_graph);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot create audio buffer sink'#10);
      goto the_end;
    end;

    ret := av_opt_set_int_list(buffersink_ctx, 'sample_fmts', @out_sample_fmts[0], SizeOf(AVSampleFormat), -1, AV_OPT_SEARCH_CHILDREN);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot set output sample format'#10);
      goto the_end;
    end;

    ret := av_opt_set_int_list(buffersink_ctx, 'channel_layouts', @out_channel_layouts[0], SizeOf(Int64), -1, AV_OPT_SEARCH_CHILDREN);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot set output channel layout'#10);
      goto the_end;
    end;

    ret := av_opt_set_int_list(buffersink_ctx, 'sample_rates', @out_sample_rates, SizeOf(Integer), -1, AV_OPT_SEARCH_CHILDREN);
    if ret < 0 then
    begin
      av_log(nil, AV_LOG_ERROR, 'Cannot set output sample rate'#10);
      goto the_end;
    end;

    (*
      * Set the endpoints for the filter graph. The filter_graph will
      * be linked to the graph described by filters_descr.
    *)

    (*
      * The buffer source output must be connected to the input pad of
      * the first filter described by filters_descr; since the first
      * filter input label is not specified, it is set to "in" by
      * default.
    *)
    outputs.name := av_strdup('in');
    outputs.filter_ctx := buffersrc_ctx;
    outputs.pad_idx := 0;
    outputs.next := nil;

    (*
      * The buffer sink input must be connected to the output pad of
      * the last filter described by filters_descr; since the last
      * filter output label is not specified, it is set to "out" by
      * default.
    *)
    inputs.name := av_strdup('out');
    inputs.filter_ctx := buffersink_ctx;
    inputs.pad_idx := 0;
    inputs.next := nil;

    ret := avfilter_graph_parse_ptr(filter_graph, filters_descr, inputs, outputs, nil);
    if ret < 0 then
      goto the_end;

    ret := avfilter_graph_config(filter_graph, nil);
    if ret < 0 then
      goto the_end;

    (* Print summary of the sink buffer
      * Note: args buffer is reused to store channel layout string *)
    outlink := buffersink_ctx.inputs^;
    av_get_channel_layout_string(@args[0], SizeOf(args), -1, outlink.channel_layout);
    av_log(nil, AV_LOG_INFO, 'Output: srate:%dHz fmt:%s chlayout:%s'#10, outlink.sample_rate,
      av_x_if_null(av_get_sample_fmt_name(AVSampleFormat(outlink.format)), PAnsiChar('?')), @args[0]);

  the_end:
    avfilter_inout_free(inputs);
    avfilter_inout_free(outputs);

    Result := ret;
  end;

  procedure print_frame(const frame: PAVFrame);
  var
    n: Integer;
    p: PSmallInt;
    p_end: PSmallInt;
  begin
    n := frame.nb_samples * av_get_channel_layout_nb_channels(frame.channel_layout);
    p := PSmallInt(frame.data[0]);
    p_end := p;
    Inc(p_end, n);

    while Integer(p) < Integer(p_end) do
    begin
      Write(AnsiChar(p^ and $FF));
      Write(AnsiChar((p^ shr 8) and $FF));
      Inc(p);
    end;
  end;

  function main(): Integer;
  var
    ret: Integer;
    packet: AVPacket;
    frame: PAVFrame;
    filt_frame: PAVFrame;
  label
    the_end;
  begin
    frame := av_frame_alloc();
    filt_frame := av_frame_alloc();
    if not Assigned(frame) or not Assigned(filt_frame) then
    begin
      Writeln(ErrOutput, 'Could not allocate frame');
      Result := 1;
      Exit;
    end;
    if ParamCount <> 1 then
    begin
      Writeln(ErrOutput, format('Usage: %s file | %s', [ExtractFileName(ParamStr(0)), player]));
      Result := 1;
      Exit;
    end;

    av_register_all();
    avfilter_register_all();

    ret := open_input_file(PAnsiChar(AnsiString(ParamStr(1))));
    if ret < 0 then
      goto the_end;
    ret := init_filters(filter_descr);
    if ret < 0 then
      goto the_end;

    (* read all packets *)
    while True do
    begin
      ret := av_read_frame(fmt_ctx, @packet);
      if ret < 0 then
        Break;

      if packet.stream_index = audio_stream_index then
      begin
        ret := avcodec_send_packet(dec_ctx, @packet);
        if ret < 0 then
        begin
          av_log(nil, AV_LOG_ERROR, 'Error while sending a packet to the decoder'#10);
          Break;
        end;

        while ret >= 0 do
        begin
          ret := avcodec_receive_frame(dec_ctx, frame);
          if (ret = AVERROR_EAGAIN) or (ret = AVERROR_EOF) then
            Break
          else if ret < 0 then
          begin
            av_log(nil, AV_LOG_ERROR, 'Error while receiving a frame from the decoder'#10);
            goto the_end;
          end;

          if ret >= 0 then
          begin
            (* push the audio data from decoded frame into the filtergraph *)
            if av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0 then
            begin
              av_log(nil, AV_LOG_ERROR, 'Error while feeding the audio filtergraph'#10);
              Break;
            end;

            (* pull filtered audio from the filtergraph *)
            while True do
            begin
              ret := av_buffersink_get_frame(buffersink_ctx, filt_frame);
              if (ret = AVERROR_EAGAIN) or (ret = AVERROR_EOF) then
                Break;
              if ret < 0 then
                goto the_end;
              print_frame(filt_frame);
              av_frame_unref(filt_frame);
            end;
            av_frame_unref(frame);
          end;
        end;
      end;
      av_packet_unref(@packet);
    end;
  the_end:
    avfilter_graph_free(filter_graph);
    avcodec_free_context(dec_ctx);
    avformat_close_input(fmt_ctx);
    av_frame_free(frame);
    av_frame_free(filt_frame);

    if (ret < 0) and (ret <> AVERROR_EOF) then
    begin
      Writeln(ErrOutput, format('Error occurred: %s', [string(av_err2str(ret))]));
      Result := 1;
      Exit;
    end;

    Result := 0;
  end;

begin
  try
    ExitCode := main();
  except
    on E: Exception do
      Writeln(ErrOutput, E.ClassName, ': ', E.Message);
  end;

end.
