blob: 10ef054f894e13ed11ad8cd5a25894172c5cd2ad [file] [log] [blame]
/* GStreamer
* Some extracts from GStreamer Plugins Base, which is:
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* Copyright 2005 Wim Taymans <wim@fluendo.com>
* Copyright 2020 Amlogic, Inc.
* Here licensed under the GNU Lesser General Public License, version 2.1
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free SoftwareFoundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
/**
* SECTION:element-gstamlhalasink
*
* The amlhalasink element connects to Amlogic audio HAL service
* and provide android like audio features.
*
*/
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
#include <gst/audio/audio.h>
#include <sys/prctl.h>
#include <stdlib.h>
#include <time.h>
#include <audio_if_client.h>
#include "gstamlhalasink_new.h"
#include "gstamlclock.h"
#include "ac4_frame_parse.h"
#include "scaletempo.h"
#include "aml_avsync.h"
#include "aml_avsync_log.h"
#include "aml_version.h"
#include "mediasync_wrap.h"
#include "gstparam_time_pair.h"
#ifdef ESSOS_RM
#include "essos-resmgr.h"
#endif
#ifdef SUPPORT_AD
#include "gstmetapesheader.h"
#include "pes_private_data.h"
#endif
#define G_ATOMIC_LOCK_FREE
GST_DEBUG_CATEGORY (gst_aml_hal_asink_debug_category);
#define GST_CAT_DEFAULT gst_aml_hal_asink_debug_category
#define GST_AML_HAL_ASINK_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_AML_HAL_ASINK, GstAmlHalAsinkPrivate))
//#define DUMP_TO_FILE
#define DEFAULT_VOLUME 1.0
#define MAX_VOLUME 1.0
#define MAX_TRANS_BUF_SIZE 0x10000
#define TRANS_DATA_OFFSET 0x40
//32KB
#define TRANS_DATA_SIZE (MAX_TRANS_BUF_SIZE - TRANS_DATA_OFFSET)
#define is_raw_type(type) (type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW)
#define EXTEND_BUF_SIZE (4096*2*2)
#define DEFAULT_BUFFER_TIME ((200 * GST_MSECOND) / GST_USECOND)
#define DEFAULT_LATENCY_TIME ((10 * GST_MSECOND) / GST_USECOND)
#define GST_AUDIO_FORMAT_TYPE_AC4 100
#define GST_AUDIO_FORMAT_TYPE_VORBIS 101
#define GST_AUDIO_FORMAT_TYPE_LPCM_PRIV1 102
#define GST_AUDIO_FORMAT_TYPE_LPCM_PRIV2 103
#define GST_AUDIO_FORMAT_TYPE_LPCM_TS 104
#define GST_AUDIO_FORMAT_TYPE_TRUE_HD 105
#define MAX_COMMIT_BYTES 30*1024
#define MAX_COMMIT_COUNT 70
#define PTS_90K 90000
#define HAL_INVALID_PTS (GST_CLOCK_TIME_NONE - 1)
static const char kCustomInstantRateChangeEventName[] = "custom-instant-rate-change";
#ifdef DUMP_TO_FILE
static guint file_index;
#endif
struct _GstAmlHalAsinkPrivate
{
audio_hw_device_t *hw_dev_;
uint32_t output_port_;
uint32_t direct_mode_;
gboolean sys_sound_;
gboolean tts_mode_;
/* current cap */
GstAudioRingBufferSpec spec;
/* tureHD*/
guchar *commit_data;
gsize commit_size;
guint64 commit_time;
gint commit_count;
/* condition lock for chain and other threads */
GCond run_ready;
GMutex feed_lock;
/* output stream */
struct audio_stream_out *stream_;
audio_format_t format_;
uint32_t sr_;
audio_channel_mask_t channel_mask_;
gboolean paused_;
gboolean flushing_;
gboolean sync_mode;
gboolean received_eos;
gboolean eos;
guint32 seqnum; /* for eos */
/* for bit stream */
guint encoded_size;
guint sample_per_frame;
guint frame_sent;
gboolean sync_frame;
/* for header attaching */
uint8_t *trans_buf;
gboolean quit_clock_wait;
GstClockTime eos_time;
GstClockTime eos_end_time;
GstClockTime last_ts;
guint64 render_samples;
/* for stats */
guint64 rendered_frames;
guint64 dropped_frames;
/* for position */
guint wrapping_time;
uint32_t last_pcr;
uint32_t first_pts;
guint64 first_pts_64;
gboolean first_pts_set;
GstSegment segment;
/* curent stream group */
guint group_id;
gboolean group_done;
/* tempo stretch */
struct scale_tempo st;
gboolean tempo_used;
float rate;
gboolean need_update_rate;
/* pause pts */
uint32_t pause_pts;
/* mute */
gboolean mute;
gboolean mute_pending;
float stream_volume_bak;
/* stream volume */
gboolean stream_volume_pending;
float stream_volume;
/* underrun detection */
GThread *xrun_thread;
gboolean quit_xrun_thread;
GTimer *xrun_timer;
gboolean xrun_paused;
gboolean disable_xrun;
#ifdef ESSOS_RM
GMutex ess_lock;
EssRMgr *rm;
int resAssignedId;
#endif
/* thread priority */
gboolean pri_set;
/* debugging */
gboolean diag_log_enable;
char *log_path;
/* pts gap info, pts/duration in ms unit */
int gap_state;
int64_t gap_start_pts;
int32_t gap_duration;
uint64_t gap_offset;
/* avsync */
void * avsync;
int session_id;
GstClock *provided_clock;
gboolean wait_video;
int aligned_timeout;
GstBuffer *start_buf; /* pcr master mode only */
gboolean start_buf_sent;
gboolean seamless_switch;
gboolean tempo_disable; /* disable tempo use */
gboolean ms12_enable;
#ifdef ENABLE_MS12
/* ac4 config */
int ac4_pres_group_idx;
int ac4_pat;
int ac4_ass_type;
int ac4_mixer_gain;
gchar *ac4_lang;
gchar *ac4_lang2;
int ms12_mix_en;
#endif
#ifdef SUPPORT_AD
gboolean is_dual_audio;
gboolean is_ad_audio;
struct ad_des des_ad;
#endif
guint64 clip_front;
guint64 clip_back;
/*audio metric info*/
gint64 drop_start;
gint64 drop_duration;
};
enum
{
GAP_IDLE,
GAP_MUTING_1,
GAP_MUTING_2,
GAP_RAMP_UP
};
enum
{
RAMP_DOWN,
RAMP_UP
};
enum
{
PROP_0,
PROP_DIRECT_MODE,
PROP_SYS_SOUND,
PROP_TTS_MODE,
PROP_OUTPUT_PORT,
PROP_MASTER_VOLUME,
PROP_STREAM_VOLUME,
PROP_MUTE,
PROP_AVSYNC_MODE,
PROP_PAUSE_PTS,
PROP_DISABLE_XRUN_TIMER,
PROP_GAP_START_PTS,
PROP_GAP_DURATION,
PROP_AVSYNC_SESSION,
PROP_WAIT_FOR_VIDEO,
PROP_SEAMLESS_SWITCH,
PROP_DISABLE_TEMPO_STRETCH,
PROP_TIME_PAIR,
#ifdef ENABLE_MS12
/* AC4 config */
PROP_AC4_P_GROUP_IDX,
PROP_AC4_LANG_1,
PROP_AC4_LANG_2,
PROP_AC4_AUTO_S_PRI,
PROP_AC4_ASS_TYPE,
PROP_AC4_MIXER_BALANCE,
PROP_MS12_MIX_EN,
#endif
#ifdef SUPPORT_AD
PROP_DUAL_AUDIO,
PROP_AD_AUDIO,
#endif
PROP_A_WAIT_TIMEOUT,
PROP_STATS,
PROP_LAST
};
enum
{
SIGNAL_PAUSEPTS,
SIGNAL_XRUN,
SIGNAL_AUDSWITCH,
SIGNAL_DROPPTS,
MAX_SIGNAL
};
typedef enum
{
POS_WALL = 0,
POS_APTS,
} pos_t;
#define COMMON_AUDIO_CAPS \
"channels = (int) [ 1, MAX ], " \
"rate = (int) [ 1, MAX ]"
/* pad templates */
static GstStaticPadTemplate gst_aml_hal_asink_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-raw,format=S16LE,rate=48000,"
"channels={2,3,4,5,6,7,8},layout=interleaved; "
"audio/x-ac3, "
COMMON_AUDIO_CAPS "; "
"audio/x-eac3, "
COMMON_AUDIO_CAPS "; "
"audio/x-ac4; "
"audio/x-true-hd; "
#ifdef ENABLE_DTS
"audio/x-dts; "
"audio/x-gst-fourcc-dtse;"
"audio/x-dtsl;"
"audio/x-gst-fourcc-dtsx;"
#endif
"audio/x-private1-lpcm; "
"audio/x-private2-lpcm; "
"audio/x-private-ts-lpcm; "
)
);
static GstStaticPadTemplate template_system_sound =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-raw,format=S16LE,rate=48000,"
"channels=2,layout=interleaved; "
)
);
#define GST_TYPE_AHAL_OUTPUT_PORT \
(gst_ahal_output_port_get_type ())
static GType
gst_ahal_output_port_get_type (void)
{
static GType ahal_output_port_type = 0;
if (!ahal_output_port_type) {
static const GEnumValue ahal_output_port[] = {
{0, "Speaker", "speaker"},
{1, "HDMI-Tx", "hdmitx"},
{2, "HDMI ARC", "hdmi-arc"},
{3, "SPDIF", "spdif"},
{0, NULL, NULL},
};
ahal_output_port_type =
g_enum_register_static ("AmlAsinkOutputPort", ahal_output_port);
}
return ahal_output_port_type;
}
/* class initialization */
#define gst_aml_hal_asink_parent_class parent_class
#if GLIB_CHECK_VERSION(2,58,0)
G_DEFINE_TYPE_WITH_CODE (GstAmlHalAsink, gst_aml_hal_asink, GST_TYPE_BASE_SINK,
GST_DEBUG_CATEGORY_INIT (gst_aml_hal_asink_debug_category, "amlhalasink", 0,
"debug category for amlhalasink element");G_ADD_PRIVATE(GstAmlHalAsink));
#else
G_DEFINE_TYPE_WITH_CODE (GstAmlHalAsink, gst_aml_hal_asink, GST_TYPE_BASE_SINK,
GST_DEBUG_CATEGORY_INIT (gst_aml_hal_asink_debug_category, "amlhalasink", 0,
"debug category for amlhalasink element");
G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL)
);
#endif
static guint g_signals[MAX_SIGNAL]= {0};
static gboolean gst_aml_hal_asink_open (GstAmlHalAsink* sink);
static gboolean gst_aml_hal_asink_close (GstAmlHalAsink* asink);
static void gst_aml_hal_asink_dispose(GObject * object);
static void gst_aml_hal_asink_set_property(GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_aml_hal_asink_get_property(GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_aml_hal_asink_change_state(GstElement *
element, GstStateChange transition);
static gboolean gst_aml_hal_asink_query(GstElement * element, GstQuery *
query);
static GstClock *gst_aml_hal_asink_provide_clock(GstElement * elem);
static inline void gst_aml_hal_asink_reset_sync (GstAmlHalAsink * sink, gboolean keep_position);
static GstClockTime gst_aml_hal_asink_get_time(GstClock * clock, GstAmlHalAsink * sink);
static GstFlowReturn gst_aml_hal_asink_render (GstAmlHalAsink * sink, GstBuffer * buffer);
static GstFlowReturn gst_aml_hal_asink_chain (GstPad * pad, GstObject * parent, GstBuffer * buf);
static gboolean gst_aml_hal_asink_event(GstAmlHalAsink *sink, GstEvent * event);
static gboolean gst_aml_hal_asink_pad_event (GstPad * pad, GstObject * parent, GstEvent * event);
static GstFlowReturn gst_aml_hal_asink_wait_event(GstBaseSink * bsink,
GstEvent * event);
static void gst_aml_hal_asink_get_times(GstBaseSink * bsink,
GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
static gboolean gst_aml_hal_asink_setcaps (GstAmlHalAsink * sink,
GstCaps * caps, gboolean force_change);
static gboolean hal_open_device (GstAmlHalAsink * sink);
static gboolean hal_close_device (GstAmlHalAsink* sink);
static gboolean hal_acquire (GstAmlHalAsink * sink, GstAudioRingBufferSpec * spec);
static gboolean hal_release (GstAmlHalAsink * sink);
static gboolean hal_start (GstAmlHalAsink * sink);
static gboolean hal_pause (GstAmlHalAsink * sink);
static gboolean hal_stop (GstAmlHalAsink * sink);
static guint hal_commit (GstAmlHalAsink * sink, guchar * data, gint size, guint64 pts_64);
static uint32_t hal_get_latency (GstAmlHalAsink * sink);
static void dump(const char* path, const uint8_t *data, int size);
static int create_av_sync(GstAmlHalAsink *sink);
static void stop_xrun_thread (GstAmlHalAsink * sink);
#if 0
static int get_sysfs_uint32(const char *path, uint32_t *value);
static int config_sys_node(const char* path, const char* value);
#endif
static void check_pause_pts (GstAmlHalAsink *sink, GstClockTime ts);
static void vol_ramp(guchar * data, gint size, gint ch_num, int dir);
#ifdef ENABLE_MS12
static void hal_set_player_overwrite (GstAmlHalAsink * sink, gboolean defaults);
#endif
static void hal_get_metric_info (GstAmlHalAsink * sink);
static void check_drop_pts (GstAmlHalAsink *sink);
static void
gst_aml_hal_asink_class_init (GstAmlHalAsinkClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseSinkClass *gstbasesink_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstbasesink_class = (GstBaseSinkClass *) klass;
#if GLIB_CHECK_VERSION(2,58,0)
#else
g_type_class_add_private (klass, sizeof (GstAmlHalAsinkPrivate));
#endif
/* Setting up pads and setting metadata should be moved to
base_class_init if you intend to subclass this class. */
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS(klass),
&gst_aml_hal_asink_sink_template);
gst_element_class_set_static_metadata (GST_ELEMENT_CLASS(klass),
"Amlogic audio HAL sink", "Sink/Audio", "gstream plugin to connect AML audio HAL",
"song.zhao@amlogic.com");
gobject_class->set_property = gst_aml_hal_asink_set_property;
gobject_class->get_property = gst_aml_hal_asink_get_property;
gobject_class->dispose = gst_aml_hal_asink_dispose;
g_object_class_install_property (gobject_class, PROP_OUTPUT_PORT,
g_param_spec_enum ("output-port", "Output Port",
"select active output port for audio",
GST_TYPE_AHAL_OUTPUT_PORT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_DIRECT_MODE,
g_param_spec_boolean ("direct-mode", "Direct Mode",
"Select this mode for main mixing port, unselect it for system sound mixing port",
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SYS_SOUND,
g_param_spec_boolean ("sys-sound", "SYS Sound",
"Select this mode for system sound, this mode doesnt have AV sync control",
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_TTS_MODE,
g_param_spec_boolean ("tts-mode", "TTS Mode",
"Select this mode for text to speech, this mode doesnt have AV sync control",
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_MASTER_VOLUME,
g_param_spec_double ("volume", "Stream volume, the same as stream-volume",
"Linear volume of current stream, 1.0=100%", 0.0, MAX_VOLUME,
DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_STREAM_VOLUME,
g_param_spec_double ("stream-volume", "Stream volume",
"Linear volume of curernt stream, 1.0=100%", 0.0, MAX_VOLUME,
DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_MUTE,
g_param_spec_boolean ("mute", "Mute",
"Mute state of system", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_AVSYNC_MODE,
g_param_spec_uint ("avsync-mode", "Avsync mode",
"Set avsync mode 0 (amaster) 1 (pcr master) 2 (iptv) 4 (freerun)",
0, 4, 0, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PAUSE_PTS,
g_param_spec_uint ("pause-pts", "pause pts",
"notify arrival of pts (90KHz), caller should pause the sink, set it in READY state",
0, G_MAXUINT, 0, G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAP_START_PTS,
g_param_spec_int64 ("gap-start-pts", "gap start pts",
"notify arrival of pts discontinuity start point in ms",
G_MININT64, G_MAXINT64, -1, G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAP_DURATION,
g_param_spec_int ("gap-duration", "gap duration",
"notify pts discontinuity length in ms",
0, G_MAXINT, 0, G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_DISABLE_XRUN_TIMER,
g_param_spec_boolean ("disable-xrun", "Disable underrun timer auto pause",
"obsolete property will remove after v2.0",
TRUE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_AVSYNC_SESSION,
g_param_spec_int ("avsync-session", "avsync session",
"avsync session id to link video and audio. If set, this sink won't create clock",
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_WAIT_FOR_VIDEO,
g_param_spec_boolean ("wait-video", "Wait for video to start",
"Audio needs to align with video at starting point", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_SEAMLESS_SWITCH,
g_param_spec_boolean ("seamless-switch", "Seamless switch audio",
"Seamless switch audio channels on different formats", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_DISABLE_TEMPO_STRETCH,
g_param_spec_boolean ("disable-tempo-stretch",
"Disable tempo stretch", "Disable the tempo stretch process", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_TIME_PAIR,
gst_param_spec_time_pair ("pts-mono-pair",
"pts mono pair", "pts and system mono time pair",
0, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
#ifdef ENABLE_MS12
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_AC4_P_GROUP_IDX,
g_param_spec_int ("ac4-presentation-group-index", "ac4 presentation group index",
"Presentation group index to be decoded. Overrides the presentation selection by preferred language and associated type 0...%d: Presentation group index -1: Switch back to automatic selection by language and associated type (default)",
-1, G_MAXINT, -1, G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_AC4_AUTO_S_PRI,
g_param_spec_string ("ac4-auto-selection-priority", "ac4 auto selection priority",
"language: Prefer selection by language, associated_type: Prefer selection by associated type, default", "default", G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_AC4_LANG_1,
g_param_spec_string ("ac4-preferred-lang1", "ac4 preferred lang1",
"1st Preferred Language code (3 Letter ISO 639), DEF for global setting",
"null", G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_AC4_LANG_2,
g_param_spec_string ("ac4-preferred-lang2", "ac4 preferred lang2",
"2nd Preferred Language code (3 Letter ISO 639), DEF for global setting",
"null", G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_AC4_ASS_TYPE,
g_param_spec_string ("ac4-associated-type", "ac4 associated type",
"Preferred associated type of service: default, visually_impaired, hearing_impaired, commentary",
"null", G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_AC4_MIXER_BALANCE,
g_param_spec_int ("ac4-mixer-balance", "ac4 mixer balance",
"mixer gain control. -32(mute assoc) <--> 32(mute main), 0 for user default",
-32, 255, 255, G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class,
PROP_MS12_MIX_EN,
g_param_spec_int ("ac4-associated-audio-mixing-enable", "ac4 associated audio mixing enable",
"enable disable ac4 associated audio, 0 (off), 1 (on), 1 (default)",
0, 255, 255, G_PARAM_WRITABLE));
#endif
#ifdef SUPPORT_AD
g_object_class_install_property (gobject_class, PROP_DUAL_AUDIO,
g_param_spec_boolean ("enable-dual-audio", "enable dual audio",
"Enable dual audio decode, apply on to main instance",
FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_AD_AUDIO,
g_param_spec_boolean ("supplementary-audio", "audio description",
"Description audio associated with main audio",
FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
#endif
g_object_class_install_property (gobject_class,
PROP_A_WAIT_TIMEOUT,
g_param_spec_int ("a-wait-timeout", "audio timeout when video not come",
"audio wait for video timeout if no video comes, effective when wait-video property is true.",
-1, 10000, 0, G_PARAM_WRITABLE));
#if GST_CHECK_VERSION(1, 18, 0)
g_object_class_override_property (gobject_class, PROP_STATS, "stats");
#else
g_object_class_install_property (gobject_class, PROP_STATS,
g_param_spec_boxed ("stats", "Statistics",
"Sink Statistics", GST_TYPE_STRUCTURE,
(GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
#endif
g_signals[SIGNAL_PAUSEPTS]= g_signal_new( "pause-pts-callback",
G_TYPE_FROM_CLASS(GST_ELEMENT_CLASS(klass)),
(GSignalFlags) (G_SIGNAL_RUN_LAST),
0, /* class offset */
NULL, /* accumulator */
NULL, /* accu data */
g_cclosure_marshal_VOID__UINT_POINTER,
G_TYPE_NONE,
2,
G_TYPE_UINT,
G_TYPE_POINTER);
g_signals[SIGNAL_XRUN]= g_signal_new( "underrun-callback",
G_TYPE_FROM_CLASS(GST_ELEMENT_CLASS(klass)),
(GSignalFlags) (G_SIGNAL_RUN_LAST),
0, /* class offset */
NULL, /* accumulator */
NULL, /* accu data */
g_cclosure_marshal_VOID__UINT_POINTER,
G_TYPE_NONE,
2,
G_TYPE_UINT,
G_TYPE_POINTER);
g_signals[SIGNAL_AUDSWITCH]= g_signal_new( "seamless-switch-callback",
G_TYPE_FROM_CLASS(GST_ELEMENT_CLASS(klass)),
(GSignalFlags) (G_SIGNAL_RUN_LAST),
0, /* class offset */
NULL, /* accumulator */
NULL, /* accu data */
g_cclosure_marshal_VOID__UINT_POINTER,
G_TYPE_NONE,
2,
G_TYPE_UINT,
G_TYPE_POINTER);
g_signals[SIGNAL_DROPPTS]= g_signal_new( "audio-frame-drops",
G_TYPE_FROM_CLASS(GST_ELEMENT_CLASS(klass)),
(GSignalFlags) (G_SIGNAL_RUN_LAST),
0, /* class offset */
NULL, /* accumulator */
NULL, /* accu data */
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT64,
G_TYPE_INT64);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_aml_hal_asink_change_state);
gstelement_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_aml_hal_asink_provide_clock);
gstelement_class->query = GST_DEBUG_FUNCPTR (gst_aml_hal_asink_query);
gstbasesink_class->wait_event =
GST_DEBUG_FUNCPTR (gst_aml_hal_asink_wait_event);
gstbasesink_class->get_times =
GST_DEBUG_FUNCPTR (gst_aml_hal_asink_get_times);
gst_element_class_set_details_simple (gstelement_class, "AmlHalAsink",
"Decoder/Sink/Audio",
"Gstreamer sink plugin for audio HAL",
"Amlogic");
/* ref class from a thread-safe context to work around missing bit of
* thread-safety in GObject */
g_type_class_ref (GST_TYPE_AUDIO_CLOCK);
}
static void
gst_aml_hal_asink_init (GstAmlHalAsink* sink)
{
GstBaseSink *basesink;
#if GLIB_CHECK_VERSION(2,58,0)
GstAmlHalAsinkPrivate *priv = gst_aml_hal_asink_get_instance_private (sink);
#else
GstAmlHalAsinkPrivate *priv = GST_AML_HAL_ASINK_GET_PRIVATE (sink);
#endif
sink->priv = priv;
basesink = GST_BASE_SINK_CAST (sink);
/* bypass sync control of basesink */
gst_base_sink_set_sync(basesink, FALSE);
gst_pad_set_event_function (basesink->sinkpad, gst_aml_hal_asink_pad_event);
gst_pad_set_chain_function (basesink->sinkpad, gst_aml_hal_asink_chain);
priv->provided_clock = gst_aml_clock_new ("GstAmlSinkClock",
(GstAmlClockGetTimeFunc) gst_aml_hal_asink_get_time, sink, NULL);
GST_OBJECT_FLAG_SET (basesink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
priv->hw_dev_ = NULL;
priv->direct_mode_ = TRUE;
priv->received_eos = FALSE;
priv->group_id = -1;
priv->pause_pts = -1;
priv->gap_state = GAP_IDLE;
priv->gap_start_pts = -1;
priv->gap_duration = 0;
priv->gap_offset = 0;
priv->format_ = AUDIO_FORMAT_PCM_16_BIT;
priv->sync_mode = AV_SYNC_MODE_AMASTER;
priv->session_id = -1;
priv->stream_volume = 1.0;
priv->ms12_enable = false;
priv->commit_data = NULL;
priv->commit_time =GST_CLOCK_TIME_NONE;
priv->commit_count = 0;
priv->commit_size = 0;
#ifdef ENABLE_MS12
priv->ac4_pat = 255;
priv->ac4_ass_type = 255;
priv->ac4_mixer_gain = 0; // mixing_level Range: -32 (mute assoc) to 32 (mute main) in steps of 1 (dB)
priv->ac4_pres_group_idx = -1;
priv->ac4_lang = priv->ac4_lang2 = NULL;
priv->ms12_mix_en = 1;
#endif
#ifdef SUPPORT_AD
priv->des_ad.fade = priv->des_ad.pan = -1;
priv->des_ad.g_c = priv->des_ad.g_f = priv->des_ad.g_s = -1;
#endif
priv->aligned_timeout = -1;
priv->clip_front = 0;
priv->clip_back = 0;
priv->drop_start = -1;
priv->drop_duration = -1;
g_mutex_init (&priv->feed_lock);
g_cond_init (&priv->run_ready);
scaletempo_init (&priv->st);
{
char *path = getenv("AV_PROGRESSION");
if (path) {
priv->log_path = g_malloc(128);
if (priv->log_path) {
snprintf(priv->log_path, 128, "%s.gtoa", path);
priv->diag_log_enable = TRUE;
GST_WARNING ("enable AV Progression logging");
}
}
}
#ifdef ESSOS_RM
g_mutex_init (&priv->ess_lock);
priv->rm = 0;
priv->resAssignedId = -1;
#endif
log_set_level(AVS_LOG_INFO);
}
static void
gst_aml_hal_asink_dispose (GObject * object)
{
GstAmlHalAsink * sink = GST_AML_HAL_ASINK(object);
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_DEBUG_OBJECT (sink, "dispose");
if (priv->provided_clock) {
gst_object_unref (priv->provided_clock);
priv->provided_clock = NULL;
}
g_mutex_clear (&priv->feed_lock);
g_cond_clear (&priv->run_ready);
#ifdef ESSOS_RM
g_mutex_clear (&priv->ess_lock);
#endif
#ifdef ENABLE_MS12
g_free (priv->ac4_lang);
g_free (priv->ac4_lang2);
#endif
g_free (priv->log_path);
if (priv->commit_data)
g_free (priv->commit_data);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static GstClock *
gst_aml_hal_asink_provide_clock (GstElement * elem)
{
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (elem);
GstAmlHalAsinkPrivate *priv = sink->priv;
GstClock *clock = NULL;
GST_OBJECT_LOCK (sink);
if (priv->provided_clock)
clock = GST_CLOCK_CAST (gst_object_ref (priv->provided_clock));
GST_OBJECT_UNLOCK (sink);
return clock;
}
static gboolean
gst_aml_hal_asink_is_self_provided_clock (GstAmlHalAsink* sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
return (priv->provided_clock && GST_IS_AML_CLOCK (priv->provided_clock));
}
static int avsync_get_time(GstAmlHalAsink* sink, pos_t pos_type, pts90K *pts, uint64_t *mono)
{
int rc = 0;
GstAmlHalAsinkPrivate *priv = sink->priv;
if (!g_atomic_pointer_compare_and_exchange (&priv->avsync, NULL, NULL)) {
if (POS_WALL == pos_type)
rc = av_sync_get_clock(priv->avsync, pts);
else if (POS_APTS == pos_type)
rc = av_sync_get_pos(priv->avsync, pts, mono);
}
return rc;
}
static gboolean
get_position (GstAmlHalAsink* sink, GstFormat format, pos_t pos_type, gint64 * cur, guint64 *pmono)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
uint64_t mono = 0;
pts90K pcr = 0;
gint64 timepassed, timepassed_90k;
int rc;
if (priv->group_done) {
/* return a little bigger time for basesink of other module
* to behavior correctly. basink assume clock keeps
* going to exit wait loop */
*cur = priv->eos_end_time + 100 * GST_MSECOND;
return TRUE;
}
if (!priv->render_samples) {
if (priv->segment.start != GST_CLOCK_TIME_NONE) {
*cur = priv->segment.start;
return TRUE;
}
else {
*cur = GST_CLOCK_TIME_NONE;
return false;
}
}
if (!priv->provided_clock) {
//TODO(song): get HAL position
if (priv->sr_)
*cur = gst_util_uint64_scale_int(priv->render_samples, GST_SECOND, priv->sr_);
if (pmono)
*pmono = 0;
} else if (gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
GstAmlClock *aclock = GST_AML_CLOCK_CAST(priv->provided_clock);
if (aclock->handle) {
mediasync_wrap_GetMediaTimeByType(aclock->handle, MEDIA_STC_TIME, MEDIASYNC_UNIT_US, cur);
GST_LOG_OBJECT (sink, "POSITION: %lld pcr: %u segment: %lld", *cur, pcr, priv->segment.start);
if (*cur < 0) {
*cur = priv->segment.start;
return TRUE;
} else if (priv->first_pts_set && (int)(priv->first_pts - (*cur * 90 / 1000)) > 0 &&
(int)(priv->first_pts - (*cur * 90 / 1000)) < 90000 &&
priv->sync_mode == AV_SYNC_MODE_AMASTER) {
*cur = priv->segment.start;
return TRUE;
}
*cur = gst_util_uint64_scale_int(*cur, GST_SECOND, GST_MSECOND);
}
if (pmono)
*pmono = 0;
if (*cur < priv->segment.start) {
//if the position smaller than segment start, return false.
GST_WARNING_OBJECT (sink, "start %lld cur %lld", priv->segment.start, *cur);
return false;
}
} else {
rc = avsync_get_time(sink, pos_type, &pcr, &mono);
if (rc)
return FALSE;
if (pmono)
*pmono = mono;
if (pcr == -1) {
if ((priv->paused_ || priv->xrun_paused) && priv->last_pcr != -1) {
pcr = priv->last_pcr;
GST_LOG_OBJECT (sink, "paused, return last %u", pcr);
} else {
pcr = priv->first_pts;
GST_LOG_OBJECT (sink, "render not start, set to first_pts %u", pcr);
}
} else if (priv->first_pts_set && (int)(priv->first_pts - pcr) > 0 &&
(int)(priv->first_pts - pcr) < 90000 &&
priv->sync_mode == AV_SYNC_MODE_AMASTER) {
pcr = priv->first_pts;
if (pmono)
*pmono = 0;
GST_LOG_OBJECT (sink, "render start with delay, set to first_pts %u", pcr);
}
if (priv->sync_mode != AV_SYNC_MODE_PCR_MASTER) {
gint64 timepassed2, diff;
/* for live streaming need to consider PTS wrapping */
if (priv->last_pcr != -1 && priv->last_pcr > 0xFFFF0000 && pcr < 10*PTS_90K) {
priv->wrapping_time++;
GST_INFO_OBJECT (sink, "pts wrapping num: %d", priv->wrapping_time);
}
if (pcr != -1)
priv->last_pcr = pcr;
if (!priv->wrapping_time)
timepassed_90k = pcr - priv->first_pts;
else
timepassed_90k = (int)(pcr - priv->first_pts) + priv->wrapping_time * 0xFFFFFFFFLL;
timepassed = gst_util_uint64_scale_int (timepassed_90k, GST_SECOND, PTS_90K);
*cur = priv->first_pts_64 + timepassed;
if (priv->eos_time != -1) {
timepassed2 = priv->eos_time - priv->first_pts_64;
diff = (timepassed2 > timepassed)?
(timepassed2 - timepassed):(timepassed - timepassed2);
if (diff > GST_SECOND * 10) {
GST_INFO_OBJECT (sink, "pts jump detected: %lld --> %lld",
timepassed, timepassed2);
*cur = timepassed2;
}
}
} else {
timepassed = gst_util_uint64_scale_int (pcr, GST_SECOND, PTS_90K);
*cur = timepassed;
}
}
GST_LOG_OBJECT (sink, "POSITION: %lld pcr: %u wrapping: %d",
*cur, pcr, priv->wrapping_time);
if (GST_FORMAT_TIME != format) {
gboolean ret;
/* convert to final format */
ret = gst_audio_info_convert (&priv->spec.info, GST_FORMAT_TIME, *cur, format, cur);
if (!ret)
return FALSE;
}
return TRUE;
}
static gboolean
gst_aml_hal_asink_query (GstElement * element, GstQuery * query)
{
gboolean res = FALSE;
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (element);
GstAmlHalAsinkPrivate *priv = sink->priv;
sink = GST_AML_HAL_ASINK (element);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
gint64 cur = 0;
GstFormat format;
gst_query_parse_position (query, &format, NULL);
/* first try to get the position based on the clock */
if ((res = get_position (sink, format, POS_WALL, &cur, NULL))) {
gst_query_set_position (query, format, cur);
GST_LOG_OBJECT (sink, "position %lld format %s", cur, gst_format_get_name (format));
check_pause_pts (sink, cur);
}
break;
}
case GST_QUERY_LATENCY:
{
gboolean live, us_live;
GstClockTime min_l, max_l;
GST_TRACE_OBJECT (sink, "latency query");
/* ask parent first, it will do an upstream query for us. */
if ((res =
gst_base_sink_query_latency (GST_BASE_SINK_CAST (sink), &live,
&us_live, &min_l, &max_l))) {
GstClockTime base_latency, min_latency, max_latency;
/* we and upstream are both live, adjust the min_latency */
if (live && us_live) {
uint32_t latency;
GST_OBJECT_LOCK (sink);
latency = hal_get_latency (sink);
GST_OBJECT_UNLOCK (sink);
base_latency =
gst_util_uint64_scale_int (latency, GST_SECOND, 1000);
/* we cannot go lower than the buffer size and the min peer latency */
min_latency = base_latency + min_l;
/* the max latency is the max of the peer, we can delay an infinite
* amount of time. */
max_latency = (max_l == -1) ? -1 : (base_latency + max_l);
GST_DEBUG_OBJECT (sink,
"peer min %" GST_TIME_FORMAT ", our min latency: %"
GST_TIME_FORMAT, GST_TIME_ARGS (min_l),
GST_TIME_ARGS (min_latency));
GST_DEBUG_OBJECT (sink,
"peer max %" GST_TIME_FORMAT ", our max latency: %"
GST_TIME_FORMAT, GST_TIME_ARGS (max_l),
GST_TIME_ARGS (max_latency));
} else {
GST_TRACE_OBJECT (sink, "peer or we are not live, don't care about latency");
min_latency = min_l;
max_latency = max_l;
}
gst_query_set_latency (query, live, min_latency, max_latency);
}
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
GST_LOG_OBJECT (sink, "query convert");
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, NULL);
GST_OBJECT_LOCK (sink);
res = gst_audio_info_convert (&priv->spec.info, src_fmt,
src_val, dest_fmt, &dest_val);
GST_OBJECT_UNLOCK (sink);
if (res) {
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
}
break;
}
default:
res = GST_ELEMENT_CLASS (parent_class)->query (element, query);
break;
}
return res;
}
#if 0
static int get_sysfs_uint32(const char *path, uint32_t *value)
{
int fd;
char valstr[64];
uint32_t val = 0;
fd = open(path, O_RDONLY);
if (fd >= 0) {
memset(valstr, 0, 64);
read(fd, valstr, 64 - 1);
valstr[strlen(valstr)] = '\0';
close(fd);
} else {
GST_ERROR("unable to open file %s", path);
return -1;
}
if (sscanf(valstr, "0x%x", &val) < 1) {
GST_ERROR("unable to get pts from: %s", valstr);
return -1;
}
*value = val;
return 0;
}
#endif
static void check_pause_pts (GstAmlHalAsink *sink, GstClockTime ts)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
uint32_t pts_90k;
if (priv->pause_pts == -1)
return;
pts_90k = gst_util_uint64_scale_int (ts, PTS_90K, GST_SECOND);
if (pts_90k > priv->pause_pts) {
GST_WARNING_OBJECT (sink, "emit pause pts signal %u", pts_90k);
g_signal_emit (G_OBJECT (sink), g_signals[SIGNAL_PAUSEPTS], 0, pts_90k, NULL);
priv->pause_pts = -1;
}
}
/* we call this function without holding the lock on sink for performance
* reasons. Try hard to not deal with and invalid ringbuffer and rate. */
static GstClockTime gst_aml_hal_asink_get_time (GstClock * clock, GstAmlHalAsink * sink)
{
gint64 position = GST_CLOCK_TIME_NONE;
get_position (sink, GST_FORMAT_TIME, POS_WALL, &position, NULL);
GST_LOG_OBJECT (sink, "time %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
return position;
}
static void
gst_aml_hal_sink_set_stream_volume (GstAmlHalAsink * sink, float vol, gboolean force)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int ret;
if (!priv->stream_) {
GST_WARNING_OBJECT(sink, "audio stream not open yet, delayed");
priv->stream_volume_pending = TRUE;
priv->stream_volume = vol;
return;
}
if (!force && vol == priv->stream_volume)
return;
ret = priv->stream_->set_volume (priv->stream_, vol, vol);
if (ret)
GST_ERROR_OBJECT(sink, "set volume fail %d", ret);
else {
GST_LOG_OBJECT(sink, "stream volume set to %f", vol);
priv->stream_volume = vol;
}
}
static void
gst_aml_hal_sink_set_stream_mute (GstAmlHalAsink * sink, gboolean mute)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int ret;
float target;
if (!priv->mute_pending && mute == priv->mute) {
GST_WARNING_OBJECT(sink, "no need to mute %d", mute);
return;
}
if (!priv->stream_) {
GST_WARNING_OBJECT(sink, "audio stream not open yet, delayed");
priv->mute_pending = TRUE;
priv->mute = mute;
return;
}
GST_WARNING_OBJECT (sink, "set stream mute:%d", mute);
if (mute) {
target = 0;
priv->stream_volume_bak = priv->stream_volume;
} else {
target = priv->stream_volume_bak;
}
ret = priv->stream_->set_volume (priv->stream_, target, target);
if (ret)
GST_ERROR_OBJECT(sink, "stream mute fail:%d", ret);
else
priv->mute = mute;
}
static void remove_clock (GObject * object)
{
GstAmlHalAsinkClass *class = GST_AML_HAL_ASINK_GET_CLASS(object);
GstElementClass *eclass = GST_ELEMENT_CLASS(class);
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (object);
GstBaseSink *basesink = GST_BASE_SINK_CAST (sink);
GstAmlHalAsinkPrivate *priv = sink->priv;
/* will not provide GstAmlClock, application should take care of the
* session */
eclass->provide_clock = NULL;
if (priv->provided_clock) {
gst_object_unref (priv->provided_clock);
priv->provided_clock = NULL;
}
GST_OBJECT_FLAG_UNSET (basesink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
}
static GstStructure* sink_get_status (GstAmlHalAsink* sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
g_return_val_if_fail (sink != NULL, NULL);
return gst_structure_new ("application/x-gst-base-sink-stats",
"dropped", G_TYPE_UINT64, priv->dropped_frames,
"rendered", G_TYPE_UINT64, priv->rendered_frames, NULL);
}
static void
gst_aml_hal_asink_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (object);
GstAmlHalAsinkPrivate *priv = sink->priv;
#ifdef ENABLE_MS12
char setting[50];
#endif
switch (property_id) {
case PROP_DIRECT_MODE:
{
priv->direct_mode_ = g_value_get_boolean(value);
GST_DEBUG_OBJECT (sink, "set direct mode:%d", priv->direct_mode_);
GST_OBJECT_LOCK (sink);
if (priv->tempo_used && !priv->direct_mode_) {
GST_DEBUG_OBJECT (sink, "disable scaletempo for non-direct mode");
scaletempo_stop (&priv->st);
priv->tempo_used = FALSE;
}
if (!priv->direct_mode_ && priv->provided_clock) {
GstAmlHalAsinkClass *class = GST_AML_HAL_ASINK_GET_CLASS(object);
GstBaseSink *basesink = GST_BASE_SINK_CAST (sink);
GstElementClass *eclass = GST_ELEMENT_CLASS(class);
gst_object_unref (priv->provided_clock);
priv->provided_clock = NULL;
eclass->provide_clock = NULL;
GST_OBJECT_FLAG_UNSET (basesink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
}
GST_OBJECT_UNLOCK (sink);
if (!priv->direct_mode_) {
GstBaseSink *basesink = GST_BASE_SINK_CAST (sink);
gst_element_remove_pad (GST_ELEMENT_CAST (basesink), basesink->sinkpad);
basesink->sinkpad = gst_pad_new_from_static_template (&template_system_sound, "sink");
gst_element_add_pad (GST_ELEMENT_CAST (basesink), basesink->sinkpad);
gst_pad_set_event_function (basesink->sinkpad, gst_aml_hal_asink_pad_event);
gst_pad_set_chain_function (basesink->sinkpad, gst_aml_hal_asink_chain);
}
break;
}
case PROP_SYS_SOUND:
priv->sys_sound_ = g_value_get_boolean(value);
if (priv->sys_sound_) {
priv->direct_mode_ = false;
remove_clock (object);
}
GST_WARNING_OBJECT (sink, "set sys sound:%d", priv->sys_sound_);
break;
case PROP_TTS_MODE:
priv->tts_mode_ = g_value_get_boolean(value);
if (priv->tts_mode_) {
priv->direct_mode_ = false;
remove_clock (object);
}
GST_WARNING_OBJECT (sink, "set tts mode:%d", priv->tts_mode_);
break;
case PROP_OUTPUT_PORT:
priv->output_port_ = g_value_get_enum (value);
GST_DEBUG_OBJECT (sink, "set output port:%d", priv->output_port_);
break;
case PROP_MASTER_VOLUME:
case PROP_STREAM_VOLUME:
{
float vol = g_value_get_double (value);
gst_aml_hal_sink_set_stream_volume (sink, vol, false);
break;
}
case PROP_MUTE:
{
gst_aml_hal_sink_set_stream_mute (sink, g_value_get_boolean (value));
break;
}
case PROP_AVSYNC_MODE:
{
guint mode = g_value_get_uint (value);
priv->sync_mode = g_value_get_uint (value);
if (mode == 0) {
priv->sync_mode = AV_SYNC_MODE_AMASTER;
} else if (mode == 1) {
priv->sync_mode = AV_SYNC_MODE_PCR_MASTER;
priv->tempo_used = FALSE;
priv->tempo_disable = TRUE;
} else if (mode == 2) {
priv->sync_mode = AV_SYNC_MODE_IPTV;
priv->tempo_used = FALSE;
priv->tempo_disable = TRUE;
} else if (mode == 4) {
priv->sync_mode = AV_SYNC_MODE_FREE_RUN;
} else {
GST_ERROR_OBJECT (sink, "invalid sync mode %d", mode);
}
GST_DEBUG_OBJECT (sink, "sync mode:%d", priv->sync_mode);
break;
}
case PROP_PAUSE_PTS:
priv->pause_pts = g_value_get_uint (value);
GST_WARNING_OBJECT (sink, "pause PTS %u", priv->pause_pts);
break;
case PROP_DISABLE_XRUN_TIMER:
priv->disable_xrun = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "disable xrun pause %d", priv->disable_xrun);
break;
case PROP_GAP_START_PTS:
priv->gap_start_pts = g_value_get_int64(value);
GST_WARNING_OBJECT (sink, "gap start PTS %" PRId64, priv->gap_start_pts);
break;
case PROP_GAP_DURATION:
priv->gap_duration = g_value_get_int(value);
GST_WARNING_OBJECT (sink, "gap duration %d", priv->gap_duration);
break;
case PROP_AVSYNC_SESSION:
{
int id = g_value_get_int (value);
if (id >= 0) {
remove_clock (object);
priv->session_id = id;
GST_WARNING_OBJECT (sink, "avsync session %u", id);
}
break;
}
case PROP_WAIT_FOR_VIDEO:
priv->wait_video = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "wait video:%d", priv->wait_video);
break;
case PROP_SEAMLESS_SWITCH:
{
gboolean enable = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "seamless switch audio:%d", priv->seamless_switch);
GstAmlClock *aclock = GST_AML_CLOCK_CAST(priv->provided_clock);
if (gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
MediaSync_wrap_audioSwitch(aclock->handle, enable, -1);
} else if (priv->avsync) {
GST_INFO_OBJECT (sink, "AV sync set seamless switch:%d",
priv->seamless_switch);
av_sync_set_audio_switch(priv->avsync, enable);
}
priv->seamless_switch = enable;
break;
}
case PROP_DISABLE_TEMPO_STRETCH:
priv->tempo_disable = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "disable tempo stretch:%d", priv->tempo_disable);
GST_OBJECT_LOCK (sink);
if (priv->tempo_used && priv->tempo_disable) {
GST_DEBUG_OBJECT (sink, "disable scaletempo");
scaletempo_stop (&priv->st);
priv->tempo_used = FALSE;
}
GST_OBJECT_UNLOCK (sink);
break;
#ifdef ENABLE_MS12
case PROP_AC4_P_GROUP_IDX:
{
priv->ac4_pres_group_idx = g_value_get_int(value);
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-ac4_pres_group_idx %d", priv->ac4_pres_group_idx);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ac4_pres_group_idx:%d", priv->ac4_pres_group_idx);
break;
}
case PROP_AC4_AUTO_S_PRI:
{
const gchar *pri = g_value_get_string (value);
if (!strncmp ("language", pri, sizeof ("language")))
priv->ac4_pat = 0;
else if (!strncmp ("associated_type", pri, sizeof ("associated_type")))
priv->ac4_pat = 1;
else if (!strncmp ("default", pri, sizeof ("default")))
priv->ac4_pat = 255;
else {
GST_ERROR_OBJECT (sink, "invalid value:%s", pri);
priv->ac4_pat = 255;
}
GST_WARNING_OBJECT (sink, "ac4_pat:%d", priv->ac4_pat);
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-pat %d", priv->ac4_pat);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
break;
}
case PROP_AC4_LANG_1:
if (priv->ac4_lang)
g_free(priv->ac4_lang);
priv->ac4_lang = g_value_dup_string (value);
if (!priv->ac4_lang) {
GST_ERROR_OBJECT (sink, "can not get string");
break;
}
if (strlen(priv->ac4_lang) != 3) {
GST_ERROR_OBJECT (sink, "wrong ac4_lang:%s", priv->ac4_lang);
g_free(priv->ac4_lang);
priv->ac4_lang = NULL;
break;
}
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-lang %s", priv->ac4_lang);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ac4_lang:%s", priv->ac4_lang);
break;
case PROP_AC4_LANG_2:
if (priv->ac4_lang2)
g_free(priv->ac4_lang2);
priv->ac4_lang2 = g_value_dup_string (value);
if (!priv->ac4_lang2) {
GST_ERROR_OBJECT (sink, "can not get string");
break;
}
if (strlen(priv->ac4_lang2) != 3) {
GST_ERROR_OBJECT (sink, "wrong ac4_lang2:%s", priv->ac4_lang2);
g_free(priv->ac4_lang2);
priv->ac4_lang2 = NULL;
break;
}
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-lang2 %s", priv->ac4_lang2);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ac4_lang2:%s", priv->ac4_lang2);
break;
case PROP_AC4_ASS_TYPE:
{
const gchar* type = g_value_get_string (value);
if (!strncmp(type, "visually_impaired", sizeof("visually_impaired")))
priv->ac4_ass_type = 1;
else if (!strncmp(type, "hearing_impaired", sizeof("hearing_impaired")))
priv->ac4_ass_type = 2;
else if (!strncmp(type, "commentary", sizeof("commentary")))
priv->ac4_ass_type = 3;
else if (!strncmp(type, "default", sizeof("default")))
priv->ac4_ass_type = 255;
else {
GST_ERROR_OBJECT (sink, "wrong ass type %s", type);
priv->ac4_ass_type = 255;
}
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-at %d", priv->ac4_ass_type);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ac4_ass_type:%d", priv->ac4_ass_type);
break;
}
case PROP_AC4_MIXER_BALANCE:
{
priv->ac4_mixer_gain = g_value_get_int(value);
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-xu %d", priv->ac4_mixer_gain);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ac4_mixer_gain:%d", priv->ac4_mixer_gain);
break;
}
case PROP_MS12_MIX_EN:
{
priv->ms12_mix_en = g_value_get_int(value);
GST_OBJECT_LOCK (sink);
if (priv->stream_) {
snprintf(setting, sizeof(setting), "ms12_runtime=-xa %d", priv->ms12_mix_en);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
GST_OBJECT_UNLOCK (sink);
GST_WARNING_OBJECT (sink, "ms12 mix enable:%d", priv->ms12_mix_en);
break;
}
#endif
#ifdef SUPPORT_AD
case PROP_DUAL_AUDIO:
{
gboolean is_dual_audio = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "dual audio: %d", is_dual_audio);
if (priv->is_ad_audio && is_dual_audio) {
GST_WARNING_OBJECT (sink, "ad ignore dual audio");
break;
}
priv->is_dual_audio = is_dual_audio;
break;
}
case PROP_AD_AUDIO:
priv->is_ad_audio = g_value_get_boolean(value);
GST_WARNING_OBJECT (sink, "ad audio: %d", priv->is_ad_audio);
if (priv->is_ad_audio && priv->is_dual_audio) {
GST_WARNING_OBJECT (sink, "ad disable dual audio");
priv->is_dual_audio = false;
remove_clock (object);
}
break;
#endif
case PROP_A_WAIT_TIMEOUT:
priv->aligned_timeout = g_value_get_int(value);
GST_WARNING_OBJECT (sink, "timeout:%d", priv->aligned_timeout);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void gst_aml_hal_asink_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (object);
GstAmlHalAsinkPrivate *priv = sink->priv;
switch (property_id) {
case PROP_DIRECT_MODE:
g_value_set_boolean(value, priv->direct_mode_);
GST_DEBUG_OBJECT (sink, "get direct mode:%d", priv->direct_mode_);
break;
case PROP_SYS_SOUND:
g_value_set_boolean(value, priv->sys_sound_);
GST_DEBUG_OBJECT (sink, "get sys sound:%d", priv->sys_sound_);
break;
case PROP_TTS_MODE:
g_value_set_boolean(value, priv->tts_mode_);
GST_DEBUG_OBJECT (sink, "get tts mode:%d", priv->tts_mode_);
break;
case PROP_OUTPUT_PORT:
g_value_set_enum(value, priv->output_port_);
GST_DEBUG_OBJECT (sink, "get output port:%d", priv->output_port_);
break;
case PROP_MASTER_VOLUME:
case PROP_STREAM_VOLUME:
if (priv->mute)
g_value_set_double (value, priv->stream_volume_bak);
else
g_value_set_double (value, priv->stream_volume);
break;
case PROP_MUTE:
g_value_set_boolean (value, priv->mute);
break;
case PROP_AVSYNC_MODE:
g_value_set_uint (value, priv->sync_mode);
break;
case PROP_PAUSE_PTS:
g_value_set_uint (value, priv->pause_pts);
break;
case PROP_AVSYNC_SESSION:
g_value_set_int (value, priv->session_id);
break;
case PROP_WAIT_FOR_VIDEO:
g_value_set_boolean (value, priv->wait_video);
break;
case PROP_SEAMLESS_SWITCH:
g_value_set_boolean (value, priv->seamless_switch);
break;
case PROP_A_WAIT_TIMEOUT:
g_value_set_int (value, priv->aligned_timeout);
break;
case PROP_DISABLE_TEMPO_STRETCH:
g_value_set_boolean (value, priv->tempo_disable);
break;
case PROP_STATS:
g_value_take_boxed(value, sink_get_status (sink));
break;
case PROP_TIME_PAIR:
{
gint64 cur = -1;
guint64 mono = 0;
if (get_position (sink, GST_FORMAT_TIME, POS_APTS, &cur, &mono)) {
/* don't change mono time if playback stops */
if (mono && !priv->paused_ && !priv->eos && !priv->group_done) {
uint64_t mono_ns;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
mono_ns = now.tv_sec * 1000000000LL + now.tv_nsec;
cur += mono_ns - mono;
mono = mono_ns;
}
gst_value_set_time_pair (value, cur, mono);
} else {
gst_value_set_time_pair (value, -1, -1);
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
parse_caps (GstAudioRingBufferSpec * spec, GstCaps * caps)
{
const gchar *mimetype;
GstStructure *structure;
GstAudioInfo info;
structure = gst_caps_get_structure (caps, 0);
gst_audio_info_init (&info);
/* we have to differentiate between int and float formats */
mimetype = gst_structure_get_name (structure);
if (g_str_equal (mimetype, "audio/x-raw")) {
if (!gst_audio_info_from_caps (&info, caps))
goto parse_error;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW;
} else if (g_str_equal (mimetype, "audio/x-iec958")) {
/* extract the needed information from the cap */
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
goto parse_error;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_IEC958;
info.bpf = 4;
} else if (g_str_equal (mimetype, "audio/x-ac3")) {
/* extract the needed information from the cap */
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
goto parse_error;
if (!(gst_structure_get_int (structure, "channels", &info.channels)))
info.channels = 2;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/x-ac4")) {
spec->type = GST_AUDIO_FORMAT_TYPE_AC4;
/* to pass the sanity check in render() */
info.bpf = 1;
info.channels = 2;
info.rate = 48000;
} else if (g_str_equal (mimetype, "audio/x-true-hd")) {
spec->type = GST_AUDIO_FORMAT_TYPE_TRUE_HD;
/* to pass the sanity check in render() */
info.bpf = 1;
info.channels = 2;
info.rate = 48000;
} else if (g_str_equal (mimetype, "audio/x-eac3")) {
/* extract the needed information from the cap */
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
info.rate = 48000;
if (!(gst_structure_get_int (structure, "channels", &info.channels)))
info.channels = 2;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/mpeg")) {
/* if cannot extract the needed information from the cap, set default value */
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
info.rate = 48000;
if (!gst_structure_get_int (structure, "channels", &info.channels))
info.channels = 2;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG4_AAC;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/x-dts")
|| g_str_equal (mimetype, "audio/x-gst-fourcc-dtse")
|| g_str_equal (mimetype, "audio/x-dtsl")
|| g_str_equal (mimetype, "audio/x-gst-fourcc-dtsx")) {
/* if cannot extract the needed information from the cap, set default value */
if (!(gst_structure_get_int (structure, "rate", &info.rate)) || info.rate <= 0)
info.rate = 48000;
if (!gst_structure_get_int (structure, "channels", &info.channels) || info.channels <= 0)
info.channels = 2;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/x-private1-lpcm")) {
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
info.rate = 48000;
if (!gst_structure_get_int (structure, "channels", &info.channels))
info.channels = 2;
spec->type = GST_AUDIO_FORMAT_TYPE_LPCM_PRIV1;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/x-private2-lpcm")) {
if (!(gst_structure_get_int (structure, "rate", &info.rate)))
info.rate = 48000;
if (!gst_structure_get_int (structure, "channels", &info.channels))
info.channels = 2;
spec->type = GST_AUDIO_FORMAT_TYPE_LPCM_PRIV2;
info.bpf = 1;
} else if (g_str_equal (mimetype, "audio/x-private-ts-lpcm")) {
if (!(gst_structure_get_int (structure, "rate", &info.rate)) || info.rate <= 0)
info.rate = 48000;
if (!gst_structure_get_int (structure, "channels", &info.channels) || info.channels <= 0)
info.channels = 2;
spec->type = GST_AUDIO_FORMAT_TYPE_LPCM_TS;
info.bpf = 1;
} else {
goto parse_error;
}
gst_caps_replace (&spec->caps, caps);
spec->info = info;
return TRUE;
/* ERRORS */
parse_error:
{
GST_DEBUG ("could not parse caps");
return FALSE;
}
}
static gboolean caps_not_changed(GstAmlHalAsink *sink, GstCaps * cur, GstCaps * new)
{
gboolean ret = FALSE;
GstAudioRingBufferSpec new_spec;
GstAmlHalAsinkPrivate *priv = sink->priv;
GstAudioRingBufferSpec *cur_spec = &priv->spec;
if (gst_caps_is_equal (cur, new))
return TRUE;
memset(&new_spec, 0, sizeof(new_spec));
if (!parse_caps (&new_spec, new))
return FALSE;
/* audio hal can handle the change for eac3 */
if (cur_spec->type == new_spec.type &&
(cur_spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3 ||
cur_spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3)) {
GST_DEBUG_OBJECT (sink, "ignore eac3/ac3 format change");
ret = TRUE;
}
gst_caps_replace (&new_spec.caps, NULL);
return ret;
}
static gboolean gst_aml_hal_asink_setcaps (GstAmlHalAsink* sink,
GstCaps * caps, gboolean force_change)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GstAudioRingBufferSpec *spec;
spec = &priv->spec;
if (!force_change &&
G_UNLIKELY (spec->caps && caps_not_changed (sink, spec->caps, caps))) {
GST_DEBUG_OBJECT (sink,
"caps haven't changed, skipping reconfiguration");
return TRUE;
}
GST_DEBUG_OBJECT (sink, "release old hal");
/* release old ringbuffer */
stop_xrun_thread (sink);
GST_OBJECT_LOCK (sink);
hal_release (sink);
priv->flushing_ = FALSE;
GST_OBJECT_UNLOCK (sink);
GST_DEBUG_OBJECT (sink, "parse caps: %" GST_PTR_FORMAT, caps);
spec->buffer_time = DEFAULT_BUFFER_TIME;
spec->latency_time = DEFAULT_LATENCY_TIME;
/* parse new caps */
if (!parse_caps (spec, caps))
goto parse_error;
GST_DEBUG_OBJECT (sink, "acquire");
GST_OBJECT_LOCK (sink);
if (!hal_acquire (sink, spec)) {
GST_OBJECT_UNLOCK (sink);
goto acquire_error;
}
GST_OBJECT_UNLOCK (sink);
if (create_av_sync(sink))
return FALSE;
if (priv->stream_volume_pending) {
priv->stream_volume_pending = FALSE;
gst_aml_hal_sink_set_stream_volume (sink, priv->stream_volume, true);
}
if (priv->mute_pending) {
gst_aml_hal_sink_set_stream_mute (sink, priv->mute);
priv->mute_pending = FALSE;
}
char scaletempo_setting[24] = {0};
int scaletempo_flag = 0;
if (priv->tempo_disable) {
scaletempo_stop (&priv->st);
priv->tempo_used = FALSE;
scaletempo_flag = 0;
} else if (is_raw_type(spec->type) && priv->direct_mode_) {
priv->tempo_used = TRUE;
scaletempo_start (&priv->st);
scaletempo_set_info (&priv->st, &spec->info);
scaletempo_flag = 0;
} else {
scaletempo_stop (&priv->st);
priv->tempo_used = FALSE;
scaletempo_flag = 1;
}
snprintf(scaletempo_setting, sizeof(scaletempo_setting), "enable_scaletempo=%d", scaletempo_flag);
priv->stream_->common.set_parameters (&priv->stream_->common, scaletempo_setting);
gst_element_post_message (GST_ELEMENT_CAST (sink),
gst_message_new_latency (GST_OBJECT (sink)));
return TRUE;
/* ERRORS */
parse_error:
{
GST_DEBUG_OBJECT (sink, "could not parse caps");
GST_ELEMENT_ERROR (sink, STREAM, FORMAT,
(NULL), ("cannot parse audio format."));
return FALSE;
}
acquire_error:
{
GST_DEBUG_OBJECT (sink, "could not acquire ringbuffer");
return FALSE;
}
}
static inline void gst_aml_hal_asink_reset_sync (GstAmlHalAsink * sink, gboolean keep_position)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
priv->eos_time = -1;
priv->received_eos = FALSE;
priv->eos = FALSE;
priv->last_ts = GST_CLOCK_TIME_NONE;
priv->commit_time = GST_CLOCK_TIME_NONE;
priv->commit_count = 0;
priv->commit_size = 0;
priv->flushing_ = FALSE;
priv->first_pts_set = FALSE;
priv->wrapping_time = 0;
priv->last_pcr = -1;
gst_caps_replace (&priv->spec.caps, NULL);
priv->segment.rate = 1.0f;
priv->gap_state = GAP_IDLE;
priv->gap_start_pts = -1;
priv->gap_duration = 0;
priv->gap_offset = 0;
priv->quit_clock_wait = FALSE;
priv->group_done = FALSE;
if (priv->start_buf) {
gst_buffer_unref (priv->start_buf);
priv->start_buf = NULL;
}
priv->start_buf_sent = FALSE;
priv->dropped_frames = 0;
priv->rendered_frames = 0;
if (!keep_position) {
priv->render_samples = 0;
priv->segment.start = GST_CLOCK_TIME_NONE;
}
}
static void gst_aml_hal_asink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
GstClockTime * start, GstClockTime * end)
{
/* our clock sync is a bit too much for the base class to handle so
* we implement it ourselves. */
*start = GST_CLOCK_TIME_NONE;
*end = GST_CLOCK_TIME_NONE;
}
static GstClockReturn sink_wait_clock (GstAmlHalAsink * sink,
GstClockTime time, GstClockTime duration)
{
GstClockReturn ret;
GstClock *clock;
gint count = 0;
GstAmlHalAsinkPrivate *priv = sink->priv;
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time)))
goto invalid_time;
clock = priv->provided_clock;
GST_INFO_OBJECT (sink,
"time %" GST_TIME_FORMAT , GST_TIME_ARGS (time));
GstClockTime now = gst_aml_hal_asink_get_time (clock, sink);
if (now == GST_CLOCK_TIME_NONE) {
ret = GST_CLOCK_UNSCHEDULED;
goto exit;
}
if (!priv->render_samples &&
priv->sync_mode == AV_SYNC_MODE_AMASTER) {
/* clock not started yet */
usleep (GST_TIME_AS_USECONDS(duration));
ret = GST_CLOCK_OK;
goto exit;
}
if (now >= time) {
GST_DEBUG_OBJECT (sink, "now: %lld", now);
ret = GST_CLOCK_EARLY;
goto exit;
}
do {
if (priv->flushing_) {
GST_INFO_OBJECT (sink, "we are flusing, do not wait");
ret = GST_CLOCK_OK;
break;
}
now = gst_aml_hal_asink_get_time (clock, sink);
if (now == GST_CLOCK_TIME_NONE) {
ret = GST_CLOCK_UNSCHEDULED;
break;
}
if (priv->eos) {
GST_INFO_OBJECT (sink, "Already reach EOS no wait at time %lld now %lld",
time, now);
ret = GST_CLOCK_OK;
break;
}
if ((time > GST_MSECOND && now < time - GST_MSECOND) ||
(time <= GST_MSECOND && now > GST_MSECOND)) {
/* when got eos, wait 300ms may enough, need quit wait for unscheduled then. */
if (priv->quit_clock_wait
|| ((10 < count) && (TRUE == priv->received_eos))) {
GST_INFO_OBJECT (sink, "unscheduled quit, wait at time %lld now %lld, count:%d, quit_clock_wait:%d",
time, now, count, priv->quit_clock_wait);
ret = GST_CLOCK_OK;
break;
}
usleep (30000);
count++;
GST_LOG_OBJECT (sink, "now: %lld, count:%d, priv->received_eos:%d", now, count, priv->received_eos);
continue;
} else {
ret = GST_CLOCK_OK;
break;
}
} while (1);
exit:
priv->quit_clock_wait = FALSE;
return ret;
/* no syncing needed */
invalid_time:
{
GST_DEBUG_OBJECT (sink, "time not valid, no sync needed");
return GST_CLOCK_BADTIME;
}
}
/* This waits for the drain to happen and can be canceled */
static GstFlowReturn sink_drain (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GstFlowReturn ret = GST_FLOW_OK;
pts90K pcr;
uint64_t mono = 0;
if ((!priv->stream_) || (!priv->spec.info.rate) || (!priv->direct_mode_)) {
return ret;
}
GST_DEBUG_OBJECT (sink, "draining");
if (priv->segment.stop != -1 &&
priv->eos_time != -1 &&
priv->segment.stop - priv->eos_time > 2 * GST_SECOND) {
GST_DEBUG_OBJECT (sink, "refine eos from %llu to %llu",
priv->eos_time, priv->segment.stop);
priv->eos_time = priv->segment.stop;
}
if (!avsync_get_time(sink, POS_WALL, &pcr, &mono) && pcr == -1 && !priv->paused_) {
GST_DEBUG_OBJECT (sink, "playback not started return");
return ret;
}
if (priv->eos_time != -1 && !priv->group_done) {
GstClockReturn cret;
GST_DEBUG_OBJECT (sink,
"last sample time %" GST_TIME_FORMAT,
GST_TIME_ARGS (priv->eos_time));
hal_commit (sink, NULL, 0, -1);
/* wait for the EOS time to be reached, this is the time when the last
* sample is played. */
cret = sink_wait_clock (sink, priv->eos_time, 0);
GST_INFO_OBJECT (sink, "drained ret: %d", cret);
if (cret == GST_CLOCK_OK || cret == GST_CLOCK_EARLY)
ret = GST_FLOW_OK;
else
ret = GST_FLOW_ERROR;
if (priv->seamless_switch && priv->avsync) {
GST_INFO_OBJECT (sink, "EOS reached clean audio seamless switch");
av_sync_set_audio_switch(priv->avsync, false);
priv->seamless_switch = false;
}
}
return ret;
}
static GstFlowReturn
gst_aml_hal_asink_wait_event (GstBaseSink * bsink, GstEvent * event)
{
//GstAmlHalAsink *sink = GST_AML_HAL_ASINK (bsink);
//GstAmlHalAsinkPrivate *priv = sink->priv;
GstFlowReturn ret = GST_FLOW_OK;
switch (GST_EVENT_TYPE (event)) {
#if 0
case GST_EVENT_GAP:
/* We must have a negotiated format before starting */
if (G_UNLIKELY (!priv->stream_)) {
GST_ELEMENT_ERROR (sink, STREAM, FORMAT, (NULL),
("Sink not negotiated before %s event.",
GST_EVENT_TYPE_NAME (event)));
return GST_FLOW_ERROR;
}
GST_OBJECT_LOCK (sink);
sink_force_start (sink);
GST_OBJECT_UNLOCK (sink);
break;
#endif
default:
break;
}
return ret;
}
static int send_playback_rate_msg(GstAmlHalAsink *sink, float rate)
{
int rc = 0;
GstAmlHalAsinkPrivate *priv = sink->priv;
char playback_rate[24] = {0};
snprintf(playback_rate, sizeof(playback_rate), "playback_rate=%f", rate);
if (priv->stream_) {
rc = priv->stream_->common.set_parameters (&priv->stream_->common, playback_rate);
}
return rc;
}
static int update_avsync_speed(GstAmlHalAsink *sink, float rate)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int rc = 0;
if (rate == priv->rate)
return 0;
#if 0
GstAmlClock *aclock = GST_AML_CLOCK_CAST(priv->provided_clock);
if (priv->tempo_used) {
if (gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
rc = mediasync_wrap_setPlaybackRate(aclock->handle, rate);
} else if (priv->avsync) {
rc = av_sync_set_speed(priv->avsync, rate);
}
} else
#endif
{
rc = send_playback_rate_msg(sink, rate);
}
if (!rc)
priv->rate = rate;
else
GST_ERROR_OBJECT (sink, "rate %f fail", rate);
return rc;
}
static int create_av_sync(GstAmlHalAsink *sink)
{
int ret = 0;
GstAmlHalAsinkPrivate *priv = sink->priv;
if (priv->direct_mode_ && gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
char id_setting[20] = {0};
char type_setting[20] = {0};
snprintf(type_setting, sizeof(type_setting), "hw_av_sync_type=%d", GST_AML_CLOCK_TYPE_MEDIASYNC);
snprintf(id_setting, sizeof(id_setting), "hw_av_sync=%d", priv->session_id);
g_mutex_lock(&priv->feed_lock);
if (priv->stream_) {
priv->stream_->common.set_parameters (&priv->stream_->common, type_setting);
priv->stream_->common.set_parameters (&priv->stream_->common, id_setting);
} else {
GST_ERROR_OBJECT (sink, "no stream opened");
ret = -1;
}
g_mutex_unlock(&priv->feed_lock);
} else if (!priv->avsync && priv->direct_mode_) {
char id_setting[20] = {0};
char type_setting[20] = {0};
struct start_policy policy;
#ifdef SUPPORT_AD
if (priv->is_ad_audio)
return 0;
#endif
g_mutex_lock(&priv->feed_lock);
if (priv->seamless_switch || priv->sync_mode == AV_SYNC_MODE_PCR_MASTER) {
/*coverity[LOCK_EVASION]: There will be no competition here*/
priv->avsync = av_sync_attach (priv->session_id, AV_SYNC_TYPE_AUDIO);
} else {
/*coverity[LOCK_EVASION]: There will be no competition here*/
priv->avsync = av_sync_create (priv->session_id, priv->sync_mode, AV_SYNC_TYPE_AUDIO, 0);
}
if (!priv->avsync) {
g_mutex_unlock(&priv->feed_lock);
GST_ERROR_OBJECT (sink, "create av sync fail");
return -1;
}
if (priv->seamless_switch)
{
GST_INFO_OBJECT (sink, "SET AVSYNC audio switch to ALIGN mode");
policy.policy = AV_SYNC_START_ALIGN;
policy.timeout = -1;
avs_sync_set_start_policy (priv->avsync, &policy);
}
if (priv->wait_video) {
policy.policy = AV_SYNC_START_ALIGN;
policy.timeout = priv->aligned_timeout;
GST_INFO_OBJECT (sink, "set policy=align, timeout=%d", policy.timeout);
avs_sync_set_start_policy (priv->avsync, &policy);
}
/* set session into hwsync id */
snprintf(type_setting, sizeof(type_setting), "hw_av_sync_type=%d", GST_AML_CLOCK_TYPE_MSYNC);
snprintf(id_setting, sizeof(id_setting), "hw_av_sync=%d", priv->session_id);
if (priv->stream_) {
priv->stream_->common.set_parameters (&priv->stream_->common, type_setting);
priv->stream_->common.set_parameters (&priv->stream_->common, id_setting);
} else {
if (priv->avsync) {
void *tmp = priv->avsync;
g_atomic_pointer_set (&priv->avsync, NULL);
av_sync_destroy(tmp);
}
GST_ERROR_OBJECT (sink, "no stream opened");
ret = -1;
}
g_mutex_unlock(&priv->feed_lock);
} else {
GST_INFO_OBJECT (sink, "no need to create av sync, direct: %d",
priv->direct_mode_);
}
return ret;
}
static gboolean
gst_aml_hal_asink_event (GstAmlHalAsink *sink, GstEvent * event)
{
gboolean result = TRUE;
GstAmlHalAsinkPrivate *priv = sink->priv;
GstBaseSink* bsink = GST_BASE_SINK_CAST (sink);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
{
GstMessage *message;
GstFlowReturn ret;
GST_DEBUG_OBJECT (sink, "receive eos");
priv->received_eos = TRUE;
GST_OBJECT_LOCK (sink);
if (priv->xrun_timer) {
g_timer_start (priv->xrun_timer);
g_timer_stop (priv->xrun_timer);
priv->xrun_paused = false;
}
GST_OBJECT_UNLOCK (sink);
ret = sink_drain (sink);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
result = FALSE;
goto done;
}
priv->eos = TRUE;
/* ok, now we can post the message */
GST_WARNING_OBJECT (sink, "Now posting EOS");
priv->seqnum = gst_event_get_seqnum (event);
GST_DEBUG_OBJECT (sink, "Got seqnum #%" G_GUINT32_FORMAT, priv->seqnum);
message = gst_message_new_eos (GST_OBJECT_CAST (sink));
gst_message_set_seqnum (message, priv->seqnum);
gst_element_post_message (GST_ELEMENT_CAST (sink), message);
break;
}
case GST_EVENT_FLUSH_START:
{
GST_DEBUG_OBJECT (sink, "flush start");
GST_OBJECT_LOCK (sink);
priv->received_eos = FALSE;
priv->flushing_ = TRUE;
priv->quit_clock_wait = TRUE;
/* unblock audio HAL wait */
if (priv->avsync)
avs_sync_stop_audio (priv->avsync);
/* unblock hal_commit() */
g_mutex_lock(&priv->feed_lock);
g_cond_signal (&priv->run_ready);
g_mutex_unlock(&priv->feed_lock);
GST_OBJECT_UNLOCK (sink);
break;
}
case GST_EVENT_FLUSH_STOP:
{
gboolean reset_time;
gst_event_parse_flush_stop (event, &reset_time);
GST_DEBUG_OBJECT (sink, "flush stop");
GST_OBJECT_LOCK (sink);
if (priv->tempo_used) {
scaletempo_force_init(&priv->st);
}
hal_stop (sink);
if (priv->xrun_timer) {
g_timer_start (priv->xrun_timer);
g_timer_stop(priv->xrun_timer);
priv->xrun_paused = false;
}
GST_OBJECT_UNLOCK (sink);
gst_aml_hal_asink_reset_sync (sink, !reset_time);
if (reset_time) {
GST_DEBUG_OBJECT (sink, "posting reset-time message");
gst_element_post_message (GST_ELEMENT_CAST (sink),
gst_message_new_reset_time (GST_OBJECT_CAST (sink), 0));
}
if (priv->provided_clock &&
gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
GstAmlClock *aclock = GST_AML_CLOCK_CAST(priv->provided_clock);
mediasync_wrap_reset(aclock->handle);
}
#ifdef DUMP_TO_FILE
file_index++;
#endif
break;
}
case GST_EVENT_SEGMENT:
{
GstSegment segment;
gboolean b_create_av_sync = TRUE;
if (GST_STATE(sink) <= GST_STATE_READY) {
GST_WARNING_OBJECT (sink, "segment event in wrong state %d", GST_STATE(sink));
break;
}
gst_event_copy_segment (event, &segment);
GST_DEBUG_OBJECT (sink, "configured segment %" GST_SEGMENT_FORMAT,
&segment);
if (segment.start != GST_CLOCK_TIME_NONE) {
GstMessage *seg_rec;
priv->segment = segment;
priv->render_samples = 0;
seg_rec = gst_message_new_info_with_details (GST_OBJECT_CAST (sink),
NULL, "segment-received", gst_structure_new_empty("segment-received"));
if (seg_rec)
gst_element_post_message (GST_ELEMENT_CAST (sink), seg_rec);
} else {
GST_WARNING_OBJECT (sink, "rate update %f -> %f", priv->segment.rate, segment.rate);
if (priv->segment.rate == segment.rate) {
GST_WARNING_OBJECT (sink, "keep current rate");
break;
}
/* if only rate change, do not recreate mediasync handle */
if (priv->direct_mode_ && gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
b_create_av_sync = FALSE;
}
priv->segment.rate = segment.rate;
}
if (priv->tempo_used)
scaletempo_update_segment (&priv->st, &priv->segment);
/* create avsync before rate change */
if (b_create_av_sync) {
if (create_av_sync(sink))
break;
}
if (priv->direct_mode_) {
if (!priv->tempo_used && segment.rate != 1.0)
update_avsync_speed(sink, segment.rate);
else
priv->need_update_rate = TRUE;
GST_INFO_OBJECT (sink, "rate to %f", segment.rate);
priv->group_done = FALSE;
#if 0
/* some loop playback will set this flag after EOS */
if ((segment.flags & GST_SEGMENT_FLAG_RESET) && priv->spec.caps) {
priv->eos_time = -1;
priv->received_eos = FALSE;
priv->eos = FALSE;
priv->last_ts = GST_CLOCK_TIME_NONE;
priv->flushing_ = FALSE;
priv->first_pts_set = FALSE;
priv->wrapping_time = 0;
priv->last_pcr = 0;
priv->quit_clock_wait = FALSE;
priv->group_done = FALSE;
/* rebuild audio stream */
gst_aml_hal_asink_setcaps(sink, priv->spec.caps, TRUE);
}
#endif
}
break;
}
case GST_EVENT_STREAM_START:
{
guint group_id;
gst_event_parse_group_id (event, &group_id);
GST_DEBUG_OBJECT (sink, "group change from %d to %d",
priv->group_id, group_id);
priv->group_id = group_id;
priv->eos_end_time = GST_CLOCK_TIME_NONE;
priv->gap_state = GAP_IDLE;
priv->gap_start_pts = -1;
priv->gap_duration = 0;
priv->gap_offset = 0;
GST_DEBUG_OBJECT (sink, "stream start, gid %d", group_id);
return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
}
case GST_EVENT_STREAM_GROUP_DONE:
{
guint group_id;
gst_event_parse_stream_group_done (event, &group_id);
if (priv->group_id != group_id) {
GST_WARNING_OBJECT (sink, "group id not match: %d vs %d",
priv->group_id, group_id);
} else {
GST_DEBUG_OBJECT (sink, "stream group done, gid %d", group_id);
}
/* if audio stop in the middle, do not touch clock and
* let video finish. Event GAP will update eos_end_time
*/
if (GST_CLOCK_TIME_IS_VALID(priv->eos_end_time)
&& (priv->eos_end_time - priv->eos_time) > GST_SECOND) {
GST_DEBUG_OBJECT (sink, "group-done ignore");
break;
}
GST_OBJECT_LOCK (sink);
if (priv->xrun_timer) {
g_timer_start (priv->xrun_timer);
g_timer_stop (priv->xrun_timer);
priv->xrun_paused = false;
}
priv->group_done = TRUE;
priv->eos_end_time = priv->eos_time;
GST_OBJECT_UNLOCK (sink);
gst_aml_hal_asink_reset_sync (sink, FALSE);
break;
}
case GST_EVENT_GAP:
{
GstClockTime timestamp, duration, wait_end;
gint64 end_time = GST_CLOCK_TIME_NONE;
gst_event_parse_gap (event, &timestamp, &duration);
wait_end = timestamp + duration;
if (GST_CLOCK_TIME_IS_VALID(priv->segment.stop))
end_time = priv->segment.stop;
else if (GST_CLOCK_TIME_IS_VALID(priv->segment.duration))
end_time = priv->segment.start + priv->segment.duration;
if (!GST_CLOCK_TIME_IS_VALID(duration) ||
!GST_CLOCK_TIME_IS_VALID(timestamp) ||
!GST_CLOCK_TIME_IS_VALID(end_time) ||
wait_end > end_time) {
GST_DEBUG_OBJECT (sink, "event-gap ignore");
break;
}
if (!priv->group_done) {
GstClockReturn cret;
GST_OBJECT_LOCK (sink);
if (priv->xrun_timer) {
g_timer_start (priv->xrun_timer);
g_timer_stop (priv->xrun_timer);
}
GST_OBJECT_UNLOCK (sink);
cret = sink_wait_clock (sink, wait_end, duration);
priv->eos_end_time = wait_end;
GST_DEBUG_OBJECT (sink, "event-gap wait %d", cret);
} else {
if (GST_CLOCK_TIME_IS_VALID(priv->eos_end_time) &&
wait_end < priv->eos_end_time) {
GST_DEBUG_OBJECT (sink, "event-gap ignore %llu < %llu", wait_end, priv->eos_end_time);
} else {
GST_DEBUG_OBJECT (sink, "sleep %d", (gint)(duration / 1000));
usleep(duration / 1000);
priv->eos_end_time = wait_end;
}
}
break;
}
case GST_EVENT_CAPS:
{
GstCaps *caps = NULL;
gst_event_parse_caps (event, &caps);
if (caps) {
result = gst_aml_hal_asink_setcaps(sink, caps, FALSE);
GST_DEBUG_OBJECT (sink, "set caps ret %d", result);
} else {
result = FALSE;
}
break;
}
case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
{
gboolean got_rate = false;
gdouble rate;
const GstStructure* s = gst_event_get_structure (event);
if (s && gst_structure_has_name (s, kCustomInstantRateChangeEventName)) {
const GValue *v = gst_structure_get_value (s, "rate");
if (v) {
rate = g_value_get_double (v);
got_rate = true;
}
}
if (!got_rate)
break;
GST_OBJECT_LOCK (sink);
if (!priv->avsync) {
GST_ERROR_OBJECT (sink, "segment event not received yet");
GST_OBJECT_UNLOCK (sink);
break;
}
if (priv->tempo_used) {
priv->segment.rate = rate;
scaletempo_update_segment (&priv->st, &priv->segment);
}
if (priv->direct_mode_) {
if (!priv->tempo_used && rate != 1.0)
update_avsync_speed(sink, rate);
else
priv->need_update_rate = TRUE;
GST_INFO_OBJECT (sink, "rate to %f", rate);
}
GST_OBJECT_UNLOCK (sink);
break;
}
default:
{
GST_LOG_OBJECT (sink, "pass to basesink");
return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
}
}
done:
gst_event_unref (event);
return result;
}
static gboolean
gst_aml_hal_asink_pad_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (parent);
GstAmlHalAsinkPrivate *priv = sink->priv;
gboolean result = TRUE;
if (GST_EVENT_TYPE (event) != GST_EVENT_TAG)
GST_DEBUG_OBJECT (sink, "received event %p %" GST_PTR_FORMAT, event, event);
if (GST_EVENT_IS_SERIALIZED (event)) {
if (G_UNLIKELY (priv->flushing_) &&
GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP)
goto flushing;
if (G_UNLIKELY (priv->received_eos))
goto after_eos;
}
result = gst_aml_hal_asink_event (sink, event);
done:
if (GST_EVENT_TYPE (event) != GST_EVENT_TAG)
GST_DEBUG_OBJECT (sink, "done");
return result;
/* ERRORS */
flushing:
{
GST_DEBUG_OBJECT (sink, "we are flushing ignore %" GST_PTR_FORMAT, event);
gst_event_unref (event);
goto done;
}
after_eos:
{
GST_DEBUG_OBJECT (sink, "Event received after EOS, dropping %" GST_PTR_FORMAT, event);
gst_event_unref (event);
goto done;
}
}
static gpointer xrun_thread(gpointer para)
{
GstAmlHalAsink *sink = (GstAmlHalAsink *)para;
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_INFO_OBJECT (sink, "enter");
while (!priv->quit_xrun_thread) {
/* cobalt cert requires pause avsync to stop video rendering */
if (priv->seamless_switch) {
bool result = false;
av_sync_get_audio_switch(priv->avsync, &result);
if (!result) {
GST_INFO_OBJECT (sink, "audio seamless switch finish");
priv->seamless_switch = false;
// emit the event here if needed
g_signal_emit (G_OBJECT (sink), g_signals[SIGNAL_AUDSWITCH], 0, 0, NULL);
g_timer_start(priv->xrun_timer);
} else {
usleep(10000);
}
continue;
}
if (!priv->xrun_paused &&
g_timer_elapsed(priv->xrun_timer, NULL) > 0.4) {
if (priv->ms12_enable) {
char *status = priv->stream_->common.get_parameters(priv->stream_, "main_input_underrun");
int underrun = 0;
if (status) {
sscanf(status,"main_input_underrun=%d", &underrun);
free (status);
}
if (!underrun) {
usleep(10000);
continue;
}
if (priv->received_eos) {
GST_INFO_OBJECT (sink, "xrun timer reached EOS");
GST_OBJECT_LOCK (sink);
priv->eos = TRUE;
GST_OBJECT_UNLOCK (sink);
} else {
g_signal_emit (G_OBJECT (sink), g_signals[SIGNAL_XRUN], 0, 0, NULL);
GST_WARNING_OBJECT (sink, "xrun signaled");
}
g_timer_start(priv->xrun_timer);
g_timer_stop(priv->xrun_timer);
} else {
if (priv->paused_) {
usleep(50000);
g_timer_start(priv->xrun_timer);
g_timer_stop(priv->xrun_timer);
continue;
}
g_signal_emit (G_OBJECT (sink), g_signals[SIGNAL_XRUN], 0, 0, NULL);
GST_WARNING_OBJECT (sink, "xrun signaled");
}
}
usleep(50000);
}
GST_INFO_OBJECT (sink, "quit");
return NULL;
}
static int start_xrun_thread (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
priv->xrun_timer = g_timer_new ();
if (!priv->xrun_timer) {
GST_ERROR_OBJECT (sink, "create timer fail");
return -1;
}
priv->quit_xrun_thread = FALSE;
priv->xrun_thread = g_thread_new ("axrun_render", xrun_thread, sink);
if (!priv->xrun_thread) {
GST_ERROR_OBJECT (sink, "create thread fail");
g_timer_destroy (priv->xrun_timer);
priv->xrun_timer = NULL;
return -1;
}
return 0;
}
static void stop_xrun_thread (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
if (priv->xrun_thread) {
priv->quit_xrun_thread = TRUE;
g_thread_join (priv->xrun_thread);
priv->xrun_thread = NULL;
g_timer_destroy (priv->xrun_timer);
priv->xrun_timer = NULL;
priv->xrun_paused = false;
}
}
static GstFlowReturn
gst_aml_hal_asink_render (GstAmlHalAsink * sink, GstBuffer * buf)
{
GstClockTime time, stop;
GstAmlHalAsinkPrivate *priv = sink->priv;
guint64 ctime, cstop;
gsize size;
guint samples;
gint bpf, rate;
GstFlowReturn ret = GST_FLOW_OK;
GstSegment clip_seg;
GstMapInfo info;
guchar * data;
if (priv->flushing_) {
ret = GST_FLOW_FLUSHING;
priv->dropped_frames++;
goto done;
}
if (G_UNLIKELY (!priv->stream_)) {
priv->dropped_frames++;
goto lost_resource;
}
if (G_UNLIKELY (priv->received_eos)) {
priv->dropped_frames++;
goto was_eos;
}
if (!priv->pri_set) {
int rc;
struct sched_param schedParam;
prctl (PR_SET_NAME, "asink_chain");
schedParam.sched_priority= 30;
rc = pthread_setschedparam( pthread_self(), SCHED_FIFO, &schedParam );
if ( rc )
GST_ERROR("failed to set chain thread policy and priority: %d errno %d", rc, errno);
else
GST_WARNING("set chain thread to policy %s priority %d", "SCHED_FIFO", 50);
priv->pri_set = true;
}
#ifdef SUPPORT_AD
if (priv->is_dual_audio || priv->is_ad_audio) {
GstMetaPesHeader* h = GST_META_PES_HEADER_GET (buf);
GST_LOG("get meta %p", h);
#ifdef DUMP_TO_FILE
if (h) {
char *name;
if (priv->is_dual_audio)
name = "/data/apes_m_";
else
name = "/data/apes_ad_";
dump (name, h->header, h->length);
}
#endif
if (h) {
struct ad_des des;
int lret = pes_get_ad_des (h->header, h->length, &des);
char setting[50];
if (!lret && memcmp(&priv->des_ad, &des, sizeof(des))) {
snprintf (setting, sizeof(setting),
"AD_descriptor=%02x %02x %02x %02x %02x",
des.fade, des.g_c, des.g_f, des.g_s, des.pan);
priv->hw_dev_->set_parameters (priv->hw_dev_, setting);
memcpy(&priv->des_ad, &des, sizeof(des));
GST_DEBUG_OBJECT (sink, "m(%d)/ad(%d) gain %d %d %d %d %d",
priv->is_dual_audio, priv->is_ad_audio,
des.fade, des.pan, des.g_c, des.g_f, des.g_s);
} else if (lret) {
GST_LOG("can not get ad ret %d", lret);
}
}
}
#endif
bpf = GST_AUDIO_INFO_BPF (&priv->spec.info);
rate = GST_AUDIO_INFO_RATE (&priv->spec.info);
size = gst_buffer_get_size (buf);
if (G_UNLIKELY (size % bpf) != 0) {
priv->dropped_frames++;
goto wrong_size;
}
if (is_raw_type(priv->spec.type))
samples = size / bpf;
else
samples = priv->sample_per_frame;
time = GST_BUFFER_TIMESTAMP (buf);
if ((!GST_CLOCK_TIME_IS_VALID (time) && !priv->first_pts_set) && priv->direct_mode_) {
priv->dropped_frames++;
GST_INFO_OBJECT (sink, "discard frame wo/ pts at beginning");
goto done;
}
if ((!GST_CLOCK_TIME_IS_VALID (time) ||
(priv->last_ts != GST_CLOCK_TIME_NONE && priv->last_ts == time)) &&
is_raw_type(priv->spec.type)) {
time = priv->first_pts_64;
time += gst_util_uint64_scale_int(priv->render_samples, GST_SECOND, rate);
GST_BUFFER_TIMESTAMP (buf) = time;
GST_LOG_OBJECT (sink, "fake time %" GST_TIME_FORMAT, GST_TIME_ARGS (time));
}
GST_LOG_OBJECT (sink,
"time %" GST_TIME_FORMAT ", start %"
GST_TIME_FORMAT ", samples %u size %u", GST_TIME_ARGS (time),
GST_TIME_ARGS (priv->segment.start), samples, size);
priv->last_ts = GST_BUFFER_TIMESTAMP (buf);
/* let's calc stop based on the number of samples in the buffer instead
* of trusting the DURATION */
stop = time + gst_util_uint64_scale_int (samples, GST_SECOND, rate);
clip_seg.format = GST_FORMAT_TIME;
clip_seg.start = priv->segment.start;
clip_seg.stop = priv->segment.stop;
clip_seg.duration = -1;
/* samples should be rendered based on their timestamp. All samples
* arriving before the segment.start or after segment.stop are to be
* thrown away. All samples should also be clipped to the segment
* boundaries */
if (G_UNLIKELY (!gst_segment_clip (&clip_seg, GST_FORMAT_TIME, time, stop,
&ctime, &cstop))) {
priv->dropped_frames++;
goto out_of_segment;
}
priv->eos_time = cstop;
if (!priv->first_pts_set) {
uint32_t pts_32 = gst_util_uint64_scale_int (time, PTS_90K, GST_SECOND);
priv->first_pts_set = TRUE;
priv->first_pts_64 = time;
priv->first_pts = pts_32;
GST_INFO_OBJECT(sink, "update first PTS %x", pts_32);
}
GST_OBJECT_LOCK (sink);
if (priv->tempo_used) {
GstBuffer *outbuffer = NULL;
gsize insize, outsize;
insize = gst_buffer_get_size (buf);
scaletempo_transform_size (&priv->st, insize, &outsize);
GST_LOG_OBJECT (sink, "in:%d out:%d", insize, outsize);
outbuffer = gst_buffer_new_allocate (NULL, outsize, NULL);
if (!outbuffer) {
GST_ERROR_OBJECT (sink, "out buffer fail %d", outsize);
ret = GST_FLOW_ERROR;
GST_OBJECT_UNLOCK (sink);
priv->dropped_frames++;
goto done;
}
gst_buffer_copy_into (outbuffer, buf, GST_BUFFER_COPY_METADATA, 0, -1);
ret = scaletempo_transform (&priv->st, buf, outbuffer);
gst_buffer_unref (buf);
buf = outbuffer;
if (ret != GST_FLOW_OK) {
GST_OBJECT_UNLOCK (sink);
GST_LOG_OBJECT (sink, "transform fail");
priv->dropped_frames++;
goto done;
}
if (!gst_buffer_get_size(buf)) {
/* lenth 0 can not be commited */
GST_OBJECT_UNLOCK (sink);
GST_LOG_OBJECT (sink, "skip length 0 buff");
priv->render_samples += samples;
goto done;
}
/* note: do not update time to output buffer
time = GST_BUFFER_TIMESTAMP (buf);
* it is based on the scaled time axis. When 2x
* playback, the time will be 1/2 of real time.
*/
}
GST_OBJECT_UNLOCK (sink);
//for those no bit stream parsed format EAC3 DTS render samples as buffers
// otherwise position always return the start position.
if (samples == 0)
samples = 1;
gst_buffer_map (buf, &info, GST_MAP_READ);
data = info.data;
size = info.size;
time = GST_BUFFER_TIMESTAMP (buf);
/*audio tureHD codec*/
if (priv->format_ == AUDIO_FORMAT_DOLBY_TRUEHD)
{
if (priv->commit_data == NULL)
{
priv->commit_data = g_malloc0 (MAX_COMMIT_BYTES);
if (priv->commit_data == NULL)
{
GST_ERROR("Memory allocation failure");
gst_buffer_unmap (buf, &info);
return GST_FLOW_OK;
}
priv->commit_count = 0;
}
if (priv->commit_count == 0)
{
/*use first data as timestamp for the entire package*/
priv->commit_time = GST_BUFFER_TIMESTAMP (buf);
}
/*if memory is not out of bounds, the loop continues to store data*/
if (priv->commit_size + size <= MAX_COMMIT_BYTES)
{
if (priv->commit_count < MAX_COMMIT_COUNT)
{
memcpy (priv->commit_data + priv->commit_size, data, size);
priv->commit_size += size;
if (priv->commit_count == MAX_COMMIT_COUNT-1)
{
/*save 70 data and send it to hal_commit*/
data = priv->commit_data;
size = priv->commit_size;
time = priv->commit_time;
}
else
{
/*keep looping to store data*/
priv->commit_count++;
gst_buffer_unmap (buf, &info);
return GST_FLOW_OK;
}
}
}
else
{
/*Send save data before memory overreach to hal_cmmit*/
GST_WARNING("mem is already insufficient,need to expand memory size:%d",size);
priv->commit_data = g_realloc(priv->commit_data, priv->commit_size + size);
if (priv->commit_data == NULL)
{
GST_ERROR("Expand memory allocation failure");
gst_buffer_unmap (buf, &info);
return GST_FLOW_OK;
}
memcpy (priv->commit_data + priv->commit_size, data, size);
priv->commit_size += size;
data = priv->commit_data;
size = priv->commit_size;
time = priv->commit_time;
}
}
g_mutex_lock(&priv->feed_lock);
/* blocked on paused */
while (priv->paused_ && !priv->flushing_)
{
GST_PAD_STREAM_UNLOCK(GST_BASE_SINK_PAD(sink));
g_cond_wait (&priv->run_ready, &priv->feed_lock);
GST_PAD_STREAM_LOCK(GST_BASE_SINK_PAD(sink));
}
if (!priv->stream_) {
priv->dropped_frames++;
goto commit_done;
}
/* update render_samples after pause logic */
priv->render_samples += samples;
if (priv->flushing_) {
GST_DEBUG_OBJECT (sink, "interrupted by stop");
gst_buffer_unmap (buf, &info);
g_mutex_unlock(&priv->feed_lock);
ret = GST_FLOW_FLUSHING;
priv->dropped_frames++;
goto done;
}
if (priv->need_update_rate) {
update_avsync_speed(sink, priv->segment.rate);
priv->need_update_rate = FALSE;
}
if (priv->sync_mode == AV_SYNC_MODE_PCR_MASTER) {
/* ms12 2.4 needs 2 frames to decode immediately */
if(!priv->start_buf_sent && !priv->start_buf) {
priv->start_buf = gst_buffer_ref (buf);
GST_DEBUG_OBJECT (sink, "cache start buf %llu", time);
goto commit_done;
} else if (!priv->start_buf_sent) {
GstMapInfo info2;
gst_buffer_map (priv->start_buf, &info2, GST_MAP_READ);
hal_commit (sink, info2.data, info2.size,
GST_BUFFER_TIMESTAMP(priv->start_buf));
gst_buffer_unmap (priv->start_buf, &info2);
gst_buffer_unref (priv->start_buf);
priv->start_buf = NULL;
priv->start_buf_sent = TRUE;
priv->rendered_frames++;
GST_DEBUG_OBJECT (sink, "sent cache start buf");
}
}
if (priv->format_ == AUDIO_FORMAT_PCM_16_BIT) {
if ((priv->gap_state == GAP_IDLE) &&
(priv->gap_start_pts != -1) &&
(time >= (priv->gap_start_pts * GST_MSECOND))) {
// PCM volume ramping down
GST_DEBUG_OBJECT(sink, "PCM volume ramping down %" PRId64 "ms @%" PRId64 " size %d",
priv->gap_start_pts, time, size);
vol_ramp(data, size, (priv->channel_mask_ == AUDIO_CHANNEL_OUT_5POINT1) ? 6 : 2, RAMP_DOWN);
hal_commit (sink, data, size, time);
// insert silence
if (priv->gap_duration > 0) {
GST_DEBUG_OBJECT(sink, "PCM insert silence %d ms", priv->gap_duration);
int32_t filled_ms = 0;
int32_t bytes_per_ms = 48 * 2 * ((priv->channel_mask_ == AUDIO_CHANNEL_OUT_5POINT1) ? 6 : 2);
uint8_t *silence = (uint8_t *)g_malloc(16 * bytes_per_ms);
if (silence) {
memset(silence, 0, 16 * bytes_per_ms);
time += (int64_t)size * GST_MSECOND / bytes_per_ms;
while ((filled_ms < priv->gap_duration) &&
(!priv->flushing_)) {
int insert_ms = priv->gap_duration - filled_ms;
if (insert_ms > 16) insert_ms = 16;
GST_DEBUG_OBJECT(sink, "PCM silence @%" PRId64, time + filled_ms * GST_MSECOND);
hal_commit (sink, silence, insert_ms * bytes_per_ms, time + filled_ms * GST_MSECOND);
filled_ms += insert_ms;
priv->render_samples += filled_ms * 48;
}
g_free(silence);
}
priv->gap_duration = 0;
}
priv->gap_start_pts = -1;
priv->gap_state = GAP_MUTING_1;
} else if (priv->gap_state == GAP_MUTING_1) {
GST_DEBUG_OBJECT(sink, "Muting 1 @%" PRId64 " size %d", time, size);
memset(data, 0, size);
hal_commit (sink, data, size, time);
priv->gap_state = GAP_MUTING_2;
} else if (priv->gap_state == GAP_MUTING_2) {
GST_DEBUG_OBJECT(sink, "Muting 2 @%" PRId64 " size %d", time, size);
memset(data, 0, size);
hal_commit (sink, data, size, time);
priv->gap_state = GAP_RAMP_UP;
} else if (priv->gap_state == GAP_RAMP_UP) {
// PCM volume ramping up
GST_DEBUG_OBJECT(sink, "PCM volume ramping up @%" PRId64 " size %d", time, size);
vol_ramp(data, size, (priv->channel_mask_ == AUDIO_CHANNEL_OUT_5POINT1) ? 6 : 2, RAMP_UP);
hal_commit (sink, data, size, time);
priv->gap_state = GAP_IDLE;
} else {
hal_commit (sink, data, size, time);
}
} else if (priv->format_ == AUDIO_FORMAT_E_AC3) {
if ((priv->gap_start_pts != -1) &&
(time >= (priv->gap_start_pts * GST_MSECOND))) {
char cmd[32] = {0};
snprintf(cmd, sizeof(cmd), "pts_gap=%llu,%d",
(unsigned long long)priv->gap_offset, priv->gap_duration);
priv->hw_dev_->set_parameters(priv->hw_dev_, cmd);
GST_DEBUG_OBJECT(sink, "E-AC3 %s", cmd);
priv->gap_start_pts = -1;
priv->gap_duration = 0;
}
hal_commit (sink, data, size, time);
priv->gap_offset += size;
} else {
hal_commit (sink, data, size, time);
}
priv->rendered_frames++;
if (priv->commit_size > MAX_COMMIT_BYTES)
{
g_free(priv->commit_data);
priv->commit_data = NULL;
}
priv->commit_count = 0;
priv->commit_size = 0;
priv->commit_time = GST_CLOCK_TIME_NONE;
commit_done:
g_mutex_unlock(&priv->feed_lock);
gst_buffer_unmap (buf, &info);
GST_OBJECT_LOCK (sink);
if (priv->sync_mode == AV_SYNC_MODE_AMASTER &&
priv->stream_ && !priv->xrun_thread &&
start_xrun_thread (sink)) {
ret = GST_FLOW_ERROR;
GST_OBJECT_UNLOCK (sink);
goto done;
}
GST_OBJECT_UNLOCK (sink);
ret = GST_FLOW_OK;
done:
gst_buffer_unref (buf);
return ret;
was_eos:
{
GST_DEBUG_OBJECT (sink, "we are EOS, return EOS");
ret = GST_FLOW_EOS;
goto done;
}
/* SPECIAL cases */
out_of_segment:
{
GST_INFO_OBJECT (sink,
"dropping sample out of segment time %" GST_TIME_FORMAT ", start %"
GST_TIME_FORMAT, GST_TIME_ARGS (time),
GST_TIME_ARGS (priv->segment.start));
ret = GST_FLOW_OK;
goto done;
}
lost_resource:
{
GST_DEBUG_OBJECT (sink, "lost resource");
ret = GST_FLOW_OK;
goto done;
}
wrong_size:
{
GST_ERROR_OBJECT (sink, "wrong size %d/%d", size, bpf);
GST_ELEMENT_ERROR (sink, STREAM, WRONG_TYPE,
(NULL), ("sink received buffer of wrong size."));
#ifdef DUMP_TO_FILE
gst_buffer_map (buf, &info, GST_MAP_READ);
data = info.data;
size = info.size;
dump ("/tmp/asink_input_", data, size);
gst_buffer_unmap (buf, &info);
#endif
ret = GST_FLOW_ERROR;
goto done;
}
}
/*|->..............org data buffer.............<-|*
*|->clip_front<-|..remain buffer..|->clip_back<-|*/
static void
aml_hal_clip_buf_by_meta (GstAmlHalAsink *sink, GstBuffer *buf)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GstAudioClippingMeta *cmeta = NULL;
GstAudioRingBufferSpec *spec = &priv->spec;
cmeta = gst_buffer_get_audio_clipping_meta (buf);
if (!cmeta || (GST_FORMAT_TIME != cmeta->format))
{
//cleanup clip info here
priv->clip_front = 0;
priv->clip_back = 0;
return;
}
if (!is_raw_type(spec->type))
{
//not raw type, just transfer the cmeta info to audio hal side.
priv->clip_front = cmeta->start;
priv->clip_back = cmeta->end;
}
return;
}
static GstFlowReturn
gst_aml_hal_asink_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (parent);
aml_hal_clip_buf_by_meta(sink, buf);
return gst_aml_hal_asink_render (sink, buf);
}
static void paused_to_ready(GstAmlHalAsink *sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
/* make sure we unblock before calling the parent state change
* so it can grab the STREAM_LOCK */
stop_xrun_thread (sink);
GST_OBJECT_LOCK (sink);
hal_release (sink);
priv->quit_clock_wait = TRUE;
priv->paused_ = FALSE;
gst_aml_hal_asink_reset_sync (sink, FALSE);
if (priv->tempo_used) {
scaletempo_stop (&priv->st);
priv->tempo_used = FALSE;
}
GST_OBJECT_UNLOCK (sink);
}
#ifdef ESSOS_RM
static void resMgrNotify(EssRMgr *rm, int event, int type, int id, void* userData)
{
GstAmlHalAsink *sink = (GstAmlHalAsink*)userData;
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_WARNING_OBJECT (sink, "resMgrNotify: enter");
switch (type) {
case EssRMgrResType_audioDecoder:
{
switch (event) {
case EssRMgrEvent_revoked:
{
#ifdef ENABLE_MS12
GST_OBJECT_LOCK (sink);
if (priv->format_ == AUDIO_FORMAT_AC4)
hal_set_player_overwrite(sink, TRUE);
GST_OBJECT_UNLOCK (sink);
#endif
GST_WARNING_OBJECT (sink, "releasing audio decoder %d", id);
paused_to_ready (sink);
#ifdef ESSOS_RM
g_mutex_lock(&priv->ess_lock);
if (priv->rm) {
EssRMgrReleaseResource (priv->rm, EssRMgrResType_audioDecoder, id);
priv->resAssignedId = -1;
}
g_mutex_unlock(&priv->ess_lock);
#endif
GST_DEBUG_OBJECT (sink, "done releasing audio decoder %d", id);
break;
}
case EssRMgrEvent_granted:
default:
break;
}
}
default:
break;
}
GST_WARNING_OBJECT (sink, "resMgrNotify: exit");
}
static void essos_rm_init(GstAmlHalAsink *sink)
{
bool result;
EssRMgrRequest resReq;
GstAmlHalAsinkPrivate *priv = sink->priv;
const char *env = getenv("AMLASINK_USE_ESSRMGR");
#ifdef SUPPORT_AD
if (priv->is_ad_audio)
return;
#endif
if (env && !atoi(env))
return;
priv->rm = EssRMgrCreate();
if (!priv->rm) {
GST_ERROR_OBJECT (sink, "fail");
return;
}
resReq.type = EssRMgrResType_audioDecoder;
resReq.usage = EssRMgrAudUse_none;
resReq.priority = 0;
resReq.asyncEnable = false;
resReq.notifyCB = resMgrNotify;
resReq.notifyUserData = sink;
resReq.assignedId = -1;
result = EssRMgrRequestResource (priv->rm, EssRMgrResType_audioDecoder, &resReq);
if (result) {
if (resReq.assignedId >= 0) {
GST_DEBUG_OBJECT (sink, "assigned id %d caps %X", resReq.assignedId, resReq.assignedCaps);
priv->resAssignedId = resReq.assignedId;
}
}
}
static void resMgrUpdateState(GstAmlHalAsinkPrivate *priv, EssRMgrResState state)
{
if (priv->rm && priv->resAssignedId >= 0)
EssRMgrResourceSetState(priv->rm, EssRMgrResType_audioDecoder, priv->resAssignedId, state);
}
#endif
static GstStateChangeReturn
gst_aml_hal_asink_change_state (GstElement * element,
GstStateChange transition)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
GstAmlHalAsink *sink = GST_AML_HAL_ASINK (element);
GstAmlHalAsinkPrivate *priv = sink->priv;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
{
GST_INFO_OBJECT(sink, "null to ready");
#ifdef ESSOS_RM
if (priv->direct_mode_)
essos_rm_init (sink);
#endif
if (priv->provided_clock) {
priv->session_id = gst_aml_clock_get_session_id(priv->provided_clock);
gst_aml_clock_set_session_mode (priv->provided_clock, priv->sync_mode);
if (gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MEDIASYNC) {
GstAmlClock *aclock = GST_AML_CLOCK_CAST(priv->provided_clock);
sync_mode mode = MEDIA_SYNC_MODE_MAX;
//set av sync mode to mediasync
switch (priv->sync_mode) {
case AV_SYNC_MODE_AMASTER:
mode = MEDIA_SYNC_AMASTER;
break;
case AV_SYNC_MODE_VMASTER:
mode = MEDIA_SYNC_VMASTER;
break;
case AV_SYNC_MODE_PCR_MASTER:
mode = MEDIA_SYNC_PCRMASTER;
break;
default:
mode = MEDIA_SYNC_MODE_MAX;
}
mediaSync_wrap_setSyncMode(aclock->handle, mode);
}
}
GST_WARNING_OBJECT(sink, "avsync session %d", priv->session_id);
GST_OBJECT_LOCK (sink);
if (!gst_aml_hal_asink_open (sink)) {
GST_OBJECT_UNLOCK (sink);
GST_ERROR_OBJECT(sink, "asink open failure");
goto open_failed;
}
if (!hal_open_device (sink)) {
GST_OBJECT_UNLOCK (sink);
goto open_failed;
}
GST_OBJECT_UNLOCK (sink);
break;
}
case GST_STATE_CHANGE_READY_TO_PAUSED:
GST_INFO_OBJECT(sink, "ready to paused");
gst_base_sink_set_async_enabled (GST_BASE_SINK_CAST(sink), FALSE);
gst_aml_hal_asink_reset_sync (sink, FALSE);
/* start in paused state until PLAYING */
priv->paused_ = TRUE;
priv->quit_clock_wait = FALSE;
/* Only post clock-provide messages if this is the clock that
* we've created. If the subclass has overriden it the subclass
* should post this messages whenever necessary */
if (gst_aml_hal_asink_is_self_provided_clock (sink))
gst_element_post_message (element,
gst_message_new_clock_provide (GST_OBJECT_CAST (element),
priv->provided_clock, TRUE));
#ifdef ESSOS_RM
resMgrUpdateState(priv, EssRMgrRes_paused);
#endif
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
{
GST_INFO_OBJECT(sink, "paused to playing");
GST_OBJECT_LOCK (sink);
hal_start (sink);
GST_OBJECT_UNLOCK (sink);
#ifdef ESSOS_RM
resMgrUpdateState(priv, EssRMgrRes_active);
#endif
if (priv->eos) {
GstMessage *message;
/* need to post EOS message here */
GST_DEBUG_OBJECT (sink, "Now posting EOS");
message = gst_message_new_eos (GST_OBJECT_CAST (sink));
gst_message_set_seqnum (message, priv->seqnum);
gst_element_post_message (GST_ELEMENT_CAST (sink), message);
}
break;
}
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
{
GstBaseSink* bsink = GST_BASE_SINK_CAST (sink);
GST_INFO_OBJECT(sink, "playing to paused");
if (priv->stream_) {
GST_OBJECT_LOCK (sink);
priv->stream_->common.set_parameters(&priv->stream_->common, "will_pause=1");
GST_OBJECT_UNLOCK (sink);
}
if (priv->avsync && priv->sync_mode == AV_SYNC_MODE_PCR_MASTER) {
GST_INFO_OBJECT(sink, "avsync to free run");
av_sync_change_mode (priv->avsync, AV_SYNC_MODE_FREE_RUN);
}
#ifdef SUPPORT_AD
else if (priv->is_ad_audio) {
GST_INFO_OBJECT(sink, "id %d to free run", priv->session_id);
av_sync_change_mode_by_id(priv->session_id, AV_SYNC_MODE_FREE_RUN);
}
#endif
GST_OBJECT_LOCK (sink);
hal_pause (sink);
/* To complete transition to paused state in async_enabled mode,
* we need a preroll buffer pushed to the pad.
* This is a workaround to avoid the need for preroll buffer. */
if (priv->xrun_timer)
g_timer_stop (priv->xrun_timer);
GST_BASE_SINK_PREROLL_LOCK (bsink);
bsink->have_preroll = 1;
GST_BASE_SINK_PREROLL_UNLOCK (bsink);
GST_OBJECT_UNLOCK (sink);
#ifdef ESSOS_RM
resMgrUpdateState(priv, EssRMgrRes_paused);
#endif
break;
}
case GST_STATE_CHANGE_PAUSED_TO_READY:
// unblock _render and upstream
// original code here has to place below parent change_state (avoid race condtion)
g_mutex_lock(&priv->feed_lock);
priv->flushing_ = TRUE;
g_cond_signal(&priv->run_ready);
g_mutex_unlock(&priv->feed_lock);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
{
GST_INFO_OBJECT(sink, "paused to ready");
paused_to_ready (sink);
#ifdef ESSOS_RM
resMgrUpdateState(priv, EssRMgrRes_idle);
#endif
break;
}
case GST_STATE_CHANGE_READY_TO_NULL:
GST_INFO_OBJECT(sink, "ready to null");
GST_OBJECT_LOCK (sink);
#ifdef ENABLE_MS12
if (priv->format_ == AUDIO_FORMAT_AC4)
hal_set_player_overwrite(sink, TRUE);
#endif
hal_close_device (sink);
gst_aml_hal_asink_close (sink);
priv->gap_state = GAP_IDLE;
priv->gap_start_pts = -1;
priv->gap_duration = 0;
priv->format_ = AUDIO_FORMAT_PCM_16_BIT;
scaletempo_init (&priv->st);
GST_OBJECT_UNLOCK (sink);
#ifdef ESSOS_RM
g_mutex_lock(&priv->ess_lock);
if (priv->rm) {
if (priv->resAssignedId >= 0) {
EssRMgrReleaseResource (priv->rm,
EssRMgrResType_audioDecoder, priv->resAssignedId);
priv->resAssignedId = -1;
}
EssRMgrDestroy (priv->rm);
priv->rm = 0;
}
g_mutex_unlock(&priv->ess_lock);
#endif
break;
default:
break;
}
return ret;
open_failed:
{
/* subclass must post a meaningful error message */
GST_DEBUG_OBJECT (sink, "open failed");
return GST_STATE_CHANGE_FAILURE;
}
}
/* open the device with given specs */
static gboolean gst_aml_hal_asink_open (GstAmlHalAsink* sink)
{
int ret, val = 0;
char *status;
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_DEBUG_OBJECT (sink, "open");
ret = audio_hw_load_interface(&priv->hw_dev_);
if (ret) {
GST_ERROR_OBJECT(sink, "fail to load hw:%d", ret);
return FALSE;
}
status = priv->hw_dev_->get_parameters (priv->hw_dev_,
"dolby_ms12_enable");
if (status) {
sscanf(status,"dolby_ms12_enable=%d", &val);
priv->ms12_enable = val;
free (status);
GST_DEBUG_OBJECT (sink, "load hw done ms12: %d", val);
}
return TRUE;
}
static gboolean gst_aml_hal_asink_close (GstAmlHalAsink* sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_DEBUG_OBJECT(sink, "close");
audio_hw_unload_interface(priv->hw_dev_);
priv->hw_dev_ = NULL;
GST_DEBUG_OBJECT(sink, "unload hw");
return TRUE;
}
/* will be called when the device should be opened. In this case we will connect
* to the server. We should not try to open any streams in this state. */
static gboolean hal_open_device (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
if (priv->direct_mode_) {
priv->trans_buf = (uint8_t *)g_malloc(MAX_TRANS_BUF_SIZE);
if (!priv->trans_buf) {
GST_ERROR_OBJECT (sink, "OOM");
return FALSE;
}
}
return TRUE;
}
/* close the device */
static gboolean hal_close_device (GstAmlHalAsink* sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_LOG_OBJECT (sink, "closing device");
if (priv->stream_) {
priv->stream_->flush(priv->stream_);
priv->hw_dev_->close_output_stream(priv->hw_dev_,
priv->stream_);
}
if (priv->trans_buf)
g_free(priv->trans_buf);
GST_LOG_OBJECT (sink, "closed device");
return TRUE;
}
static gboolean
hal_parse_spec (GstAmlHalAsink * sink, GstAudioRingBufferSpec * spec)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
gint channels;
gboolean raw_data = is_raw_type(spec->type);
switch (spec->type) {
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW:
switch (GST_AUDIO_INFO_FORMAT (&spec->info)) {
case GST_AUDIO_FORMAT_S16LE:
priv->format_ = AUDIO_FORMAT_PCM_16_BIT;
break;
default:
goto error;
}
break;
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3:
priv->format_ = AUDIO_FORMAT_AC3;
break;
case GST_AUDIO_FORMAT_TYPE_AC4:
priv->format_ = AUDIO_FORMAT_AC4;
break;
case GST_AUDIO_FORMAT_TYPE_TRUE_HD:
priv->format_ = AUDIO_FORMAT_DOLBY_TRUEHD;
break;
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3:
priv->format_ = AUDIO_FORMAT_E_AC3;
break;
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG4_AAC:
priv->format_ = AUDIO_FORMAT_HE_AAC_V2;
break;
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS:
priv->format_ = AUDIO_FORMAT_DTS;
break;
case GST_AUDIO_FORMAT_TYPE_LPCM_PRIV1:
priv->format_ = AUDIO_FORMAT_PCM_LPCM_DVD;
break;
case GST_AUDIO_FORMAT_TYPE_LPCM_PRIV2:
priv->format_ = AUDIO_FORMAT_PCM_LPCM_1394;
break;
case GST_AUDIO_FORMAT_TYPE_LPCM_TS:
priv->format_ = AUDIO_FORMAT_PCM_LPCM_BLURAY;
break;
default:
goto error;
}
priv->sr_ = GST_AUDIO_INFO_RATE (&spec->info);
channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
if (!priv->direct_mode_ && channels != 2 && channels != 1) {
GST_ERROR_OBJECT (sink, "unsupported channel number:%d", channels);
goto error;
}
if (raw_data) {
if (channels == 1)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_MONO;
else if (channels == 2)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_STEREO;
else if (channels == 3)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_2POINT1;
else if (channels == 4)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_2POINT0POINT2;
else if (channels == 5 || channels == 6)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_5POINT1;
else if (channels == 7)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_6POINT1;
else if (channels == 8)
priv->channel_mask_ = AUDIO_CHANNEL_OUT_7POINT1;
else {
GST_ERROR_OBJECT (sink, "unsupported channel number:%d", channels);
goto error;
}
} else {
//decoder gets channal mask from ES data.
priv->channel_mask_ = AUDIO_CHANNEL_OUT_STEREO;
}
GST_DEBUG_OBJECT (sink, "format:0x%x, sr:%d, ch:%d",
priv->format_, priv->sr_, channels);
GST_DEBUG_OBJECT (sink, "buffer_time:%lld, peroid_time:%lld",
spec->buffer_time, spec->latency_time);
return TRUE;
/* ERRORS */
error:
return FALSE;
}
/* prepare resources and state to operate with the given specs */
static gboolean
aml_open_output_stream (GstAmlHalAsink * sink, GstAudioRingBufferSpec * spec)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
struct audio_config config;
int ret;
audio_output_flags_t flag;
audio_devices_t device;
GST_DEBUG_OBJECT (sink, "prepare");
if (!hal_parse_spec (sink, spec))
return FALSE;
memset(&config, 0, sizeof(config));
config.sample_rate = priv->sr_;
config.channel_mask = priv->channel_mask_;
config.format = priv->format_;
if (priv->tts_mode_)
flag = AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_PRIMARY;
else if (priv->direct_mode_)
flag = AUDIO_OUTPUT_FLAG_DIRECT | AUDIO_OUTPUT_FLAG_HW_AV_SYNC;
else if (priv->sys_sound_)
flag = AUDIO_OUTPUT_FLAG_PRIMARY | AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
else
flag = AUDIO_OUTPUT_FLAG_PRIMARY;
#if SUPPORT_AD
if (priv->is_dual_audio) {
char setting[50];
snprintf(setting, sizeof(setting), "dual_decoder_support=1");
priv->hw_dev_->set_parameters(priv->hw_dev_, setting);
}
if (priv->is_ad_audio) {
char setting[50];
flag |= AUDIO_OUTPUT_FLAG_AD_STREAM;
snprintf(setting, sizeof(setting), "associate_audio_mixing_enable_force=1");
priv->hw_dev_->set_parameters(priv->hw_dev_, setting);
}
#endif
if (priv->output_port_ == 0)
device = AUDIO_DEVICE_OUT_SPEAKER;
else if (priv->output_port_ == 1)
device = AUDIO_DEVICE_OUT_HDMI;
else if (priv->output_port_ == 2)
device = AUDIO_DEVICE_OUT_HDMI_ARC;
else if (priv->output_port_ == 3)
device = AUDIO_DEVICE_OUT_SPDIF;
else {
GST_ERROR_OBJECT(sink, "invalid port:%d", priv->output_port_);
return FALSE;
}
ret = priv->hw_dev_->open_output_stream(priv->hw_dev_,
0, device,
flag, &config,
&priv->stream_, NULL);
if (ret) {
GST_ERROR_OBJECT(sink, "can not open output stream:%d", ret);
return FALSE;
}
#ifdef ENABLE_MS12
if (priv->format_ == AUDIO_FORMAT_AC4)
hal_set_player_overwrite(sink, FALSE);
#endif
GST_DEBUG_OBJECT (sink, "done");
return TRUE;
}
/* This method should create a new stream of the given @spec. No playback should
* start yet so we start in the corked state. */
static gboolean hal_acquire (GstAmlHalAsink * sink,
GstAudioRingBufferSpec * spec)
{
if (!aml_open_output_stream (sink, spec))
return FALSE;
/* TODO:: configure volume when we changed it, else we leave the default */
GST_DEBUG_OBJECT(sink, "done");
return TRUE;
}
/* free the stream that we acquired before */
static gboolean hal_release (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_INFO_OBJECT (sink, "enter");
hal_stop(sink);
g_mutex_lock(&priv->feed_lock);
if (priv->stream_) {
priv->hw_dev_->close_output_stream(priv->hw_dev_, priv->stream_);
priv->stream_ = NULL;
priv->render_samples = 0;
}
g_mutex_unlock(&priv->feed_lock);
#if SUPPORT_AD
if (priv->is_dual_audio) {
char setting[50];
snprintf(setting, sizeof(setting), "dual_decoder_support=0");
priv->hw_dev_->set_parameters(priv->hw_dev_, setting);
}
if (priv->is_ad_audio) {
char setting[50];
snprintf(setting, sizeof(setting), "associate_audio_mixing_enable_force=255");
priv->hw_dev_->set_parameters(priv->hw_dev_, setting);
}
#endif
GST_INFO_OBJECT(sink, "done");
return TRUE;
}
#if 0
static int config_sys_node(const char* path, const char* value)
{
int fd;
fd = open(path, O_RDWR);
if (fd < 0) {
GST_ERROR("fail to open %s", path);
return -1;
}
if (write(fd, value, strlen(value)) != strlen(value)) {
GST_ERROR("fail to write %s to %s", value, path);
close(fd);
return -1;
}
close(fd);
return 0;
}
static int tsync_set_first_apts(uint32_t pts)
{
char val[20];
snprintf (val, sizeof(val), "%u", pts);
return config_sys_node(TSYNC_APTS, val);
}
static int tsync_send_audio_event(const char* event)
{
char *val;
val = "AUDIO_PAUSE";
return config_sys_node(TSYNC_EVENT, val);
}
static int tsync_reset_pcr (GstAmlHalAsink * sink)
{
config_sys_node(TSYNC_PCRSCR, "0");
return 0;
}
static int tsync_enable (GstAmlHalAsink * sink, gboolean enable)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
if (!priv->direct_mode_)
return 0;
if (enable && !priv->tsync_enable_) {
config_sys_node(TSYNC_ENABLE, "1");
priv->tsync_enable_ = TRUE;
GST_DEBUG_OBJECT (sink, "tsync enable");
if (!priv->pcr_master_)
config_sys_node(TSYNC_MODE, "1");//audio master
else
config_sys_node(TSYNC_MODE, "2");//pcr master
return 0;
}
if (!enable && priv->tsync_enable_) {
config_sys_node(TSYNC_ENABLE, "0");
priv->tsync_enable_ = FALSE;
GST_DEBUG_OBJECT (sink, "tsync disable");
}
return 0;
}
#endif
#define HAL_WRONG_STAT 3
static gboolean hal_start (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GST_DEBUG_OBJECT (sink, "enter");
if (!priv->stream_) {
GST_INFO_OBJECT (sink, "stream not created yet");
priv->paused_ = FALSE;
} else {
g_mutex_lock(&priv->feed_lock);
if (priv->paused_) {
int ret;
ret = priv->stream_->resume(priv->stream_);
if (ret)
GST_WARNING_OBJECT (sink, "resume failure:%d", ret);
GST_DEBUG_OBJECT (sink, "resume");
/* if need to wait for video, start the timer after first hal_commit */
if (!priv->wait_video && priv->xrun_timer)
g_timer_start(priv->xrun_timer);
priv->paused_ = FALSE;
g_cond_signal (&priv->run_ready);
}
g_mutex_unlock(&priv->feed_lock);
}
return TRUE;
}
/* pause/stop playback ASAP */
static gboolean hal_pause (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int ret;
GST_INFO_OBJECT (sink, "enter");
if (!priv->stream_) {
return FALSE;
}
g_mutex_lock(&priv->feed_lock);
if (priv->paused_) {
g_mutex_unlock(&priv->feed_lock);
GST_DEBUG_OBJECT (sink, "already in pause state");
return TRUE;
}
/* set paused_ before pause() to make sure get position
* returns correct value
*/
priv->paused_ = TRUE;
ret = priv->stream_->pause(priv->stream_);
if (ret)
GST_WARNING_OBJECT (sink, "pause failure:%d", ret);
g_mutex_unlock(&priv->feed_lock);
GST_INFO_OBJECT (sink, "done");
return TRUE;
}
/* stop playback, we flush everything. */
static gboolean hal_stop (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int ret;
GST_DEBUG_OBJECT (sink, "enter");
if (!priv->stream_) {
return FALSE;
}
ret = priv->stream_->flush(priv->stream_);
if (ret) {
GST_ERROR_OBJECT (sink, "pause failure:%d", ret);
return FALSE;
}
/* unblock audio HAL wait */
if (priv->avsync)
avs_sync_stop_audio (priv->avsync);
g_mutex_lock (&priv->feed_lock);
priv->flushing_ = TRUE;
g_cond_signal (&priv->run_ready);
GST_DEBUG_OBJECT (sink, "stop");
if (priv->avsync) {
void *tmp;
/* if session is still alive, recover mode for next playback */
if (priv->sync_mode == AV_SYNC_MODE_PCR_MASTER) {
GST_INFO_OBJECT(sink, "recover avsync mode");
av_sync_change_mode (priv->avsync, AV_SYNC_MODE_PCR_MASTER);
}
tmp = priv->avsync;
g_atomic_pointer_set (&priv->avsync, NULL);
av_sync_destroy (tmp);
}
g_mutex_unlock (&priv->feed_lock);
return TRUE;
}
static guint table_5_13[38][4] = {
{96, 69, 64, 32},
{96, 70, 64, 32},
{120, 87, 80, 40},
{120, 88, 80, 40},
{144, 104, 96, 48},
{144, 105, 96, 48},
{168, 121, 112, 56},
{168, 122, 112, 56},
{192, 139, 128, 64},
{192, 140, 128, 64},
{240, 174, 160, 80},
{240, 175, 160, 80},
{288, 208, 192, 96},
{288, 209, 192, 96},
{336, 243, 224, 112},
{336, 244, 224, 112},
{384, 278, 256, 128},
{384, 279, 256, 128},
{480, 348, 320, 160},
{480, 349, 320, 160},
{576, 417, 384, 192},
{576, 418, 384, 192},
{672, 487, 448, 224},
{672, 488, 448, 224},
{768, 557, 512, 256},
{768, 558, 512, 256},
{960, 696, 640, 320},
{960, 697, 640, 320},
{1152, 835, 768, 384},
{1152, 836, 768, 384},
{1344, 975, 896, 448},
{1344, 976, 896, 448},
{1536, 1114, 1024, 512},
{1536, 1115, 1024, 512},
{1728, 1253, 1152, 576},
{1728, 1254, 1152, 576},
{1920, 1393, 1280, 640},
{1920, 1394, 1280, 640}
};
static int parse_bit_stream(GstAmlHalAsink *sink,
guchar * data, gint size)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
GstAudioRingBufferSpec * spec = &priv->spec;
if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3) {
/* Digital Audio Compression Standard (AC-3 E-AC-3) 5.3 */
guint8 frmsizecod;
guint8 fscod;
if (size < 5) {
return -1;
}
/* check sync word */
if (data[0] != 0x0b || data[1] != 0x77)
return -1;
fscod = (data[4] >> 6);
frmsizecod = data[4]&0x3F;
GST_LOG_OBJECT (sink, "fscod:%d frmsizecod:%d", fscod, frmsizecod);
if (fscod > 2)
return -1;
if (frmsizecod > 37)
return -1;
priv->encoded_size = table_5_13[frmsizecod][2 - fscod] * 2;
priv->sample_per_frame = 1536;
GST_LOG_OBJECT (sink, "encoded_size:%d", priv->encoded_size);
return 0;
} else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3) {
/* Digital Audio Compression Standard (AC-3 E-AC-3) Annex E */
guint16 frmsizecod;
guint8 fscod, fscod2;
guint8 numblkscod;
if (size < 5) {
return -1;
}
/* check sync word */
if (data[0] != 0x0b || data[1] != 0x77)
return -1;
fscod = (data[4] >> 6);
frmsizecod = data[3] + ((data[2]&0x7) << 8) + 1;
GST_LOG_OBJECT (sink, "fscod:%d frmsizecod:%d", fscod, frmsizecod);
if (fscod > 3)
return -1;
if (frmsizecod > 2048)
return -1;
if (fscod == 3) {
fscod2 = (data[4] >> 4) & 0x3;
GST_LOG_OBJECT (sink, "fscod2:%d", fscod2);
if (fscod2 == 0)
priv->sr_ = 24000;
else if (fscod2 == 1)
priv->sr_ = 22050;
else if (fscod2 == 2)
priv->sr_ = 16000;
else {
return -1;
}
priv->sample_per_frame = 256*6;
} else {
numblkscod = (data[4] >> 4) & 0x3;
GST_LOG_OBJECT (sink, "numblkscod:%d", numblkscod);
if (numblkscod == 0)
priv->sample_per_frame = 256;
else if (numblkscod == 1)
priv->sample_per_frame = 256 * 2;
else if (numblkscod == 2)
priv->sample_per_frame = 256 * 3;
else if (numblkscod == 3)
priv->sample_per_frame = 256 * 6;
}
priv->encoded_size = frmsizecod * 2;
GST_LOG_OBJECT (sink, "encoded_size:%d spf:%d",
priv->encoded_size, priv->sample_per_frame);
return 0;
} else if (spec->type == GST_AUDIO_FORMAT_TYPE_AC4) {
struct ac4_info info;
if (ac4_toc_parse(data, size, &info)) {
GST_ERROR_OBJECT (sink, "parse ac4 fail");
dump("/tmp/ac4.dat", data, size);
return -1;
}
priv->sample_per_frame = info.samples_per_frame;
priv->sr_ = info.frame_rate;
priv->spec.info.rate = priv->sr_;
priv->sync_frame = info.sync_frame;
GST_DEBUG_OBJECT (sink, "sr:%d spf:%d sync:%d",
priv->sr_, priv->sample_per_frame, priv->sync_frame);
return 0;
} else if (spec->type == GST_AUDIO_FORMAT_TYPE_TRUE_HD) {
return 0;
}
return -1;
}
struct hw_sync_header_v2 {
uint8_t version[4];
uint8_t size[4]; /* big endian */
uint8_t pts[8]; /* big endian */
uint8_t offset[4]; /* big endian */
};
struct hw_sync_header_v3 {
uint8_t version[4];
uint8_t size[4]; /* big endian */
uint8_t pts[8]; /* big endian */
uint8_t offset[4]; /* big endian */
uint8_t c_start_duration[8]; /* big endian */
uint8_t c_end_duration[8]; /* big endian */
};
static void hw_sync_set_header_ver(uint8_t aversion[], uint8_t ver_no)
{
aversion[0] = 0x55;
aversion[1] = 0x55;
aversion[2] = 0;
aversion[3] = ver_no;
}
static void hw_sync_set_header_pts(uint8_t apts[], uint64_t pts_64)
{
apts[0] = (pts_64&0xff00000000000000ull) >> 56;
apts[1] = (pts_64&0x00ff000000000000ull) >> 48;
apts[2] = (pts_64&0x0000ff0000000000ull) >> 40;
apts[3] = (pts_64&0x000000ff00000000ull) >> 32;
apts[4] = (pts_64&0x00000000ff000000ull) >> 24;
apts[5] = (pts_64&0x0000000000ff0000ull) >> 16;
apts[6] = (pts_64&0x000000000000ff00ull) >> 8;
apts[7] = (pts_64&0x00000000000000ffull);
}
static void hw_sync_set_header_size(uint8_t asize[], uint32_t size)
{
asize[0] = (size&0xFF000000) >> 24;
asize[1] = (size&0xFF0000) >> 16;
asize[2] = (size&0xFF00) >> 8;
asize[3] = (size&0xFF);
}
static void hw_sync_set_header_offset(uint8_t aoffset[], uint32_t offset)
{
aoffset[0] = (offset & 0xFF000000) >> 24;
aoffset[1] = (offset & 0xFF0000) >> 16;
aoffset[2] = (offset & 0xFF00) >> 8;
aoffset[3] = (offset & 0xFF);
}
static void hw_sync_set_ver(struct hw_sync_header_v2* header)
{
hw_sync_set_header_ver(header->version, 0x02);
}
static void hw_sync_set_ver_v3(struct hw_sync_header_v2* header)
{
hw_sync_set_header_ver(header->version, 0x03);
}
static void hw_sync_set_size(struct hw_sync_header_v2* header, uint32_t size)
{
hw_sync_set_header_size(header->size, size);
}
static void hw_sync_set_pts(struct hw_sync_header_v2* header, uint64_t pts_64)
{
hw_sync_set_header_pts(header->pts, pts_64);
}
static void hw_sync_set_offset(struct hw_sync_header_v2* header, uint32_t offset)
{
hw_sync_set_header_offset(header->offset, offset);
}
static void dump(const char* path, const uint8_t *data, int size) {
#ifdef DUMP_TO_FILE
char name[50];
FILE* fd;
sprintf(name, "%s%d.dat", path, file_index);
fd = fopen(name, "ab");
if (!fd)
return;
fwrite(data, 1, size, fd);
fclose(fd);
#endif
}
static void diag_print(GstAmlHalAsink * sink, uint32_t pts_90k)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
struct timespec ts;
FILE *fd;
clock_gettime(CLOCK_MONOTONIC, &ts);
fd = fopen(priv->log_path, "a+");
if (fd) {
fprintf(fd, "[%6lu.%06lu] %u 0 A GtoA\n", ts.tv_sec, ts.tv_nsec/1000, pts_90k);
fclose(fd);
}
}
/* audio gap is for Netflix 2ch AAC, 2/6ch LPCM PCM16_LE are the only cases */
static void vol_ramp(guchar * data, gint size, gint ch_num, int dir)
{
int i, ch;
int frames = size / 2 / ch_num;
int16_t *p = (int16_t *)(data);
if (dir == RAMP_DOWN) {
for (i = 0; i < frames; i++) {
float t = (float)(i) / frames;
for (ch = 0; ch < ch_num; ch++)
*p++ *= 1.0f - t * t * t;
}
} else {
for (i = 0; i < frames; i++) {
float t = (float)i / frames - 1.0;
for (ch = 0; ch < ch_num; ch++)
*p++ *= t * t * t + 1;
}
}
}
static guint hal_commit (GstAmlHalAsink * sink, guchar * data,
gint size, guint64 pts_64)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
guint towrite;
gboolean raw_data;
guint offset = 0;
guint hw_header_s = sizeof (struct hw_sync_header_v3);
if (!priv->stream_) {
GST_WARNING_OBJECT (sink, "stream closed");
return 0;
}
raw_data = is_raw_type(priv->spec.type);
towrite = size;
if (towrite)
dump ("/data/asink_", data, towrite);
if (priv->format_ == AUDIO_FORMAT_AC4) {
/* parse sync frame */
parse_bit_stream(sink, data, towrite);
}
/* notify EOS */
if (!towrite && priv->direct_mode_) {
struct hw_sync_header_v3 header;
hw_sync_set_ver_v3(&header);
hw_sync_set_header_size(header.size, 0);
hw_sync_set_header_pts(header.pts, -1);
hw_sync_set_header_pts(header.c_start_duration, 0);
hw_sync_set_header_pts(header.c_end_duration, 0);
hw_sync_set_header_offset(header.offset, 0);
priv->stream_->write(priv->stream_, &header, hw_header_s);
return 0;
}
while (towrite > 0) {
gboolean trans = false;
int header_size = 0;
int written;
int cur_size;
uint64_t pts_inc = 0;
guchar * trans_data = NULL;
if (priv->flushing_)
break;
if (priv->format_ == AUDIO_FORMAT_AC3) {
/* Frame aligned
* AC4 has constant bit rate (CBR) and variable bit reate(VBR) streams
* And VBR doesn't have to be encoded size aligned.
* EAC3 has dependent stream and substream. Hard to split.
*/
if (parse_bit_stream(sink, data, towrite) < 0 || towrite < priv->encoded_size) {
GST_WARNING_OBJECT (sink, "stream not frame aligned left %d discarded", towrite);
return size;
}
cur_size = priv->encoded_size;
} else if (priv->tempo_used) {
cur_size = scaletemp_get_stride(&priv->st);
if (cur_size > towrite)
cur_size = towrite;
} else
cur_size = towrite;
if (priv->format_ == AUDIO_FORMAT_AC4 && !priv->sync_frame) {
int32_t ac4_header_len;
uint8_t *header;
header = ac4_syncframe_header(towrite, &ac4_header_len);
header_size += ac4_header_len;
if (cur_size > TRANS_DATA_SIZE) {
GST_ERROR_OBJECT(sink, "frame too big %d", cur_size);
return offset;
}
memcpy(priv->trans_buf + TRANS_DATA_OFFSET,
data, cur_size);
trans_data = priv->trans_buf + TRANS_DATA_OFFSET - ac4_header_len;
memcpy(trans_data, header, ac4_header_len);
cur_size += ac4_header_len;
trans = true;
}
if (priv->direct_mode_) {
struct hw_sync_header_v3 *hw_sync;
uint32_t pts_32 = -1;
//truncate to 32bit PTS
if (pts_64 != GST_CLOCK_TIME_NONE && pts_64 != HAL_INVALID_PTS) {
if (gst_aml_clock_get_clock_type(priv->provided_clock) == GST_AML_CLOCK_TYPE_MSYNC) {
pts_32 = gst_util_uint64_scale_int(pts_64, PTS_90K, GST_SECOND);
pts_64 = gst_util_uint64_scale_int(pts_32, GST_SECOND, PTS_90K);
}
} else {
pts_64 = HAL_INVALID_PTS;
}
if (!trans) {
hw_sync = (struct hw_sync_header_v3 *)priv->trans_buf;
if (cur_size > MAX_TRANS_BUF_SIZE - hw_header_s) {
if (raw_data) {
/* truncate and alight to 16B */
cur_size = MAX_TRANS_BUF_SIZE - hw_header_s;
cur_size &= 0xFFFFFFF0;
} else {
GST_ERROR_OBJECT(sink, "frame too big %d", cur_size);
return offset;
}
}
memcpy(priv->trans_buf + sizeof (*hw_sync),
data, cur_size);
trans_data = priv->trans_buf;
header_size += hw_header_s;
trans = true;
} else {
header_size += hw_header_s;
if (header_size > TRANS_DATA_OFFSET) {
GST_ERROR_OBJECT(sink, "header too big %d", header_size);
return offset;
}
hw_sync = (struct hw_sync_header_v3 *)(trans_data - hw_header_s);
trans_data -= hw_header_s;
}
hw_sync_set_ver_v3(hw_sync);
hw_sync_set_header_size(hw_sync->size, cur_size);
hw_sync_set_header_pts(hw_sync->pts, pts_64);
hw_sync_set_header_pts(hw_sync->c_start_duration, priv->clip_front);
hw_sync_set_header_pts(hw_sync->c_end_duration, priv->clip_back);
hw_sync_set_header_offset(hw_sync->offset, 0);
cur_size += hw_header_s;
if (priv->diag_log_enable && pts_32 != -1)
diag_print (sink, pts_32);
} else if (raw_data) {
/* audio hal can not handle too big frame, limit to 8K*/
if (cur_size > 8*1024) {
cur_size = 8*1024;
}
}
if (trans) {
written = priv->stream_->write(priv->stream_, trans_data, cur_size);
if (written == cur_size)
written -= header_size;
else {
GST_ERROR_OBJECT (sink, "trans mode write fail %d/%d", written, cur_size);
return written;
}
} else {
/* should consume all the PCM data */
written = priv->stream_->write(priv->stream_, data, cur_size);
if (written < 0) {
GST_ERROR_OBJECT (sink, "drop data %d/%d", written, cur_size);
return cur_size;
}
}
towrite -= written;
data += written;
GST_LOG_OBJECT (sink,
"write %d/%d left %d ts: %llu", written, cur_size, towrite, pts_64);
/* update PTS for next sample */
if (priv->direct_mode_ && pts_64 != HAL_INVALID_PTS) {
if (priv->sr_) {
if (raw_data) {
gint bpf = GST_AUDIO_INFO_BPF (&priv->spec.info);
if (bpf)
pts_inc = gst_util_uint64_scale_int (written/bpf,
GST_SECOND, priv->sr_) * priv->rate;
} else
pts_inc = gst_util_uint64_scale_int (priv->sample_per_frame,
GST_SECOND, priv->sr_);
} else
GST_WARNING_OBJECT (sink, "invalid sample rate %d", priv->sr_);
pts_64 += pts_inc;
}
if (priv->xrun_timer)
g_timer_start(priv->xrun_timer);
}
check_drop_pts(sink);
if (!raw_data)
priv->frame_sent++;
return size;
}
static uint32_t hal_get_latency (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
int latency = 0;
GST_DEBUG_OBJECT (sink, "enter");
if (!priv->stream_) {
GST_ERROR_OBJECT (sink, "null pointer");
return 0;
}
priv->stream_->pause(priv->stream_);
latency = priv->stream_->get_latency(priv->stream_);
GST_DEBUG_OBJECT (sink, "latency %u", latency);
return latency;
}
static void hal_get_metric_info (GstAmlHalAsink * sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
char *metric = NULL;
GST_DEBUG_OBJECT (sink, "enter");
if (!priv->stream_) {
GST_ERROR_OBJECT (sink, "null pointer");
return 0;
}
metric = priv->stream_->common.get_parameters(priv->stream_, "get_metric_info");
if (NULL != metric) {
sscanf(metric, "drop_start=%"PRId64",drop_duration=%"PRId64"", &priv->drop_start, &priv->drop_duration);
free (metric);
}
GST_DEBUG_OBJECT (sink, " drop_start=%" PRId64 ", drop_duration=%" PRId64 "", priv->drop_start, priv->drop_duration);
return;
}
static void check_drop_pts (GstAmlHalAsink *sink)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
//get metric info
hal_get_metric_info(sink);
if (-1 == priv->drop_duration) {
return;
}
GST_WARNING_OBJECT (sink, "emit drop pts signal start:%" PRId64 ", duration:%" PRId64 "", priv->drop_start, priv->drop_duration);
g_signal_emit (G_OBJECT (sink), g_signals[SIGNAL_DROPPTS], 0, priv->drop_start, priv->drop_duration, NULL);
priv->drop_duration = -1;
priv->drop_start = -1;
return;
}
#ifdef ENABLE_MS12
static void hal_set_player_overwrite(GstAmlHalAsink * sink, gboolean defaults)
{
GstAmlHalAsinkPrivate *priv = sink->priv;
char setting[50];
if (!priv->hw_dev_)
return;
/*ac4 setting */
if (!priv->stream_) {
return;
}
snprintf(setting, sizeof(setting), "ms12_runtime=-lang %s", defaults ? "DEF" : priv->ac4_lang);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-lang2 %s", defaults ? "DEF" : priv->ac4_lang2);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-pat %d", defaults ? 255 : priv->ac4_pat);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-ac4_pres_group_idx %d", defaults ? -1 : priv->ac4_pres_group_idx);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-at %d", defaults ? 255 : priv->ac4_ass_type);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-xu %d", defaults ? 0 : priv->ac4_mixer_gain);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
snprintf(setting, sizeof(setting), "ms12_runtime=-xa %d", defaults ? 1 : priv->ms12_mix_en);
priv->stream_->common.set_parameters (&priv->stream_->common, setting);
}
#endif
GstClock *gst_aml_hal_asink_get_clock (GstElement *element)
{
return gst_aml_hal_asink_provide_clock (element);
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "amlhalasink", GST_RANK_PRIMARY + 1,
GST_TYPE_AML_HAL_ASINK);
}
#ifndef VERSION
#define VERSION "0.1.0"
#endif
#ifndef PACKAGE
#define PACKAGE "aml_package"
#endif
#ifndef PACKAGE_NAME
#define PACKAGE_NAME "aml_media"
#endif
#ifndef GST_PACKAGE_ORIGIN
#define GST_PACKAGE_ORIGIN "http://amlogic.com"
#endif
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
amlhalasink,
"Amlogic plugin for audio rendering",
plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)