diff --git a/KeyConfig.cpp b/KeyConfig.cpp index 0a2be00a..2aadbd8f 100644 --- a/KeyConfig.cpp +++ b/KeyConfig.cpp @@ -43,7 +43,7 @@ int convertStringToAction(string str_action) if(str_action == "EXIT") return KeyConfig::ACTION_EXIT; if(str_action == "PAUSE") - return KeyConfig::ACTION_PAUSE; + return KeyConfig::ACTION_PLAYPAUSE; if(str_action == "DECREASE_VOLUME") return KeyConfig::ACTION_DECREASE_VOLUME; if(str_action == "INCREASE_VOLUME") @@ -116,8 +116,8 @@ map KeyConfig::buildDefaultKeymap() keymap['f'] = ACTION_INCREASE_SUBTITLE_DELAY; keymap['q'] = ACTION_EXIT; keymap[KEY_ESC] = ACTION_EXIT; - keymap['p'] = ACTION_PAUSE; - keymap[' '] = ACTION_PAUSE; + keymap['p'] = ACTION_PLAYPAUSE; + keymap[' '] = ACTION_PLAYPAUSE; keymap['-'] = ACTION_DECREASE_VOLUME; keymap['+'] = ACTION_INCREASE_VOLUME; keymap['='] = ACTION_INCREASE_VOLUME; diff --git a/KeyConfig.h b/KeyConfig.h index b1beb8ef..7de06c7d 100644 --- a/KeyConfig.h +++ b/KeyConfig.h @@ -22,7 +22,7 @@ class KeyConfig ACTION_DECREASE_SUBTITLE_DELAY = 13, ACTION_INCREASE_SUBTITLE_DELAY = 14, ACTION_EXIT = 15, - ACTION_PAUSE = 16, + ACTION_PLAYPAUSE = 16, ACTION_DECREASE_VOLUME = 17, ACTION_INCREASE_VOLUME = 18, ACTION_SEEK_BACK_SMALL = 19, @@ -40,7 +40,9 @@ class KeyConfig ACTION_SHOW_SUBTITLES = 31, ACTION_SET_ALPHA = 32, ACTION_SET_ASPECT_MODE = 33, - ACTION_CROP_VIDEO = 34 + ACTION_CROP_VIDEO = 34, + ACTION_PAUSE = 35, + ACTION_PLAY = 36, }; #define KEY_LEFT 0x5b44 diff --git a/Makefile b/Makefile index bcfadfb8..2857e94c 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,14 @@ include Makefile.include CFLAGS+=-std=c++0x -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -DTARGET_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -DHAVE_CMAKE_CONFIG -D__VIDEOCORE4__ -U_FORTIFY_SOURCE -Wall -DHAVE_OMXLIB -DUSE_EXTERNAL_FFMPEG -DHAVE_LIBAVCODEC_AVCODEC_H -DHAVE_LIBAVUTIL_OPT_H -DHAVE_LIBAVUTIL_MEM_H -DHAVE_LIBAVUTIL_AVUTIL_H -DHAVE_LIBAVFORMAT_AVFORMAT_H -DHAVE_LIBAVFILTER_AVFILTER_H -DHAVE_LIBSWRESAMPLE_SWRESAMPLE_H -DOMX -DOMX_SKIP64BIT -ftree-vectorize -DUSE_EXTERNAL_OMX -DTARGET_RASPBERRY_PI -DUSE_EXTERNAL_LIBBCM_HOST -LDFLAGS+=-L./ -Lffmpeg_compiled/usr/local/lib/ -lc -lWFC -lGLESv2 -lEGL -lbcm_host -lopenmaxil -lfreetype -lz +LDFLAGS+=-L./ -Lffmpeg_compiled/usr/local/lib/ -lc -lWFC -lGLESv2 -lEGL -lbcm_host -lopenmaxil -lfreetype -lz -lasound INCLUDES+=-I./ -Ilinux -Iffmpeg_compiled/usr/local/include/ -I /usr/include/dbus-1.0 -I /usr/lib/arm-linux-gnueabihf/dbus-1.0/include DIST ?= omxplayer-dist -SRC=linux/XMemUtils.cpp \ +SRC= linux/XMemUtils.cpp \ + linux/OMXAlsa.cpp \ utils/log.cpp \ DynamicDll.cpp \ utils/PCMRemap.cpp \ diff --git a/Makefile.ffmpeg b/Makefile.ffmpeg index bf25743f..ea12aadc 100644 --- a/Makefile.ffmpeg +++ b/Makefile.ffmpeg @@ -250,7 +250,7 @@ clean: .PHONY : checkout checkout: - git clone git://source.ffmpeg.org/ffmpeg ffmpeg -b release/3.0 --depth=1 + git clone git://source.ffmpeg.org/ffmpeg ffmpeg -b release/3.1 --depth=1 .PHONY : install install: diff --git a/OMXAudio.cpp b/OMXAudio.cpp index ac5ee0ff..a5877866 100644 --- a/OMXAudio.cpp +++ b/OMXAudio.cpp @@ -112,6 +112,11 @@ bool COMXAudio::PortSettingsChanged() if(!m_omx_render_hdmi.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit)) return false; } + if (m_config.device == "omx:alsa") + { + if(!m_omx_render_analog.Initialize("OMX.alsa.audio_render", OMX_IndexParamAudioInit)) + return false; + } UpdateAttenuation(); @@ -235,7 +240,7 @@ bool COMXAudio::PortSettingsChanged() OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; OMX_INIT_STRUCTURE(audioDest); - strncpy((char *)audioDest.sName, "local", strlen("local")); + strncpy((char *)audioDest.sName, m_config.device == "omx:alsa" ? m_config.subdevice.c_str() : "local", sizeof(audioDest.sName)); omx_err = m_omx_render_analog.SetConfig(OMX_IndexConfigBrcmAudioDestination, &audioDest); if (omx_err != OMX_ErrorNone) { diff --git a/OMXAudio.h b/OMXAudio.h index 5b56e97c..6657a329 100644 --- a/OMXAudio.h +++ b/OMXAudio.h @@ -46,6 +46,7 @@ class OMXAudioConfig COMXStreamInfo hints; bool use_thread; CStdString device; + CStdString subdevice; enum PCMLayout layout; bool boostOnDownmix; bool passthrough; diff --git a/OMXAudioCodecOMX.cpp b/OMXAudioCodecOMX.cpp index 58d75a9b..aba4c62f 100644 --- a/OMXAudioCodecOMX.cpp +++ b/OMXAudioCodecOMX.cpp @@ -164,6 +164,11 @@ int COMXAudioCodecOMX::Decode(BYTE* pData, int iSize, double dts, double pts) if (!m_pCodecContext) return -1; AVPacket avpkt; + if (!m_iBufferOutputUsed) + { + m_dts = dts; + m_pts = pts; + } if (m_bGotFrame) return 0; @@ -194,12 +199,6 @@ int COMXAudioCodecOMX::Decode(BYTE* pData, int iSize, double dts, double pts) m_pFrame1->data[0], m_pFrame1->data[1], m_pFrame1->data[2], m_pFrame1->data[3], m_pFrame1->data[4], m_pFrame1->data[5], m_pFrame1->data[6], m_pFrame1->data[7] ); } - - if (!m_iBufferOutputUsed) - { - m_dts = dts; - m_pts = pts; - } return iBytesUsed; } diff --git a/OMXControl.cpp b/OMXControl.cpp index 834461f4..7b6aa999 100644 --- a/OMXControl.cpp +++ b/OMXControl.cpp @@ -13,6 +13,47 @@ #include "OMXControl.h" #include "KeyConfig.h" + +void ToURI(const std::string& str, char *uri) +{ + //Test if URL/URI + bool isURL=true; + auto result = str.find("://"); + if(result == std::string::npos || result == 0) + { + isURL=false; + } + + if(isURL) + { + for(size_t i = 0; i < result; ++i) + { + if(!isalpha(str[i])) + isURL=false; + } + } + + //Build URI if needed + if(isURL) + { + //Just write URL as it is + strncpy(uri, str.c_str(), PATH_MAX); + } + else + { + //Get file full path and add file:// + char * real_path=realpath(str.c_str(), NULL); + sprintf(uri, "file://%s", real_path); + free(real_path); + } +} + +void deprecatedMessage() +{ + CLog::Log(LOGWARNING, "DBus property access through direct method is deprecated. Use Get/Set methods instead."); +} + + #define CLASSNAME "OMXControl" OMXControlResult::OMXControlResult( int newKey ) { @@ -158,16 +199,399 @@ OMXControlResult OMXControl::getEvent() return KeyConfig::ACTION_BLANK; CLog::Log(LOGDEBUG, "Popped message member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + OMXControlResult result = handle_event(m); + dbus_message_unref(m); + return result; +} + +OMXControlResult OMXControl::handle_event(DBusMessage *m) +{ + //----------------------------DBus root interface----------------------------- + //Methods: if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_ROOT, "Quit")) { - dbus_respond_ok(m); + dbus_respond_ok(m);//Note: No reply according to MPRIS2 specs return KeyConfig::ACTION_EXIT; } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_ROOT, "Raise")) + { + //Does nothing + return KeyConfig::ACTION_BLANK; + } + //Properties Get method: + //TODO: implement GetAll + else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) + { + DBusError error; + dbus_error_init(&error); + + //Retrieve interface and property name + const char *interface, *property; + dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID); + //Root interface: + if (strcmp(interface, OMXPLAYER_DBUS_INTERFACE_ROOT)==0) + { + if (strcmp(property, "CanRaise")==0) + { + dbus_respond_boolean(m, 0); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanQuit")==0) + { + dbus_respond_boolean(m, 1); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanSetFullscreen")==0) + { + dbus_respond_boolean(m, 0); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Fullscreen")==0) //Fullscreen is read/write in theory not read only, but read only at the moment so... + { + dbus_respond_boolean(m, 1); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "HasTrackList")==0) + { + dbus_respond_boolean(m, 0); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Identity")==0) + { + dbus_respond_string(m, "OMXPlayer"); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "SupportedUriSchemes")==0) //TODO: Update ? + { + const char *UriSchemes[] = {"file", "http", "rtsp", "rtmp"}; + dbus_respond_array(m, UriSchemes, 4); // Array is of length 4 + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "SupportedMimeTypes")==0) //Vinc: TODO: Minimal list of supported types based on ffmpeg minimal support ? + { + const char *MimeTypes[] = {}; // Needs supplying + dbus_respond_array(m, MimeTypes, 0); + return KeyConfig::ACTION_BLANK; + } + //Wrong property + else + { + //Error + CLog::Log(LOGWARNING, "Unhandled dbus property message, member: %s interface: %s type: %d path: %s property: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m), property ); + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + return KeyConfig::ACTION_BLANK; + } + } + //Player interface: + else if (strcmp(interface, OMXPLAYER_DBUS_INTERFACE_PLAYER)==0) + { + //MPRIS2 properties: + if (strcmp(property, "CanGoNext")==0) + { + dbus_respond_boolean(m, 0); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanGoPrevious")==0) + { + dbus_respond_boolean(m, 0); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanSeek")==0) + { + dbus_respond_boolean(m, reader->CanSeek()); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanControl")==0) + { + dbus_respond_boolean(m, 1); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanPlay")==0) + { + dbus_respond_boolean(m, 1); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "CanPause")==0) + { + dbus_respond_boolean(m, 1); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Position")==0) + { + // Returns the current position in microseconds + int64_t pos = clock->OMXMediaTime(); + dbus_respond_int64(m, pos); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "PlaybackStatus")==0) + { + const char *status; + if (clock->OMXIsPaused()) + { + status = "Paused"; + } + else + { + status = "Playing"; + } + dbus_respond_string(m, status); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "MinimumRate")==0) + { + dbus_respond_double(m, (MIN_RATE)/1000.); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "MaximumRate")==0) + { + dbus_respond_double(m, (MAX_RATE)/1000.); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Rate")==0) + { + //return current playing rate + dbus_respond_double(m, (double)clock->OMXPlaySpeed()/1000.); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Volume")==0) + { + //return current volume + dbus_respond_double(m, audio->GetVolume()); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Metadata")==0) + { + DBusMessage *reply; + reply = dbus_message_new_method_return(m); + if(reply) + { + //Create iterator: Array of dict entries, composed of string (key)) and variant (value) + DBusMessageIter array_cont, dict_cont, dict_entry_cont, var; + dbus_message_iter_init_append(reply, &array_cont); + dbus_message_iter_open_container(&array_cont, DBUS_TYPE_ARRAY, "{sv}", &dict_cont); + //First dict entry: URI + const char *key1 = "xesam:url"; + char uri[PATH_MAX+7]; + ToURI(reader->getFilename(), uri); + const char *value1=uri; + dbus_message_iter_open_container(&dict_cont, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_cont); + dbus_message_iter_append_basic(&dict_entry_cont, DBUS_TYPE_STRING, &key1); + dbus_message_iter_open_container(&dict_entry_cont, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &var); + dbus_message_iter_append_basic(&var, DBUS_TYPE_STRING, &value1); + dbus_message_iter_close_container(&dict_entry_cont, &var); + dbus_message_iter_close_container(&dict_cont, &dict_entry_cont); + //Second dict entry: duration in us + const char *key2 = "mpris:length"; + dbus_int64_t value2 = reader->GetStreamLength()*1000; + reader->GetStreamLength(); + dbus_message_iter_open_container(&dict_cont, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_cont); + dbus_message_iter_append_basic(&dict_entry_cont, DBUS_TYPE_STRING, &key2); + dbus_message_iter_open_container(&dict_entry_cont, DBUS_TYPE_VARIANT, DBUS_TYPE_INT64_AS_STRING, &var); + dbus_message_iter_append_basic(&var, DBUS_TYPE_INT64, &value2); + dbus_message_iter_close_container(&dict_entry_cont, &var); + dbus_message_iter_close_container(&dict_cont, &dict_entry_cont); + dbus_message_iter_close_container(&array_cont, &dict_cont); + //Send message + dbus_connection_send(bus, reply, NULL); + dbus_message_unref(reply); + } + return KeyConfig::ACTION_BLANK; + } + + //Non-MPRIS2 properties: + else if (strcmp(property, "Aspect")==0) + { + // Returns aspect ratio + double ratio = reader->GetAspectRatio(); + dbus_respond_double(m, ratio); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "VideoStreamCount")==0) + { + // Returns number of video streams + int64_t vcount = reader->VideoStreamCount(); + dbus_respond_int64(m, vcount); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "ResWidth")==0) + { + // Returns width of video + int64_t width = reader->GetWidth(); + dbus_respond_int64(m, width); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "ResHeight")==0) + { + // Returns height of video + int64_t height = reader->GetHeight(); + dbus_respond_int64(m, height); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Duration")==0) + { + // Returns the duration in microseconds + int64_t dur = reader->GetStreamLength(); + dur *= 1000; // ms -> us + dbus_respond_int64(m, dur); + return KeyConfig::ACTION_BLANK; + } + //Wrong property + else + { + //Error + CLog::Log(LOGWARNING, "Unhandled dbus property message, member: %s interface: %s type: %d path: %s property: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m), property ); + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + return KeyConfig::ACTION_BLANK; + } + } + //Wrong interface: + else + { + //Error + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return KeyConfig::ACTION_BLANK; + } + } + //Properties Set method: + //TODO: implement signal generation on some property changes + else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) + { + DBusError error; + dbus_error_init(&error); + + //Retrieve interface, property name and value + //Message has the form message[STRING:interface STRING:property DOUBLE:value] or message[STRING:interface STRING:property VARIANT[DOUBLE:value]] + const char *interface, *property; + double new_property_value; + DBusMessageIter args; + dbus_message_iter_init(m, &args); + if(dbus_message_iter_has_next(&args)) + { + //The interface name + if( DBUS_TYPE_STRING == dbus_message_iter_get_arg_type(&args) ) + dbus_message_iter_get_basic (&args, &interface); + else + { + printf("setE1\n"); + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_error_free(&error); + dbus_respond_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"); + return KeyConfig::ACTION_BLANK; + } + //The property name + if( dbus_message_iter_next(&args) && DBUS_TYPE_STRING == dbus_message_iter_get_arg_type(&args) ) + dbus_message_iter_get_basic (&args, &property); + else + { + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_error_free(&error); + dbus_respond_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"); + return KeyConfig::ACTION_BLANK; + } + //The value (either double or double in variant) + if (dbus_message_iter_next(&args)) + { + //Simply a double + if (DBUS_TYPE_DOUBLE == dbus_message_iter_get_arg_type(&args)) + { + dbus_message_iter_get_basic(&args, &new_property_value); + } + //A double within a variant + else if(DBUS_TYPE_VARIANT == dbus_message_iter_get_arg_type(&args)) + { + DBusMessageIter variant; + dbus_message_iter_recurse(&args, &variant); + if(DBUS_TYPE_DOUBLE == dbus_message_iter_get_arg_type(&variant)) + { + dbus_message_iter_get_basic(&variant, &new_property_value); + } + } + else + { + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_error_free(&error); + dbus_respond_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"); + return KeyConfig::ACTION_BLANK; + } + } + } + if ( dbus_error_is_set(&error) ) + { + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_error_free(&error); + dbus_respond_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"); + return KeyConfig::ACTION_BLANK; + } + //Player interface: + if (strcmp(interface, OMXPLAYER_DBUS_INTERFACE_PLAYER)==0) + { + if (strcmp(property, "Volume")==0) + { + double volume=new_property_value; + //Min value is 0 + if(volume<.0) + { + volume=.0; + } + audio->SetVolume(volume); + dbus_respond_double(m, volume); + return KeyConfig::ACTION_BLANK; + } + else if (strcmp(property, "Rate")==0) + { + double rate=new_property_value; + if(rate>MAX_RATE/1000.) + { + rate=MAX_RATE/1000.; + } + if(rateOMXPlaySpeed()/1000.); + return KeyConfig::ACTION_PAUSE; + } + int iSpeed=(int)(rate*1000.); + if(!clock) + { + dbus_respond_double(m, .0);//What value ???? + return KeyConfig::ACTION_BLANK; + } + //Can't do trickplay here so limit max speed + if(iSpeed > MAX_RATE) + iSpeed=MAX_RATE; + dbus_respond_double(m, iSpeed/1000.);//Reply before applying to be faster + clock->OMXSetSpeed(iSpeed, false, true); + return KeyConfig::ACTION_PLAY; + } + //Wrong property + else + { + //Error + CLog::Log(LOGWARNING, "Unhandled dbus property message, member: %s interface: %s type: %d path: %s property: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m), property ); + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + return KeyConfig::ACTION_BLANK; + } + } + //Wrong interface: + else + { + //Error + CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return KeyConfig::ACTION_BLANK; + } + } + //---------------------------------------------------------------------------- + + + //-------------------------DEPRECATED PROPERTIES METHODS---------------------- else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanQuit") || dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Fullscreen")) { dbus_respond_boolean(m, 1); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanSetFullscreen") @@ -175,34 +599,40 @@ OMXControlResult OMXControl::getEvent() || dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "HasTrackList")) { dbus_respond_boolean(m, 0); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Identity")) { dbus_respond_string(m, "OMXPlayer"); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "SupportedUriSchemes")) { - const char *UriSchemes[] = {"file", "http"}; + const char *UriSchemes[] = {"file", "http", "rtsp", "rtmp"}; dbus_respond_array(m, UriSchemes, 2); // Array is of length 2 + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "SupportedMimeTypes")) { const char *MimeTypes[] = {}; // Needs supplying dbus_respond_array(m, MimeTypes, 0); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanGoNext") || dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanGoPrevious")) { dbus_respond_boolean(m, 0); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanSeek")) { dbus_respond_boolean(m, reader->CanSeek()); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanControl") @@ -210,123 +640,9 @@ OMXControlResult OMXControl::getEvent() || dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "CanPause")) { dbus_respond_boolean(m, 1); + deprecatedMessage(); + return KeyConfig::ACTION_BLANK; } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Next")) - { - dbus_respond_ok(m); - return KeyConfig::ACTION_NEXT_CHAPTER; - } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Previous")) - { - dbus_respond_ok(m); - return KeyConfig::ACTION_PREVIOUS_CHAPTER; - } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Pause") - || dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "PlayPause")) - { - dbus_respond_ok(m); - return KeyConfig::ACTION_PAUSE; - } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Stop")) - { - dbus_respond_ok(m); - return KeyConfig::ACTION_EXIT; - } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Seek")) - { - DBusError error; - dbus_error_init(&error); - - int64_t offset; - dbus_message_get_args(m, &error, DBUS_TYPE_INT64, &offset, DBUS_TYPE_INVALID); - - // Make sure a value is sent for seeking - if (dbus_error_is_set(&error)) - { - CLog::Log(LOGWARNING, "Seek D-Bus Error: %s", error.message ); - dbus_error_free(&error); - dbus_respond_ok(m); - return KeyConfig::ACTION_BLANK; - } - else - { - dbus_respond_int64(m, offset); - return OMXControlResult(KeyConfig::ACTION_SEEK_RELATIVE, offset); - } - } - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetPosition")) - { - DBusError error; - dbus_error_init(&error); - - int64_t position; - const char *oPath; // ignoring path right now because we don't have a playlist - dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_INT64, &position, DBUS_TYPE_INVALID); - - // Make sure a value is sent for setting position - if (dbus_error_is_set(&error)) - { - CLog::Log(LOGWARNING, "SetPosition D-Bus Error: %s", error.message ); - dbus_error_free(&error); - dbus_respond_ok(m); - return KeyConfig::ACTION_BLANK; - } - else - { - dbus_respond_int64(m, position); - return OMXControlResult(KeyConfig::ACTION_SEEK_ABSOLUTE, position); - } - } - - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetAlpha")) - { - DBusError error; - dbus_error_init(&error); - - int64_t alpha; - const char *oPath; // ignoring path right now because we don't have a playlist - dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_INT64, &alpha, DBUS_TYPE_INVALID); - - // Make sure a value is sent for setting alpha - if (dbus_error_is_set(&error)) - { - CLog::Log(LOGWARNING, "SetAlpha D-Bus Error: %s", error.message ); - dbus_error_free(&error); - dbus_respond_ok(m); - return KeyConfig::ACTION_BLANK; - } - else - { - dbus_respond_int64(m, alpha); - return OMXControlResult(KeyConfig::ACTION_SET_ALPHA, alpha); - } - } - - else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetAspectMode")) - { - DBusError error; - dbus_error_init(&error); - - const char *aspectMode; - const char *oPath; // ignoring path right now because we don't have a playlist - dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_STRING, &aspectMode, DBUS_TYPE_INVALID); - - // Make sure a value is sent for setting aspect mode - if (dbus_error_is_set(&error)) - { - CLog::Log(LOGWARNING, "SetAspectMode D-Bus Error: %s", error.message ); - dbus_error_free(&error); - dbus_respond_ok(m); - return KeyConfig::ACTION_BLANK; - } - else - { - dbus_respond_string(m, aspectMode); - return OMXControlResult(KeyConfig::ACTION_SET_ASPECT_MODE, aspectMode); - } - } - - else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "PlaybackStatus")) { const char *status; @@ -340,11 +656,13 @@ OMXControlResult OMXControl::getEvent() } dbus_respond_string(m, status); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetSource")) { dbus_respond_string(m, reader->getFilename().c_str()); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Volume")) @@ -359,12 +677,19 @@ OMXControlResult OMXControl::getEvent() { // i.e. Get current volume dbus_error_free(&error); dbus_respond_double(m, audio->GetVolume()); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else { + //Min value is 0 + if(volume<.0) + { + volume=.0; + } audio->SetVolume(volume); dbus_respond_double(m, volume); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } } @@ -372,12 +697,14 @@ OMXControlResult OMXControl::getEvent() { audio->SetMute(true); dbus_respond_ok(m); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Unmute")) { audio->SetMute(false); dbus_respond_ok(m); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Position")) @@ -385,6 +712,7 @@ OMXControlResult OMXControl::getEvent() // Returns the current position in microseconds int64_t pos = clock->OMXMediaTime(); dbus_respond_int64(m, pos); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Aspect")) @@ -392,6 +720,7 @@ OMXControlResult OMXControl::getEvent() // Returns aspect ratio double ratio = reader->GetAspectRatio(); dbus_respond_double(m, ratio); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "VideoStreamCount")) @@ -399,6 +728,7 @@ OMXControlResult OMXControl::getEvent() // Returns number of video streams int64_t vcount = reader->VideoStreamCount(); dbus_respond_int64(m, vcount); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "ResWidth")) @@ -406,6 +736,7 @@ OMXControlResult OMXControl::getEvent() // Returns width of video int64_t width = reader->GetWidth(); dbus_respond_int64(m, width); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "ResHeight")) @@ -413,6 +744,7 @@ OMXControlResult OMXControl::getEvent() // Returns height of video int64_t height = reader->GetHeight(); dbus_respond_int64(m, height); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Duration")) @@ -421,19 +753,163 @@ OMXControlResult OMXControl::getEvent() int64_t dur = reader->GetStreamLength(); dur *= 1000; // ms -> us dbus_respond_int64(m, dur); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "MinimumRate")) { dbus_respond_double(m, 0.0); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "MaximumRate")) { - dbus_respond_double(m, 1.125); + //TODO: to be made consistent + dbus_respond_double(m, 10.125); + deprecatedMessage(); return KeyConfig::ACTION_BLANK; + } + //---------------------------------------------------------------------------- + + + //--------------------------Player interface methods-------------------------- + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "GetSource")) + { + dbus_respond_string(m, reader->getFilename().c_str()); + return KeyConfig::ACTION_BLANK; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Next")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_NEXT_CHAPTER; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Previous")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_PREVIOUS_CHAPTER; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Pause")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_PAUSE; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Play")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_PLAY; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "PlayPause")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_PLAYPAUSE; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Stop")) + { + dbus_respond_ok(m); + return KeyConfig::ACTION_EXIT; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Seek")) + { + DBusError error; + dbus_error_init(&error); + + int64_t offset; + dbus_message_get_args(m, &error, DBUS_TYPE_INT64, &offset, DBUS_TYPE_INVALID); - // Implement extra OMXPlayer controls + // Make sure a value is sent for seeking + if (dbus_error_is_set(&error)) + { + CLog::Log(LOGWARNING, "Seek D-Bus Error: %s", error.message ); + dbus_error_free(&error); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; + } + else + { + dbus_respond_int64(m, offset); + return OMXControlResult(KeyConfig::ACTION_SEEK_RELATIVE, offset); + } + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetPosition")) + { + DBusError error; + dbus_error_init(&error); + + int64_t position; + const char *oPath; // ignoring path right now because we don't have a playlist + dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_INT64, &position, DBUS_TYPE_INVALID); + + // Make sure a value is sent for setting position + if (dbus_error_is_set(&error)) + { + CLog::Log(LOGWARNING, "SetPosition D-Bus Error: %s", error.message ); + dbus_error_free(&error); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; + } + else + { + dbus_respond_int64(m, position); + return OMXControlResult(KeyConfig::ACTION_SEEK_ABSOLUTE, position); + } + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetAlpha")) + { + DBusError error; + dbus_error_init(&error); + + int64_t alpha; + const char *oPath; // ignoring path right now because we don't have a playlist + dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_INT64, &alpha, DBUS_TYPE_INVALID); + + // Make sure a value is sent for setting alpha + if (dbus_error_is_set(&error)) + { + CLog::Log(LOGWARNING, "SetAlpha D-Bus Error: %s", error.message ); + dbus_error_free(&error); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; + } + else + { + dbus_respond_int64(m, alpha); + return OMXControlResult(KeyConfig::ACTION_SET_ALPHA, alpha); + } + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "SetAspectMode")) + { + DBusError error; + dbus_error_init(&error); + + const char *aspectMode; + const char *oPath; // ignoring path right now because we don't have a playlist + dbus_message_get_args(m, &error, DBUS_TYPE_OBJECT_PATH, &oPath, DBUS_TYPE_STRING, &aspectMode, DBUS_TYPE_INVALID); + + // Make sure a value is sent for setting aspect mode + if (dbus_error_is_set(&error)) + { + CLog::Log(LOGWARNING, "SetAspectMode D-Bus Error: %s", error.message ); + dbus_error_free(&error); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; + } + else + { + dbus_respond_string(m, aspectMode); + return OMXControlResult(KeyConfig::ACTION_SET_ASPECT_MODE, aspectMode); + } + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Mute")) + { + audio->SetMute(true); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; + } + else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "Unmute")) + { + audio->SetMute(false); + dbus_respond_ok(m); + return KeyConfig::ACTION_BLANK; } else if (dbus_message_is_method_call(m, OMXPLAYER_DBUS_INTERFACE_PLAYER, "ListSubtitles")) { @@ -647,13 +1123,31 @@ OMXControlResult OMXControl::getEvent() return action; // Directly return enum } } + //---------------------------------------------------------------------------- else { CLog::Log(LOGWARNING, "Unhandled dbus message, member: %s interface: %s type: %d path: %s", dbus_message_get_member(m), dbus_message_get_interface(m), dbus_message_get_type(m), dbus_message_get_path(m) ); + if (dbus_message_get_type(m) == DBUS_MESSAGE_TYPE_METHOD_CALL) + dbus_respond_error(m, DBUS_ERROR_UNKNOWN_METHOD, "Unknown method"); } return KeyConfig::ACTION_BLANK; } +DBusHandlerResult OMXControl::dbus_respond_error(DBusMessage *m, const char *name, const char *msg) +{ + DBusMessage *reply; + + reply = dbus_message_new_error(m, name, msg); + + if (!reply) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_connection_send(bus, reply, NULL); + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + DBusHandlerResult OMXControl::dbus_respond_ok(DBusMessage *m) { DBusMessage *reply; diff --git a/OMXControl.h b/OMXControl.h index b2786d52..91395bf1 100644 --- a/OMXControl.h +++ b/OMXControl.h @@ -7,6 +7,11 @@ #include "OMXPlayerAudio.h" #include "OMXPlayerSubtitles.h" + +#define MIN_RATE (1) +#define MAX_RATE (4 * DVD_PLAYSPEED_NORMAL) + + class OMXControlResult { int key; int64_t arg; @@ -38,6 +43,8 @@ class OMXControl private: int dbus_connect(std::string& dbus_name); void dbus_disconnect(); + OMXControlResult handle_event(DBusMessage *m); + DBusHandlerResult dbus_respond_error(DBusMessage *m, const char *name, const char *msg); DBusHandlerResult dbus_respond_ok(DBusMessage *m); DBusHandlerResult dbus_respond_int64(DBusMessage *m, int64_t i); DBusHandlerResult dbus_respond_double(DBusMessage *m, double d); diff --git a/OMXCore.cpp b/OMXCore.cpp index 9d5fe610..548ed2fa 100644 --- a/OMXCore.cpp +++ b/OMXCore.cpp @@ -35,6 +35,7 @@ #ifdef TARGET_LINUX #include "XMemUtils.h" +#include "OMXAlsa.h" #endif //#define OMX_DEBUG_EVENTS @@ -1429,6 +1430,11 @@ bool COMXCoreComponent::Initialize( const std::string &component_name, OMX_INDEX // Get video component handle setting up callbacks, component is in loaded state on return. if(!m_handle) { +#ifdef TARGET_LINUX + if (strncmp("OMX.alsa.", component_name.c_str(), 9) == 0) + omx_err = OMXALSA_GetHandle(&m_handle, (char*) component_name.c_str(), this, &m_callbacks); + else +#endif omx_err = m_DllOMX->OMX_GetHandle(&m_handle, (char*)component_name.c_str(), this, &m_callbacks); if (!m_handle || omx_err != OMX_ErrorNone) { @@ -1505,6 +1511,11 @@ bool COMXCoreComponent::Deinitialize() CLog::Log(LOGDEBUG, "COMXCoreComponent::Deinitialize : %s handle %p\n", m_componentName.c_str(), m_handle); +#ifdef TARGET_LINUX + if (strncmp("OMX.alsa.", m_componentName.c_str(), 9) == 0) + omx_err = OMXALSA_FreeHandle(m_handle); + else +#endif omx_err = m_DllOMX->OMX_FreeHandle(m_handle); if (omx_err != OMX_ErrorNone) { diff --git a/OMXReader.cpp b/OMXReader.cpp index f419ffbf..a1c23017 100644 --- a/OMXReader.cpp +++ b/OMXReader.cpp @@ -207,10 +207,13 @@ bool OMXReader::Open(std::string filename, bool dump_format, bool live /* =false m_filename = m_filename.substr(0, idx); // Enable seeking if http, ftp - if(!live && (m_filename.substr(0,7) == "http://" || m_filename.substr(0,6) == "ftp://" || - m_filename.substr(0,7) == "sftp://" || m_filename.substr(0,6) == "smb://")) + if(m_filename.substr(0,7) == "http://" || m_filename.substr(0,6) == "ftp://" || + m_filename.substr(0,7) == "sftp://" || m_filename.substr(0,6) == "smb://") { - av_dict_set(&d, "seekable", "1", 0); + if(!live) + { + av_dict_set(&d, "seekable", "1", 0); + } if(!cookie.empty()) { av_dict_set(&d, "cookies", cookie.c_str(), 0); diff --git a/OMXVideo.h b/OMXVideo.h index 93597266..9b25c867 100644 --- a/OMXVideo.h +++ b/OMXVideo.h @@ -71,7 +71,7 @@ class OMXVideoConfig src_rect.SetRect(0, 0, 0, 0); display_aspect = 0.0f; deinterlace = VS_DEINTERLACEMODE_AUTO; - advanced_hd_deinterlace = false; + advanced_hd_deinterlace = true; anaglyph = OMX_ImageFilterAnaglyphNone; hdmi_clock_sync = false; allow_mvc = false; diff --git a/README.md b/README.md index 777de57e..241b8d67 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Usage: omxplayer [OPTIONS] [FILE] -v --version Print version info -k --keys Print key bindings -n --aidx index Audio stream index : e.g. 1 - -o --adev device Audio out device : e.g. hdmi/local/both + -o --adev device Audio out device : e.g. hdmi/local/both/alsa[:device] -i --info Dump stream format and exit -I --with-info dump stream format before playback -s --stats Pts and buffer stats @@ -64,7 +64,7 @@ Usage: omxplayer [OPTIONS] [FILE] --nodeinterlace Force no deinterlacing --nativedeinterlace let display handle interlace --anaglyph type convert 3d to anaglyph - --advanced Allow advanced deinterlace for HD videos + --advanced[=0] Enable/disable advanced deinterlace for HD videos (default enabled) -w --hw Hw audio decoding -3 --3d mode Switch tv into 3d mode (e.g. SBS/TB) -M --allow-mvc Allow decoding of both views of MVC stereo stream @@ -206,258 +206,161 @@ take a look at the supplied [dbuscontrol.sh](dbuscontrol.sh) file. The root interface is accessible under the name `org.mpris.MediaPlayer2`. -#### Quit +#### Methods + +Root interface methods can be accessed through `org.mpris.MediaPlayer2.MethodName`. + +##### Quit Stops the currently playing video. This will cause the currently running omxplayer process to terminate. - Params | Type + Params | Type +:-------------: | ------- + Return | `null` + +##### Raise + +No effect. + + Params | Type :-------------: | ------- Return | `null` -### Properties Interface +#### Properties -The properties interface is accessible under the name -`org.freedesktop.DBus.Properties`. +Root interface properties can be accessed through `org.freedesktop.DBus.Properties.Get` +and `org.freedesktop.DBus.Properties.Set` methods with the string +`"org.mpris.MediaPlayer2"` as first argument and the string `"PropertyName"` as +second argument. -#### CanQuit +##### CanQuit (ro) Whether or not the player can quit. - Params | Type + Params | Type :-------------: | --------- Return | `boolean` -#### Fullscreen +##### Fullscreen (ro) Whether or not the player can is fullscreen. - Params | Type + Params | Type :-------------: | --------- Return | `boolean` -#### CanSetFullscreen +##### CanSetFullscreen (ro) Whether or not the player can set fullscreen. - Params | Type + Params | Type :-------------: | --------- Return | `boolean` -#### CanRaise +##### CanRaise (ro) Whether the display window can be brought to the top of all the window. - Params | Type + Params | Type :-------------: | --------- Return | `boolean` -#### HasTrackList +##### HasTrackList (ro) Whether or not the player has a track list. - Params | Type + Params | Type :-------------: | --------- Return | `boolean` -#### Identity +##### Identity (ro) Name of the player. - Params | Type + Params | Type :-------------: | -------- Return | `string` -#### SupportedUriSchemes +##### SupportedUriSchemes (ro) Playable URI formats. - Params | Type + Params | Type :-------------: | ---------- Return | `string[]` -#### SupportedMimeTypes +##### SupportedMimeTypes (ro) Supported mime types. **Note**: currently not implemented. - Params | Type + Params | Type :-------------: | ---------- Return | `string[]` -#### CanGoNext - -Whether or not the play can skip to the next track. - - Params | Type -:-------------: | --------- - Return | `boolean` - -#### CanGoPrevious - -Whether or not the player can skip to the previous track. - - Params | Type -:-------------: | --------- - Return | `boolean` - -#### CanSeek - -Whether or not the player can seek. - - Params | Type -:-------------: | --------- - Return | `boolean` - - -#### CanControl - -Whether or not the player can be controlled. - - Params | Type -:-------------: | --------- - Return | `boolean` - -#### CanPlay - -Whether or not the player can play. - -Return type: `boolean`. - -#### CanPause - -Whether or not the player can pause. - - Params | Type -:-------------: | --------- - Return | `boolean` - -#### PlaybackStatus - -The current state of the player, either "Paused" or "Playing". - - Params | Type -:-------------: | --------- - Return | `string` - -#### GetSource - -The current file or stream that is being played. - - Params | Type -:-------------: | --------- - Return | `string` - -#### Volume - -When called with an argument it will set the volume and return the current -volume. When called without an argument it will simply return the current -volume. As defined by the [MPRIS][MPRIS_volume] specifications, this value -should be greater than or equal to 0. 1 is the normal volume. -Everything below is quieter than normal, everything above is louder. - -Millibels can be converted to/from acceptable values using the following: - - volume = pow(10, mB / 2000.0); - mB = 2000.0 * log10(volume) - - Params | Type | Description -:-------------: | --------- | --------------------------- - 1 (optional) | `double` | Volume to set - Return | `double` | Current volume - -[MPRIS_volume]: http://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#Simple-Type:Volume - -#### Mute - -Mute the audio stream. If the volume is already muted, this does nothing. - Params | Type -:-------------: | ------- - Return | `null` - -#### Unmute - -Unmute the audio stream. If the stream is already unmuted, this does nothing. - - Params | Type -:-------------: | ------- - Return | `null` - -#### Position +### Player Interface -Returns the current position of the playing media. +The player interface is accessible under the name +`org.mpris.MediaPlayer2.Player`. - Params | Type | Description -:-------------: | --------- | -------------------------------- - Return | `int64` | Current position in microseconds +#### Methods -#### Duration +Player interface methods can be accessed through `org.mpris.MediaPlayer2.Player.MethodName`. -Returns the total length of the playing media. +##### Next - Params | Type | Description -:-------------: | --------- | ---------------------------- - Return | `int64` | Total length in microseconds - - -#### MinimumRate - -Returns the minimum playback rate of the video. +Skip to the next chapter. Params | Type :-------------: | ------- - Return | `double` + Return | `null` -#### MaximumRate +##### Previous -Returns the maximum playback rate of the video. +Skip to the previous chapter. Params | Type :-------------: | ------- - Return | `double` - - -### Player Interface - + Return | `null` -#### Next +##### Play -Skip to the next chapter. +Play the video. If the video is playing, it has no effect, if it is +paused it will play from current position and if it is stopped it will +play from the beginning. Params | Type :-------------: | ------- - Return | `null` + Return | `null` -#### Previous +##### Pause -Skip to the previous chapter. +Pause the video. If the video is playing, it will be paused, if it is +paused it will stay in pause (no effect). Params | Type :-------------: | ------- - Return | `null` + Return | `null` -#### Pause +##### PlayPause Toggles the play state. If the video is playing, it will be paused, if it is paused it will start playing. Params | Type :-------------: | ------- - Return | `null` - -#### PlayPause - -Same as the `Pause` method. + Return | `null` -#### Stop +##### Stop Stops the video. Params | Type :-------------: | ------- - Return | `null` + Return | `null` -#### Seek +##### Seek Perform a *relative* seek, i.e. seek plus or minus a certain number of microseconds from the current position in the video. @@ -467,7 +370,7 @@ microseconds from the current position in the video. 1 | `int64` | Microseconds to seek Return | `null` or `int64` | If the supplied offset is invalid, `null` is returned, otherwise the offset (in microseconds) is returned -#### SetPosition +##### SetPosition Seeks to a specific location in the file. This is an *absolute* seek. @@ -477,8 +380,23 @@ Seeks to a specific location in the file. This is an *absolute* seek. 2 | `int64` | Position to seek to, in microseconds Return | `null` or `int64` | If the supplied position is invalid, `null` is returned, otherwise the position (in microseconds) is returned +##### Mute + +Mute the audio stream. If the volume is already muted, this does nothing. + + Params | Type +:-------------: | ------- + Return | `null` + +##### Unmute + +Unmute the audio stream. If the stream is already unmuted, this does nothing. + + Params | Type +:-------------: | ------- + Return | `null` -#### ListSubtitles +##### ListSubtitles Returns a array of all known subtitles. The length of the array is the number of subtitles. Each item in the araay is a string in the following format: @@ -495,7 +413,7 @@ or an empty string. :-------------: | ---------- Return | `string[]` -#### ListAudio +##### ListAudio Returns and array of all known audio streams. The length of the array is the number of streams. Each item in the array is a string in the following format: @@ -511,7 +429,7 @@ example of a possible string is: :-------------: | ---------- Return | `string[]` -#### ListVideo +##### ListVideo Returns and array of all known video streams. The length of the array is the number of streams. Each item in the array is a string in the following format: @@ -527,7 +445,7 @@ example of a possible string is: :-------------: | ---------- Return | `string[]` -#### SelectSubtitle +##### SelectSubtitle Selects the subtitle at a given index. @@ -537,7 +455,7 @@ Selects the subtitle at a given index. Return | `boolean` | Returns `true` if subtitle was selected, `false otherwise -#### SelectAudio +##### SelectAudio Selects the audio stream at a given index. @@ -546,23 +464,32 @@ Selects the audio stream at a given index. 1 | `int32` | Index of audio stream to select Return | `boolean` | Returns `true` if stream was selected, `false otherwise -#### ShowSubtitles +##### ShowSubtitles Turns on subtitles. Params | Type :-------------: | ------- - Return | `null` + Return | `null` -#### HideSubtitles +##### HideSubtitles Turns off subtitles. Params | Type :-------------: | ------- - Return | `null` + Return | `null` + +##### GetSource + +The current file or stream that is being played. + + Params | Type +:-------------: | --------- + Return | `string` + -#### Action +##### Action Execute a "keyboard" command. For available codes, see [KeyConfig.h](KeyConfig.h). @@ -572,3 +499,172 @@ Execute a "keyboard" command. For available codes, see :-------------: | ----------| ------------------ 1 | `int32` | Command to execute Return | `null` | + + +#### Properties + +Player interface properties can be accessed through `org.freedesktop.DBus.Properties.Get` +and `org.freedesktop.DBus.Properties.Set` methods with the string +`"org.mpris.MediaPlayer2"` as first argument and the string `"PropertyName"` as +second argument. + +##### CanGoNext (ro) + +Whether or not the play can skip to the next track. + + Params | Type +:-------------: | --------- + Return | `boolean` + +##### CanGoPrevious (ro) + +Whether or not the player can skip to the previous track. + + Params | Type +:-------------: | --------- + Return | `boolean` + +##### CanSeek (ro) + +Whether or not the player can seek. + + Params | Type +:-------------: | --------- + Return | `boolean` + + +##### CanControl (ro) + +Whether or not the player can be controlled. + + Params | Type +:-------------: | --------- + Return | `boolean` + +##### CanPlay (ro) + +Whether or not the player can play. + +Return type: `boolean`. + +##### CanPause (ro) + +Whether or not the player can pause. + + Params | Type +:-------------: | --------- + Return | `boolean` + +##### PlaybackStatus (ro) + +The current state of the player, either "Paused" or "Playing". + + Params | Type +:-------------: | --------- + Return | `string` + +##### Volume (rw) + +When called with an argument it will set the volume and return the current +volume. When called without an argument it will simply return the current +volume. As defined by the [MPRIS][MPRIS_volume] specifications, this value +should be greater than or equal to 0. 1 is the normal volume. +Everything below is quieter than normal, everything above is louder. + +Millibels can be converted to/from acceptable values using the following: + + volume = pow(10, mB / 2000.0); + mB = 2000.0 * log10(volume) + + Params | Type | Description +:-------------: | --------- | --------------------------- + 1 (optional) | `double` | Volume to set + Return | `double` | Current volume + +[MPRIS_volume]: http://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#Simple-Type:Volume + +##### Position (ro) + +Returns the current position of the playing media. + + Params | Type | Description +:-------------: | --------- | -------------------------------- + Return | `int64` | Current position in microseconds + +##### MinimumRate (ro) + +Returns the minimum playback rate of the video. + + Params | Type +:-------------: | ------- + Return | `double` + +##### MaximumRate (ro) + +Returns the maximum playback rate of the video. + + Params | Type +:-------------: | ------- + Return | `double` + +##### Rate (rw) + +When called with an argument it will set the playing rate and return the +current rate. When called without an argument it will simply return the +current rate. Rate of 1.0 is the normal playing rate. A value of 2.0 +corresponds to two times faster than normal rate, a value of 0.5 corresponds +to two times slower than the normal rate. + + Params | Type | Description +:-------------: | --------- | --------------------------- + 1 (optional) | `double` | Rate to set + Return | `double` | Current rate + +##### Metadata (ro) + +Returns track information: URI and length. + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `dict` | Dictionnary entries with key:value pairs + +##### Aspect (ro) + +Returns the aspect ratio. + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `double` | Aspect ratio + +##### VideoStreamCount (ro) + +Returns the number of video streams. + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `int64` | Number of video streams + +##### ResWidth (ro) + +Returns video width + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `int64` | Video width in px + +##### ResHeight (ro) + +Returns video width + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `int64` | Video height in px + +##### Duration (ro) + +Returns the total length of the playing media. + + Params | Type | Description +:-------------: | --------- | ---------------------------- + Return | `int64` | Total length in microseconds + diff --git a/dbuscontrol.sh b/dbuscontrol.sh index bea2b7d1..c1105948 100755 --- a/dbuscontrol.sh +++ b/dbuscontrol.sh @@ -11,15 +11,15 @@ export DBUS_SESSION_BUS_PID=`cat $OMXPLAYER_DBUS_PID` case $1 in status) - duration=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Duration` + duration=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:"org.mpris.MediaPlayer2.Player" string:"Duration"` [ $? -ne 0 ] && exit 1 duration="$(awk '{print $2}' <<< "$duration")" - position=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Position` + position=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:"org.mpris.MediaPlayer2.Player" string:"Position"` [ $? -ne 0 ] && exit 1 position="$(awk '{print $2}' <<< "$position")" - playstatus=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.PlaybackStatus` + playstatus=`dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:"org.mpris.MediaPlayer2.Player" string:"PlaybackStatus"` [ $? -ne 0 ] && exit 1 playstatus="$(sed 's/^ *//;s/ *$//;' <<< "$playstatus")" @@ -31,7 +31,7 @@ status) ;; volume) - volume=`dbus-send --print-reply=double --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Volume ${2:+double:}$2` + volume=`dbus-send --print-reply=double --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Set string:"org.mpris.MediaPlayer2.Player" string:"Volume" ${2:+double:}$2` [ $? -ne 0 ] && exit 1 volume="$(awk '{print $2}' <<< "$volume")" echo "Volume: $volume" @@ -97,11 +97,11 @@ showsubtitles) dbus-send --print-reply=literal --session --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Action int32:31 >/dev/null ;; getsource) - source=$(dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.GetSource) + source=$(dbus-send --print-reply=literal --session --reply-timeout=500 --dest=org.mpris.MediaPlayer2.omxplayer /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.GetSource) echo "$source" | sed 's/^ *//' ;; *) - echo "usage: $0 status|pause|stop|seek|volumeup|volumedown|setposition [position in microseconds]|hidevideo|unhidevideo|togglesubtitles|hidesubtitles|showsubtitles|setvideopos [x1 y1 x2 y2]|setvideocroppos [x1 y1 x2 y2]|setaspectmode [letterbox,fill,stretch,default]|setalpha [alpha (0..255)]" >&2 + echo "usage: $0 status|pause|stop|seek|volumeup|volumedown|setposition [position in microseconds]|hidevideo|unhidevideo|togglesubtitles|hidesubtitles|showsubtitles|setvideopos [x1 y1 x2 y2]|setvideocroppos [x1 y1 x2 y2]|setaspectmode [letterbox,fill,stretch,default]|setalpha [alpha (0..255)]|getsource" >&2 exit 1 ;; esac diff --git a/linux/OMXAlsa.cpp b/linux/OMXAlsa.cpp new file mode 100644 index 00000000..521fdb41 --- /dev/null +++ b/linux/OMXAlsa.cpp @@ -0,0 +1,1353 @@ +/* + * OMX IL Alsa Sink component + * Copyright (c) 2016 Timo Teräs + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * TODO: + * - timeouts for state transition failures + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +struct _GOMX_COMMAND; +struct _GOMX_PORT; +struct _GOMX_COMPONENT; + +template static inline X max(X a, X b) +{ + return (a > b) ? a : b; +} + +template static OMX_ERRORTYPE omx_cast(X* &toptr, OMX_PTR fromptr) +{ + toptr = (X*) fromptr; + if (toptr->nSize < sizeof(X)) return OMX_ErrorBadParameter; + if (toptr->nVersion.nVersion != OMX_VERSION) return OMX_ErrorVersionMismatch; + return OMX_ErrorNone; +} + +template static void omx_init(X &omx) +{ + omx.nSize = sizeof(X); + omx.nVersion.nVersion = OMX_VERSION; +} + +#if 1 +#include +#define CLOG(notice, comp, port, msg, ...) do { \ + struct _GOMX_PORT *_port = (struct _GOMX_PORT *) port; \ + if (_port) CLog::Log(notice ? LOGNOTICE : LOGDEBUG, "[%p port %d]: %s: " msg "\n", comp, _port->def.nPortIndex, __func__ , ##__VA_ARGS__); \ + else CLog::Log(notice ? LOGNOTICE : LOGDEBUG, "[%p] %s: " msg "\n", comp, __func__ , ##__VA_ARGS__); \ +} while (0) +#else +#define CLOG(notice, comp, port, msg, ...) do { \ + struct _GOMX_PORT *_port = (struct _GOMX_PORT *) port; \ + if (_port) fprintf(stderr, "[%p port %d]: %s: " msg "\n", comp, _port->def.nPortIndex, __func__ , ##__VA_ARGS__); \ + else fprintf(stderr, "[%p] %s: " msg "\n", comp, __func__ , ##__VA_ARGS__); \ +} while (0) +#endif + +#define CINFO(comp, port, msg, ...) CLOG(1, comp, port, msg , ##__VA_ARGS__) +#define CDEBUG(comp, port, msg, ...) CLOG(0, comp, port, msg , ##__VA_ARGS__) + +/* Generic OMX helpers */ + +typedef struct _GOMX_QUEUE { + void *head, *tail; + ptrdiff_t offset; + size_t num; +} GOMX_QUEUE; + +static void gomxq_init(GOMX_QUEUE *q, ptrdiff_t offset) +{ + q->head = q->tail = 0; + q->offset = offset; + q->num = 0; +} + +static void **gomxq_nextptr(GOMX_QUEUE *q, void *item) +{ + return (void**) ((uint8_t*)item + q->offset); +} + +static void gomxq_enqueue(GOMX_QUEUE *q, void *item) +{ + *gomxq_nextptr(q, item) = 0; + if (q->tail) { + *gomxq_nextptr(q, q->tail) = item; + q->tail = item; + } else { + q->head = q->tail = item; + } + q->num++; +} + +static void *gomxq_dequeue(GOMX_QUEUE *q) +{ + void *item = q->head; + if (item) { + q->head = *gomxq_nextptr(q, item); + if (!q->head) q->tail = 0; + q->num--; + } + return item; +} + +typedef struct _GOMX_COMMAND { + void *next; + OMX_COMMANDTYPE cmd; + OMX_U32 param; + OMX_PTR data; +} GOMX_COMMAND; + +typedef struct _GOMX_PORT { + OMX_BOOL new_enabled; + OMX_PARAM_PORTDEFINITIONTYPE def; + + size_t num_buffers, num_buffers_old; + pthread_cond_t cond_no_buffers; + pthread_cond_t cond_populated; + pthread_cond_t cond_idle; + + OMX_HANDLETYPE tunnel_comp; + OMX_U32 tunnel_port; + bool tunnel_supplier; + GOMX_QUEUE tunnel_supplierq; + + OMX_ERRORTYPE (*do_buffer)(struct _GOMX_COMPONENT *, struct _GOMX_PORT *, OMX_BUFFERHEADERTYPE *); + OMX_ERRORTYPE (*flush)(struct _GOMX_COMPONENT *, struct _GOMX_PORT *); +} GOMX_PORT; + +typedef struct _GOMX_COMPONENT { + OMX_COMPONENTTYPE omx; + OMX_CALLBACKTYPE cb; + OMX_STATETYPE state, wanted_state; + + pthread_t component_thread, worker_thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + + const char *name; + size_t nports; + GOMX_PORT *ports; + GOMX_QUEUE cmdq; + + void* (*worker)(void *); + OMX_ERRORTYPE (*statechange)(struct _GOMX_COMPONENT *); +} GOMX_COMPONENT; + +static GOMX_PORT *gomx_get_port(GOMX_COMPONENT *comp, size_t idx) +{ + if (idx < 0 || idx >= comp->nports) return 0; + return &comp->ports[idx]; +} + +OMX_ERRORTYPE gomx_get_component_version( + OMX_HANDLETYPE hComponent, OMX_STRING pComponentName, + OMX_VERSIONTYPE *pComponentVersion, OMX_VERSIONTYPE *pSpecVersion, OMX_UUIDTYPE *pComponentUUID) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + CDEBUG(comp, 0, "enter"); + strcpy(pComponentName, comp->name); + pComponentVersion->nVersion = OMX_VERSION; + pSpecVersion->nVersion = OMX_VERSION; + memcpy(pComponentUUID, &hComponent, sizeof hComponent); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_get_parameter(OMX_HANDLETYPE hComponent, OMX_INDEXTYPE nParamIndex, OMX_PTR pComponentParameterStructure) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_PORT *port; + OMX_PORT_PARAM_TYPE *ppt; + OMX_PARAM_PORTDEFINITIONTYPE *pdt; + OMX_ERRORTYPE r; + OMX_PORTDOMAINTYPE domain; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + + CDEBUG(comp, 0, "called %x, %p", nParamIndex, pComponentParameterStructure); + switch (nParamIndex) { + case OMX_IndexParamAudioInit: + domain = OMX_PortDomainAudio; + goto param_init; + case OMX_IndexParamVideoInit: + domain = OMX_PortDomainVideo; + goto param_init; + case OMX_IndexParamImageInit: + domain = OMX_PortDomainImage; + goto param_init; + case OMX_IndexParamOtherInit: + domain = OMX_PortDomainOther; + goto param_init; + param_init: + if ((r = omx_cast(ppt, pComponentParameterStructure))) return r; + ppt->nPorts = 0; + ppt->nStartPortNumber = 0; + for (size_t i = 0; i < comp->nports; i++) { + if (comp->ports[i].def.eDomain != domain) + continue; + if (!ppt->nPorts) + ppt->nStartPortNumber = i; + ppt->nPorts++; + } + break; + case OMX_IndexParamPortDefinition: + if ((r = omx_cast(pdt, pComponentParameterStructure))) return r; + if (!(port = gomx_get_port(comp, pdt->nPortIndex))) return OMX_ErrorBadPortIndex; + memcpy(pComponentParameterStructure, &port->def, sizeof *pdt); + break; + default: + CINFO(comp, 0, "UNSUPPORTED %x, %p", nParamIndex, pComponentParameterStructure); + return OMX_ErrorNotImplemented; + } + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_get_state(OMX_HANDLETYPE hComponent, OMX_STATETYPE *pState) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + *pState = comp->state; + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_component_tunnel_request( + OMX_HANDLETYPE hComponent, OMX_U32 nPort, + OMX_HANDLETYPE hTunneledComp, OMX_U32 nTunneledPort, OMX_TUNNELSETUPTYPE* pTunnelSetup) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_PORT *port = 0; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (!(port = gomx_get_port(comp, nPort))) return OMX_ErrorBadPortIndex; + if (comp->state != OMX_StateLoaded && port->def.bEnabled) + return OMX_ErrorIncorrectStateOperation; + + if (hTunneledComp == 0 || pTunnelSetup == 0) { + port->tunnel_comp = 0; + return OMX_ErrorNone; + } + + if (port->def.eDir == OMX_DirInput) { + /* Negotiate parameters */ + OMX_PARAM_PORTDEFINITIONTYPE param; + omx_init(param); + param.nPortIndex = nTunneledPort; + if (OMX_GetParameter(hTunneledComp, OMX_IndexParamPortDefinition, ¶m)) + goto not_compatible; + if (param.eDomain != port->def.eDomain) + goto not_compatible; + + param.nBufferCountActual = max(param.nBufferCountMin, port->def.nBufferCountMin); + param.nBufferSize = max(port->def.nBufferSize, param.nBufferSize); + param.nBufferAlignment = max(port->def.nBufferAlignment, param.nBufferAlignment); + port->def.nBufferCountActual = param.nBufferCountActual; + port->def.nBufferSize = param.nBufferSize; + port->def.nBufferAlignment = param.nBufferAlignment; + if (OMX_SetParameter(hTunneledComp, OMX_IndexParamPortDefinition, ¶m)) + goto not_compatible; + + /* Negotiate buffer supplier */ + OMX_PARAM_BUFFERSUPPLIERTYPE suppl; + omx_init(suppl); + suppl.nPortIndex = nTunneledPort; + if (OMX_GetParameter(hTunneledComp, OMX_IndexParamCompBufferSupplier, &suppl)) + goto not_compatible; + + /* Being supplier is not supported so ask the other side to be it */ + suppl.eBufferSupplier = + (pTunnelSetup->eSupplier == OMX_BufferSupplyOutput) + ? OMX_BufferSupplyOutput : OMX_BufferSupplyInput; + if (OMX_SetParameter(hTunneledComp, OMX_IndexParamCompBufferSupplier, &suppl)) + goto not_compatible; + + port->tunnel_comp = hTunneledComp; + port->tunnel_port = nTunneledPort; + port->tunnel_supplier = (suppl.eBufferSupplier == OMX_BufferSupplyInput); + pTunnelSetup->eSupplier = suppl.eBufferSupplier; + CINFO(comp, port, "ComponentTunnnelRequest: %p %d", hTunneledComp, nTunneledPort); + } else { + CINFO(comp, port, "OUTPUT TUNNEL UNSUPPORTED: %p, %d, %p", hTunneledComp, nTunneledPort, pTunnelSetup); + return OMX_ErrorNotImplemented; + } + return OMX_ErrorNone; + +not_compatible: + CINFO(comp, port, "ComponentTunnnelRequest: %p %d - NOT COMPATIBLE", hTunneledComp, nTunneledPort); + return OMX_ErrorPortsNotCompatible; +} + +static void __gomx_event(GOMX_COMPONENT *comp, OMX_EVENTTYPE eEvent, OMX_U32 nData1, OMX_U32 nData2, OMX_PTR pEventData) +{ + if (!comp->cb.EventHandler) return; + pthread_mutex_unlock(&comp->mutex); + comp->cb.EventHandler((OMX_HANDLETYPE) comp, comp->omx.pApplicationPrivate, eEvent, nData1, nData2, pEventData); + pthread_mutex_lock(&comp->mutex); +} + +static void __gomx_port_update_buffer_state(GOMX_COMPONENT *comp, GOMX_PORT *port) +{ + if (port->num_buffers_old == port->num_buffers) + return; + + port->def.bPopulated = (port->num_buffers >= port->def.nBufferCountActual) ? OMX_TRUE : OMX_FALSE; + if (port->num_buffers == 0) + pthread_cond_signal(&port->cond_no_buffers); + else if (port->num_buffers == port->def.nBufferCountActual) + pthread_cond_signal(&port->cond_populated); +} + +static OMX_ERRORTYPE gomx_use_buffer(OMX_HANDLETYPE hComponent, OMX_BUFFERHEADERTYPE **ppBufferHdr, + OMX_U32 nPortIndex, OMX_PTR pAppPrivate, OMX_U32 nSizeBytes, OMX_U8* pBuffer) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + OMX_BUFFERHEADERTYPE *hdr; + GOMX_PORT *port; + void *buf; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (!(port = gomx_get_port(comp, nPortIndex))) return OMX_ErrorBadPortIndex; + + if (!((comp->state == OMX_StateLoaded && comp->wanted_state == OMX_StateIdle) || + (port->def.bEnabled == OMX_FALSE && + (comp->state == OMX_StateExecuting || + comp->state == OMX_StatePause || + comp->state == OMX_StateIdle)))) + return OMX_ErrorIncorrectStateOperation; + + buf = malloc(sizeof(OMX_BUFFERHEADERTYPE) + (pBuffer ? 0 : nSizeBytes)); + if (!buf) return OMX_ErrorInsufficientResources; + + hdr = (OMX_BUFFERHEADERTYPE *) buf; + memset(hdr, 0, sizeof *hdr); + omx_init(*hdr); + hdr->pBuffer = pBuffer ? (OMX_U8*)pBuffer : (OMX_U8*)((char*)buf + nSizeBytes); + hdr->nAllocLen = nSizeBytes; + hdr->pAppPrivate = pAppPrivate; + if (port->def.eDir == OMX_DirInput) { + hdr->nInputPortIndex = nPortIndex; + hdr->pOutputPortPrivate = pAppPrivate; + } else { + hdr->nOutputPortIndex = nPortIndex; + hdr->pInputPortPrivate = pAppPrivate; + } + pthread_mutex_lock(&comp->mutex); + port->num_buffers++; + __gomx_port_update_buffer_state(comp, port); + pthread_mutex_unlock(&comp->mutex); + + CDEBUG(comp, port, "allocated: %d, %p, %u, %p", nPortIndex, pAppPrivate, nSizeBytes, pBuffer); + *ppBufferHdr = hdr; + + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_allocate_buffer(OMX_HANDLETYPE hComponent, OMX_BUFFERHEADERTYPE **ppBufferHdr, + OMX_U32 nPortIndex, OMX_PTR pAppPrivate, OMX_U32 nSizeBytes) +{ + return gomx_use_buffer(hComponent, ppBufferHdr, nPortIndex, pAppPrivate, nSizeBytes, 0); +} + +static OMX_ERRORTYPE gomx_free_buffer(OMX_HANDLETYPE hComponent, OMX_U32 nPortIndex, OMX_BUFFERHEADERTYPE* pBuffer) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_PORT *port; + + if (!(port = gomx_get_port(comp, nPortIndex))) return OMX_ErrorBadPortIndex; + + /* Freeing buffer is allowed in all states, so destructor can + * synchronize successfully. */ + + pthread_mutex_lock(&comp->mutex); + + if (!((comp->state == OMX_StateIdle && comp->wanted_state == OMX_StateLoaded) || + (port->def.bEnabled == OMX_FALSE && + (comp->state == OMX_StateExecuting || + comp->state == OMX_StatePause || + comp->state == OMX_StateIdle)))) { + /* In unexpected states the port unpopulated error is sent. */ + if (port->num_buffers == port->def.nBufferCountActual) + __gomx_event(comp, OMX_EventError, OMX_ErrorPortUnpopulated, nPortIndex, 0); + /* FIXME? should we mark the port also down */ + } + + port->num_buffers--; + __gomx_port_update_buffer_state(comp, port); + + pthread_mutex_unlock(&comp->mutex); + + free(pBuffer); + + return OMX_ErrorNone; +} + +static void __gomx_port_queue_supplier_buffer(GOMX_PORT *port, OMX_BUFFERHEADERTYPE *hdr) +{ + gomxq_enqueue(&port->tunnel_supplierq, (void *) hdr); + if (port->tunnel_supplierq.num == port->num_buffers) + pthread_cond_broadcast(&port->cond_idle); +} + +static OMX_ERRORTYPE __gomx_empty_buffer_done(GOMX_COMPONENT *comp, OMX_BUFFERHEADERTYPE *hdr) +{ + GOMX_PORT *port = gomx_get_port(comp, hdr->nInputPortIndex); + OMX_ERRORTYPE r; + + if (port->tunnel_comp) { + /* Buffers are sent to the tunneled port once emptied as long as + * the component is in the OMX_StateExecuting state */ + if ((comp->state == OMX_StateExecuting && port->def.bEnabled) || + !port->tunnel_supplier) { + pthread_mutex_unlock(&comp->mutex); + r = OMX_FillThisBuffer(port->tunnel_comp, hdr); + pthread_mutex_lock(&comp->mutex); + } else { + r = OMX_ErrorIncorrectStateOperation; + } + } else { + pthread_mutex_unlock(&comp->mutex); + r = comp->cb.EmptyBufferDone((OMX_HANDLETYPE) comp, hdr->pAppPrivate, hdr); + pthread_mutex_lock(&comp->mutex); + } + + if (r != OMX_ErrorNone && port->tunnel_supplier) { + __gomx_port_queue_supplier_buffer(port, hdr); + r = OMX_ErrorNone; + } + + return r; +} + +static OMX_ERRORTYPE gomx_empty_this_buffer(OMX_HANDLETYPE hComponent, OMX_BUFFERHEADERTYPE* pBuffer) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_PORT *port; + OMX_ERRORTYPE r; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (comp->state != OMX_StatePause && comp->state != OMX_StateExecuting && + comp->wanted_state != OMX_StateExecuting) + return OMX_ErrorIncorrectStateOperation; + + if (!(port = gomx_get_port(comp, pBuffer->nInputPortIndex))) + return OMX_ErrorBadPortIndex; + + pthread_mutex_lock(&comp->mutex); + if (port->def.bEnabled) { + if (port->do_buffer) + r = port->do_buffer(comp, port, pBuffer); + else + r = __gomx_empty_buffer_done(comp, pBuffer); + } else { + if (port->tunnel_supplier) { + __gomx_port_queue_supplier_buffer(port, pBuffer); + r = OMX_ErrorNone; + } else { + r = OMX_ErrorIncorrectStateOperation; + } + } + pthread_mutex_unlock(&comp->mutex); + return r; +} + +static OMX_ERRORTYPE gomx_fill_this_buffer(OMX_HANDLETYPE hComponent, OMX_BUFFERHEADERTYPE* pBuffer) +{ + CDEBUG(hComponent, 0, "stub"); + return OMX_ErrorNotImplemented; +} + +static void __gomx_process_mark(GOMX_COMPONENT *comp, OMX_BUFFERHEADERTYPE *hdr) +{ + if (hdr->hMarkTargetComponent == (OMX_HANDLETYPE) comp) { + __gomx_event(comp, OMX_EventMark, 0, 0, hdr->pMarkData); + hdr->hMarkTargetComponent = 0; + hdr->pMarkData = 0; + } +} + +static OMX_ERRORTYPE __gomx_port_unpopulate(GOMX_COMPONENT *comp, GOMX_PORT *port) +{ + OMX_BUFFERHEADERTYPE *hdr; + void *buf; + + if (port->tunnel_supplier) { + CINFO(comp, port, "waiting for supplier buffers (%d / %d)", + (int)port->tunnel_supplierq.num, (int)port->num_buffers); + while (port->tunnel_supplierq.num != port->num_buffers) + pthread_cond_wait(&port->cond_idle, &comp->mutex); + + CINFO(comp, port, "free tunnel buffers"); + while ((hdr = (OMX_BUFFERHEADERTYPE*)gomxq_dequeue(&port->tunnel_supplierq)) != 0) { + buf = hdr->pBuffer; + OMX_FreeBuffer(port->tunnel_comp, port->tunnel_port, hdr); + free(buf); + port->num_buffers--; + __gomx_port_update_buffer_state(comp, port); + } + } else { + /* Wait client / tunnel supplier to allocate buffers */ + CINFO(comp, port, "waiting %d buffers to be freed", (int)port->num_buffers); + while (port->num_buffers > 0) + pthread_cond_wait(&port->cond_no_buffers, &comp->mutex); + } + + CINFO(comp, port, "UNPOPULATED"); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE __gomx_port_populate(GOMX_COMPONENT *comp, GOMX_PORT *port) +{ + OMX_ERRORTYPE r; + OMX_BUFFERHEADERTYPE *hdr; + void *buf; + + if (port->tunnel_supplier) { + CINFO(comp, port, "Allocating tunnel buffers"); + while (port->num_buffers < port->def.nBufferCountActual) { + pthread_mutex_unlock(&comp->mutex); + r = OMX_ErrorInsufficientResources; + buf = malloc(port->def.nBufferSize); + if (buf) { + r = OMX_UseBuffer(port->tunnel_comp, &hdr, + port->tunnel_port, 0, + port->def.nBufferSize, (OMX_U8*) buf); + if (r != OMX_ErrorNone) free(buf); + } + if (r == OMX_ErrorInvalidState || + r == OMX_ErrorIncorrectStateOperation) { + /* Non-supplier is not transitioned yet. + * Wait for a bit and retry */ + usleep(1000); + pthread_mutex_lock(&comp->mutex); + continue; + } + pthread_mutex_lock(&comp->mutex); + + if (r != OMX_ErrorNone) { + /* Hard error. Cancel and bail out */ + __gomx_port_unpopulate(comp, port); + return r; + } + + if (port->def.eDir == OMX_DirInput) + hdr->nInputPortIndex = port->def.nPortIndex; + else + hdr->nOutputPortIndex = port->def.nPortIndex; + gomxq_enqueue(&port->tunnel_supplierq, (void*) hdr); + port->num_buffers++; + __gomx_port_update_buffer_state(comp, port); + } + } else { + /* Wait client / tunnel supplier to allocate buffers */ + CINFO(comp, port, "waiting buffers"); + while (!port->def.bPopulated) + pthread_cond_wait(&port->cond_populated, &comp->mutex); + } + + CINFO(comp, port, "POPULATED"); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_send_command(OMX_HANDLETYPE hComponent, OMX_COMMANDTYPE Cmd, OMX_U32 nParam1, OMX_PTR pCmdData) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_COMMAND *c; + + /* OMX IL Specification is unclear which errors can be returned + * inline and which need to be reported with a callback. + * This just does minimal state checking, and queues everything + * to worker and reports any real errors via the callback. */ + if (!hComponent) return OMX_ErrorInvalidComponent; + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (!comp->cb.EventHandler) return OMX_ErrorNotReady; + + c = (GOMX_COMMAND*) malloc(sizeof(GOMX_COMMAND)); + if (!c) return OMX_ErrorInsufficientResources; + + CINFO(comp, 0, "SendCommand %x, %x, %p", Cmd, nParam1, pCmdData); + c->cmd = Cmd; + c->param = nParam1; + c->data = pCmdData; + + pthread_mutex_lock(&comp->mutex); + gomxq_enqueue(&comp->cmdq, (void*) c); + pthread_cond_signal(&comp->cond); + pthread_mutex_unlock(&comp->mutex); + + return OMX_ErrorNone; +} + +#define GOMX_TRANS(a,b) ((((uint32_t)a) << 16) | (uint32_t)b) + +static OMX_ERRORTYPE gomx_do_set_state(GOMX_COMPONENT *comp, GOMX_COMMAND *cmd) +{ + OMX_STATETYPE new_state = (OMX_STATETYPE) cmd->param; + OMX_ERRORTYPE r; + GOMX_PORT *port; + size_t i; + + if (comp->state == new_state) return OMX_ErrorSameState; + + if (new_state == OMX_StateInvalid) { + /* Transition to invalid state is always valid and immediate */ + comp->state = new_state; + return OMX_ErrorNone; + } + + CDEBUG(comp, 0, "starting transition to state %d", new_state); + + comp->wanted_state = new_state; + + if (comp->statechange) { + r = comp->statechange(comp); + if (r != OMX_ErrorNone) goto err; + } + + switch (GOMX_TRANS(comp->state, new_state)) { + case GOMX_TRANS(OMX_StateLoaded, OMX_StateIdle): + /* populate or wait for all enabled ports to be populated */ + for (i = 0; i < comp->nports; i++) { + if (!comp->ports[i].def.bEnabled) continue; + r = __gomx_port_populate(comp, &comp->ports[i]); + if (r) goto err; + } + break; + case GOMX_TRANS(OMX_StateIdle, OMX_StateLoaded): + /* free or wait all ports to be unpopulated */ + for (i = 0; i < comp->nports; i++) { + r = __gomx_port_unpopulate(comp, &comp->ports[i]); + if (r) goto err; + } + break; + case GOMX_TRANS(OMX_StateIdle, OMX_StateExecuting): + /* start threads */ + r = OMX_ErrorInsufficientResources; + if (comp->worker && + pthread_create(&comp->worker_thread, 0, comp->worker, comp) != 0) + goto err; + break; + case GOMX_TRANS(OMX_StateExecuting, OMX_StateIdle): + /* stop/join threads & wait buffers to be returned to suppliers */ + if (comp->worker_thread) { + pthread_mutex_unlock(&comp->mutex); + pthread_join(comp->worker_thread, 0); + pthread_mutex_lock(&comp->mutex); + comp->worker_thread = 0; + } + for (i = 0; i < comp->nports; i++) { + port = &comp->ports[i]; + if (!port->tunnel_supplier || !port->def.bEnabled) continue; + while (port->tunnel_supplierq.num != port->num_buffers) + pthread_cond_wait(&port->cond_idle, &comp->mutex); + } + break; + default: + /* FIXME: Pause and WaitForResources states not supported */ + r = OMX_ErrorIncorrectStateTransition; + goto err; + } + comp->state = new_state; + CDEBUG(comp, 0, "transition to state %d: success", new_state); + return OMX_ErrorNone; +err: + comp->wanted_state = comp->state; + CDEBUG(comp, 0, "transition to state %d: result %x", new_state, r); + return r; +} + +static OMX_ERRORTYPE gomx_do_port_command(GOMX_COMPONENT *comp, GOMX_PORT *port, GOMX_COMMAND *cmd) +{ + OMX_ERRORTYPE r = OMX_ErrorNone; + + switch (cmd->cmd) { + case OMX_CommandFlush: + if (port->flush) r = port->flush(comp, port); + break; + case OMX_CommandPortEnable: + port->def.bEnabled = OMX_TRUE; + r = __gomx_port_populate(comp, port); + if (r != OMX_ErrorNone) + port->def.bEnabled = OMX_FALSE; + break; + case OMX_CommandPortDisable: + port->def.bEnabled = OMX_FALSE; + if (port->flush) port->flush(comp, port); + r = __gomx_port_unpopulate(comp, port); + break; + default: + r = OMX_ErrorNotImplemented; + break; + } + return r; +} + +static OMX_ERRORTYPE gomx_do_command(GOMX_COMPONENT *comp, GOMX_COMMAND *cmd) +{ + GOMX_PORT *port; + + switch (cmd->cmd) { + case OMX_CommandStateSet: + CINFO(comp, 0, "state %x", cmd->param); + return gomx_do_set_state(comp, cmd); + case OMX_CommandFlush: + case OMX_CommandPortEnable: + case OMX_CommandPortDisable: + /* FIXME: OMX_ALL is not supported (but not used in omxplayer) */ + if (!(port = gomx_get_port(comp, cmd->param))) + return OMX_ErrorBadPortIndex; + CINFO(comp, port, "command %x", cmd->cmd); + return gomx_do_port_command(comp, port, cmd); + case OMX_CommandMarkBuffer: + /* FIXME: Not implemented (but not used in omxplayer) */ + default: + CINFO(comp, 0, "UNSUPPORTED %x, %x, %p", cmd->cmd, cmd->param, cmd->data); + return OMX_ErrorNotImplemented; + } +} + +static void *gomx_worker(void *ptr) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) ptr; + GOMX_PORT *port; + GOMX_COMMAND *cmd; + OMX_BUFFERHEADERTYPE *hdr; + OMX_ERRORTYPE r; + + CINFO(comp, 0, "start"); + pthread_mutex_lock(&comp->mutex); + while (comp->state != OMX_StateInvalid) { + cmd = (GOMX_COMMAND *) gomxq_dequeue(&comp->cmdq); + if (cmd) { + r = gomx_do_command(comp, cmd); + if (r == OMX_ErrorNone) + __gomx_event(comp, OMX_EventCmdComplete, + cmd->cmd, cmd->param, cmd->data); + else + __gomx_event(comp, OMX_EventError, r, 0, 0); + } else { + pthread_cond_wait(&comp->cond, &comp->mutex); + } + + if (comp->state != OMX_StateExecuting) + continue; + + /* FIXME: Rate limit and retry if needed suppplier buffer enqueuing */ + for (size_t i = 0; i < comp->nports; i++) { + port = &comp->ports[i]; + while ((hdr = (OMX_BUFFERHEADERTYPE*)gomxq_dequeue(&port->tunnel_supplierq)) != 0) { + pthread_mutex_unlock(&comp->mutex); + r = OMX_FillThisBuffer(port->tunnel_comp, hdr); + pthread_mutex_lock(&comp->mutex); + if (r != OMX_ErrorNone) { + __gomx_port_queue_supplier_buffer(port, hdr); + break; + } + } + } + } + pthread_mutex_unlock(&comp->mutex); + /* FIXME: make sure all buffers are returned and worker threads stopped */ + CINFO(comp, 0, "stop"); + return 0; +} + +static OMX_ERRORTYPE gomx_set_callbacks(OMX_HANDLETYPE hComponent, OMX_CALLBACKTYPE* pCallbacks, OMX_PTR pAppData) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (comp->state != OMX_StateLoaded) return OMX_ErrorIncorrectStateOperation; + pthread_mutex_lock(&comp->mutex); + comp->omx.pApplicationPrivate = pAppData; + comp->cb = *pCallbacks; + pthread_mutex_unlock(&comp->mutex); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE gomx_use_egl_image(OMX_HANDLETYPE hComponent, + OMX_BUFFERHEADERTYPE** ppBufferHdr, OMX_U32 nPortIndex, + OMX_PTR pAppPrivate, void* eglImage) +{ + return OMX_ErrorNotImplemented; +} + +static OMX_ERRORTYPE gomx_component_role_enum(OMX_HANDLETYPE hComponent, OMX_U8 *cRole, OMX_U32 nIndex) +{ + return OMX_ErrorNotImplemented; +} + +static void gomx_init(GOMX_COMPONENT *comp, const char *name, OMX_PTR pAppData, OMX_CALLBACKTYPE* pCallbacks, GOMX_PORT *ports, size_t nports) +{ + comp->omx.nSize = sizeof comp->omx; + comp->omx.nVersion.nVersion = OMX_VERSION; + comp->omx.pApplicationPrivate = pAppData; + comp->omx.GetComponentVersion = gomx_get_component_version; + comp->omx.SendCommand = gomx_send_command; + comp->omx.GetParameter = gomx_get_parameter; + comp->omx.GetState = gomx_get_state; + comp->omx.ComponentTunnelRequest = gomx_component_tunnel_request; + comp->omx.UseBuffer = gomx_use_buffer; + comp->omx.AllocateBuffer = gomx_allocate_buffer; + comp->omx.FreeBuffer = gomx_free_buffer; + comp->omx.EmptyThisBuffer = gomx_empty_this_buffer; + comp->omx.FillThisBuffer = gomx_fill_this_buffer; + comp->omx.SetCallbacks = gomx_set_callbacks; + comp->omx.UseEGLImage = gomx_use_egl_image; + comp->omx.ComponentRoleEnum = gomx_component_role_enum; + + comp->name = name; + comp->cb = *pCallbacks; + comp->state = OMX_StateLoaded; + comp->nports = nports; + comp->ports = ports; + + gomxq_init(&comp->cmdq, offsetof(GOMX_COMMAND, next)); + pthread_cond_init(&comp->cond, 0); + pthread_mutex_init(&comp->mutex, 0); + pthread_create(&comp->component_thread, 0, gomx_worker, comp); + + for (size_t i = 0; i < comp->nports; i++) { + GOMX_PORT *port = &comp->ports[i]; + pthread_cond_init(&port->cond_no_buffers, 0); + pthread_cond_init(&port->cond_populated, 0); + pthread_cond_init(&port->cond_idle, 0); + gomxq_init(&port->tunnel_supplierq, + port->def.eDir == OMX_DirInput + ? offsetof(OMX_BUFFERHEADERTYPE, pInputPortPrivate) + : offsetof(OMX_BUFFERHEADERTYPE, pOutputPortPrivate)); + } +} + +static void gomx_fini(GOMX_COMPONENT *comp) +{ + CINFO(comp, 0, "destroying"); + pthread_mutex_lock(&comp->mutex); + comp->state = OMX_StateInvalid; + pthread_cond_broadcast(&comp->cond); + pthread_mutex_unlock(&comp->mutex); + pthread_join(comp->component_thread, 0); + + for (size_t i = 0; i < comp->nports; i++) { + GOMX_PORT *port = &comp->ports[i]; + pthread_cond_destroy(&port->cond_no_buffers); + pthread_cond_destroy(&port->cond_populated); + pthread_cond_destroy(&port->cond_idle); + } + pthread_mutex_destroy(&comp->mutex); + pthread_cond_destroy(&comp->cond); +} + +/* ALSA Sink OMX Component */ + +#define OMXALSA_PORT_AUDIO 0 +#define OMXALSA_PORT_CLOCK 1 + +typedef struct _OMX_ALSASINK { + GOMX_COMPONENT gcomp; + GOMX_PORT port_data[2]; + GOMX_QUEUE playq; + pthread_cond_t cond_play; + size_t frame_size, sample_rate, play_queue_size; + int64_t starttime; + int32_t timescale; + OMX_AUDIO_PARAM_PCMMODETYPE pcm; + snd_pcm_format_t pcm_format; + snd_pcm_state_t pcm_state; + snd_pcm_sframes_t pcm_delay; + char device_name[16]; +} OMX_ALSASINK; + +static OMX_ERRORTYPE omxalsasink_set_parameter(OMX_HANDLETYPE hComponent, OMX_INDEXTYPE nParamIndex, OMX_PTR pComponentParameterStructure) +{ + static const struct { + snd_pcm_format_t fmt; + unsigned char pcm_mode; + unsigned char bits_per_sample; + unsigned char numerical_data; + unsigned char endianess; + } fmtmap[] = { + { SND_PCM_FORMAT_S8, OMX_AUDIO_PCMModeLinear, 8, OMX_NumericalDataSigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_U8, OMX_AUDIO_PCMModeLinear, 8, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_S16_LE, OMX_AUDIO_PCMModeLinear, 16, OMX_NumericalDataSigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_U16_LE, OMX_AUDIO_PCMModeLinear, 16, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_S16_BE, OMX_AUDIO_PCMModeLinear, 16, OMX_NumericalDataSigned, OMX_EndianBig }, + { SND_PCM_FORMAT_U16_BE, OMX_AUDIO_PCMModeLinear, 16, OMX_NumericalDataUnsigned, OMX_EndianBig }, + { SND_PCM_FORMAT_S24_LE, OMX_AUDIO_PCMModeLinear, 24, OMX_NumericalDataSigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_U24_LE, OMX_AUDIO_PCMModeLinear, 24, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_S24_BE, OMX_AUDIO_PCMModeLinear, 24, OMX_NumericalDataSigned, OMX_EndianBig }, + { SND_PCM_FORMAT_U24_BE, OMX_AUDIO_PCMModeLinear, 24, OMX_NumericalDataUnsigned, OMX_EndianBig }, + { SND_PCM_FORMAT_S32_LE, OMX_AUDIO_PCMModeLinear, 32, OMX_NumericalDataSigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_U32_LE, OMX_AUDIO_PCMModeLinear, 32, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_S32_BE, OMX_AUDIO_PCMModeLinear, 32, OMX_NumericalDataSigned, OMX_EndianBig }, + { SND_PCM_FORMAT_U32_BE, OMX_AUDIO_PCMModeLinear, 32, OMX_NumericalDataUnsigned, OMX_EndianBig }, + { SND_PCM_FORMAT_A_LAW, OMX_AUDIO_PCMModeALaw, 8, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + { SND_PCM_FORMAT_MU_LAW, OMX_AUDIO_PCMModeMULaw, 8, OMX_NumericalDataUnsigned, OMX_EndianLittle }, + }; + OMX_ALSASINK *sink = (OMX_ALSASINK *) hComponent; + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + GOMX_PORT *port; + OMX_AUDIO_PARAM_PCMMODETYPE *pmt; + OMX_ERRORTYPE r; + snd_pcm_format_t pcm_format = SND_PCM_FORMAT_UNKNOWN; + + /* Valid in OMX_StateLoaded or on disabled port... so can't check + * state without port number which is in index specific struct. */ + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + + switch (nParamIndex) { + case OMX_IndexParamAudioPcm: + if ((r = omx_cast(pmt, pComponentParameterStructure))) return r; + if (!(port = gomx_get_port(comp, pmt->nPortIndex))) return OMX_ErrorBadPortIndex; + if (pmt->nPortIndex != OMXALSA_PORT_AUDIO) return OMX_ErrorBadParameter; + + if (comp->state != OMX_StateLoaded && port->def.bEnabled) + return OMX_ErrorIncorrectStateOperation; + + for (size_t i = 0; i < ARRAY_SIZE(fmtmap); i++) { + if (fmtmap[i].pcm_mode == pmt->ePCMMode && + fmtmap[i].bits_per_sample == pmt->nBitPerSample && + fmtmap[i].numerical_data == pmt->eNumData && + fmtmap[i].endianess == pmt->eEndian) { + pcm_format = fmtmap[i].fmt; + break; + } + } + if (pcm_format == SND_PCM_FORMAT_UNKNOWN) + return OMX_ErrorBadParameter; + + memcpy(&sink->pcm, pmt, sizeof *pmt); + sink->pcm_format = pcm_format; + break; + default: + CINFO(comp, 0, "UNSUPPORTED %x, %p", nParamIndex, pComponentParameterStructure); + return OMX_ErrorNotImplemented; + } + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_get_config(OMX_HANDLETYPE hComponent, OMX_INDEXTYPE nIndex, OMX_PTR pComponentConfigStructure) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + OMX_ALSASINK *sink = (OMX_ALSASINK *) hComponent; + OMX_PARAM_U32TYPE *u32param; + OMX_ERRORTYPE r; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + if (!sink->frame_size) return OMX_ErrorInvalidState; + + switch (nIndex) { + case OMX_IndexConfigAudioRenderingLatency: + if ((r = omx_cast(u32param, pComponentConfigStructure))) return r; + /* Number of samples received but not played */ + pthread_mutex_lock(&comp->mutex); + u32param->nU32 = sink->play_queue_size / sink->frame_size; + if (sink->pcm_state == SND_PCM_STATE_RUNNING) + u32param->nU32 += sink->pcm_delay; + pthread_mutex_unlock(&comp->mutex); + CDEBUG(comp, 0, "OMX_IndexConfigAudioRenderingLatency %d", u32param->nU32); + break; + default: + CINFO(comp, 0, "UNSUPPORTED %x, %p", nIndex, pComponentConfigStructure); + return OMX_ErrorNotImplemented; + } + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_set_config(OMX_HANDLETYPE hComponent, OMX_INDEXTYPE nIndex, OMX_PTR pComponentConfigStructure) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT*) hComponent; + OMX_ALSASINK *sink = (OMX_ALSASINK*) hComponent; + OMX_CONFIG_BOOLEANTYPE *bt; + OMX_CONFIG_BRCMAUDIODESTINATIONTYPE *adest; + OMX_ERRORTYPE r; + + if (comp->state == OMX_StateInvalid) return OMX_ErrorInvalidState; + + switch (nIndex) { + case OMX_IndexConfigBrcmClockReferenceSource: + if ((r = omx_cast(bt, pComponentConfigStructure))) return r; + CDEBUG(comp, 0, "OMX_IndexConfigBrcmClockReferenceSource %d", bt->bEnabled); + break; + case OMX_IndexConfigBrcmAudioDestination: + if ((r = omx_cast(adest, pComponentConfigStructure))) return r; + strncpy(sink->device_name, (const char*) adest->sName, sizeof sink->device_name - 1); + CDEBUG(comp, 0, "OMX_IndexConfigBrcmAudioDestination %s", adest->sName); + break; + default: + CINFO(comp, 0, "UNSUPPORTED %x, %p", nIndex, pComponentConfigStructure); + return OMX_ErrorNotImplemented; + } + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_get_extension_index(OMX_HANDLETYPE hComponent, OMX_STRING cParameterName, OMX_INDEXTYPE *pIndexType) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) hComponent; + CINFO(comp, 0, "UNSUPPORTED '%s', %p", cParameterName, pIndexType); + return OMX_ErrorNotImplemented; +} + +static OMX_ERRORTYPE omxalsasink_deinit(OMX_HANDLETYPE hComponent) +{ + OMX_ALSASINK *sink = (OMX_ALSASINK *) hComponent; + gomx_fini(&sink->gcomp); + free(sink); + return OMX_ErrorNone; +} + +static void *omxalsasink_worker(void *ptr) +{ + GOMX_COMPONENT *comp = (GOMX_COMPONENT *) ptr; + OMX_HANDLETYPE hComponent = (OMX_HANDLETYPE) comp; + OMX_ALSASINK *sink = (OMX_ALSASINK *) hComponent; + OMX_BUFFERHEADERTYPE *buf; + GOMX_PORT *audio_port = &comp->ports[OMXALSA_PORT_AUDIO]; + GOMX_PORT *clock_port = &comp->ports[OMXALSA_PORT_CLOCK]; + snd_pcm_t *dev = 0; + snd_pcm_sframes_t n, delay; + snd_pcm_hw_params_t *hwp; + snd_pcm_uframes_t buffer_size, period_size, period_size_max; + SwrContext *resampler = 0; + uint8_t *resample_buf = 0; + int32_t timescale; + uint64_t layout; + size_t resample_bufsz; + unsigned int rate; + struct timespec ts; + int err; + + CINFO(comp, 0, "worker started"); + + err = snd_pcm_open(&dev, sink->device_name, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) goto alsa_error; + + rate = sink->pcm.nSamplingRate; + buffer_size = rate / 5; + period_size = buffer_size / 4; + period_size_max = buffer_size / 3; + + snd_pcm_hw_params_alloca(&hwp); + snd_pcm_hw_params_any(dev, hwp); + err = snd_pcm_hw_params_set_channels(dev, hwp, sink->pcm.nChannels); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_access(dev, hwp, sink->pcm.bInterleaved ? SND_PCM_ACCESS_RW_INTERLEAVED : SND_PCM_ACCESS_RW_NONINTERLEAVED); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_rate_near(dev, hwp, &rate, 0); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_format(dev, hwp, sink->pcm_format); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_period_size_max(dev, hwp, &period_size_max, 0); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_buffer_size_near(dev, hwp, &buffer_size); + if (err) goto alsa_error; + err = snd_pcm_hw_params_set_period_size_near(dev, hwp, &period_size, 0); + if (err) goto alsa_error; + err = snd_pcm_hw_params(dev, hwp); + if (err) goto alsa_error; + + sink->pcm.nSamplingRate = rate; + sink->frame_size = (sink->pcm.nChannels * sink->pcm.nBitPerSample) >> 3; + sink->sample_rate = rate; + + layout = av_get_default_channel_layout(sink->pcm.nChannels); + resampler = swr_alloc_set_opts(NULL, + layout, AV_SAMPLE_FMT_S16, rate, + layout, AV_SAMPLE_FMT_S16, rate, + 0, NULL); + if (!resampler) goto err; + + av_opt_set_double(resampler, "cutoff", 0.985, 0); + av_opt_set_int(resampler,"filter_size", 64, 0); + if (swr_init(resampler) < 0) goto err; + + resample_bufsz = audio_port->def.nBufferSize * 2; + resample_buf = (uint8_t *) malloc(resample_bufsz); + if (!resample_buf) goto err; + + CINFO(comp, 0, "sample_rate %d, frame_size %d", rate, sink->frame_size); + + pthread_mutex_lock(&comp->mutex); + while (comp->wanted_state == OMX_StateExecuting) { + /* Update hw buffer length, and xrun state */ + sink->pcm_state = snd_pcm_state(dev); + delay = 0; + snd_pcm_delay(dev, &delay); + if (resampler) delay += swr_get_delay(resampler, rate); + sink->pcm_delay = delay; + + /* Wait for buffer, or timeout to refresh state */ + buf = 0; + timescale = sink->timescale; + if (timescale) + buf = (OMX_BUFFERHEADERTYPE*) gomxq_dequeue(&sink->playq); + if (!buf) { + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_nsec += 10000000UL; /* 10 ms */ + if (ts.tv_nsec >= 1000000000L) { + ts.tv_nsec -= 1000000000UL; + ts.tv_sec++; + } + pthread_cond_timedwait(&sink->cond_play, &comp->mutex, &ts); + continue; + } + + if (clock_port->tunnel_comp && !(buf->nFlags & OMX_BUFFERFLAG_TIME_UNKNOWN)) { + OMX_TIME_CONFIG_TIMESTAMPTYPE tst; + int64_t pts = omx_ticks_to_s64(buf->nTimeStamp); + + omx_init(tst); + tst.nPortIndex = clock_port->tunnel_port; + tst.nTimestamp = buf->nTimeStamp; + if (resampler && buf->nFlags & OMX_BUFFERFLAG_STARTTIME) + swr_init(resampler); + if (buf->nFlags & (OMX_BUFFERFLAG_STARTTIME|OMX_BUFFERFLAG_DISCONTINUITY)) { + CINFO(comp, 0, "STARTTIME nTimeStamp=%llx", pts); + sink->starttime = pts; + } + + pts -= (int64_t)sink->pcm_delay * OMX_TICKS_PER_SECOND / rate; + + pthread_mutex_unlock(&comp->mutex); + if (buf->nFlags & (OMX_BUFFERFLAG_STARTTIME|OMX_BUFFERFLAG_DISCONTINUITY)) + OMX_SetConfig(clock_port->tunnel_comp, OMX_IndexConfigTimeClientStartTime, &tst); + if (pts >= sink->starttime) { + tst.nTimestamp = omx_ticks_from_s64(pts); + OMX_SetConfig(clock_port->tunnel_comp, OMX_IndexConfigTimeCurrentAudioReference, &tst); + } + pthread_mutex_lock(&comp->mutex); + } + + if (buf->nFlags & (OMX_BUFFERFLAG_DECODEONLY|OMX_BUFFERFLAG_CODECCONFIG|OMX_BUFFERFLAG_DATACORRUPT)) { + CDEBUG(comp, 0, "skipping: %d bytes, flags %x", buf->nFilledLen, buf->nFlags); + sink->play_queue_size -= buf->nFilledLen; + } else { + uint8_t *out_ptr, *in_ptr; + int in_len, out_len; + + pthread_mutex_unlock(&comp->mutex); + + in_ptr = (uint8_t *)(buf->pBuffer + buf->nOffset); + in_len = buf->nFilledLen / sink->frame_size; + + if (resampler) { + int delta = 0; + + if (timescale != 0x10000 && timescale >= 0x0100 && timescale <= 0x20000) + delta = ((int64_t)in_len*(0x10000-timescale))>>16; + + out_len = resample_bufsz / sink->frame_size; + swr_set_compensation(resampler, delta, in_len); + + out_ptr = resample_buf; + out_len = swr_convert(resampler, &out_ptr, out_len, + (const uint8_t **) &in_ptr, in_len); + + if (out_len < 0) out_len = 0; + } else { + out_ptr = in_ptr; + out_len = in_len; + } + + pthread_mutex_lock(&comp->mutex); + sink->play_queue_size -= buf->nFilledLen; + sink->pcm_delay += out_len; + pthread_mutex_unlock(&comp->mutex); + + while (out_len > 0) { + n = snd_pcm_writei(dev, out_ptr, out_len); + if (n < 0) { + CINFO(comp, 0, "alsa error: %ld: %s", n, snd_strerror(n)); + snd_pcm_recover(dev, n, 1); + n = 0; + } + out_len -= n; + n *= sink->frame_size; + out_ptr += n; + } + pthread_mutex_lock(&comp->mutex); + } + + __gomx_process_mark(comp, buf); + if (buf->nFlags & OMX_BUFFERFLAG_EOS) { + CDEBUG(comp, 0, "end-of-stream"); + pthread_mutex_unlock(&comp->mutex); + snd_pcm_drain(dev); + snd_pcm_prepare(dev); + pthread_mutex_lock(&comp->mutex); + sink->pcm_state = SND_PCM_STATE_PREPARED; + sink->pcm_delay = 0; + __gomx_event(comp, OMX_EventBufferFlag, OMXALSA_PORT_AUDIO, buf->nFlags, 0); + } + __gomx_empty_buffer_done(comp, buf); + } + pthread_mutex_unlock(&comp->mutex); +cleanup: + if (dev) snd_pcm_close(dev); + if (resampler) swr_close(resampler); + free(resample_buf); + CINFO(comp, 0, "worker stopped"); + return 0; + +alsa_error: + CINFO(comp, 0, "ALSA error: %s", snd_strerror(err)); +err: + pthread_mutex_lock(&comp->mutex); + /* FIXME: Current we just go to invalid state, but we might go + * back to Idle with ErrorResourcesPreempted and let the client + * recover. However, omxplayer does not care about this. */ + comp->state = OMX_StateInvalid; + __gomx_event(comp, OMX_EventError, OMX_StateInvalid, 0, 0); + pthread_mutex_unlock(&comp->mutex); + goto cleanup; +} + +static OMX_ERRORTYPE omxalsasink_audio_do_buffer(GOMX_COMPONENT *comp, GOMX_PORT *port, OMX_BUFFERHEADERTYPE *buf) +{ + OMX_ALSASINK *sink = (OMX_ALSASINK *) comp; + sink->play_queue_size += buf->nFilledLen; + gomxq_enqueue(&sink->playq, (void *) buf); + pthread_cond_signal(&sink->cond_play); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_audio_flush(GOMX_COMPONENT *comp, GOMX_PORT *port) +{ + OMX_ALSASINK *sink = (OMX_ALSASINK *) comp; + OMX_BUFFERHEADERTYPE *buf; + while ((buf = (OMX_BUFFERHEADERTYPE *) gomxq_dequeue(&sink->playq)) != 0) { + sink->play_queue_size -= buf->nFilledLen; + __gomx_empty_buffer_done(comp, buf); + } + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_clock_do_buffer(GOMX_COMPONENT *comp, GOMX_PORT *port, OMX_BUFFERHEADERTYPE *buf) +{ + OMX_ALSASINK *sink = (OMX_ALSASINK *) comp; + OMX_TIME_MEDIATIMETYPE *pMediaTime; + int wake = 0; + + if (omx_cast(pMediaTime, buf->pBuffer) == OMX_ErrorNone) { + CDEBUG(comp, port, "%p: clock %u bytes, flags=%x, nTimeStamp=%llx, eState=%d, xScale=%x", + buf, buf->nFilledLen, buf->nFlags, omx_ticks_to_s64(buf->nTimeStamp), + pMediaTime->eState, pMediaTime->xScale); + wake = (!sink->timescale && pMediaTime->xScale); + sink->timescale = pMediaTime->xScale; + } else { + CDEBUG(comp, port, "%p: clock %u bytes, flags=%x, nTimeStamp=%llx", + buf, buf->nFilledLen, buf->nFlags, omx_ticks_to_s64(buf->nTimeStamp)); + } + __gomx_process_mark(comp, buf); + __gomx_empty_buffer_done(comp, buf); + if (wake) pthread_cond_signal(&sink->cond_play); + + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_statechange(GOMX_COMPONENT *comp) +{ + OMX_ALSASINK *sink = (OMX_ALSASINK *) comp; + pthread_cond_signal(&sink->cond_play); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE omxalsasink_create(OMX_HANDLETYPE *pHandle, OMX_PTR pAppData, OMX_CALLBACKTYPE *pCallbacks) +{ + OMX_ALSASINK *sink; + GOMX_PORT *port; + pthread_condattr_t attr; + + sink = (OMX_ALSASINK *) calloc(1, sizeof *sink); + if (!sink) return OMX_ErrorInsufficientResources; + + strncpy(sink->device_name, "default", sizeof sink->device_name - 1); + gomxq_init(&sink->playq, offsetof(OMX_BUFFERHEADERTYPE, pInputPortPrivate)); + + /* Audio port */ + port = &sink->port_data[OMXALSA_PORT_AUDIO]; + port->def.nSize = sizeof *port; + port->def.nVersion.nVersion = OMX_VERSION; + port->def.nPortIndex = OMXALSA_PORT_AUDIO; + port->def.eDir = OMX_DirInput; + port->def.nBufferCountMin = 4; + port->def.nBufferCountActual = 4; + port->def.nBufferSize = 8 * 1024; + port->def.bEnabled = OMX_TRUE; + port->def.eDomain = OMX_PortDomainAudio; + port->def.format.audio.cMIMEType = (char *) "raw/audio"; + port->def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; + port->def.nBufferAlignment = 4; + port->do_buffer = omxalsasink_audio_do_buffer; + port->flush = omxalsasink_audio_flush; + + /* Clock port */ + port = &sink->port_data[OMXALSA_PORT_CLOCK]; + port->def.nSize = sizeof *port; + port->def.nVersion.nVersion = OMX_VERSION; + port->def.nPortIndex = OMXALSA_PORT_CLOCK; + port->def.eDir = OMX_DirInput; + port->def.nBufferCountMin = 1; + port->def.nBufferCountActual = 1; + port->def.nBufferSize = sizeof(OMX_TIME_MEDIATIMETYPE); + port->def.bEnabled = OMX_TRUE; + port->def.eDomain = OMX_PortDomainOther; + port->def.format.other.eFormat = OMX_OTHER_FormatTime; + port->def.nBufferAlignment = 4; + port->do_buffer = omxalsasink_clock_do_buffer; + + gomx_init(&sink->gcomp, "OMX.alsa.audio_render", pAppData, pCallbacks, sink->port_data, ARRAY_SIZE(sink->port_data)); + sink->gcomp.omx.SetParameter = omxalsasink_set_parameter; + sink->gcomp.omx.GetConfig = omxalsasink_get_config; + sink->gcomp.omx.SetConfig = omxalsasink_set_config; + sink->gcomp.omx.GetExtensionIndex = omxalsasink_get_extension_index; + sink->gcomp.omx.ComponentDeInit = omxalsasink_deinit; + sink->gcomp.worker = omxalsasink_worker; + sink->gcomp.statechange = omxalsasink_statechange; + + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); + pthread_cond_init(&sink->cond_play, &attr); + pthread_condattr_destroy(&attr); + + *pHandle = (OMX_HANDLETYPE) sink; + return OMX_ErrorNone; +} + +/* OMX Glue to get the handle */ + +#include + +OMX_ERRORTYPE OMXALSA_GetHandle(OMX_OUT OMX_HANDLETYPE* pHandle, OMX_IN OMX_STRING cComponentName, + OMX_IN OMX_PTR pAppData, OMX_IN OMX_CALLBACKTYPE* pCallbacks) +{ + if (strcmp(cComponentName, "OMX.alsa.audio_render") == 0) + return omxalsasink_create(pHandle, pAppData, pCallbacks); + + return OMX_ErrorComponentNotFound; +} + +OMX_ERRORTYPE OMXALSA_FreeHandle(OMX_IN OMX_HANDLETYPE hComponent) +{ + return ((OMX_COMPONENTTYPE*)hComponent)->ComponentDeInit(hComponent); +} diff --git a/linux/OMXAlsa.h b/linux/OMXAlsa.h new file mode 100644 index 00000000..b8492887 --- /dev/null +++ b/linux/OMXAlsa.h @@ -0,0 +1,11 @@ +#pragma once +#include + +OMX_API OMX_ERRORTYPE OMX_APIENTRY OMXALSA_GetHandle( + OMX_OUT OMX_HANDLETYPE* pHandle, + OMX_IN OMX_STRING cComponentName, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_CALLBACKTYPE* pCallBacks); + +OMX_API OMX_ERRORTYPE OMX_APIENTRY OMXALSA_FreeHandle( + OMX_IN OMX_HANDLETYPE hComponent); diff --git a/omxplayer.cpp b/omxplayer.cpp index 5c03171b..428ccb66 100644 --- a/omxplayer.cpp +++ b/omxplayer.cpp @@ -453,7 +453,8 @@ static int get_mem_gpu(void) static void blank_background(uint32_t rgba) { - if (!rgba) + // if alpha is fully transparent then background has no effect + if (!(rgba & 0xff000000)) return; // we create a 1x1 black pixel image that is added to display just behind video DISPMANX_DISPLAY_HANDLE_T display; @@ -463,7 +464,6 @@ static void blank_background(uint32_t rgba) int ret; uint32_t vc_image_ptr; VC_IMAGE_TYPE_T type = VC_IMAGE_ARGB8888; - uint16_t image = 0x0000; // black int layer = m_config_video.layer - 1; VC_RECT_T dst_rect, src_rect; @@ -586,7 +586,7 @@ int main(int argc, char *argv[]) { "nodeinterlace",no_argument, NULL, no_deinterlace_opt }, { "nativedeinterlace",no_argument, NULL, native_deinterlace_opt }, { "anaglyph", required_argument, NULL, anaglyph_opt }, - { "advanced", no_argument, NULL, advanced_opt }, + { "advanced", optional_argument, NULL, advanced_opt }, { "hw", no_argument, NULL, 'w' }, { "3d", required_argument, NULL, '3' }, { "allow-mvc", no_argument, NULL, 'M' }, @@ -694,7 +694,7 @@ int main(int argc, char *argv[]) m_config_video.anaglyph = (OMX_IMAGEFILTERANAGLYPHTYPE)atoi(optarg); break; case advanced_opt: - m_config_video.advanced_hd_deinterlace = true; + m_config_video.advanced_hd_deinterlace = optarg ? (atoi(optarg) ? true : false): true; break; case 'w': m_config_audio.hwdecode = true; @@ -706,10 +706,24 @@ int main(int argc, char *argv[]) m_stats = true; break; case 'o': - m_config_audio.device = optarg; - if(m_config_audio.device != "local" && m_config_audio.device != "hdmi" && m_config_audio.device != "both") { - printf("Bad argument for -%c: Output device must be `local', `hdmi' or `both'\n", c); + CStdString str = optarg; + int colon = str.Find(':'); + if(colon >= 0) + { + m_config_audio.device = str.Mid(0, colon); + m_config_audio.subdevice = str.Mid(colon + 1, str.GetLength() - colon); + } + else + { + m_config_audio.device = str; + m_config_audio.subdevice = ""; + } + } + if(m_config_audio.device != "local" && m_config_audio.device != "hdmi" && m_config_audio.device != "both" && + m_config_audio.device != "alsa") + { + printf("Bad argument for -%c: Output device must be `local', `hdmi', `both' or `alsa'\n", c); return EXIT_FAILURE; } m_config_audio.device = "omx:" + m_config_audio.device; @@ -1134,6 +1148,9 @@ int main(int argc, char *argv[]) m_config_audio.device = "omx:local"; } + if(m_config_audio.device == "omx:alsa" && m_config_audio.subdevice.empty()) + m_config_audio.subdevice = "default"; + if ((m_config_audio.hints.codec == AV_CODEC_ID_AC3 || m_config_audio.hints.codec == AV_CODEC_ID_EAC3) && m_BcmHost.vc_tv_hdmi_audio_supported(EDID_AudioFormat_eAC3, 2, EDID_AudioSampleRate_e44KHz, EDID_AudioSampleSize_16bit ) != 0) m_config_audio.passthrough = false; @@ -1175,7 +1192,7 @@ int main(int argc, char *argv[]) if (update) { OMXControlResult result = control_err - ? (OMXControlResult)m_keyboard->getEvent() + ? (OMXControlResult)(m_keyboard ? m_keyboard->getEvent() : KeyConfig::ACTION_BLANK) : m_omxcontrol.getEvent(); double oldPos, newPos; @@ -1414,7 +1431,21 @@ int main(int argc, char *argv[]) case KeyConfig::ACTION_SET_ALPHA: m_player_video.SetAlpha(result.getArg()); break; + case KeyConfig::ACTION_PLAY: + m_Pause=false; + if(m_has_subtitle) + { + m_player_subtitles.Resume(); + } + break; case KeyConfig::ACTION_PAUSE: + m_Pause=true; + if(m_has_subtitle) + { + m_player_subtitles.Pause(); + } + break; + case KeyConfig::ACTION_PLAYPAUSE: m_Pause = !m_Pause; if (m_av_clock->OMXPlaySpeed() != DVD_PLAYSPEED_NORMAL && m_av_clock->OMXPlaySpeed() != DVD_PLAYSPEED_PAUSE) { diff --git a/prepare-native-raspbian.sh b/prepare-native-raspbian.sh index add873b6..57201c1e 100755 --- a/prepare-native-raspbian.sh +++ b/prepare-native-raspbian.sh @@ -15,7 +15,7 @@ if [ -z `which sudo` ] ; then fi echo "Checking dpkg database for missing packages" -REQUIRED_PKGS="ca-certificates git-core subversion binutils libva1 libpcre3-dev libidn11-dev libboost1.50-dev libfreetype6-dev libusb-1.0-0-dev libdbus-1-dev libssl-dev libssh-dev libsmbclient-dev gcc-4.7 g++-4.7 sed pkg-config" +REQUIRED_PKGS="ca-certificates git-core subversion binutils libasound2-dev libva1 libpcre3-dev libidn11-dev libboost1.50-dev libfreetype6-dev libusb-1.0-0-dev libdbus-1-dev libssl-dev libssh-dev libsmbclient-dev gcc-4.7 g++-4.7 sed pkg-config" MISSING_PKGS="" for pkg in $REQUIRED_PKGS do