├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── LICENSE ├── README.md ├── SubjectivePlayer.apk ├── SubjectivePlayer.iml ├── create_playlists_mobile.py ├── gen └── org │ └── univie │ └── subjectiveplayer │ ├── BuildConfig.java │ ├── Manifest.java │ └── R.java ├── project.properties ├── res ├── drawable-hdpi │ ├── ic_menu_close_clear_cancel.png │ ├── ic_menu_info_details.png │ ├── ic_menu_preferences.png │ └── icon.png ├── drawable-ldpi │ ├── ic_menu_close_clear_cancel.png │ ├── ic_menu_info_details.png │ ├── ic_menu_preferences.png │ └── icon.png ├── drawable-mdpi │ ├── ic_menu_close_clear_cancel.png │ ├── ic_menu_info_details.png │ ├── ic_menu_preferences.png │ └── icon.png ├── layout │ ├── dialog_acr_custom.xml │ ├── dialog_continuous_view.xml │ ├── dialog_continuous_view_no_ticks.xml │ ├── main.xml │ ├── video_view.xml │ └── video_view_overlay.xml ├── menu │ └── subjective_menu.xml ├── values-de │ └── strings.xml ├── values-en │ └── strings.xml ├── values │ ├── strings.xml │ └── styles.xml └── xml │ └── preferences.xml └── src └── org └── univie └── subjectiveplayer ├── Configuration.java ├── CustomDialog.java ├── Logger.java ├── Methods.java ├── Session.java ├── SubjectivePlayer.java ├── SubjectivePlayerPreferences.java └── SubjectivePlayerSession.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .DS_Store -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubjectivePlayer 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The GNU General Public License v3 2 | 3 | SubjectivePlayer for Android 4 | Copyright (c) 2012-2017 Werner Robitza 5 | 6 | SubjectivePlayer for Android is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | SubjectivePlayer for Android is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with SubjectivePlayer for Android. If not, see . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://dl.dropbox.com/u/84665/cacmtv/subjectiveplayer-logo.png) 2 | 3 | Author: Werner Robitza 4 | 5 | ## Introduction 6 | 7 | Thanks for using SubjectivePlayer for Android. I built this software because there was nothing like it available yet and we definitely needed a video player capable of recording user opinion. 8 | 9 | There are quite a few tools around that exist for traditional PCs, mostly Windows-based, which show videos or still images to test persons and ask them to rate the respective video or image quality. Such tools adhere to the ITU recommendations on subjective evaluation of audio and video and have been used for a long time. The hardware that should be used is described precisely, and so are the testing procedures. 10 | 11 | Because the context of usage in mobile multimedia is totally different from the good old desktop PC at home, these ITU test recommendations do not really fit perfectly. We tried to come up with a simple and easy solution to present videos on an Android device and record the subjective opinion of viewers. Of course, when possible, ITU recommendations were followed or reinterpreted for mobile usage. 12 | 13 | ---- 14 | 15 | ## Features and Non-Features 16 | 17 | To put it in a brief list, SubjectivePlayer for Android can: 18 | 19 | * play an indefinitely long list of MPEG-4 AVC (H.264) coded videos, either in .mp4 or .3gpp format 20 | * after each video ask the user for their opinion using a certain methodology 21 | * record a user ID so as to identify the test person later 22 | * log the user opinion to text files 23 | 24 | ---- 25 | 26 | ## Requirements 27 | 28 | In order to use SubjectivePlayer for Android, you need to: 29 | 30 | * get a mobile phone running Android (tested with Galaxy S5 – use the "legacy" branch for older phones) 31 | * equip the phone with a SD card 32 | * activate the Debugging mode on the phone so you can use unsigned apps 33 | * preferably: a computer with the appropriate Android SDK installed to make your own changes 34 | * videos :) 35 | 36 | ## Installation 37 | 38 | Download the `SubjectivePlayer.apk` file in this repository. It's the newest build. You can point your mobile device to this file directly, and it will prompt you to install the software. 39 | 40 | Make sure you allow the application to access your SD card. 41 | 42 | ## Usage 43 | 44 | The application loads videos from the SD card of the device. If the device does not have a physical SD card, Android will simulate that there is one. 45 | 46 | Here is what you have to do to perform a test: 47 | 48 | - Prepare your movies as H.264/AAC video and audio, multiplexed in MP4. That ensures compatibility with the device. 49 | - Prepare a playlist, which consists of a list of video names, and call it `playlist1.cfg`, for example. 50 | - For each participant that you want to test, you need a new playlist file with their ID, so for example, you need to create the files `playlist1.cfg` and `playlist2.cfg` for two users. 51 | 52 | You can generate playlists using the `create_playlists_mobile.py` script. 53 | 54 | Now let's put everything on the device: 55 | 56 | ### If you have an SD card 57 | 58 | - Start the app 59 | - Go to options (hold the option button) and enable SD card 60 | - Quit and start the app again 61 | - Connect the phone and you will see three folders in `Android/data/org.univie.subjectiveplayer/files`: `SubjectiveMovies`, `SubjectiveCfg` and `SubjectiveLogs`. 62 | - Place your video files in the `SubjectiveMovies` folder. 63 | - Place your playlist in the `SubjectiveCfg` folder. 64 | 65 | ### If you have no SD card 66 | 67 | - Start the app once 68 | - Connect the phone and you will see three folders on the internal memory: `SubjectiveMovies`, `SubjectiveCfg` and `SubjectiveLogs`. 69 | - Place your video files in the `SubjectiveMovies` folder. 70 | - Place your playlist in the `SubjectiveCfg` folder. 71 | 72 | If you cannot write to these folders, you need to [use adb](http://lifehacker.com/the-easiest-way-to-install-androids-adb-and-fastboot-to-1586992378) and run something similar to this: 73 | 74 | adb push local-file /mnt/sdcard/SubjectiveMovies/ 75 | 76 | ## Run the Test 77 | 78 | Now, when you start the app again, you can select the playlist by having the user enter their respective ID. 79 | 80 | After a test, the results are stored in the `SubjectiveLogs` folder. Each file corresponds to one user's test. The files contain the running number of video, the video name, and the corresponding rating as an integer number. 81 | 82 | ## License 83 | 84 | The GNU General Public License v3 85 | 86 | SubjectivePlayer for Android 87 | Copyright (c) 2012-2017 Werner Robitza 88 | 89 | SubjectivePlayer for Android is free software: you can redistribute it and/or modify 90 | it under the terms of the GNU General Public License as published by 91 | the Free Software Foundation, either version 3 of the License, or 92 | (at your option) any later version. 93 | 94 | SubjectivePlayer for Android is distributed in the hope that it will be useful, 95 | but WITHOUT ANY WARRANTY; without even the implied warranty of 96 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 97 | GNU General Public License for more details. 98 | 99 | You should have received a copy of the GNU General Public License 100 | along with SubjectivePlayer for Android. If not, see . 101 | -------------------------------------------------------------------------------- /SubjectivePlayer.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/SubjectivePlayer.apk -------------------------------------------------------------------------------- /SubjectivePlayer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /create_playlists_mobile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Generate mobile playlists for SubjectivePlayer 3 | # Author: Werner Robitza 4 | 5 | import os 6 | import argparse 7 | import glob 8 | import re 9 | import math 10 | import pprint 11 | import random 12 | 13 | TRAINING_SET = [ 14 | "01_tr03_SRC200_HRC03.mp4", 15 | "02_tr03_SRC414_HRC61.mp4", 16 | "03_tr03_SRC118_HRC68.mp4", 17 | "04_tr03_SRC115_HRC76.mp4", 18 | "05_tr03_SRC305_HRC69.mp4", 19 | "06_tr03_SRC218_HRC02.mp4", 20 | "07_TR00_SRC00_HRC01.mp4" 21 | ] 22 | 23 | SUFFIX = ".cfg" 24 | 25 | parser = argparse.ArgumentParser( 26 | description='''This program generates mobile playlists for a number of users in a random fashion''') 27 | parser.add_argument('-i', '--input', type = str, default="./avbuffpvs/", help = '''Path to PVS''') 28 | parser.add_argument('-o', '--output', type = str, default="./playlists_mobile/", help = '''Path to output dir''') 29 | parser.add_argument('-n', '--number', type = int, help = '''Number of playlists to generate''', default = 30) 30 | parser.add_argument('-s', '--sessions', type = int, help = "Number of sessions", default = 3) 31 | parser.add_argument('-p', '--prime', default=False, action="store_true", 32 | help="Use prime numbers for ID generation") 33 | 34 | args = parser.parse_args() 35 | 36 | # --------------------------------------------------------------------------------------------------------- 37 | 38 | # check if number is prime 39 | def is_prime(n): 40 | for i in range(2, int(math.sqrt(n)) + 1): 41 | if n % i == 0: 42 | return False 43 | return True 44 | 45 | # get a number of primes starting with a number 46 | def get_primes(n, init = 0): 47 | primes = list() 48 | i = init 49 | while(len(primes) < n): 50 | if(is_prime(i)): 51 | primes.append(i) 52 | i = i + 1 53 | return primes 54 | 55 | 56 | # split a list into equal parts 57 | def get_randomized_and_split(input_list, n_per_split): 58 | shuffled = sorted(input_list, key = lambda k: random.random()) 59 | l = [ shuffled [i:i + n_per_split] for i in range(0, (args.sessions-1)*n_per_split, n_per_split)] 60 | l.append(shuffled[(args.sessions-1)*n_per_split:]) 61 | return l 62 | 63 | # create test list 64 | pvs = [ os.path.basename(f) for f in glob.glob(args.input + "*.mp4") ] 65 | 66 | # remove training videos 67 | pvs = [ p for p in pvs if p not in TRAINING_SET ] 68 | 69 | # split into sessions 70 | pvs_per_session = int(len(pvs) / args.sessions) 71 | 72 | # get prime numbers for ID generation 73 | primes = get_primes(args.number, init = 3) 74 | 75 | # create output dir if necessary 76 | if not os.path.exists(args.output): 77 | os.makedirs(args.output) 78 | 79 | # generate playlists and write to files 80 | for user_id, prime in enumerate(primes): 81 | # start with ID 1 for users 82 | user_id = user_id + 1 83 | playlists = get_randomized_and_split(pvs, pvs_per_session) 84 | 85 | # IDs for files 86 | training_id = str(user_id) + "0" 87 | session_ids = list() 88 | 89 | # session 1, 2, 3 90 | for i in range(1, args.sessions + 1): 91 | if args.prime: 92 | session_ids.append(str(prime ** i)) 93 | else: 94 | session_ids.append(str(user_id) + str(i)) 95 | 96 | training_file = open(args.output + 'playlist' + training_id + SUFFIX, 'w') 97 | print("[info] writing training file for user " + str(user_id) + ": " + training_file.name) 98 | for f in TRAINING_SET: 99 | training_file.write(f + "\n") 100 | training_file.close() 101 | 102 | for session_id, playlist in zip(session_ids, playlists): 103 | session_file = open(args.output + 'playlist' + session_id + SUFFIX, 'w') 104 | print("[info] writing session file for user " + str(user_id) + ": " + session_file.name) 105 | for f in playlist: 106 | session_file.write(f + "\n") 107 | session_file.close() 108 | -------------------------------------------------------------------------------- /gen/org/univie/subjectiveplayer/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package org.univie.subjectiveplayer; 4 | 5 | /* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */ 6 | public final class BuildConfig { 7 | public final static boolean DEBUG = Boolean.parseBoolean(null); 8 | } -------------------------------------------------------------------------------- /gen/org/univie/subjectiveplayer/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package org.univie.subjectiveplayer; 4 | 5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ 6 | public final class Manifest { 7 | } -------------------------------------------------------------------------------- /gen/org/univie/subjectiveplayer/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package org.univie.subjectiveplayer; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class drawable { 14 | public static final int ic_menu_close_clear_cancel=0x7f020000; 15 | public static final int ic_menu_info_details=0x7f020001; 16 | public static final int ic_menu_preferences=0x7f020002; 17 | public static final int icon=0x7f020003; 18 | } 19 | public static final class id { 20 | public static final int button_filebrowser=0x7f070015; 21 | public static final int button_methodbrowser=0x7f070018; 22 | public static final int button_start=0x7f07001b; 23 | public static final int dialog_continuous_label0=0x7f070002; 24 | public static final int dialog_continuous_label1=0x7f070003; 25 | public static final int dialog_continuous_label10=0x7f07000c; 26 | public static final int dialog_continuous_label2=0x7f070004; 27 | public static final int dialog_continuous_label3=0x7f070005; 28 | public static final int dialog_continuous_label4=0x7f070006; 29 | public static final int dialog_continuous_label5=0x7f070007; 30 | public static final int dialog_continuous_label6=0x7f070008; 31 | public static final int dialog_continuous_label7=0x7f070009; 32 | public static final int dialog_continuous_label8=0x7f07000a; 33 | public static final int dialog_continuous_label9=0x7f07000b; 34 | public static final int dialog_continuous_no_ticks_label_max=0x7f070011; 35 | public static final int dialog_continuous_no_ticks_label_min=0x7f070010; 36 | public static final int dialog_continuous_no_ticks_tablerow1=0x7f07000f; 37 | public static final int dialog_continuous_seekbar=0x7f07000d; 38 | public static final int dialog_continuous_tablelayout=0x7f070000; 39 | public static final int dialog_continuous_tablelayout_no_ticks=0x7f07000e; 40 | public static final int dialog_continuous_tablerow1=0x7f070001; 41 | public static final int edit_id=0x7f07001a; 42 | public static final int menu_about=0x7f07001e; 43 | public static final int menu_exit=0x7f07001f; 44 | public static final int menu_preferences=0x7f07001d; 45 | public static final int scroll_view=0x7f070012; 46 | public static final int text_select_config=0x7f070013; 47 | public static final int text_select_config_selected=0x7f070014; 48 | public static final int text_select_id=0x7f070019; 49 | public static final int text_select_method=0x7f070016; 50 | public static final int text_select_method_selected=0x7f070017; 51 | public static final int video_surface=0x7f07001c; 52 | } 53 | public static final class layout { 54 | public static final int dialog_continuous_view=0x7f030000; 55 | public static final int dialog_continuous_view_no_ticks=0x7f030001; 56 | public static final int main=0x7f030002; 57 | public static final int video_view=0x7f030003; 58 | public static final int video_view_overlay=0x7f030004; 59 | } 60 | public static final class menu { 61 | public static final int subjective_menu=0x7f060000; 62 | } 63 | public static final class string { 64 | public static final int about_body=0x7f050010; 65 | public static final int about_caption=0x7f05000f; 66 | public static final int app_name=0x7f050001; 67 | public static final int dialog_no_ticks_max=0x7f050019; 68 | public static final int dialog_no_ticks_min=0x7f05001a; 69 | public static final int error_nodata_body=0x7f05000d; 70 | public static final int error_nodata_caption=0x7f05000e; 71 | public static final int hello=0x7f050000; 72 | public static final int menu_about=0x7f050004; 73 | public static final int menu_exit=0x7f050005; 74 | public static final int menu_preferences=0x7f050009; 75 | public static final int preferences_acrnumbers=0x7f050015; 76 | public static final int preferences_acrnumbers_summary_false=0x7f050017; 77 | public static final int preferences_acrnumbers_summary_true=0x7f050016; 78 | public static final int preferences_categories_acr=0x7f050014; 79 | public static final int preferences_categories_config=0x7f05000c; 80 | public static final int preferences_categories_continuous=0x7f05001b; 81 | public static final int preferences_configsuffix=0x7f05000a; 82 | public static final int preferences_configsuffix_summary=0x7f05000b; 83 | public static final int preferences_noticks_caption=0x7f05001c; 84 | public static final int preferences_noticks_summary_false=0x7f05001e; 85 | public static final int preferences_noticks_summary_true=0x7f05001d; 86 | public static final int rate_ACR_caption=0x7f050013; 87 | public static final int rate_continuous_caption=0x7f050018; 88 | public static final int select_config=0x7f050002; 89 | public static final int select_config_selected=0x7f050011; 90 | public static final int select_filebrowser=0x7f050006; 91 | public static final int select_id=0x7f050003; 92 | public static final int select_method=0x7f050007; 93 | public static final int select_method_browser=0x7f050008; 94 | public static final int select_method_selected=0x7f050012; 95 | } 96 | public static final class xml { 97 | public static final int preferences=0x7f040000; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-4 12 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_menu_close_clear_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-hdpi/ic_menu_close_clear_cancel.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_menu_info_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-hdpi/ic_menu_info_details.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_menu_preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-hdpi/ic_menu_preferences.png -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_menu_close_clear_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-ldpi/ic_menu_close_clear_cancel.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_menu_info_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-ldpi/ic_menu_info_details.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_menu_preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-ldpi/ic_menu_preferences.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_menu_close_clear_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-mdpi/ic_menu_close_clear_cancel.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_menu_info_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-mdpi/ic_menu_info_details.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_menu_preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-mdpi/ic_menu_preferences.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slhck/SubjectivePlayer/599ab8de11dec791ce6a36892baedae17c3e4f99/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/layout/dialog_acr_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 18 | 19 | 27 | 28 | 31 | 32 | 40 | 41 | 49 | 50 | 51 | 52 | 55 | 56 | 64 | 65 | 72 | 73 | 74 | 77 | 78 | 86 | 87 | 94 | 95 | 96 | 99 | 100 | 108 | 109 | 116 | 117 | 118 | 119 | 122 | 123 | 131 | 132 | 139 | 140 | 141 | 142 |