summaryrefslogtreecommitdiff
path: root/cras/src/server/cras_hfp_info.c
blob: fc407b295b6f3173e9f646ac10b9183f29366c56 (plain)
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>

#include "audio_thread.h"
#include "bluetooth.h"
#include "byte_buffer.h"
#include "cras_hfp_info.h"
#include "cras_hfp_slc.h"
#include "cras_iodev_list.h"
#include "cras_plc.h"
#include "cras_sbc_codec.h"
#include "cras_server_metrics.h"
#include "utlist.h"
#include "packet_status_logger.h"

/* The max buffer size. Note that the actual used size must set to multiple
 * of SCO packet size, and the packet size does not necessarily be equal to
 * MTU. We should keep this as common multiple of possible packet sizes, for
 * example: 48, 60, 64, 128.
 */
#define MAX_HFP_BUF_SIZE_BYTES 28800

/* rate(8kHz) * sample_size(2 bytes) * channels(1) */
#define HFP_BYTE_RATE 16000

/* Per Bluetooth Core v5.0 and HFP 1.7 specification. */
#define MSBC_H2_HEADER_LEN 2
#define MSBC_FRAME_LEN 57
#define MSBC_FRAME_SIZE 59
#define MSBC_CODE_SIZE 240
#define MSBC_SYNC_WORD 0xAD

/* For one mSBC 1 compressed wideband audio channel the HCI packets will
 * be 3 octets of HCI header + 60 octets of data. */
#define MSBC_PKT_SIZE 60

#define H2_HEADER_0 0x01

/* Supported HCI SCO packet sizes. The wideband speech mSBC frame parsing
 * code ties to limited packet size values. Specifically list them out
 * to check against when setting packet size.
 *
 * Temp buffer size should be set to least common multiple of HCI SCO packet
 * size and MSBC_PKT_SIZE for optimizing buffer copy.
 * To add a new supported packet size value, add corresponding entry to the
 * lists, test the read/write msbc code, and fix the code if needed.
 */
static const size_t wbs_supported_packet_size[] = { 60, 24, 0 };
static const size_t wbs_hci_sco_buffer_size[] = { 60, 120, 0 };

/* Second octet of H2 header is composed by 4 bits fixed 0x8 and 4 bits
 * sequence number 0000, 0011, 1100, 1111. */
static const uint8_t h2_header_frames_count[] = { 0x08, 0x38, 0xc8, 0xf8 };

/* Structure to hold variables for a HFP connection. Since HFP supports
 * bi-direction audio, two iodevs should share one hfp_info if they
 * represent two directions of the same HFP headset
 * Members:
 *     fd - The file descriptor for SCO socket.
 *     started - If the hfp_info has started to read/write SCO data.
 *     mtu - The max transmit unit reported from BT adapter.
 *     packet_size - The size of SCO packet to read/write preferred by
 *         adapter, could be different than mtu.
 *     capture_buf - The buffer to hold samples read from SCO socket.
 *     playback_buf - The buffer to hold samples about to write to SCO socket.
 *     msbc_read - mSBC codec to decode input audio in wideband speech mode.
 *     msbc_write - mSBC codec to encode output audio in wideband speech mode.
 *     msbc_plc - PLC component to handle the packet loss of input audio in
 *         wideband speech mode.
 *     msbc_num_out_frames - Number of total written mSBC frames.
 *     msbc_num_in_frames - Number of total read mSBC frames.
 *     msbc_num_lost_frames - Number of total lost mSBC frames.
 *     read_cb - Callback to call when SCO socket can read. It returns the
 *         number of PCM bytes read.
 *     write_cb - Callback to call when SCO socket can write.
 *     write_buf - Temp buffer for writeing HCI SCO packet in wideband.
 *     read_buf - Temp buffer for reading HCI SCO packet in wideband.
 *     input_format_bytes - The audio format bytes for input device. 0 means
 *         there is no input device for the hfp_info.
 *     output_format_bytes - The audio format bytes for output device. 0 means
 *         there is no output device for the hfp_info.
 *     write_wp - Write pointer of write_buf.
 *     write_rp - Read pointer of write_buf.
 *     read_wp - Write pointer of read_buf.
 *     read_rp - Read pointer of read_buf.
 *     read_align_cb - Callback used to align mSBC frame reading with read buf.
 *     msbc_read_current_corrupted - Flag to mark if the current mSBC frame
 *         read is corrupted.
 *     wbs_logger - The logger for packet status in WBS.
 */
