aboutsummaryrefslogtreecommitdiff
path: root/components/V4L2EncodeComponent.cpp
blob: f9d735d7a75e29ac96d290a93c95f5d3e957da04 (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
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//#define LOG_NDEBUG 0
#define LOG_TAG "V4L2EncodeComponent"

#include <v4l2_codec2/components/V4L2EncodeComponent.h>

#include <inttypes.h>

#include <algorithm>
#include <utility>

#include <C2AllocatorGralloc.h>
#include <C2PlatformSupport.h>
#include <C2Work.h>
#include <android/hardware/graphics/common/1.0/types.h>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <cutils/properties.h>
#include <log/log.h>
#include <media/stagefright/MediaDefs.h>
#include <ui/GraphicBuffer.h>
#include <ui/Size.h>

#include <v4l2_codec2/common/Common.h>
#include <v4l2_codec2/common/EncodeHelpers.h>
#include <v4l2_codec2/common/FormatConverter.h>
#include <v4l2_codec2/common/VideoPixelFormat.h>
#include <v4l2_codec2/components/BitstreamBuffer.h>
#include <v4l2_codec2/components/V4L2EncodeInterface.h>
#include <v4l2_codec2/components/V4L2Encoder.h>

using android::hardware::graphics::common::V1_0::BufferUsage;

namespace android {

namespace {

const VideoPixelFormat kInputPixelFormat = VideoPixelFormat::NV12;

// The peak bitrate in function of the target bitrate, used when the bitrate mode is VBR.
constexpr uint32_t kPeakBitrateMultiplier = 2u;

// Get the video frame layout from the specified |inputBlock|.
// TODO(dstaessens): Clean up code extracting layout from a C2GraphicBlock.
std::optional<std::vector<VideoFramePlane>> getVideoFrameLayout(const C2ConstGraphicBlock& block,
                                                                VideoPixelFormat* format) {
    ALOGV("%s()", __func__);

    // Get the C2PlanarLayout from the graphics block. The C2GraphicView returned by block.map()
    // needs to be released before calling getGraphicBlockInfo(), or the lockYCbCr() call will block
    // Indefinitely.
    C2PlanarLayout layout = block.map().get().layout();

    // The above layout() cannot fill layout information and memset 0 instead if the input format is
    // IMPLEMENTATION_DEFINED and its backed format is RGB. We fill the layout by using
    // ImplDefinedToRGBXMap in the case.
    if (layout.type == C2PlanarLayout::TYPE_UNKNOWN) {
        std::unique_ptr<ImplDefinedToRGBXMap> idMap = ImplDefinedToRGBXMap::Create(block);
        if (idMap == nullptr) {
            ALOGE("Unable to parse RGBX_8888 from IMPLEMENTATION_DEFINED");
            return std::nullopt;
        }
        layout.type = C2PlanarLayout::TYPE_RGB;
        // These parameters would be used in TYPE_GRB case below.
        layout.numPlanes = 3;   // same value as in C2AllocationGralloc::map()
        layout.rootPlanes = 1;  // same value as in C2AllocationGralloc::map()
        layout.planes[C2PlanarLayout::PLANE_R].offset = idMap->offset();
        layout.planes[C2PlanarLayout::PLANE_R].rowInc = idMap->rowInc();
    }

    std::vector<uint32_t> offsets(layout.numPlanes, 0u);
    std::vector<uint32_t> strides(layout.numPlanes, 0u);
    switch (layout.type) {
    case C2PlanarLayout::TYPE_YUV: {
        android_ycbcr ycbcr = getGraphicBlockInfo(block);
        offsets[C2PlanarLayout::PLANE_Y] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.y));
        offsets[C2PlanarLayout::PLANE_U] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.cb));
        offsets[C2PlanarLayout::PLANE_V] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.cr));
        strides[C2PlanarLayout::PLANE_Y] = static_cast<uint32_t>(ycbcr.ystride);
        strides[C2PlanarLayout::PLANE_U] = static_cast<uint32_t>(ycbcr.cstride);
        strides[C2PlanarLayout::PLANE_V] = static_cast<uint32_t>(ycbcr.cstride);

        bool crcb = false;
        if (offsets[C2PlanarLayout::PLANE_U] > offsets[C2PlanarLayout::PLANE_V]) {
            // Swap offsets, no need to swap strides as they are identical for both chroma planes.
            std::swap(offsets[C2PlanarLayout::PLANE_U], offsets[C2PlanarLayout::PLANE_V]);
            crcb = true;
        }

        bool semiplanar = false;
        if (ycbcr.chroma_step >
            offsets[C2PlanarLayout::PLANE_V] - offsets[C2PlanarLayout::PLANE_U]) {
            semiplanar = true;
        }

        if (!crcb && !semiplanar) {
            *format = VideoPixelFormat::I420;
        } else if (!crcb && semiplanar) {
            *format = VideoPixelFormat::NV12;
        } else if (crcb && !semiplanar) {
            // HACK: pretend YV12 is I420 now since VEA only accepts I420. (YV12 will be used
            //       for input byte-buffer mode).
            // TODO(dstaessens): Is this hack still necessary now we're not using the VEA directly?
            //format = VideoPixelFormat::YV12;
            *format = VideoPixelFormat::I420;
        } else {
            *format = VideoPixelFormat::NV21;
        }
        break;
    }
    case C2PlanarLayout::TYPE_RGB: {
        offsets[C2PlanarLayout::PLANE_R] = layout.planes[C2PlanarLayout::PLANE_R].offset;
        strides[C2PlanarLayout::PLANE_R] =
                static_cast<uint32_t>(layout.planes[C2PlanarLayout::PLANE_R].rowInc);
        *format = VideoPixelFormat::ARGB;
        break;
    }
    default:
        ALOGW("Unknown layout type: %u", static_cast<uint32_t>(layout.type));
        return std::nullopt;
    }

    std::vector<VideoFramePlane> planes;
    for (uint32_t i = 0; i < layout.rootPlanes; ++i) {
        // The mSize field is not used in our case, so we can safely set it to zero.
        planes.push_back({strides[i], offsets[i], 0});
    }
    return planes;
}

