1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.one.v2.sharedimagereader;
18
19import android.media.ImageReader;
20import android.view.Surface;
21
22import com.android.camera.async.BufferQueue;
23import com.android.camera.async.BufferQueueController;
24import com.android.camera.async.ConcurrentBufferQueue;
25import com.android.camera.async.Lifetime;
26import com.android.camera.one.v2.camera2proxy.ImageProxy;
27import com.android.camera.one.v2.core.CaptureStream;
28import com.android.camera.one.v2.core.FrameServer;
29import com.android.camera.one.v2.core.RequestBuilder;
30import com.android.camera.one.v2.core.ResourceAcquisitionFailedException;
31import com.android.camera.one.v2.sharedimagereader.imagedistributor.ImageDistributor;
32import com.android.camera.one.v2.sharedimagereader.imagedistributor.ImageStream;
33import com.android.camera.one.v2.sharedimagereader.ticketpool.ReservableTicketPool;
34import com.android.camera.one.v2.sharedimagereader.ticketpool.TicketPool;
35import com.android.camera.one.v2.sharedimagereader.util.ImageCloser;
36
37/**
38 * Builds {@link CaptureStream}s which can share the same underlying
39 * {@link ImageReader}.
40 * <p>
41 * Example usage:
42 *
43 * <pre>
44 * RequestBuilder builder = ...;
45 *
46 * // Create a stream which holds 3 images.
47 * ImageStream stream = managedImageReader.createStream(3);
48 *
49 * builder.addStream(stream);
50 * builder.setParam(...);
51 *
52 * frameServer.sendRequest(builder.build());
53 * frameServer.sendRequest(builder.build());
54 * frameServer.sendRequest(builder.build());
55 *
56 * // Synchronously receive the images as they arrive...
57 * ImageProxy image1 = stream.getNext();
58 * ImageProxy image2 = stream.getNext();
59 * ImageProxy image3 = stream.getNext();
60 *
61 * // Close the stream when no more images are expected.
62 * stream.close();
63 *
64 * // Process the Images...
65 *
66 * // Close the images.
67 * image1.close();
68 * image2.close();
69 * image3.close();
70 * </pre>
71 */
72public class ManagedImageReader {
73    private final Lifetime mLifetime;
74    private final TicketPool mTicketPool;
75    /**
76     * The {@link ImageReader} surface.
77     */
78    private final Surface mSurface;
79
80    private final ImageDistributor mImageDistributor;
81
82    /**
83     * @param lifetime
84     * @param ticketPool
85     * @param surface
86     * @param imageDistributor
87     */
88    public ManagedImageReader(Lifetime lifetime, TicketPool ticketPool, Surface surface,
89                              ImageDistributor imageDistributor) {
90        mLifetime = lifetime;
91        mTicketPool = ticketPool;
92        mSurface = surface;
93        mImageDistributor = imageDistributor;
94    }
95
96    private AllocatingImageStream createUnallocatedStream(int capacity) {
97        ReservableTicketPool ticketPool = new ReservableTicketPool(mTicketPool);
98        mLifetime.add(ticketPool);
99
100        ConcurrentBufferQueue<ImageProxy> imageStream = new ConcurrentBufferQueue<>(new
101                ImageCloser());
102        mLifetime.add(imageStream);
103
104        BufferQueueController<ImageProxy> imageStreamController = new
105                TicketRequiredFilter(ticketPool, imageStream);
106        mLifetime.add(imageStreamController);
107
108        AllocatingImageStream stream = new AllocatingImageStream(capacity,
109                ticketPool, imageStream, imageStreamController, mImageDistributor, mSurface);
110        mLifetime.add(stream);
111
112        return stream;
113    }
114
115    /**
116     * Creates a logical bounded stream of images with the specified capacity.
117     * Note that the required image space will be allocated/acquired the first
118     * time {@link CaptureStream#bind(BufferQueue)} is called, but it will be
119     * reused on subsequent invocations. So, for example, the stream provider
120     * may be attached to multiple {@link RequestBuilder}s and the images for
121     * those requests will share the same ticket pool with size specified by the
122     * given capacity.
123     *
124     * @param capacity The maximum number of images which can be simultaneously
125     *            held from the resulting image queue before images are dropped.
126     */
127    public ImageStream createStream(int capacity) {
128        return createUnallocatedStream(capacity);
129    }
130
131    /**
132     * Creates a logical bounded stream of images with the specified capacity,
133     * blocking until the required capacity can be reserved.
134     * <p>
135     * Note: Unlike using {@link #createStream} with a {@link FrameServer}, this
136     * method cannot guarantee non-circular-wait to eliminate the possibility of
137     * deadlock. FrameServer's session lock has already been acquired. Thus,
138     * there is a possibility of deadlock if callers have not already acquired
139     * an exclusive session before calling this method.
140     * <p>
141     * TODO Use a CycleDetectingLockFactory to detect deadlock-prone misuse of
142     * this method, and document the required order of resource acquisition at a
143     * higher-level.
144     *
145     * @param capacity The maximum number of images which can be simultaneously
146     *            held from the resulting image queue before images are dropped.
147     */
148    public ImageStream createPreallocatedStream(int capacity) throws InterruptedException,
149            ResourceAcquisitionFailedException {
150        AllocatingImageStream stream = createUnallocatedStream(capacity);
151        try {
152            stream.allocate();
153            return stream;
154        } catch (Exception e) {
155            // If allocation failed, close the stream before returning.
156            stream.close();
157            throw e;
158        }
159    }
160}
161