struct hfp_info {
	int fd;
	int started;
	unsigned int mtu;
	unsigned int packet_size;
	struct byte_buffer *capture_buf;
	struct byte_buffer *playback_buf;
	struct cras_audio_codec *msbc_read;
	struct cras_audio_codec *msbc_write;
	struct cras_msbc_plc *msbc_plc;
	unsigned int msbc_num_out_frames;
	unsigned int msbc_num_in_frames;
	unsigned int msbc_num_lost_frames;
	int (*read_cb)(struct hfp_info *info);
	int (*write_cb)(struct hfp_info *info);
	uint8_t *write_buf;
	uint8_t *read_buf;
	size_t input_format_bytes;
	size_t output_format_bytes;
	size_t write_wp;
	size_t write_rp;
	size_t read_wp;
	size_t read_rp;
	int (*read_align_cb)(uint8_t *buf);
	bool msbc_read_current_corrupted;
	struct packet_status_logger *wbs_logger;
};

int hfp_info_add_iodev(struct hfp_info *info,
		       enum CRAS_STREAM_DIRECTION direction,
		       struct cras_audio_format *format)
{
	if (direction == CRAS_STREAM_OUTPUT) {
		if (info->output_format_bytes)
			goto invalid;
		info->output_format_bytes = cras_get_format_bytes(format);

		buf_reset(info->playback_buf);
	} else if (direction == CRAS_STREAM_INPUT) {
		if (info->input_format_bytes)
			goto invalid;
		info->input_format_bytes = cras_get_format_bytes(format);

		buf_reset(info->capture_buf);
	}

	return 0;

invalid:
	return -EINVAL;
}

int hfp_info_rm_iodev(struct hfp_info *info,
		      enum CRAS_STREAM_DIRECTION direction)
{
	if (direction == CRAS_STREAM_OUTPUT && info->output_format_bytes) {
		memset(info->playback_buf->bytes, 0,
		       info->playback_buf->used_size);
		info->output_format_bytes = 0;
	} else if (direction == CRAS_STREAM_INPUT && info->input_format_bytes) {
		info->input_format_bytes = 0;
	} else {
		return -EINVAL;
	}

	return 0;
}

int hfp_info_has_iodev(struct hfp_info *info)
{
	return info->output_format_bytes || info->input_format_bytes;
}

void hfp_buf_acquire(struct hfp_info *info,
		     enum CRAS_STREAM_DIRECTION direction, uint8_t **buf,
		     unsigned *count)
{
	size_t format_bytes;
	unsigned int buf_avail;

	if (direction == CRAS_STREAM_OUTPUT && info->output_format_bytes) {
		*buf = buf_write_pointer_size(info->playback_buf, &buf_avail);
		format_bytes = info->output_format_bytes;
	} else if (direction == CRAS_STREAM_INPUT && info->input_format_bytes) {
		*buf = buf_read_pointer_size(info->capture_buf, &buf_avail);
		format_bytes = info->input_format_bytes;
	} else {
		*count = 0;
		return;
	}

	if (*count * format_bytes > buf_avail)
		*count = buf_avail / format_bytes;
}

int hfp_buf_size(struct hfp_info *info, enum CRAS_STREAM_DIRECTION direction)
{
	if (direction == CRAS_STREAM_OUTPUT && info->output_format_bytes)
		return info->playback_buf->used_size /
		       info->output_format_bytes;
	else if (direction == CRAS_STREAM_INPUT && info->input_format_bytes)
		return info->capture_buf->used_size / info->input_format_bytes;
	return 0;
}

void hfp_buf_release(struct hfp_info *info,
		     enum CRAS_STREAM_DIRECTION direction,
		     unsigned written_frames)
{
	if (direction == CRAS_STREAM_OUTPUT && info->output_format_bytes)
		buf_increment_write(info->playback_buf,
				    written_frames * info->output_format_bytes);
	else if (direction == CRAS_STREAM_INPUT && info->input_format_bytes)
		buf_increment_read(info->capture_buf,
				   written_frames * info->input_format_bytes);
	else
		written_frames = 0;
}