// Get the video frame stride for the specified |format| and |size|.
std::optional<uint32_t> getVideoFrameStride(VideoPixelFormat format, ui::Size size) {
    // Fetch a graphic block from the pool to determine the stride.
    std::shared_ptr<C2BlockPool> pool;
    c2_status_t status = GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool);
    if (status != C2_OK) {
        ALOGE("Failed to get basic graphic block pool (err=%d)", status);
        return std::nullopt;
    }

    // Android HAL format doesn't have I420, we use YV12 instead and swap the U and V planes when
    // converting to NV12. YCBCR_420_888 will allocate NV12 by minigbm.
    HalPixelFormat halFormat = (format == VideoPixelFormat::I420) ? HalPixelFormat::YV12
                                                                  : HalPixelFormat::YCBCR_420_888;

    std::shared_ptr<C2GraphicBlock> block;
    status = pool->fetchGraphicBlock(size.width, size.height, static_cast<uint32_t>(halFormat),
                                     C2MemoryUsage(C2MemoryUsage::CPU_READ), &block);
    if (status != C2_OK) {
        ALOGE("Failed to fetch graphic block (err=%d)", status);
        return std::nullopt;
    }

    const C2ConstGraphicBlock constBlock = block->share(C2Rect(size.width, size.height), C2Fence());
    VideoPixelFormat pixelFormat;
    std::optional<std::vector<VideoFramePlane>> planes =
            getVideoFrameLayout(constBlock, &pixelFormat);
    if (!planes || planes.value().empty()) {
        ALOGE("Failed to get video frame layout from block");
        return std::nullopt;
    }

    return planes.value()[0].mStride;
}

// Create an input frame from the specified graphic block.
std::unique_ptr<V4L2Encoder::InputFrame> CreateInputFrame(const C2ConstGraphicBlock& block,
                                                          uint64_t index, int64_t timestamp) {
    VideoPixelFormat format;
    std::optional<std::vector<VideoFramePlane>> planes = getVideoFrameLayout(block, &format);
    if (!planes) {
        ALOGE("Failed to get input block's layout");
        return nullptr;
    }

    std::vector<int> fds;
    const C2Handle* const handle = block.handle();
    for (int i = 0; i < handle->numFds; i++) {
        fds.emplace_back(handle->data[i]);
    }

    return std::make_unique<V4L2Encoder::InputFrame>(std::move(fds), std::move(planes.value()),
                                                     format, index, timestamp);
}

// Check whether the specified |profile| is an H.264 profile.
bool IsH264Profile(C2Config::profile_t profile) {
    return (profile >= C2Config::PROFILE_AVC_BASELINE &&
            profile <= C2Config::PROFILE_AVC_ENHANCED_MULTIVIEW_DEPTH_HIGH);
}

}  // namespace

// static
std::atomic<int32_t> V4L2EncodeComponent::sConcurrentInstances = 0;

// static
std::shared_ptr<C2Component> V4L2EncodeComponent::create(
        C2String name, c2_node_id_t id, std::shared_ptr<C2ReflectorHelper> helper,
        C2ComponentFactory::ComponentDeleter deleter) {
    ALOGV("%s(%s)", __func__, name.c_str());

    static const int32_t kMaxConcurrentInstances =
            property_get_int32("ro.vendor.v4l2_codec2.encode_concurrent_instances", -1);

    static std::mutex mutex;
    std::lock_guard<std::mutex> lock(mutex);
    if (kMaxConcurrentInstances >= 0 && sConcurrentInstances.load() >= kMaxConcurrentInstances) {
        ALOGW("Cannot create additional encoder, maximum number of instances reached: %d",
              kMaxConcurrentInstances);
        return nullptr;
    }

    auto interface = std::make_shared<V4L2EncodeInterface>(name, std::move(helper));
    if (interface->status() != C2_OK) {
        ALOGE("Component interface initialization failed (error code %d)", interface->status());
        return nullptr;
    }

    return std::shared_ptr<C2Component>(new V4L2EncodeComponent(name, id, std::move(interface)),
                                        deleter);
}

