~nfiewiqp4n321fdsaniro34214/apport/apport-symptoms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# Sound/audio related problem troubleshooter/triager
# Written by David Henningsson 2010, diwic@ubuntu.com
# License: BSD (see /usr/share/common-licenses/BSD )

description = 'Sound/audio related problems'

import apport
import re
import os
import subprocess

def run(report, ui):

    # is pulseaudio installed and running?
    package = check_pulseaudio_running(report, ui)
    if package is not None:
        return package

    # what problem does the user observe?
    problem = ask_problem(report, ui)

    # soundcard query
    package, card = soundcard_query(report, ui)
    if package is not None:
        return package

    # check that the default pulseaudio profile is correct
    package, channelcount = check_pulseaudio_profile(report, ui, card, problem)
    if package is not None:
        return package

    # playback problems
    if problem == 0 or problem == 1:
        check_volumes(report, ui, card, problem)
        # speaker test to see if it's pulseaudio or alsa.
        package = speaker_test(report, ui, card, channelcount)
        if package is not None:
            return package


    #TODO: Recording problems

    # hopefully, we should never come here
    report['PackageFailed'] = 'Troubleshooter failed to determine a package'
    return 'alsa-base' 


def ask_problem(report, ui):
    ''' returns problem code: 
      0 => Stereo playback
      1 => Surround playback
      2 => Recording
      or raises StopIteration '''
    problem = ui.choice('What particular problem do you observe?',
        ['Playback does not work, or is crackling', 
         'Surround playback problem (but stereo playback works)',
         'Recording does not work properly',
         'Sound problem with one or a few applications only'
        ])

    if problem is None:
        raise StopIteration
    problem = problem[0]

    if problem == 3:
        ui.information('Please use the "report bug" feature, available in '
         'the menu of the application,\nor use "ubuntu-bug <packagename>" '
         'to report a bug against the particular package.')
        raise StopIteration

    if not ui.yesno('Have you checked that your sound system is ' 
     'plugged in, and turned on?'):
        raise StopIteration
    return problem


def check_pulseaudio_running(report, ui):
    ''' Possible outcomes: None if pulseaudio is running, 
        pulseaudio if installed but not running, or
        StopIteration if pulseaudio is not installed. '''
    if subprocess.call(['pgrep', '-u', str(os.getuid()), '-x', 'pulseaudio']) == 0:
        return None 
    if subprocess.call(['pgrep', '-u', 'pulse', '-x', 'pulseaudio']) == 0:
        ui.information('PulseAudio is running as a system-wide daemon.\n'
         'This mode of operation is not supported by Ubuntu.\n'
         'If this is not intentional, ask for help on answers.launchpad.net.')
        raise StopIteration
    try:
        if apport.packaging.get_version('pulseaudio'):
            report['PulseAudioRunning'] = 'no'
            if ui.yesno('PulseAudio seems to have crashed. '
             'Do you wish to report a bug?'):
                return 'pulseaudio'
            raise StopIteration
    except ValueError:
        pass

    ui.information('This troubleshooter currently '
     'only works for the Ubuntu flavors that use PulseAudio.\n'
     'Sorry for the inconvenience.')
    raise StopIteration

def pulseaudio_to_alsa_card(report, ui, pacardname):
    ''' Returns alsa card index '''

    pactlvalues = run_subprocess(report, 'PactlList',  ['pactl', 'list'])
    right_card = 0
    for line in pactlvalues.splitlines():
        if re.match('^\w+ #\d+', line):
            right_card = 0
            if ('Sink' in line) or ('Source' in line):
                right_card = 1
        if pacardname in line and right_card == 1: 
            right_card = 2
        if right_card != 2:
            continue
        s = re.search('alsa\.card = \"(.+)\"', line)
        if s is not None:
            return int(s.groups(1)[0])
    report['PactlListCardNotFound'] = pacardname
    return -1
        

def check_pulseaudio_profile(report, ui, card, problem):
    ''' Returns package, channelmap '''
    # run pactl stat
    pactlvalues = run_subprocess(report, 'PactlStat', ['pactl', 'stat'])

    # parse pa card name and channel map
    pacardname = None
    if problem == 2:
        ss = "Default Source: alsa_input." 
    else:
        ss = "Default Sink: alsa_output."
    for line in pactlvalues.splitlines():
        if line.startswith(ss): 
            dummy1, dummy2, pacardname = line.partition(".")
            dummy1, dummy2, pacardnamefull = line.partition(": ")

    if pacardname is None:
        # something is seriously wrong with pulseaudio if we come here
        report['PactlStat'] = pactlvalues
        return 'pulseaudio', None

    pacardname, dummy2, paprofile = pacardname.rpartition(".")

    # verify user has selected the correct fallback device
    # TODO: pacardname is not in the same format as the alsa card...
    if pulseaudio_to_alsa_card(report, ui, pacardnamefull) != int(card[0]):
        if not ui.yesno('You seem to have configured PulseAudio to use '
         'the "%s" card, while you want output from "%s".\n Please correct '
         'that using pavucontrol or the GNOME volume control. ' 
         'Continue anyway?' % (pacardname, card[2])):
            raise StopIteration

    if problem == 2:
        return None, None

    # check that the profile is correct
    if problem == 0 and (not "stereo" in paprofile):
        if not ui.yesno('You seem to have configured PulseAudio for '
         'surround output (%s).\n Please correct that using pavucontrol '
         'or the GNOME volume control. Continue anyway?' % paprofile):
            raise StopIteration
    if problem == 1 and (not "surround" in paprofile):
        if not ui.yesno("You don't seem to have configured PulseAudio "
         'for surround output (%s).\n Please correct that using pavucontrol '
         'or the GNOME volume control. Continue anyway?' % paprofile):
            raise StopIteration

    if problem == 0:
        return None, 2
    
    # Figure out channel count for speaker test later
    # e g surround-51 => 51 => 5+1 => 6
    dummy1, dummy2, cc = paprofile.rpartition("-")
    cc = int(cc)/10 + int(cc) % 10
    return None, cc

 