int hfp_buf_queued(struct hfp_info *info, enum CRAS_STREAM_DIRECTION direction)
{
	if (direction == CRAS_STREAM_OUTPUT && info->output_format_bytes)
		return buf_queued(info->playback_buf) /
		       info->output_format_bytes;
	else if (direction == CRAS_STREAM_INPUT && info->input_format_bytes)
		return buf_queued(info->capture_buf) / info->input_format_bytes;
	else
		return 0;
}

int hfp_fill_output_with_zeros(struct hfp_info *info, unsigned int nframes)
{
	unsigned int buf_avail;
	unsigned int nbytes;
	uint8_t *buf;
	int i;
	int ret = 0;

	if (info->output_format_bytes) {
		nbytes = nframes * info->output_format_bytes;
		/* Loop twice to make sure ring buffer is filled. */
		for (i = 0; i < 2; i++) {
			buf = buf_write_pointer_size(info->playback_buf,
						     &buf_avail);
			if (buf_avail == 0)
				break;
			buf_avail = MIN(nbytes, buf_avail);
			memset(buf, 0, buf_avail);
			buf_increment_write(info->playback_buf, buf_avail);
			nbytes -= buf_avail;
			ret += buf_avail / info->output_format_bytes;
		}
	}
	return ret;
}

void hfp_force_output_level(struct hfp_info *info, unsigned int level)
{
	if (info->output_format_bytes) {
		level *= info->output_format_bytes;
		level = MIN(level, MAX_HFP_BUF_SIZE_BYTES);
		buf_adjust_readable(info->playback_buf, level);
	}
}

int hfp_write_msbc(struct hfp_info *info)
{
	size_t encoded;
	int err;
	int pcm_encoded;
	unsigned int pcm_avail, to_write;
	uint8_t *samples;
	uint8_t *wp;

	if (info->write_rp + info->packet_size <= info->write_wp)
		goto msbc_send_again;

	/* Make sure there are MSBC_CODE_SIZE bytes to encode. */
	samples = buf_read_pointer_size(info->playback_buf, &pcm_avail);
	if (pcm_avail < MSBC_CODE_SIZE) {
		to_write = MSBC_CODE_SIZE - pcm_avail;
		/*
		 * Size of playback_buf is multiple of MSBC_CODE_SIZE so we
		 * are safe to prepare the buffer by appending some zero bytes.
		 */
		wp = buf_write_pointer_size(info->playback_buf, &pcm_avail);
		memset(wp, 0, to_write);
		buf_increment_write(info->playback_buf, to_write);

		samples = buf_read_pointer_size(info->playback_buf, &pcm_avail);
		if (pcm_avail < MSBC_CODE_SIZE)
			return -EINVAL;
	}

	/* Encode the next MSBC_CODE_SIZE of bytes. */
	wp = info->write_buf + info->write_wp;
	wp[0] = H2_HEADER_0;
	wp[1] = h2_header_frames_count[info->msbc_num_out_frames % 4];
	pcm_encoded = info->msbc_write->encode(
		info->msbc_write, samples, pcm_avail, wp + MSBC_H2_HEADER_LEN,
		MSBC_PKT_SIZE - MSBC_H2_HEADER_LEN, &encoded);
	if (pcm_encoded < 0) {
		syslog(LOG_ERR, "msbc encoding err: %s", strerror(pcm_encoded));
		return pcm_encoded;
	}
	buf_increment_read(info->playback_buf, pcm_encoded);
	pcm_avail -= pcm_encoded;
	info->write_wp += MSBC_PKT_SIZE;
	info->msbc_num_out_frames++;

	if (info->write_rp + info->packet_size > info->write_wp)
		return 0;

msbc_send_again:
	err = send(info->fd, info->write_buf + info->write_rp,
		   info->packet_size, 0);
	if (err < 0) {
		if (errno == EINTR)
			goto msbc_send_again;
		return err;
	}
	if (err != (int)info->packet_size) {
		syslog(LOG_ERR, "Partially write %d bytes for mSBC", err);
		return -1;
	}
	info->write_rp += info->packet_size;
	if (info->write_rp == info->write_wp) {
		info->write_rp = 0;
		info->write_wp = 0;
	}

	return err;
}