V4L2EncodeComponent::V4L2EncodeComponent(C2String name, c2_node_id_t id,
                                         std::shared_ptr<V4L2EncodeInterface> interface)
      : mName(name),
        mId(id),
        mInterface(std::move(interface)),
        mComponentState(ComponentState::LOADED) {
    ALOGV("%s(%s)", __func__, name.c_str());

    sConcurrentInstances.fetch_add(1, std::memory_order_relaxed);
}

V4L2EncodeComponent::~V4L2EncodeComponent() {
    ALOGV("%s()", __func__);

    // Stop encoder thread and invalidate pointers if component wasn't stopped before destroying.
    if (mEncoderThread.IsRunning()) {
        mEncoderTaskRunner->PostTask(
                FROM_HERE, ::base::BindOnce(
                                   [](::base::WeakPtrFactory<V4L2EncodeComponent>* weakPtrFactory) {
                                       weakPtrFactory->InvalidateWeakPtrs();
                                   },
                                   &mWeakThisFactory));
        mEncoderThread.Stop();
    }

    sConcurrentInstances.fetch_sub(1, std::memory_order_relaxed);
    ALOGV("%s(): done", __func__);
}

c2_status_t V4L2EncodeComponent::start() {
    ALOGV("%s()", __func__);

    // Lock while starting, to synchronize start/stop/reset/release calls.
    std::lock_guard<std::mutex> lock(mComponentLock);

    // According to the specification start() should only be called in the LOADED state.
    if (mComponentState != ComponentState::LOADED) {
        return C2_BAD_STATE;
    }

    if (!mEncoderThread.Start()) {
        ALOGE("Failed to start encoder thread");
        return C2_CORRUPTED;
    }
    mEncoderTaskRunner = mEncoderThread.task_runner();
    mWeakThis = mWeakThisFactory.GetWeakPtr();

    // Initialize the encoder on the encoder thread.
    ::base::WaitableEvent done;
    bool success = false;
    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::Bind(&V4L2EncodeComponent::startTask, mWeakThis, &success, &done));
    done.Wait();

    if (!success) {
        ALOGE("Failed to initialize encoder");
        return C2_CORRUPTED;
    }

    setComponentState(ComponentState::RUNNING);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::stop() {
    ALOGV("%s()", __func__);

    // Lock while stopping, to synchronize start/stop/reset/release calls.
    std::lock_guard<std::mutex> lock(mComponentLock);

    if (mComponentState != ComponentState::RUNNING && mComponentState != ComponentState::ERROR) {
        return C2_BAD_STATE;
    }

    // Return immediately if the component is already stopped.
    if (!mEncoderThread.IsRunning()) {
        return C2_OK;
    }

    // Wait for the component to stop.
    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::stopTask, mWeakThis, &done));
    done.Wait();
    mEncoderThread.Stop();

    setComponentState(ComponentState::LOADED);

    ALOGV("%s() - done", __func__);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::reset() {
    ALOGV("%s()", __func__);

    // The interface specification says: "This method MUST be supported in all (including tripped)
    // states other than released".
    if (mComponentState == ComponentState::UNLOADED) {
        return C2_BAD_STATE;
    }

    // TODO(dstaessens): Reset the component's interface to default values.
    stop();

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::release() {
    ALOGV("%s()", __func__);

    // The interface specification says: "This method MUST be supported in stopped state.", but the
    // release method seems to be called in other states as well.
    reset();

    setComponentState(ComponentState::UNLOADED);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::queue_nb(std::list<std::unique_ptr<C2Work>>* const items) {
    ALOGV("%s()", __func__);

    if (mComponentState != ComponentState::RUNNING) {
        ALOGE("Trying to queue work item while component is not running");
        return C2_BAD_STATE;
    }

    while (!items->empty()) {
        mEncoderTaskRunner->PostTask(FROM_HERE,
                                     ::base::BindOnce(&V4L2EncodeComponent::queueTask, mWeakThis,
                                                      std::move(items->front())));
        items->pop_front();
    }

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::drain_nb(drain_mode_t mode) {
    ALOGV("%s()", __func__);

    if (mode == DRAIN_CHAIN) {
        return C2_OMITTED;  // Tunneling is not supported for now.
    }

    if (mComponentState != ComponentState::RUNNING) {
        return C2_BAD_STATE;
    }

    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::drainTask, mWeakThis, mode));
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::flush_sm(flush_mode_t mode,
                                          std::list<std::unique_ptr<C2Work>>* const flushedWork) {
    ALOGV("%s()", __func__);

    if (mode != FLUSH_COMPONENT) {
        return C2_OMITTED;  // Tunneling is not supported by now
    }

    if (mComponentState != ComponentState::RUNNING) {
        return C2_BAD_STATE;
    }

    // Work that can be immediately discarded should be returned in |flushedWork|. This method may
    // be momentarily blocking but must return within 5ms, which should give us enough time to
    // immediately abandon all non-started work on the encoder thread. We can return all work that
    // can't be immediately discarded using onWorkDone() later.
    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::flushTask,
                                                             mWeakThis, &done, flushedWork));
    done.Wait();

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::announce_nb(const std::vector<C2WorkOutline>& items) {
    return C2_OMITTED;  // Tunneling is not supported by now
}

c2_status_t V4L2EncodeComponent::setListener_vb(const std::shared_ptr<Listener>& listener,
                                                c2_blocking_t mayBlock) {
    ALOG_ASSERT(mComponentState != ComponentState::UNLOADED);

    // Lock so we're sure the component isn't currently starting or stopping.
    std::lock_guard<std::mutex> lock(mComponentLock);

    // If the encoder thread is not running it's safe to update the listener directly.
    if (!mEncoderThread.IsRunning()) {
        mListener = listener;
        return C2_OK;
    }

    // The listener should be updated before exiting this function. If called while the component is
    // currently running we should be allowed to block, as we can only change the listener on the
    // encoder thread.
    ALOG_ASSERT(mayBlock == c2_blocking_t::C2_MAY_BLOCK);

    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::setListenerTask,
                                                             mWeakThis, listener, &done));
    done.Wait();

    return C2_OK;
}