def soundcard_query(report, ui):
    ''' Returns a tuple of (package, card)
        card is [index, name, long-name]
        If package is not None, no need to examine further. '''

    # Parse /proc/asound/cards
    alsacards = []
    choices = []
    for card in open('/proc/asound/cards'):
        m = re.search(' (\d+) \[(\w+)\s*\]: (.+)', card)
        if not m is None:
            alsacards.append(m.groups(1))
            choices.append(m.groups(1)[2])

    # query user
    choices = choices + ['Another card not listed above', "I don't know"]
    cardchoice = ui.choice('What is the name of the sound card you are having problems with?', choices)
    if cardchoice is None:
        raise StopIteration
    cardchoice = cardchoice[0]
    
    # if the user does not know, just assume the first sound card...
    if cardchoice == len(alsacards)+1:
        cardchoice = 0 
 
    # if the card was not detected...
    if cardchoice == len(alsacards):
        if ui.yesno('Is your soundcard an external firewire card?'):
            # TODO: better ffado troubleshooting as well...
            if ui.yesno('External firewire cards require manual setup.\n'
             'Documentation is here: https://help.ubuntu.com/community/HowToJACKConfiguration\n'
             'Would you like to continue?'):
                return 'libffado1', None
            raise StopIteration
        # soundcard not detected
        report['SelectedCard'] = 'User specified that soundcard was not detected.'
        return 'alsa-base', None

    report['SelectedCard'] = ' '.join(alsacards[cardchoice])
    return None, alsacards[cardchoice]


def check_volumes(report, ui, card, problem):
    ''' check for mutes '''
    amixer_mon = subprocess.Popen(['amixer', '-c', str(card[0])], 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    mixervalues, mixererr = amixer_mon.communicate()
    if mixererr is not None:
        report['AmixerError'] = ' '.join(mixererr)
    checkthisone = 0
    levels = ""
    mixervalues = mixervalues.splitlines()
    for m in mixervalues:
        s = re.search("\w[^']*'(.+)'", m)
        if not s is None:
            currentcontrol = s.groups(1)[0]
            checkthisone = 0
            if currentcontrol in ['Master', 'PCM', 'Wave', 'Front', 
             'Surround', 'Headphone', 'Speaker']:
                checkthisone = 1
            if problem == 1 and currentcontrol in ['Center', 'LFE', 'Side']:
                checkthisone = 1
        elif checkthisone != 1:
            continue
        if "[off]" in m:
            levels += currentcontrol + ' is muted\n'
        s = re.search("\[(\d+)%\]", m)
        if s is None:
            continue
        level = int(s.groups(1)[0])
        if level >= 70:
            continue
        levels += currentcontrol + ' is at {0}%\n'.format(level)

    if levels != "":
        if not ui.yesno('The following mixer control(s) are set quite low: \n' + levels +
         'Please try to set them higher (e g by running "alsamixer -c' +
         str(card[0]) + '" in a terminal) and see if that solves the problem.\n' 
         'Would you like to continue troubleshooting anyway?\n'):
            raise StopIteration


def speaker_test(report, ui, card, channelcount):
    ''' Returns package if it successfully finds one. '''

    ui.information('Next, a speaker test will be performed. For your safety,\n'
     'if you have headphones on, take them off to avoid damaging your ears.\n'
     'Press OK to hear the test tone. It should alternate between %d channels.'
     % channelcount)
    run_subprocess(report, 'SpeakerTestAlsa', ['pasuspender', '--', 
        'speaker-test', '-l', '3', '-c', str(channelcount), '-b', '100000', 
        '-D', 'plughw:' + str(card[0]), '-t', 'sine']) 
    if not ui.yesno('Were the test tones played back correctly?'):
        return 'alsa-base'

    ui.information('Press OK to hear the second test tone. It should alternate between channels.')
    run_subprocess(report, 'SpeakerTestPulse', ['speaker-test', '-l', '3', 
     '-c', str(channelcount), '-b', '100000', '-D', 'pulse', '-t', 'sine'])
    if not ui.yesno('Were the test tones played back correctly?'):
        return 'pulseaudio'

    report['SpeakerTestPulseTone'] = 'Test tone heard'
    return None

def run_subprocess(report, reportkey, args):
    ''' Helper function to run a subprocess.  
        Returns stdout and writes stderr, if any, to the report. '''
    sub_mon = subprocess.Popen(args,
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    sub_out, sub_err = sub_mon.communicate()
    if sub_err is not None and (len(str(sub_err).strip()) > 0):
        report[reportkey+'Stderr'] = ' '.join(sub_err)
    return sub_out