int hfp_write(struct hfp_info *info)
{
	int err = 0;
	unsigned to_send;
	uint8_t *samples;

	/* Write something */
	samples = buf_read_pointer_size(info->playback_buf, &to_send);
	if (to_send < info->packet_size)
		return 0;
	to_send = info->packet_size;

send_sample:
	err = send(info->fd, samples, to_send, 0);
	if (err < 0) {
		if (errno == EINTR)
			goto send_sample;

		return err;
	}

	if (err != (int)info->packet_size) {
		syslog(LOG_ERR,
		       "Partially write %d bytes for SCO packet size %u", err,
		       info->packet_size);
		return -1;
	}

	buf_increment_read(info->playback_buf, to_send);

	return err;
}

static int h2_header_get_seq(const uint8_t *p)
{
	int i;
	for (i = 0; i < 4; i++) {
		if (*p == h2_header_frames_count[i])
			return i;
	}
	return -1;
}

/*
 * Extract mSBC frame from SCO socket input bytes, given that the mSBC frame
 * could be lost or corrupted.
 * Args:
 *    input - Pointer to input bytes read from SCO socket.
 *    len - Length of input bytes.
 *    seq_out - To be filled by the sequence number of mSBC packet.
 * Returns:
 *    The starting position of mSBC frame if found.
 */
static const uint8_t *extract_msbc_frame(const uint8_t *input, int len,
					 unsigned int *seq_out)
{
	int rp = 0;
	int seq = -1;
	while (len - rp >= MSBC_FRAME_SIZE) {
		if ((input[rp] != H2_HEADER_0) ||
		    (input[rp + 2] != MSBC_SYNC_WORD)) {
			rp++;
			continue;
		}
		seq = h2_header_get_seq(input + rp + 1);
		if (seq < 0) {
			rp++;
			continue;
		}
		// `seq` is guaranteed to be positive now.
		*seq_out = (unsigned int)seq;
		return input + rp;
	}
	return NULL;
}

/* Log value 0 when packet is received. */
static void log_wbs_packet_received(struct hfp_info *info)
{
	if (info->wbs_logger)
		packet_status_logger_update(info->wbs_logger, 0);
}

/* Log value 1 when packet is lost. */
static void log_wbs_packet_lost(struct hfp_info *info)
{
	if (info->wbs_logger)
		packet_status_logger_update(info->wbs_logger, 1);
}

/*
 * Handle the case when mSBC frame is considered lost.
 * Args:
 *    info - The hfp_info instance holding mSBC codec and PLC objects.
 */
static int handle_packet_loss(struct hfp_info *info)
{
	int decoded;
	unsigned int pcm_avail;
	uint8_t *in_bytes;

	/* It's possible client doesn't consume data causing overrun. In that
	 * case we treat it as one mSBC frame read but dropped. */
	info->msbc_num_in_frames++;
	info->msbc_num_lost_frames++;

	log_wbs_packet_lost(info);

	in_bytes = buf_write_pointer_size(info->capture_buf, &pcm_avail);
	if (pcm_avail < MSBC_CODE_SIZE)
		return 0;

	decoded = cras_msbc_plc_handle_bad_frames(info->msbc_plc,
						  info->msbc_read, in_bytes);
	if (decoded < 0)
		return decoded;

	buf_increment_write(info->capture_buf, decoded);

	return decoded;
}

/* Checks if mSBC frame header aligns with the beginning of buffer. */
static int msbc_frame_align(uint8_t *buf)
{
	if ((buf[0] != H2_HEADER_0) || (buf[2] != MSBC_SYNC_WORD)) {
		syslog(LOG_DEBUG, "Waiting for valid mSBC frame head");
		return 0;
	}
	return 1;
}