std::shared_ptr<C2ComponentInterface> V4L2EncodeComponent::intf() {
    return std::make_shared<SimpleInterface<V4L2EncodeInterface>>(mName.c_str(), mId, mInterface);
}

void V4L2EncodeComponent::startTask(bool* success, ::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    *success = initializeEncoder();
    done->Signal();
}

void V4L2EncodeComponent::stopTask(::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Flushing the encoder will abort all pending work.
    flush();

    mInputFormatConverter.reset();

    mEncoder.reset();
    mOutputBlockPool.reset();

    // Invalidate all weak pointers so no more functions will be executed on the encoder thread.
    mWeakThisFactory.InvalidateWeakPtrs();

    done->Signal();
}

void V4L2EncodeComponent::queueTask(std::unique_ptr<C2Work> work) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoder);

    // Currently only a single worklet per work item is supported. An input buffer should always be
    // supplied unless this is a drain or CSD request.
    ALOG_ASSERT(work->input.buffers.size() <= 1u && work->worklets.size() == 1u);

    // Set the default values for the output worklet.
    work->worklets.front()->output.flags = static_cast<C2FrameData::flags_t>(0);
    work->worklets.front()->output.buffers.clear();
    work->worklets.front()->output.ordinal = work->input.ordinal;

    uint64_t index = work->input.ordinal.frameIndex.peeku();
    int64_t timestamp = static_cast<int64_t>(work->input.ordinal.timestamp.peeku());
    bool endOfStream = work->input.flags & C2FrameData::FLAG_END_OF_STREAM;
    ALOGV("Queuing next encode (index: %" PRIu64 ", timestamp: %" PRId64 ", EOS: %d)", index,
          timestamp, endOfStream);

    // The codec 2.0 framework might queue an empty CSD request, but this is currently not
    // supported. We will return the CSD with the first encoded buffer work.
    if (work->input.buffers.empty() && !endOfStream) {
        ALOGV("Discarding empty CSD request");
        reportWork(std::move(work));
        return;
    }

    // By the time we get an input buffer, the output block pool should be configured.
    if (!mOutputBlockPool && !getBlockPool()) {
        reportError(C2_CORRUPTED);
        return;
    }

    // If conversion is required but no free buffers are available we queue the work item.
    if (mInputFormatConverter && !mInputFormatConverter->isReady()) {
        ALOGV("Input format convertor ran out of buffers");
        mInputConverterQueue.push(std::move(work));
        return;
    }

    // If we have data to encode send it to the encoder. If conversion is required we will first
    // convert the data to the requested pixel format.
    if (!work->input.buffers.empty()) {
        C2ConstGraphicBlock inputBlock =
                work->input.buffers.front()->data().graphicBlocks().front();
        if (mInputFormatConverter) {
            ALOGV("Converting input block (index: %" PRIu64 ")", index);
            c2_status_t status = C2_CORRUPTED;
            inputBlock = mInputFormatConverter->convertBlock(index, inputBlock, &status);
            if (status != C2_OK) {
                ALOGE("Failed to convert input block (index: %" PRIu64 ")", index);
                reportError(status);
                return;
            }
        }
        if (!encode(inputBlock, index, timestamp)) {
            return;
        }
    }

    mWorkQueue.push_back(std::move(work));
    if (endOfStream) {
        mEncoder->drain();
    }
}

void V4L2EncodeComponent::drainTask(drain_mode_t /*drainMode*/) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // We can only start draining if all work has been queued in the encoder, so we mark the last
    // item waiting for conversion as EOS if required.
    if (!mInputConverterQueue.empty()) {
        C2Work* work = mInputConverterQueue.back().get();
        work->input.flags = static_cast<C2FrameData::flags_t>(work->input.flags |
                                                              C2FrameData::FLAG_END_OF_STREAM);
        return;
    }

    // Mark the last item in the output work queue as EOS, so we will only report it as finished
    // after draining has completed.
    if (!mWorkQueue.empty()) {
        ALOGV("Starting drain and marking last item in output work queue as EOS");
        C2Work* work = mWorkQueue.back().get();
        work->input.flags = static_cast<C2FrameData::flags_t>(work->input.flags |
                                                              C2FrameData::FLAG_END_OF_STREAM);
        mEncoder->drain();
    }
}