int hfp_read_msbc(struct hfp_info *info)
{
	int err = 0;
	unsigned int pcm_avail = 0;
	int decoded;
	size_t pcm_decoded = 0;
	size_t pcm_read = 0;
	uint8_t *capture_buf;
	const uint8_t *frame_head = NULL;
	unsigned int seq;

	struct msghdr msg = { 0 };
	struct iovec iov;
	struct cmsghdr *cmsg;
	const unsigned int control_size = CMSG_SPACE(sizeof(int));
	char control[control_size];
	uint8_t pkt_status;

	memset(control, 0, sizeof(control));
recv_msbc_bytes:
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	iov.iov_base = info->read_buf + info->read_wp;
	iov.iov_len = info->packet_size;
	msg.msg_control = control;
	msg.msg_controllen = control_size;

	err = recvmsg(info->fd, &msg, 0);
	if (err < 0) {
		syslog(LOG_ERR, "HCI SCO packet read err %s", strerror(errno));
		if (errno == EINTR)
			goto recv_msbc_bytes;
		return err;
	}
	/*
	 * Treat return code 0 (socket shutdown) as error here. BT stack
	 * shall send signal to main thread for device disconnection.
	 */
	if (err != (int)info->packet_size) {
		syslog(LOG_ERR, "Partially read %d bytes for mSBC packet", err);
		return -1;
	}

	/* Offset in input data breaks mSBC frame parsing. Discard this packet
	 * until read alignment succeed. */
	if (info->read_align_cb) {
		if (!info->read_align_cb(info->read_buf))
			return 0;
		else
			info->read_align_cb = NULL;
	}
	info->read_wp += err;

	pkt_status = 0;
	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
	     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
		if (cmsg->cmsg_level == SOL_BLUETOOTH &&
		    cmsg->cmsg_type == BT_SCM_PKT_STATUS) {
			size_t len = cmsg->cmsg_len - sizeof(*cmsg);
			memcpy(&pkt_status, CMSG_DATA(cmsg), len);
		}
	}

	/*
	 * HCI SCO packet status flag:
	 * 0x00 - correctly received data.
	 * 0x01 - possibly invalid data.
	 * 0x10 - No data received.
	 * 0x11 - Data partially lost.
	 *
	 * If the latest SCO packet read doesn't cross the boundary of a mSBC
	 * frame, the packet status flag can be used to derive if the current
	 * mSBC frame is corrupted.
	 */
	if (info->read_rp + MSBC_PKT_SIZE >= info->read_wp)
		info->msbc_read_current_corrupted |= (pkt_status > 0);

	/* Read buffer not enough to parse another mSBC frame. */
	if (info->read_rp + MSBC_PKT_SIZE > info->read_wp)
		return 0;

	if (info->msbc_read_current_corrupted) {
		syslog(LOG_DEBUG, "mSBC frame corrputed from packet status");
		info->msbc_read_current_corrupted = 0;
		frame_head = NULL;
	} else {
		frame_head =
			extract_msbc_frame(info->read_buf + info->read_rp,
					   info->read_wp - info->read_rp, &seq);
		if (!frame_head)
			syslog(LOG_DEBUG, "Failed to extract msbc frame");
	}

	/*
	 * Done with parsing the raw bytes just read. If mSBC frame head not
	 * found, we shall handle it as packet loss.
	 */
	info->read_rp += MSBC_PKT_SIZE;
	if (info->read_rp == info->read_wp) {
		info->read_rp = 0;
		info->read_wp = 0;
	}
	if (!frame_head)
		return handle_packet_loss(info);

	/*
	 * Consider packet loss when found discontinuity in sequence number.
	 */
	while (seq != (info->msbc_num_in_frames % 4)) {
		syslog(LOG_DEBUG, "SCO packet seq unmatch");
		err = handle_packet_loss(info);
		if (err < 0)
			return err;
		pcm_read += err;
	}

	/* Check if there's room for more PCM. */
	capture_buf = buf_write_pointer_size(info->capture_buf, &pcm_avail);
	if (pcm_avail < MSBC_CODE_SIZE)
		return pcm_read;

	decoded = info->msbc_read->decode(info->msbc_read,
					  frame_head + MSBC_H2_HEADER_LEN,
					  MSBC_FRAME_LEN, capture_buf,
					  pcm_avail, &pcm_decoded);
	if (decoded < 0) {
		/*
		 * If mSBC frame cannot be decoded, consider this packet is
		 * corrupted and lost.
		 */
		syslog(LOG_ERR, "mSBC decode failed");
		err = handle_packet_loss(info);
		if (err < 0)
			return err;
		pcm_read += err;
	} else {
		/* Good mSBC frame decoded. */
		log_wbs_packet_received(info);
		buf_increment_write(info->capture_buf, pcm_decoded);
		info->msbc_num_in_frames++;
		cras_msbc_plc_handle_good_frames(info->msbc_plc, capture_buf,
						 capture_buf);
		pcm_read += pcm_decoded;
	}
	return pcm_read;
}