void V4L2EncodeComponent::onDrainDone(bool success) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mWorkQueue.empty());

    if (!success) {
        ALOGE("draining the encoder failed");
        reportError(C2_CORRUPTED);
        return;
    }

    // Find the first work item marked as EOS. This might not be the first item in the queue, as
    // previous buffers in the queue might still be waiting for their associated input buffers.
    auto it = std::find_if(
            mWorkQueue.cbegin(), mWorkQueue.cend(), [](const std::unique_ptr<C2Work>& work) {
                return ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
                        !(work->worklets.back()->output.flags & C2FrameData::FLAG_END_OF_STREAM));
            });
    if (it == mWorkQueue.end()) {
        ALOGW("No EOS work item found in queue");
        return;
    }

    // Mark the item in the output work queue as EOS done.
    C2Work* eosWork = it->get();
    eosWork->worklets.back()->output.flags = C2FrameData::FLAG_END_OF_STREAM;

    // Draining is done which means all buffers on the device output queue have been returned, but
    // not all buffers on the device input queue might have been returned yet.
    if ((eosWork != mWorkQueue.front().get()) || !isWorkDone(*eosWork)) {
        ALOGV("Draining done, waiting for input buffers to be returned");
        return;
    }

    ALOGV("Draining done");
    reportWork(std::move(mWorkQueue.front()));
    mWorkQueue.pop_front();
}

void V4L2EncodeComponent::flushTask(::base::WaitableEvent* done,
                                    std::list<std::unique_ptr<C2Work>>* const flushedWork) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Move all work that can immediately be aborted to flushedWork, and notify the caller.
    if (flushedWork) {
        while (!mInputConverterQueue.empty()) {
            std::unique_ptr<C2Work> work = std::move(mInputConverterQueue.front());
            work->input.buffers.clear();
            flushedWork->push_back(std::move(work));
            mInputConverterQueue.pop();
        }
    }
    done->Signal();

    flush();
}

void V4L2EncodeComponent::setListenerTask(const std::shared_ptr<Listener>& listener,
                                          ::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    mListener = listener;
    done->Signal();
}

bool V4L2EncodeComponent::initializeEncoder() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mInputFormatConverter);
    ALOG_ASSERT(!mEncoder);

    mLastFrameTime = std::nullopt;

    // Get the requested profile and level.
    C2Config::profile_t outputProfile = mInterface->getOutputProfile();

    // CSD only needs to be extracted when using an H.264 profile.
    mExtractCSD = IsH264Profile(outputProfile);

    std::optional<uint8_t> h264Level;
    if (IsH264Profile(outputProfile)) {
        h264Level = c2LevelToV4L2Level(mInterface->getOutputLevel());
    }

    // Get the stride used by the C2 framework, as this might be different from the stride used by
    // the V4L2 encoder.
    std::optional<uint32_t> stride =
            getVideoFrameStride(kInputPixelFormat, mInterface->getInputVisibleSize());
    if (!stride) {
        ALOGE("Failed to get video frame stride");
        reportError(C2_CORRUPTED);
        return false;
    }

    // Get the requested bitrate mode and bitrate. The C2 framework doesn't offer a parameter to
    // configure the peak bitrate, so we use a multiple of the target bitrate.
    mBitrateMode = mInterface->getBitrateMode();
    if (property_get_bool("persist.vendor.v4l2_codec2.disable_vbr", false)) {
        // NOTE: This is a workaround for b/235771157.
        ALOGW("VBR is disabled on this device");
        mBitrateMode = C2Config::BITRATE_CONST;
    }

    mBitrate = mInterface->getBitrate();

    mEncoder = V4L2Encoder::create(
            outputProfile, h264Level, mInterface->getInputVisibleSize(), *stride,
            mInterface->getKeyFramePeriod(), mBitrateMode, mBitrate,
            mBitrate * kPeakBitrateMultiplier,
            ::base::BindRepeating(&V4L2EncodeComponent::fetchOutputBlock, mWeakThis),
            ::base::BindRepeating(&V4L2EncodeComponent::onInputBufferDone, mWeakThis),
            ::base::BindRepeating(&V4L2EncodeComponent::onOutputBufferDone, mWeakThis),
            ::base::BindRepeating(&V4L2EncodeComponent::onDrainDone, mWeakThis),
            ::base::BindRepeating(&V4L2EncodeComponent::reportError, mWeakThis, C2_CORRUPTED),
            mEncoderTaskRunner);
    if (!mEncoder) {
        ALOGE("Failed to create V4L2Encoder (profile: %s)", profileToString(outputProfile));
        return false;
    }

    // Add an input format convertor if the device doesn't support the requested input format.
    ALOGV("Creating input format convertor (%s)",
          videoPixelFormatToString(mEncoder->inputFormat()).c_str());
    mInputFormatConverter =
            FormatConverter::Create(mEncoder->inputFormat(), mEncoder->visibleSize(),
                                    V4L2Encoder::kInputBufferCount, mEncoder->codedSize());
    if (!mInputFormatConverter) {
        ALOGE("Failed to created input format convertor");
        return false;
    }

    return true;
}