int hfp_read(struct hfp_info *info)
{
	int err = 0;
	unsigned to_read;
	uint8_t *capture_buf;

	capture_buf = buf_write_pointer_size(info->capture_buf, &to_read);

	if (to_read < info->packet_size)
		return 0;
	to_read = info->packet_size;

recv_sample:
	err = recv(info->fd, capture_buf, to_read, 0);
	if (err < 0) {
		syslog(LOG_ERR, "Read error %s", strerror(errno));
		if (errno == EINTR)
			goto recv_sample;

		return err;
	}

	if (err != (int)info->packet_size) {
		/* Allow the SCO packet size be modified from the default MTU
		 * value to the size of SCO data we first read. This is for
		 * some adapters who prefers a different value than MTU for
		 * transmitting SCO packet.
		 */
		if (err && (info->packet_size == info->mtu)) {
			info->packet_size = err;
		} else {
			syslog(LOG_ERR,
			       "Partially read %d bytes for %u size SCO packet",
			       err, info->packet_size);
			return -1;
		}
	}

	buf_increment_write(info->capture_buf, err);

	return err;
}

/* Callback function to handle sample read and write.
 * Note that we poll the SCO socket for read sample, since it reflects
 * there is actual some sample to read while the socket always reports
 * writable even when device buffer is full.
 * The strategy is to synchronize read & write operations:
 * 1. Read one chunk of MTU bytes of data.
 * 2. When input device not attached, ignore the data just read.
 * 3. When output device attached, write one chunk of MTU bytes of data.
 */
static int hfp_info_callback(void *arg, int revents)
{
	struct hfp_info *info = (struct hfp_info *)arg;
	int err = 0;

	if (!info->started)
		return 0;

	/* Allow last read before handling error or hang-up events. */
	if (revents & POLLIN) {
		err = info->read_cb(info);
		if (err < 0) {
			syslog(LOG_ERR, "Read error");
			goto read_write_error;
		}
	}
	/* Ignore the bytes just read if input dev not in present */
	if (!info->input_format_bytes)
		buf_increment_read(info->capture_buf, err);

	if (revents & (POLLERR | POLLHUP)) {
		syslog(LOG_ERR, "Error polling SCO socket, revent %d", revents);
		goto read_write_error;
	}

	/* Without output stream's presence, we shall still send zero packets
	 * to HF. This is required for some HF devices to start sending non-zero
	 * data to AG.
	 */
	if (!info->output_format_bytes)
		buf_increment_write(info->playback_buf,
				    info->msbc_write ? err : info->packet_size);

	err = info->write_cb(info);
	if (err < 0) {
		syslog(LOG_ERR, "Write error");
		goto read_write_error;
	}

	return 0;

read_write_error:
	/*
	 * This callback is executing in audio thread, so it's safe to
	 * unregister itself by audio_thread_rm_callback().
	 */
	audio_thread_rm_callback(info->fd);
	close(info->fd);
	info->fd = 0;
	info->started = 0;

	return 0;
}

struct hfp_info *hfp_info_create()
{
	struct hfp_info *info;
	info = (struct hfp_info *)calloc(1, sizeof(*info));
	if (!info)
		goto error;

	info->capture_buf = byte_buffer_create(MAX_HFP_BUF_SIZE_BYTES);
	if (!info->capture_buf)
		goto error;

	info->playback_buf = byte_buffer_create(MAX_HFP_BUF_SIZE_BYTES);
	if (!info->playback_buf)
		goto error;