bool V4L2EncodeComponent::updateEncodingParameters() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Ask device to change bitrate if it's different from the currently configured bitrate. The C2
    // framework doesn't offer a parameter to configure the peak bitrate, so we'll use a multiple of
    // the target bitrate here. The peak bitrate is only used if the bitrate mode is set to VBR.
    uint32_t bitrate = mInterface->getBitrate();
    if (mBitrate != bitrate) {
        ALOG_ASSERT(bitrate > 0u);
        ALOGV("Setting bitrate to %u", bitrate);
        if (!mEncoder->setBitrate(bitrate)) {
            reportError(C2_CORRUPTED);
            return false;
        }
        mBitrate = bitrate;

        if (mBitrateMode == C2Config::BITRATE_VARIABLE) {
            ALOGV("Setting peak bitrate to %u", bitrate * kPeakBitrateMultiplier);
            // TODO(b/190336806): Our stack doesn't support dynamic peak bitrate changes yet, ignore
            // errors for now.
            mEncoder->setPeakBitrate(bitrate * kPeakBitrateMultiplier);
        }
    }

    // Ask device to change framerate if it's different from the currently configured framerate.
    uint32_t framerate = static_cast<uint32_t>(std::round(mInterface->getFramerate()));
    if (mFramerate != framerate) {
        ALOG_ASSERT(framerate > 0u);
        ALOGV("Setting framerate to %u", framerate);
        if (!mEncoder->setFramerate(framerate)) {
            ALOGE("Requesting framerate change failed");
            reportError(C2_CORRUPTED);
            return false;
        }
        mFramerate = framerate;
    }

    // Check whether an explicit key frame was requested, if so reset the key frame counter to
    // immediately request a key frame.
    C2StreamRequestSyncFrameTuning::output requestKeyFrame;
    c2_status_t status = mInterface->query({&requestKeyFrame}, {}, C2_DONT_BLOCK, nullptr);
    if (status != C2_OK) {
        ALOGE("Failed to query interface for key frame request (error code: %d)", status);
        reportError(status);
        return false;
    }
    if (requestKeyFrame.value == C2_TRUE) {
        mEncoder->requestKeyframe();
        requestKeyFrame.value = C2_FALSE;
        std::vector<std::unique_ptr<C2SettingResult>> failures;
        status = mInterface->config({&requestKeyFrame}, C2_MAY_BLOCK, &failures);
        if (status != C2_OK) {
            ALOGE("Failed to reset key frame request on interface (error code: %d)", status);
            reportError(status);
            return false;
        }
    }

    return true;
}

bool V4L2EncodeComponent::encode(C2ConstGraphicBlock block, uint64_t index, int64_t timestamp) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoder);

    ALOGV("Encoding input block (index: %" PRIu64 ", timestamp: %" PRId64 ", size: %dx%d)", index,
          timestamp, block.width(), block.height());

    // Dynamically adjust framerate based on the frame's timestamp if required.
    constexpr int64_t kMaxFramerateDiff = 5;
    if (mLastFrameTime && (timestamp > *mLastFrameTime)) {
        int64_t newFramerate = std::max(
                static_cast<int64_t>(std::round(1000000.0 / (timestamp - *mLastFrameTime))),
                static_cast<int64_t>(1LL));
        if (abs(mFramerate - newFramerate) > kMaxFramerateDiff) {
            ALOGV("Adjusting framerate to %" PRId64 " based on frame timestamps", newFramerate);
            mInterface->setFramerate(static_cast<uint32_t>(newFramerate));
        }
    }
    mLastFrameTime = timestamp;

    // Update dynamic encoding parameters (bitrate, framerate, key frame) if requested.
    if (!updateEncodingParameters()) return false;

    // Create an input frame from the graphic block.
    std::unique_ptr<V4L2Encoder::InputFrame> frame = CreateInputFrame(block, index, timestamp);
    if (!frame) {
        ALOGE("Failed to create video frame from input block (index: %" PRIu64
              ", timestamp: %" PRId64 ")",
              index, timestamp);
        reportError(C2_CORRUPTED);
        return false;
    }

    if (!mEncoder->encode(std::move(frame))) {
        return false;
    }

    return true;
}

void V4L2EncodeComponent::flush() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    mEncoder->flush();

    // Report all queued work items as aborted.
    std::list<std::unique_ptr<C2Work>> abortedWorkItems;
    while (!mInputConverterQueue.empty()) {
        std::unique_ptr<C2Work> work = std::move(mInputConverterQueue.front());
        work->result = C2_NOT_FOUND;
        work->input.buffers.clear();
        abortedWorkItems.push_back(std::move(work));
        mInputConverterQueue.pop();
    }
    while (!mWorkQueue.empty()) {
        std::unique_ptr<C2Work> work = std::move(mWorkQueue.front());
        // Return buffer to the input format convertor if required.
        if (mInputFormatConverter && work->input.buffers.empty()) {
            mInputFormatConverter->returnBlock(work->input.ordinal.frameIndex.peeku());
        }
        work->result = C2_NOT_FOUND;
        work->input.buffers.clear();
        abortedWorkItems.push_back(std::move(work));
        mWorkQueue.pop_front();
    }
    if (!abortedWorkItems.empty()) {
        mListener->onWorkDone_nb(weak_from_this(), std::move(abortedWorkItems));
    }
}

void V4L2EncodeComponent::fetchOutputBlock(uint32_t size,
                                           std::unique_ptr<BitstreamBuffer>* buffer) {
    ALOGV("Fetching linear block (size: %u)", size);
    std::shared_ptr<C2LinearBlock> block;
    c2_status_t status = mOutputBlockPool->fetchLinearBlock(
            size,
            C2MemoryUsage(C2MemoryUsage::CPU_READ |
                          static_cast<uint64_t>(BufferUsage::VIDEO_ENCODER)),
            &block);
    if (status != C2_OK) {
        ALOGE("Failed to fetch linear block (error: %d)", status);
        reportError(status);
    }

    *buffer = std::make_unique<BitstreamBuffer>(std::move(block), 0, size);
}

void V4L2EncodeComponent::onInputBufferDone(uint64_t index) {
    ALOGV("%s(): Input buffer done (index: %" PRIu64 ")", __func__, index);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoder);

    // There are no guarantees the input buffers are returned in order, so we need to find the work
    // item which this buffer belongs to.
    C2Work* work = getWorkByIndex(index);
    if (!work) {
        ALOGE("Failed to find work associated with input buffer %" PRIu64, index);
        reportError(C2_CORRUPTED);
        return;
    }

    // We're done using the input block, release reference to return the block to the client.
    LOG_ASSERT(!work->input.buffers.empty());
    work->input.buffers.front().reset();

    // Return the block to the convertor if required. If we have buffers awaiting conversion, we can
    // now attempt to convert and encode them again.
    if (mInputFormatConverter) {
        c2_status_t status = mInputFormatConverter->returnBlock(index);
        if (status != C2_OK) {
            reportError(status);
            return;
        }
        while (!mInputConverterQueue.empty() && mInputFormatConverter->isReady()) {
            std::unique_ptr<C2Work> work = std::move(mInputConverterQueue.front());
            mInputConverterQueue.pop();
            queueTask(std::move(work));
        }
    }

    // Return all completed work items. The work item might have been waiting for it's input buffer
    // to be returned, in which case we can report it as completed now. As input buffers are not
    // necessarily returned in order we might be able to return multiple ready work items now.
    while (!mWorkQueue.empty() && isWorkDone(*mWorkQueue.front())) {
        reportWork(std::move(mWorkQueue.front()));
        mWorkQueue.pop_front();
    }
}

void V4L2EncodeComponent::onOutputBufferDone(size_t dataSize, int64_t timestamp, bool keyFrame,
                                             std::unique_ptr<BitstreamBuffer> buffer) {
    ALOGV("%s(): output buffer done (timestamp: %" PRId64 ", size: %zu, keyframe: %d)", __func__,
          timestamp, dataSize, keyFrame);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(buffer->dmabuf);

    C2ConstLinearBlock constBlock =
            buffer->dmabuf->share(buffer->dmabuf->offset(), dataSize, C2Fence());

    // If no CSD (content-specific-data, e.g. SPS for H.264) has been submitted yet, we expect this
    // output block to contain CSD. We only submit the CSD once, even if it's attached to each key
    // frame.
    if (mExtractCSD) {
        ALOGV("No CSD submitted yet, extracting CSD");
        std::unique_ptr<C2StreamInitDataInfo::output> csd;
        C2ReadView view = constBlock.map().get();
        if (!extractCSDInfo(&csd, view.data(), view.capacity())) {
            ALOGE("Failed to extract CSD");
            reportError(C2_CORRUPTED);
            return;
        }

        // Attach the CSD to the first item in our output work queue.
        LOG_ASSERT(!mWorkQueue.empty());
        C2Work* work = mWorkQueue.front().get();
        work->worklets.front()->output.configUpdate.push_back(std::move(csd));
        mExtractCSD = false;
    }

    // Get the work item associated with the timestamp.
    C2Work* work = getWorkByTimestamp(timestamp);
    if (!work) {
        // It's possible we got an empty CSD request with timestamp 0, which we currently just
        // discard.
        if (timestamp != 0) {
            reportError(C2_CORRUPTED);
        }
        return;
    }

    std::shared_ptr<C2Buffer> linearBuffer = C2Buffer::CreateLinearBuffer(std::move(constBlock));
    if (!linearBuffer) {
        ALOGE("Failed to create linear buffer from block");
        reportError(C2_CORRUPTED);
        return;
    }

    if (keyFrame) {
        linearBuffer->setInfo(
                std::make_shared<C2StreamPictureTypeMaskInfo::output>(0u, C2Config::SYNC_FRAME));
    }
    work->worklets.front()->output.buffers.emplace_back(std::move(linearBuffer));

    // We can report the work item as completed if its associated input buffer has also been
    // released. As output buffers are not necessarily returned in order we might be able to return
    // multiple ready work items now.
    while (!mWorkQueue.empty() && isWorkDone(*mWorkQueue.front())) {
        reportWork(std::move(mWorkQueue.front()));
        mWorkQueue.pop_front();
    }
}