	return info;

error:
	if (info) {
		if (info->capture_buf)
			byte_buffer_destroy(&info->capture_buf);
		if (info->playback_buf)
			byte_buffer_destroy(&info->playback_buf);
		free(info);
	}
	return NULL;
}

void hfp_info_set_wbs_logger(struct hfp_info *info,
			     struct packet_status_logger *wbs_logger)
{
	info->wbs_logger = wbs_logger;
}

int hfp_info_running(struct hfp_info *info)
{
	return info->started;
}

int hfp_info_start(int fd, unsigned int mtu, int codec, struct hfp_info *info)
{
	info->fd = fd;
	info->mtu = mtu;

	/* Initialize to MTU, it may change when actually read the socket. */
	info->packet_size = mtu;
	buf_reset(info->playback_buf);
	buf_reset(info->capture_buf);

	if (codec == HFP_CODEC_ID_MSBC) {
		int i;
		for (i = 0; wbs_supported_packet_size[i] != 0; i++) {
			if (info->packet_size == wbs_supported_packet_size[i])
				break;
		}
		/* In case of unsupported value, error log and fallback to
		 * MSBC_PKT_SIZE(60). */
		if (wbs_supported_packet_size[i] == 0) {
			syslog(LOG_ERR, "Unsupported packet size %u",
			       info->packet_size);
			i = 0;
		}
		info->packet_size = wbs_supported_packet_size[i];
		info->write_buf = (uint8_t *)malloc(wbs_hci_sco_buffer_size[i]);
		info->read_buf = (uint8_t *)malloc(wbs_hci_sco_buffer_size[i]);

		info->write_cb = hfp_write_msbc;
		info->read_cb = hfp_read_msbc;
		info->msbc_read = cras_msbc_codec_create();
		info->msbc_write = cras_msbc_codec_create();
		info->msbc_plc = cras_msbc_plc_create();

		packet_status_logger_init(info->wbs_logger);
	} else {
		info->write_cb = hfp_write;
		info->read_cb = hfp_read;
	}

	audio_thread_add_events_callback(info->fd, hfp_info_callback, info,
					 POLLIN | POLLERR | POLLHUP);

	info->started = 1;
	info->msbc_num_out_frames = 0;
	info->msbc_num_in_frames = 0;
	info->msbc_num_lost_frames = 0;
	info->write_rp = 0;
	info->write_wp = 0;
	info->read_rp = 0;
	info->read_wp = 0;

	/* Mark as aligned if packet size equals to MSBC_PKT_SIZE. */
	info->read_align_cb =
		(info->packet_size == MSBC_PKT_SIZE) ? NULL : msbc_frame_align;
	info->msbc_read_current_corrupted = 0;

	return 0;
}

int hfp_info_stop(struct hfp_info *info)
{
	if (!info->started)
		return 0;

	audio_thread_rm_callback_sync(cras_iodev_list_get_audio_thread(),
				      info->fd);

	close(info->fd);
	info->fd = 0;
	info->started = 0;

	/* Unset the write/read callbacks. */
	info->write_cb = NULL;
	info->read_cb = NULL;

	if (info->write_buf)
		free(info->write_buf);
	if (info->read_buf)
		free(info->read_buf);

	if (info->msbc_read) {
		cras_sbc_codec_destroy(info->msbc_read);
		info->msbc_read = NULL;
	}
	if (info->msbc_write) {
		cras_sbc_codec_destroy(info->msbc_write);
		info->msbc_write = NULL;
	}
	if (info->msbc_plc) {
		cras_msbc_plc_destroy(info->msbc_plc);
		info->msbc_plc = NULL;
	}

	if (info->msbc_num_in_frames) {
		cras_server_metrics_hfp_packet_loss(
			(float)info->msbc_num_lost_frames /
			info->msbc_num_in_frames);
	}

	return 0;
}

void hfp_info_destroy(struct hfp_info *info)
{
	if (info->capture_buf)
		byte_buffer_destroy(&info->capture_buf);

	if (info->playback_buf)
		byte_buffer_destroy(&info->playback_buf);

	free(info);
}