C2Work* V4L2EncodeComponent::getWorkByIndex(uint64_t index) {
    ALOGV("%s(): getting work item (index: %" PRIu64 ")", __func__, index);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    auto it = std::find_if(mWorkQueue.begin(), mWorkQueue.end(),
                           [index](const std::unique_ptr<C2Work>& w) {
                               return w->input.ordinal.frameIndex.peeku() == index;
                           });
    if (it == mWorkQueue.end()) {
        ALOGE("Failed to find work (index: %" PRIu64 ")", index);
        return nullptr;
    }
    return it->get();
}

C2Work* V4L2EncodeComponent::getWorkByTimestamp(int64_t timestamp) {
    ALOGV("%s(): getting work item (timestamp: %" PRId64 ")", __func__, timestamp);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(timestamp >= 0);

    // Find the work with specified timestamp by looping over the output work queue. This should be
    // very fast as the output work queue will never be longer then a few items. Ignore empty work
    // items that are marked as EOS, as their timestamp might clash with other work items.
    auto it = std::find_if(
            mWorkQueue.begin(), mWorkQueue.end(), [timestamp](const std::unique_ptr<C2Work>& w) {
                return !(w->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
                       w->input.ordinal.timestamp.peeku() == static_cast<uint64_t>(timestamp);
            });
    if (it == mWorkQueue.end()) {
        ALOGE("Failed to find work (timestamp: %" PRIu64 ")", timestamp);
        return nullptr;
    }
    return it->get();
}

bool V4L2EncodeComponent::isWorkDone(const C2Work& work) const {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if ((work.input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
        !(work.worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM)) {
        ALOGV("Work item %" PRIu64 " is marked as EOS but draining has not finished yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    if (!work.input.buffers.empty() && work.input.buffers.front()) {
        ALOGV("Input buffer associated with work item %" PRIu64 " not returned yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    // If the work item had an input buffer to be encoded, it should have an output buffer set.
    if (!work.input.buffers.empty() && work.worklets.front()->output.buffers.empty()) {
        ALOGV("Output buffer associated with work item %" PRIu64 " not returned yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    return true;
}

void V4L2EncodeComponent::reportWork(std::unique_ptr<C2Work> work) {
    ALOG_ASSERT(work);
    ALOGV("%s(): Reporting work item as finished (index: %llu, timestamp: %llu)", __func__,
          work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull());
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    work->result = C2_OK;
    work->workletsProcessed = static_cast<uint32_t>(work->worklets.size());

    std::list<std::unique_ptr<C2Work>> finishedWorkList;
    finishedWorkList.emplace_back(std::move(work));
    mListener->onWorkDone_nb(weak_from_this(), std::move(finishedWorkList));
}

bool V4L2EncodeComponent::getBlockPool() {
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    auto sharedThis = weak_from_this().lock();
    if (!sharedThis) {
        ALOGI("%s(): V4L2EncodeComponent instance is already destroyed", __func__);
        return false;
    }

    C2BlockPool::local_id_t poolId = mInterface->getBlockPoolId();
    if (poolId == C2BlockPool::BASIC_LINEAR) {
        ALOGW("Using unoptimized linear block pool");
    }
    c2_status_t status = GetCodec2BlockPool(poolId, std::move(sharedThis), &mOutputBlockPool);
    if (status != C2_OK || !mOutputBlockPool) {
        ALOGE("Failed to get output block pool, error: %d", status);
        return false;
    }
    return true;
}

void V4L2EncodeComponent::reportError(c2_status_t error) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // TODO(dstaessens): Report all pending work items as finished upon failure.
    std::lock_guard<std::mutex> lock(mComponentLock);
    if (mComponentState != ComponentState::ERROR) {
        setComponentState(ComponentState::ERROR);
        mListener->onError_nb(weak_from_this(), static_cast<uint32_t>(error));
    }
}

void V4L2EncodeComponent::setComponentState(ComponentState state) {
    // Check whether the state change is valid.
    switch (state) {
    case ComponentState::UNLOADED:
        ALOG_ASSERT(mComponentState == ComponentState::LOADED);
        break;
    case ComponentState::LOADED:
        ALOG_ASSERT(mComponentState == ComponentState::UNLOADED ||
                    mComponentState == ComponentState::RUNNING ||
                    mComponentState == ComponentState::ERROR);
        break;
    case ComponentState::RUNNING:
        ALOG_ASSERT(mComponentState == ComponentState::LOADED);
        break;
    case ComponentState::ERROR:
        break;
    }

    ALOGV("Changed component state from %s to %s", componentStateToString(mComponentState),
          componentStateToString(state));
    mComponentState = state;
}

const char* V4L2EncodeComponent::componentStateToString(V4L2EncodeComponent::ComponentState state) {
    switch (state) {
    case ComponentState::UNLOADED:
        return "UNLOADED";
    case ComponentState::LOADED:
        return "LOADED";
    case ComponentState::RUNNING:
        return "RUNNING";
    case ComponentState::ERROR:
        return "ERROR";
    }
}

}  // namespace android